@nudojs/core 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-N7E2A7IK.js +7216 -0
- package/dist/exec.d.ts +2 -0
- package/dist/exec.js +174 -0
- package/dist/index-DgyudAkw.d.ts +911 -0
- package/dist/index.d.ts +64 -710
- package/dist/index.js +3269 -8426
- package/package.json +18 -5
- package/CHANGELOG.md +0 -34
- package/src/__tests__/environment.test.ts +0 -48
- package/src/__tests__/fn-sig-impl.test.ts +0 -57
- package/src/__tests__/ops-refined.test.ts +0 -196
- package/src/__tests__/ops.test.ts +0 -84
- package/src/__tests__/type-value-phase2.test.ts +0 -117
- package/src/__tests__/type-value-phase3.test.ts +0 -94
- package/src/__tests__/type-value-refined.test.ts +0 -210
- package/src/__tests__/type-value.test.ts +0 -319
- package/src/algebra/__tests__/abs-native.test.ts +0 -176
- package/src/algebra/__tests__/arithmetic.test.ts +0 -170
- package/src/algebra/__tests__/ast-eval.test.ts +0 -154
- package/src/algebra/__tests__/bridge.test.ts +0 -160
- package/src/algebra/__tests__/call-budget.test.ts +0 -117
- package/src/algebra/__tests__/check-case-consistency.test.ts +0 -69
- package/src/algebra/__tests__/check-gold.test.ts +0 -290
- package/src/algebra/__tests__/check-json.test.ts +0 -57
- package/src/algebra/__tests__/check-mini-repo.test.ts +0 -49
- package/src/algebra/__tests__/check-real-commander.test.ts +0 -43
- package/src/algebra/__tests__/check-real-packages.test.ts +0 -165
- package/src/algebra/__tests__/check-recall-gold.test.ts +0 -1024
- package/src/algebra/__tests__/check-require-dep-memo.test.ts +0 -45
- package/src/algebra/__tests__/check-return-contract.test.ts +0 -90
- package/src/algebra/__tests__/check-shape-gold.test.ts +0 -303
- package/src/algebra/__tests__/check-source-memo.test.ts +0 -204
- package/src/algebra/__tests__/check.test.ts +0 -69
- package/src/algebra/__tests__/denote.test.ts +0 -77
- package/src/algebra/__tests__/diagnostics.test.ts +0 -64
- package/src/algebra/__tests__/differential.test.ts +0 -124
- package/src/algebra/__tests__/div-mod.test.ts +0 -38
- package/src/algebra/__tests__/example0.test.ts +0 -103
- package/src/algebra/__tests__/exec-analyze-throws.test.ts +0 -60
- package/src/algebra/__tests__/exec-async.test.ts +0 -46
- package/src/algebra/__tests__/exec-bpath.test.ts +0 -193
- package/src/algebra/__tests__/exec-class.test.ts +0 -56
- package/src/algebra/__tests__/exec-e2e.test.ts +0 -97
- package/src/algebra/__tests__/exec-generator.test.ts +0 -53
- package/src/algebra/__tests__/exec-inherit-default.test.ts +0 -111
- package/src/algebra/__tests__/exec-optional-nested.test.ts +0 -87
- package/src/algebra/__tests__/exec-rest.test.ts +0 -55
- package/src/algebra/__tests__/exec-spread-forof.test.ts +0 -85
- package/src/algebra/__tests__/exec-static-computed.test.ts +0 -61
- package/src/algebra/__tests__/exec-switch.test.ts +0 -74
- package/src/algebra/__tests__/exec-trycatch.test.ts +0 -71
- package/src/algebra/__tests__/fn-fp-memo.test.ts +0 -166
- package/src/algebra/__tests__/fn-fp-multidecl.test.ts +0 -49
- package/src/algebra/__tests__/format.test.ts +0 -34
- package/src/algebra/__tests__/generalize-memo.test.ts +0 -285
- package/src/algebra/__tests__/generalize.test.ts +0 -113
- package/src/algebra/__tests__/hash-source.test.ts +0 -19
- package/src/algebra/__tests__/hof.test.ts +0 -80
- package/src/algebra/__tests__/inlay.test.ts +0 -30
- package/src/algebra/__tests__/language.test.ts +0 -77
- package/src/algebra/__tests__/leq.test.ts +0 -109
- package/src/algebra/__tests__/load-deps-fp-memo.test.ts +0 -132
- package/src/algebra/__tests__/methods.test.ts +0 -37
- package/src/algebra/__tests__/node-types.test.ts +0 -26
- package/src/algebra/__tests__/nudo-constraints.ts +0 -46
- package/src/algebra/__tests__/objects.test.ts +0 -117
- package/src/algebra/__tests__/parse-source-cache.test.ts +0 -42
- package/src/algebra/__tests__/refine-import.test.ts +0 -109
- package/src/algebra/__tests__/stable-source-key-block.test.ts +0 -23
- package/src/algebra/__tests__/stable-source-key.test.ts +0 -31
- package/src/algebra/__tests__/surface.test.ts +0 -41
- package/src/algebra/__tests__/this-inherit.test.ts +0 -130
- package/src/algebra/abs-fn.ts +0 -49
- package/src/algebra/abs-modules.ts +0 -139
- package/src/algebra/abs.ts +0 -206
- package/src/algebra/arithmetic.ts +0 -691
- package/src/algebra/ast-eval.ts +0 -1763
- package/src/algebra/bridge.ts +0 -350
- package/src/algebra/builtins.ts +0 -373
- package/src/algebra/check-report.ts +0 -150
- package/src/algebra/check.ts +0 -1867
- package/src/algebra/constraint.ts +0 -304
- package/src/algebra/denote.ts +0 -166
- package/src/algebra/diagnostics.ts +0 -210
- package/src/algebra/exec/call.ts +0 -29
- package/src/algebra/exec/calls.ts +0 -96
- package/src/algebra/exec/class-registry.ts +0 -29
- package/src/algebra/exec/class.ts +0 -329
- package/src/algebra/exec/index.ts +0 -6
- package/src/algebra/exec/member-diag.ts +0 -141
- package/src/algebra/exec/run.ts +0 -341
- package/src/algebra/exec/runtime.ts +0 -583
- package/src/algebra/exec/transpile.ts +0 -993
- package/src/algebra/fn-fp.ts +0 -294
- package/src/algebra/format.ts +0 -91
- package/src/algebra/generalize.ts +0 -776
- package/src/algebra/hash-source.ts +0 -27
- package/src/algebra/index.ts +0 -34
- package/src/algebra/inlay.ts +0 -198
- package/src/algebra/language.ts +0 -289
- package/src/algebra/leak.ts +0 -68
- package/src/algebra/leq.ts +0 -301
- package/src/algebra/load-deps-fp.ts +0 -97
- package/src/algebra/methods.ts +0 -128
- package/src/algebra/objects.ts +0 -251
- package/src/algebra/parse-source.ts +0 -64
- package/src/algebra/phi.ts +0 -42
- package/src/algebra/pred.ts +0 -395
- package/src/algebra/refine.ts +0 -227
- package/src/algebra/stable-source-key.ts +0 -81
- package/src/algebra/surface.ts +0 -206
- package/src/algebra/template.ts +0 -143
- package/src/algebra/term.ts +0 -119
- package/src/environment.ts +0 -74
- package/src/index.ts +0 -73
- package/src/mock-helpers.ts +0 -166
- package/src/ops.ts +0 -191
- package/src/refinements/range.ts +0 -86
- package/src/refinements/template-predicates.ts +0 -11
- package/src/refinements/template.ts +0 -176
- package/src/strip-types.ts +0 -236
- package/src/type-value.ts +0 -513
- package/tsconfig.json +0 -8
package/src/algebra/ast-eval.ts
DELETED
|
@@ -1,1763 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Babel AST 抽象求值:把「类型即计算」接到真实 JS 源码。
|
|
3
|
-
* 支持表达式、函数声明/箭头、return、if、const/let、块作用域、类、循环。
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { parseSource as parse } from "./parse-source.ts";
|
|
7
|
-
import type {
|
|
8
|
-
Node,
|
|
9
|
-
Expression,
|
|
10
|
-
Statement,
|
|
11
|
-
File,
|
|
12
|
-
Identifier,
|
|
13
|
-
BinaryExpression,
|
|
14
|
-
NumericLiteral,
|
|
15
|
-
StringLiteral,
|
|
16
|
-
BooleanLiteral,
|
|
17
|
-
ArrowFunctionExpression,
|
|
18
|
-
FunctionDeclaration,
|
|
19
|
-
CallExpression,
|
|
20
|
-
VariableDeclaration,
|
|
21
|
-
BlockStatement,
|
|
22
|
-
ReturnStatement,
|
|
23
|
-
IfStatement,
|
|
24
|
-
ExpressionStatement,
|
|
25
|
-
ForStatement,
|
|
26
|
-
WhileStatement,
|
|
27
|
-
DoWhileStatement,
|
|
28
|
-
ForOfStatement,
|
|
29
|
-
TryStatement,
|
|
30
|
-
} from "@babel/types";
|
|
31
|
-
|
|
32
|
-
import type { Term } from "./term.ts";
|
|
33
|
-
import { lit, v as termVar, app as termApp, simplifyTerm, termToString } from "./term.ts";
|
|
34
|
-
import type { Pred, Phi } from "./pred.ts";
|
|
35
|
-
import { and, gt, ge, lt, le, pTrue, predToString } from "./pred.ts";
|
|
36
|
-
import type { Abs } from "./abs.ts";
|
|
37
|
-
import {
|
|
38
|
-
abs,
|
|
39
|
-
confJoin,
|
|
40
|
-
numLit,
|
|
41
|
-
numVar,
|
|
42
|
-
strLit,
|
|
43
|
-
boolLit,
|
|
44
|
-
unknown,
|
|
45
|
-
litValue,
|
|
46
|
-
isNumPrim,
|
|
47
|
-
isStrPrim,
|
|
48
|
-
} from "./abs.ts";
|
|
49
|
-
import { add, sub, mul, div, mod, cmp, trueConstraint, falseConstraint } from "./arithmetic.ts";
|
|
50
|
-
import { typeofAbs, negAbs, notAbs, strictEqAbs } from "./surface.ts";
|
|
51
|
-
import { leakIfNeeded, defaultLeakBudget, type LeakBudget } from "./leak.ts";
|
|
52
|
-
import { spread, joinAbs } from "./objects.ts";
|
|
53
|
-
import { absFunction, attachFnImpl, getFnImpl } from "./abs-fn.ts";
|
|
54
|
-
import { concatString, isTemplateLike } from "./template.ts";
|
|
55
|
-
import {
|
|
56
|
-
evalGlobalFn,
|
|
57
|
-
evalNamespaceCall,
|
|
58
|
-
evalBuiltinNew,
|
|
59
|
-
evalBuiltinInstanceMethod,
|
|
60
|
-
} from "./builtins.ts";
|
|
61
|
-
import { callAbsMethod, getAbsProperty } from "./methods.ts";
|
|
62
|
-
import { noteMemberDispatchMiss, noteUnknownMemberMissing } from "./exec/member-diag.ts";
|
|
63
|
-
import { registerBClass } from "./exec/class-registry.ts";
|
|
64
|
-
import { bindImports, type AbsModuleExports } from "./abs-modules.ts";
|
|
65
|
-
import {
|
|
66
|
-
defineClass,
|
|
67
|
-
getClass,
|
|
68
|
-
getClassChain,
|
|
69
|
-
classFromMethods,
|
|
70
|
-
instantiateClass,
|
|
71
|
-
instanceOf,
|
|
72
|
-
projectBrand,
|
|
73
|
-
lookupMethod,
|
|
74
|
-
lookupMethodWithOwner,
|
|
75
|
-
lookupSuperMethod,
|
|
76
|
-
superNameOf,
|
|
77
|
-
awaitAbs,
|
|
78
|
-
coerceAsyncReturn,
|
|
79
|
-
wrapPromise,
|
|
80
|
-
type MethodDef,
|
|
81
|
-
} from "./language.ts";
|
|
82
|
-
|
|
83
|
-
// --- 环境 ---
|
|
84
|
-
|
|
85
|
-
export type AstEnv = {
|
|
86
|
-
vars: Map<string, Abs>;
|
|
87
|
-
/** 用户函数:name → { params, body } */
|
|
88
|
-
fns: Map<string, { params: string[]; body: Node; async?: boolean; kind?: string }>;
|
|
89
|
-
/** class 表(旁路,withVar 必须保留) */
|
|
90
|
-
classes?: Map<string, unknown>;
|
|
91
|
-
/** 当前正在求值的方法所属类名(super.x() 从它的父类派发) */
|
|
92
|
-
currentOwner?: string;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
export function emptyEnv(): AstEnv {
|
|
96
|
-
return { vars: new Map(), fns: new Map() };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function withVar(env: AstEnv, name: string, value: Abs): AstEnv {
|
|
100
|
-
const vars = new Map(env.vars);
|
|
101
|
-
vars.set(name, value);
|
|
102
|
-
return { vars, fns: env.fns, classes: env.classes };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export type EvalOptions = {
|
|
106
|
-
phi?: Phi;
|
|
107
|
-
budget?: LeakBudget;
|
|
108
|
-
/** 预解析 AST(check 等批量场景避免重复 parse) */
|
|
109
|
-
file?: File;
|
|
110
|
-
/** host 已求值的相对依赖导出表 */
|
|
111
|
-
modules?: Record<string, AbsModuleExports>;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// --- 调用预算(与 TypeValue evaluator 对齐)---
|
|
115
|
-
// 抽象求值对递归做展开而非不动点:参数类型每层变形(fac(n-1))时
|
|
116
|
-
// cycle key 不重复,必须靠深度/总调用数封顶。超限结果 conf=opaque。
|
|
117
|
-
|
|
118
|
-
export const MAX_CALL_DEPTH = 64;
|
|
119
|
-
export const MAX_TOTAL_CALLS = 200_000;
|
|
120
|
-
|
|
121
|
-
let _absCallDepth = 0;
|
|
122
|
-
let _absTotalCalls = 0;
|
|
123
|
-
let _activeCallKeys: string[] = [];
|
|
124
|
-
const _fnCallIds = new WeakMap<object, string>();
|
|
125
|
-
let _fnCallIdSeq = 0;
|
|
126
|
-
|
|
127
|
-
function stableCallId(obj: object): string {
|
|
128
|
-
let id = _fnCallIds.get(obj);
|
|
129
|
-
if (id === undefined) {
|
|
130
|
-
id = `#${++_fnCallIdSeq}`;
|
|
131
|
-
_fnCallIds.set(obj, id);
|
|
132
|
-
}
|
|
133
|
-
return id;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** 宿主入口(evalProgramAbs / analyzeFn / check)前重置 */
|
|
137
|
-
export function resetAbsCallBudget(): void {
|
|
138
|
-
_absCallDepth = 0;
|
|
139
|
-
_absTotalCalls = 0;
|
|
140
|
-
_activeCallKeys = [];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** 截断结果:分析无信息,conf=opaque(不是 any) */
|
|
144
|
-
function truncatedAbs(): Abs {
|
|
145
|
-
return abs({ k: "unknown" }, undefined, undefined, "opaque");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** 调用指纹:命名函数用 name+arg shapes;一等函数用对象身份 */
|
|
149
|
-
function callBudgetKey(kind: string, id: string, args: Abs[]): string {
|
|
150
|
-
const parts = args.map((a) => {
|
|
151
|
-
const t = a.term ? termToString(a.term) : "";
|
|
152
|
-
return `${a.shape.k}:${t}`;
|
|
153
|
-
});
|
|
154
|
-
return `${kind}|${id}|${parts.join(",")}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let absTruncCollector: ((fnLabel: string) => void) | null = null;
|
|
158
|
-
|
|
159
|
-
/** 记录被截断的递归(service 可映射为 nudo:recursion-truncated) */
|
|
160
|
-
export function setAbsTruncationCollector(
|
|
161
|
-
collector: ((fnLabel: string) => void) | null,
|
|
162
|
-
): void {
|
|
163
|
-
absTruncCollector = collector;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function noteTruncation(label: string): void {
|
|
167
|
-
if (!absTruncCollector) return;
|
|
168
|
-
try {
|
|
169
|
-
absTruncCollector(label);
|
|
170
|
-
} catch {
|
|
171
|
-
// collector 不得打断求值
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** 进入调用:超限/cycle 则不执行 body,返回 opaque */
|
|
176
|
-
function enterCall(key: string, label: string): boolean {
|
|
177
|
-
if (
|
|
178
|
-
_activeCallKeys.includes(key) ||
|
|
179
|
-
_absCallDepth >= MAX_CALL_DEPTH ||
|
|
180
|
-
_absTotalCalls >= MAX_TOTAL_CALLS
|
|
181
|
-
) {
|
|
182
|
-
noteTruncation(label);
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
_activeCallKeys.push(key);
|
|
186
|
-
_absCallDepth++;
|
|
187
|
-
_absTotalCalls++;
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function exitCall(): void {
|
|
192
|
-
_absCallDepth--;
|
|
193
|
-
_activeCallKeys.pop();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** Abs 域调用记录(自包含程序可不经 TypeValue evaluator) */
|
|
197
|
-
export type AbsCallRecord = {
|
|
198
|
-
fnName: string;
|
|
199
|
-
args: Abs[];
|
|
200
|
-
result: Abs;
|
|
201
|
-
callLoc?: { line: number; column: number };
|
|
202
|
-
threw?: boolean;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
let absCallCollector: ((r: AbsCallRecord) => void) | null = null;
|
|
206
|
-
|
|
207
|
-
export function setAbsCallCollector(
|
|
208
|
-
collector: ((r: AbsCallRecord) => void) | null,
|
|
209
|
-
): void {
|
|
210
|
-
absCallCollector = collector;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function recordAbsCall(
|
|
214
|
-
fnName: string,
|
|
215
|
-
args: Abs[],
|
|
216
|
-
result: Abs,
|
|
217
|
-
callLoc?: { line: number; column: number },
|
|
218
|
-
threw?: boolean,
|
|
219
|
-
): void {
|
|
220
|
-
if (!absCallCollector) return;
|
|
221
|
-
try {
|
|
222
|
-
absCallCollector({ fnName, args, result, callLoc, threw });
|
|
223
|
-
} catch {
|
|
224
|
-
// collector 不得打断求值
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/** 节点 → Abs(LSP hover/inlay 的无损表,不经 TypeValue) */
|
|
229
|
-
let absNodeCollector: ((node: Node, value: Abs) => void) | null = null;
|
|
230
|
-
|
|
231
|
-
export function setAbsNodeCollector(
|
|
232
|
-
collector: ((node: Node, value: Abs) => void) | null,
|
|
233
|
-
): void {
|
|
234
|
-
absNodeCollector = collector;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function recordAbsNode(node: Node, value: Abs): void {
|
|
238
|
-
if (!absNodeCollector || !value) return;
|
|
239
|
-
try {
|
|
240
|
-
absNodeCollector(node, value);
|
|
241
|
-
} catch {
|
|
242
|
-
// ignore
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** 赋值记录:结构可赋值检查用 */
|
|
247
|
-
export type AbsAssignRecord = {
|
|
248
|
-
name: string;
|
|
249
|
-
prev?: Abs;
|
|
250
|
-
next: Abs;
|
|
251
|
-
line?: number;
|
|
252
|
-
column?: number;
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
let absAssignCollector: ((r: AbsAssignRecord) => void) | null = null;
|
|
256
|
-
|
|
257
|
-
export function setAbsAssignCollector(
|
|
258
|
-
collector: ((r: AbsAssignRecord) => void) | null,
|
|
259
|
-
): void {
|
|
260
|
-
absAssignCollector = collector;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function recordAbsAssign(
|
|
264
|
-
name: string,
|
|
265
|
-
prev: Abs | undefined,
|
|
266
|
-
next: Abs,
|
|
267
|
-
loc?: { start: { line: number; column: number } },
|
|
268
|
-
): void {
|
|
269
|
-
if (!absAssignCollector) return;
|
|
270
|
-
try {
|
|
271
|
-
absAssignCollector({
|
|
272
|
-
name,
|
|
273
|
-
prev,
|
|
274
|
-
next,
|
|
275
|
-
line: loc?.start.line,
|
|
276
|
-
column: loc?.start.column,
|
|
277
|
-
});
|
|
278
|
-
} catch {
|
|
279
|
-
// ignore
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export type EvalResult = {
|
|
284
|
-
value: Abs;
|
|
285
|
-
phi: Phi;
|
|
286
|
-
env: AstEnv;
|
|
287
|
-
/** 函数已通过 return 跳出,块内后续语句不可达 */
|
|
288
|
-
returned?: boolean;
|
|
289
|
-
/** break 跳出最近循环 */
|
|
290
|
-
brk?: boolean;
|
|
291
|
-
/** continue 进入下一轮 */
|
|
292
|
-
cont?: boolean;
|
|
293
|
-
/** throw 了 value(未捕获时向上传播) */
|
|
294
|
-
threw?: boolean;
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// --- 源码入口 ---
|
|
298
|
-
// parseSource 唯一实现在 ./parse-source.ts;此处经 `parse` 别名使用,不再重复导出。
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* 分析一段 JS 源码。
|
|
302
|
-
* 若含多个顶层函数声明,先注册全部,再对 `main` 或最后一个有调用的函数求值。
|
|
303
|
-
* 更简单:提供 `analyzeFunction(source, fnName, args)`。
|
|
304
|
-
*/
|
|
305
|
-
export function evalSource(
|
|
306
|
-
source: string,
|
|
307
|
-
entry: { fn: string; args: Abs[] },
|
|
308
|
-
opts: EvalOptions = {},
|
|
309
|
-
): EvalResult {
|
|
310
|
-
resetAbsCallBudget();
|
|
311
|
-
const file = opts.file ?? parse(source);
|
|
312
|
-
const env = emptyEnv();
|
|
313
|
-
let phi = opts.phi ?? pTrue;
|
|
314
|
-
|
|
315
|
-
if (opts.modules) {
|
|
316
|
-
for (const stmt of file.program.body) {
|
|
317
|
-
if (stmt.type === "ImportDeclaration") {
|
|
318
|
-
bindImports(stmt, env, opts.modules);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// 第一遍:注册函数与 class(class 必须在调用前登记 methods)
|
|
324
|
-
for (const stmt of file.program.body) {
|
|
325
|
-
if (stmt.type === "ImportDeclaration") continue;
|
|
326
|
-
if (stmt.type === "FunctionDeclaration" && stmt.id) {
|
|
327
|
-
registerFunction(env, stmt);
|
|
328
|
-
}
|
|
329
|
-
if (stmt.type === "ClassDeclaration") {
|
|
330
|
-
registerClassDecl(env, stmt);
|
|
331
|
-
}
|
|
332
|
-
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
333
|
-
const d = stmt.declaration;
|
|
334
|
-
if (d.type === "FunctionDeclaration" && d.id) registerFunction(env, d);
|
|
335
|
-
if (d.type === "ClassDeclaration") registerClassDecl(env, d);
|
|
336
|
-
}
|
|
337
|
-
if (stmt.type === "VariableDeclaration") {
|
|
338
|
-
// const add = (a,b) => a+b
|
|
339
|
-
for (const d of stmt.declarations) {
|
|
340
|
-
if (
|
|
341
|
-
d.id.type === "Identifier" &&
|
|
342
|
-
(d.init?.type === "ArrowFunctionExpression" ||
|
|
343
|
-
d.init?.type === "FunctionExpression")
|
|
344
|
-
) {
|
|
345
|
-
const init = d.init as ArrowFunctionExpression;
|
|
346
|
-
env.fns.set(d.id.name, {
|
|
347
|
-
params: init.params.map(paramName),
|
|
348
|
-
body: init.body,
|
|
349
|
-
async: init.async === true,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const value = callFunction(env, entry.fn, entry.args, phi, opts.budget);
|
|
357
|
-
return { value, phi, env };
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/** 注册 ClassDeclaration 到 env.classes 与 env.vars */
|
|
361
|
-
function registerClassDecl(env: AstEnv, node: Node): void {
|
|
362
|
-
const cls = node as {
|
|
363
|
-
id?: { name: string };
|
|
364
|
-
superClass?: Node;
|
|
365
|
-
body: {
|
|
366
|
-
body: Array<{
|
|
367
|
-
type: string;
|
|
368
|
-
key?: Node;
|
|
369
|
-
params?: Node[];
|
|
370
|
-
body?: Node;
|
|
371
|
-
kind?: string;
|
|
372
|
-
async?: boolean;
|
|
373
|
-
}>;
|
|
374
|
-
};
|
|
375
|
-
};
|
|
376
|
-
const name = cls.id?.name ?? "AnonymousClass";
|
|
377
|
-
const methods: MethodDef[] = cls.body.body
|
|
378
|
-
.filter((m) => m.type === "ClassMethod" || m.type === "ObjectMethod")
|
|
379
|
-
.map((m) => ({
|
|
380
|
-
name:
|
|
381
|
-
m.key?.type === "Identifier"
|
|
382
|
-
? (m.key as Identifier).name
|
|
383
|
-
: m.key?.type === "StringLiteral"
|
|
384
|
-
? (m.key as StringLiteral).value
|
|
385
|
-
: "method",
|
|
386
|
-
params: (m.params ?? []).map(paramName),
|
|
387
|
-
body: m.body as Node,
|
|
388
|
-
kind: m.kind,
|
|
389
|
-
async: m.async === true,
|
|
390
|
-
}));
|
|
391
|
-
let superName: string | undefined;
|
|
392
|
-
let superShape: Abs | undefined;
|
|
393
|
-
if (cls.superClass?.type === "Identifier") {
|
|
394
|
-
superName = (cls.superClass as Identifier).name;
|
|
395
|
-
const sd = getClass(env, superName);
|
|
396
|
-
if (sd) superShape = sd.instanceShape;
|
|
397
|
-
}
|
|
398
|
-
const def = classFromMethods(name, methods, superName, superShape);
|
|
399
|
-
defineClass(env, def);
|
|
400
|
-
// 桥接进 B classRegistry:import 后的 `new C(...)` 走 $new 能找到 ctor
|
|
401
|
-
{
|
|
402
|
-
const callM = (m: MethodDef) => (thisVal: Abs, ...args: Abs[]) =>
|
|
403
|
-
evalMethodBody(m, args, thisVal, env, pTrue, defaultLeakBudget, name);
|
|
404
|
-
const ctorDef = def.methods.get("constructor");
|
|
405
|
-
const methodImpls: Record<string, (thisVal: Abs, ...args: Abs[]) => Abs> = {};
|
|
406
|
-
for (const [mn, md] of def.methods) {
|
|
407
|
-
if (mn === "constructor") continue;
|
|
408
|
-
methodImpls[mn] = callM(md);
|
|
409
|
-
}
|
|
410
|
-
registerBClass({
|
|
411
|
-
name,
|
|
412
|
-
superName,
|
|
413
|
-
ctor: ctorDef ? callM(ctorDef) : undefined,
|
|
414
|
-
methods: methodImpls,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
const classVal = abs(
|
|
418
|
-
{ k: "brand", name, shape: def.instanceShape },
|
|
419
|
-
undefined,
|
|
420
|
-
undefined,
|
|
421
|
-
"exact",
|
|
422
|
-
);
|
|
423
|
-
env.vars.set(name, classVal);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function paramName(p: Node): string {
|
|
427
|
-
if (p.type === "Identifier") return p.name;
|
|
428
|
-
return "_";
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function registerFunction(env: AstEnv, decl: FunctionDeclaration): void {
|
|
432
|
-
if (!decl.id) return;
|
|
433
|
-
env.fns.set(decl.id.name, {
|
|
434
|
-
params: decl.params.map(paramName),
|
|
435
|
-
body: decl.body,
|
|
436
|
-
async: decl.async === true,
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
export function callFunction(
|
|
441
|
-
env: AstEnv,
|
|
442
|
-
name: string,
|
|
443
|
-
args: Abs[],
|
|
444
|
-
phi: Phi = pTrue,
|
|
445
|
-
budget: LeakBudget = defaultLeakBudget,
|
|
446
|
-
): Abs {
|
|
447
|
-
const fn = env.fns.get(name);
|
|
448
|
-
if (!fn) return unknown;
|
|
449
|
-
|
|
450
|
-
const key = callBudgetKey("fn", name, args);
|
|
451
|
-
if (!enterCall(key, name)) return truncatedAbs();
|
|
452
|
-
try {
|
|
453
|
-
// 继承程序级 vars(含 @nudo:mock seed);与 applyAbsFn 一致拷贝,避免写穿外层
|
|
454
|
-
let local: AstEnv = { vars: new Map(env.vars), fns: env.fns };
|
|
455
|
-
// classes 随 env 传递
|
|
456
|
-
const cls = (env as AstEnv & { classes?: Map<string, unknown> }).classes;
|
|
457
|
-
if (cls) (local as AstEnv & { classes?: Map<string, unknown> }).classes = cls;
|
|
458
|
-
|
|
459
|
-
fn.params.forEach((p, i) => {
|
|
460
|
-
local.vars.set(p, args[i] ?? unknown);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
const result = evalNode(fn.body, local, phi, budget);
|
|
464
|
-
if (result.threw) {
|
|
465
|
-
return abs({ k: "never" }, undefined, undefined, "exact");
|
|
466
|
-
}
|
|
467
|
-
if (fn.async) return coerceAsyncReturn(result.value);
|
|
468
|
-
return result.value;
|
|
469
|
-
} finally {
|
|
470
|
-
exitCall();
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* 在 this=receiver 下求值方法体。
|
|
476
|
-
* constructor 无显式 return 时返回更新后的 this(字段写入已并入 env)。
|
|
477
|
-
*/
|
|
478
|
-
export function evalMethodBody(
|
|
479
|
-
method: { params: string[]; body: Node; async?: boolean; kind?: string },
|
|
480
|
-
args: Abs[],
|
|
481
|
-
thisVal: Abs,
|
|
482
|
-
env: AstEnv,
|
|
483
|
-
phi: Phi = pTrue,
|
|
484
|
-
budget: LeakBudget = defaultLeakBudget,
|
|
485
|
-
/** 方法定义所在类名(用于 super 派发) */
|
|
486
|
-
ownerClass?: string,
|
|
487
|
-
): Abs {
|
|
488
|
-
const methodId = `${ownerClass ?? ""}.${method.kind ?? "method"}:${stableCallId(method.body as unknown as object)}`;
|
|
489
|
-
const key = callBudgetKey("method", methodId, [thisVal, ...args]);
|
|
490
|
-
if (!enterCall(key, ownerClass ?? "method")) return truncatedAbs();
|
|
491
|
-
try {
|
|
492
|
-
const local = emptyEnv();
|
|
493
|
-
local.fns = env.fns;
|
|
494
|
-
const cls = (env as AstEnv & { classes?: Map<string, unknown> }).classes;
|
|
495
|
-
if (cls) (local as AstEnv & { classes?: Map<string, unknown> }).classes = cls;
|
|
496
|
-
// super.x() 从当前方法所属类的父类派发
|
|
497
|
-
if (ownerClass) local.currentOwner = ownerClass;
|
|
498
|
-
local.vars.set("this", thisVal);
|
|
499
|
-
method.params.forEach((p, i) => {
|
|
500
|
-
local.vars.set(p, args[i] ?? unknown);
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
const result = evalNode(method.body, local, phi, budget);
|
|
504
|
-
let value = result.value;
|
|
505
|
-
|
|
506
|
-
// 从求值后的 env 取 this(AssignmentExpression 用 withVar 换 env)
|
|
507
|
-
const thisAfter = result.env.vars.get("this") ?? local.vars.get("this");
|
|
508
|
-
if (method.kind === "constructor") {
|
|
509
|
-
if (thisAfter) value = thisAfter;
|
|
510
|
-
} else if (thisAfter && thisAfter !== thisVal && value.shape.k === "unknown") {
|
|
511
|
-
value = thisAfter;
|
|
512
|
-
}
|
|
513
|
-
if (method.async) return coerceAsyncReturn(value);
|
|
514
|
-
return value;
|
|
515
|
-
} finally {
|
|
516
|
-
exitCall();
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// --- 节点求值 ---
|
|
521
|
-
|
|
522
|
-
/** 对外入口:求值并记入节点表(Map<Node, Abs> 由 collector 消费) */
|
|
523
|
-
export function evalNode(
|
|
524
|
-
node: Node,
|
|
525
|
-
env: AstEnv,
|
|
526
|
-
phi: Phi,
|
|
527
|
-
budget: LeakBudget,
|
|
528
|
-
): EvalResult {
|
|
529
|
-
const r = evalNodeInner(node, env, phi, budget);
|
|
530
|
-
recordAbsNode(node, r.value);
|
|
531
|
-
return r;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function evalNodeInner(
|
|
535
|
-
node: Node,
|
|
536
|
-
env: AstEnv,
|
|
537
|
-
phi: Phi,
|
|
538
|
-
budget: LeakBudget,
|
|
539
|
-
): EvalResult {
|
|
540
|
-
switch (node.type) {
|
|
541
|
-
case "NumericLiteral":
|
|
542
|
-
return ok(numLit((node as NumericLiteral).value), phi, env);
|
|
543
|
-
case "StringLiteral":
|
|
544
|
-
return ok(strLit((node as StringLiteral).value), phi, env);
|
|
545
|
-
case "BooleanLiteral":
|
|
546
|
-
return ok(boolLit((node as BooleanLiteral).value), phi, env);
|
|
547
|
-
case "NullLiteral":
|
|
548
|
-
return ok(abs({ k: "unknown" }, lit(null), pTrue, "exact"), phi, env);
|
|
549
|
-
case "Identifier": {
|
|
550
|
-
const id = node as Identifier;
|
|
551
|
-
if (id.name === "undefined") {
|
|
552
|
-
return ok(abs({ k: "unknown" }, lit(undefined), pTrue, "exact"), phi, env);
|
|
553
|
-
}
|
|
554
|
-
return ok(env.vars.get(id.name) ?? unknown, phi, env);
|
|
555
|
-
}
|
|
556
|
-
case "BinaryExpression":
|
|
557
|
-
return evalBinary(node as BinaryExpression, env, phi, budget);
|
|
558
|
-
case "CallExpression":
|
|
559
|
-
return evalCall(node as CallExpression, env, phi, budget);
|
|
560
|
-
case "ArrowFunctionExpression":
|
|
561
|
-
case "FunctionExpression": {
|
|
562
|
-
const fn = node as ArrowFunctionExpression;
|
|
563
|
-
const params = fn.params.map(paramName);
|
|
564
|
-
const impl = {
|
|
565
|
-
params,
|
|
566
|
-
body: fn.body,
|
|
567
|
-
async: fn.async === true,
|
|
568
|
-
env,
|
|
569
|
-
};
|
|
570
|
-
return ok(absFunction(params, impl), phi, env);
|
|
571
|
-
}
|
|
572
|
-
case "ClassDeclaration": {
|
|
573
|
-
registerClassDecl(env, node);
|
|
574
|
-
const name = (node as { id?: { name: string } }).id?.name;
|
|
575
|
-
if (name) {
|
|
576
|
-
return { value: env.vars.get(name) ?? unknown, phi, env };
|
|
577
|
-
}
|
|
578
|
-
return ok(unknown, phi, env);
|
|
579
|
-
}
|
|
580
|
-
case "NewExpression": {
|
|
581
|
-
const ne = node as { callee: Node; arguments: Node[] };
|
|
582
|
-
if (ne.callee.type !== "Identifier") return ok(unknown, phi, env);
|
|
583
|
-
const className = (ne.callee as Identifier).name;
|
|
584
|
-
const args = ne.arguments.map((a) => evalNode(a, env, phi, budget).value);
|
|
585
|
-
// 内置构造
|
|
586
|
-
const builtin = evalBuiltinNew(className, args);
|
|
587
|
-
if (builtin) return ok(builtin, phi, env);
|
|
588
|
-
const inst = instantiateClass(env, className, args, (ctor, cargs, thisVal, e) =>
|
|
589
|
-
evalMethodBody(ctor, cargs, thisVal, e, phi, budget),
|
|
590
|
-
);
|
|
591
|
-
return ok(inst, phi, env);
|
|
592
|
-
}
|
|
593
|
-
case "AssignmentExpression": {
|
|
594
|
-
const ae = node as {
|
|
595
|
-
operator: string;
|
|
596
|
-
left: Node;
|
|
597
|
-
right: Node;
|
|
598
|
-
};
|
|
599
|
-
if (ae.operator !== "=") return ok(unknown, phi, env);
|
|
600
|
-
const rhs = evalNode(ae.right, env, phi, budget).value;
|
|
601
|
-
// this.field = v → 更新 env 中的 this
|
|
602
|
-
if (
|
|
603
|
-
ae.left.type === "MemberExpression" &&
|
|
604
|
-
(ae.left as { object: Node }).object.type === "ThisExpression"
|
|
605
|
-
) {
|
|
606
|
-
const prop = (ae.left as { property: Node; computed?: boolean }).property;
|
|
607
|
-
const key =
|
|
608
|
-
!ae.left.computed && prop.type === "Identifier"
|
|
609
|
-
? (prop as Identifier).name
|
|
610
|
-
: prop.type === "StringLiteral"
|
|
611
|
-
? (prop as StringLiteral).value
|
|
612
|
-
: undefined;
|
|
613
|
-
if (key) {
|
|
614
|
-
const thisVal = env.vars.get("this");
|
|
615
|
-
if (thisVal && thisVal.shape.k === "brand") {
|
|
616
|
-
const slots: Record<string, { value: Abs }> = {};
|
|
617
|
-
const inner = thisVal.shape.shape;
|
|
618
|
-
if (inner.shape.k === "obj") {
|
|
619
|
-
Object.assign(slots, inner.shape.slots);
|
|
620
|
-
}
|
|
621
|
-
slots[key] = { value: rhs };
|
|
622
|
-
const updated = abs(
|
|
623
|
-
{
|
|
624
|
-
k: "brand",
|
|
625
|
-
name: thisVal.shape.name,
|
|
626
|
-
shape: abs({ k: "obj", slots }, undefined, undefined, "exact"),
|
|
627
|
-
},
|
|
628
|
-
undefined,
|
|
629
|
-
undefined,
|
|
630
|
-
thisVal.conf,
|
|
631
|
-
);
|
|
632
|
-
return { value: rhs, phi, env: withVar(env, "this", updated) };
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
// 普通标识符赋值
|
|
637
|
-
if (ae.left.type === "Identifier") {
|
|
638
|
-
const name = (ae.left as Identifier).name;
|
|
639
|
-
const prev = env.vars.get(name);
|
|
640
|
-
const loc = (node as { loc?: { start: { line: number; column: number } } }).loc;
|
|
641
|
-
recordAbsAssign(name, prev, rhs, loc);
|
|
642
|
-
return { value: rhs, phi, env: withVar(env, name, rhs) };
|
|
643
|
-
}
|
|
644
|
-
return ok(rhs, phi, env);
|
|
645
|
-
}
|
|
646
|
-
case "AwaitExpression": {
|
|
647
|
-
const ae = node as { argument: Node };
|
|
648
|
-
const inner = evalNode(ae.argument, env, phi, budget).value;
|
|
649
|
-
return ok(awaitAbs(inner), phi, env);
|
|
650
|
-
}
|
|
651
|
-
case "ThisExpression":
|
|
652
|
-
return ok(env.vars.get("this") ?? unknown, phi, env);
|
|
653
|
-
case "BlockStatement":
|
|
654
|
-
return evalBlock(node as BlockStatement, env, phi, budget);
|
|
655
|
-
case "BreakStatement":
|
|
656
|
-
return { value: unknown, phi, env, brk: true };
|
|
657
|
-
case "ContinueStatement":
|
|
658
|
-
return { value: unknown, phi, env, cont: true };
|
|
659
|
-
case "ThrowStatement": {
|
|
660
|
-
const arg = (node as { argument?: Node }).argument;
|
|
661
|
-
const v = arg ? evalNode(arg, env, phi, budget).value : unknown;
|
|
662
|
-
return { value: v, phi, env, threw: true };
|
|
663
|
-
}
|
|
664
|
-
case "ForStatement":
|
|
665
|
-
return evalFor(node as ForStatement, env, phi, budget);
|
|
666
|
-
case "WhileStatement":
|
|
667
|
-
return evalWhile(node as WhileStatement, env, phi, budget);
|
|
668
|
-
case "DoWhileStatement":
|
|
669
|
-
return evalDoWhile(node as DoWhileStatement, env, phi, budget);
|
|
670
|
-
case "ForOfStatement":
|
|
671
|
-
return evalForOf(node as ForOfStatement, env, phi, budget);
|
|
672
|
-
case "TryStatement":
|
|
673
|
-
return evalTry(node as TryStatement, env, phi, budget);
|
|
674
|
-
case "UpdateExpression": {
|
|
675
|
-
const ue = node as { operator: string; argument: Node; prefix?: boolean };
|
|
676
|
-
if (ue.argument.type !== "Identifier") return ok(unknown, phi, env);
|
|
677
|
-
const name = (ue.argument as Identifier).name;
|
|
678
|
-
const cur = env.vars.get(name) ?? unknown;
|
|
679
|
-
const lv = litValue(cur);
|
|
680
|
-
const delta = ue.operator === "++" ? 1 : -1;
|
|
681
|
-
const next =
|
|
682
|
-
typeof lv === "number" ? numLit(lv + delta) : abs({ k: "prim", type: "number" }, undefined, undefined, "path");
|
|
683
|
-
const env2 = withVar(env, name, next);
|
|
684
|
-
return { value: ue.prefix ? next : cur, phi, env: env2 };
|
|
685
|
-
}
|
|
686
|
-
case "ReturnStatement": {
|
|
687
|
-
const arg = (node as ReturnStatement).argument;
|
|
688
|
-
if (!arg) return { value: unknown, phi, env, returned: true };
|
|
689
|
-
const r = evalNode(arg, env, phi, budget);
|
|
690
|
-
return { ...r, returned: true };
|
|
691
|
-
}
|
|
692
|
-
case "IfStatement":
|
|
693
|
-
return evalIf(node as IfStatement, env, phi, budget);
|
|
694
|
-
case "ExpressionStatement":
|
|
695
|
-
return evalNode((node as ExpressionStatement).expression, env, phi, budget);
|
|
696
|
-
case "VariableDeclaration":
|
|
697
|
-
return evalVarDecl(node as VariableDeclaration, env, phi, budget);
|
|
698
|
-
case "UnaryExpression": {
|
|
699
|
-
const u = node as { operator: string; argument: Node };
|
|
700
|
-
const a = evalNode(u.argument, env, phi, budget).value;
|
|
701
|
-
if (u.operator === "-") return ok(negAbs(a, phi), phi, env);
|
|
702
|
-
if (u.operator === "!") return ok(notAbs(a), phi, env);
|
|
703
|
-
if (u.operator === "typeof") return ok(typeofAbs(a), phi, env);
|
|
704
|
-
if (u.operator === "+") {
|
|
705
|
-
const lv = litValue(a);
|
|
706
|
-
if (typeof lv === "number") return ok(numLit(lv), phi, env);
|
|
707
|
-
return ok(unknown, phi, env);
|
|
708
|
-
}
|
|
709
|
-
return ok(unknown, phi, env);
|
|
710
|
-
}
|
|
711
|
-
case "LogicalExpression": {
|
|
712
|
-
const le = node as { operator: string; left: Node; right: Node };
|
|
713
|
-
const l = evalNode(le.left, env, phi, budget);
|
|
714
|
-
const lv = litValue(l.value);
|
|
715
|
-
const falsy = lv === false || lv === null || lv === undefined;
|
|
716
|
-
const truthy = lv !== undefined && !falsy;
|
|
717
|
-
if (le.operator === "&&") {
|
|
718
|
-
if (falsy) return ok(l.value, phi, env);
|
|
719
|
-
if (truthy) return evalNode(le.right, env, phi, budget);
|
|
720
|
-
const r = evalNode(le.right, env, phi, budget);
|
|
721
|
-
return ok(joinAbs(l.value, r.value), phi, env);
|
|
722
|
-
}
|
|
723
|
-
if (le.operator === "||") {
|
|
724
|
-
if (truthy) return ok(l.value, phi, env);
|
|
725
|
-
if (falsy) return evalNode(le.right, env, phi, budget);
|
|
726
|
-
const r = evalNode(le.right, env, phi, budget);
|
|
727
|
-
return ok(joinAbs(l.value, r.value), phi, env);
|
|
728
|
-
}
|
|
729
|
-
return ok(unknown, phi, env);
|
|
730
|
-
}
|
|
731
|
-
case "TemplateLiteral": {
|
|
732
|
-
const tl = node as {
|
|
733
|
-
quasis: Array<{ value: { cooked?: string | null; raw: string } }>;
|
|
734
|
-
expressions: Node[];
|
|
735
|
-
};
|
|
736
|
-
const parts: Abs[] = [];
|
|
737
|
-
for (let i = 0; i < tl.quasis.length; i++) {
|
|
738
|
-
const cooked = tl.quasis[i]!.value.cooked ?? tl.quasis[i]!.value.raw;
|
|
739
|
-
if (cooked) parts.push(strLit(cooked));
|
|
740
|
-
if (i < tl.expressions.length) {
|
|
741
|
-
parts.push(evalNode(tl.expressions[i]!, env, phi, budget).value);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
if (parts.length === 0) return ok(strLit(""), phi, env);
|
|
745
|
-
let acc = parts[0]!;
|
|
746
|
-
for (let i = 1; i < parts.length; i++) {
|
|
747
|
-
acc = concatString(acc, parts[i]!);
|
|
748
|
-
}
|
|
749
|
-
return ok(acc, phi, env);
|
|
750
|
-
}
|
|
751
|
-
case "MemberExpression": {
|
|
752
|
-
const m = node as { object: Node; property: Node; computed?: boolean };
|
|
753
|
-
const obj = evalNode(m.object, env, phi, budget).value;
|
|
754
|
-
// brand 字段/方法投影
|
|
755
|
-
if (obj.shape.k === "brand" && !m.computed && m.property.type === "Identifier") {
|
|
756
|
-
const key = (m.property as Identifier).name;
|
|
757
|
-
const projected = projectBrand(obj, key);
|
|
758
|
-
if (projected.shape.k !== "unknown") {
|
|
759
|
-
return ok(projected, phi, env);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
// obj.length
|
|
763
|
-
if (
|
|
764
|
-
!m.computed &&
|
|
765
|
-
m.property.type === "Identifier" &&
|
|
766
|
-
(m.property as Identifier).name === "length"
|
|
767
|
-
) {
|
|
768
|
-
const viaProp = getAbsProperty(obj, "length");
|
|
769
|
-
if (viaProp) return ok(viaProp, phi, env);
|
|
770
|
-
if (obj.shape.k === "tuple") {
|
|
771
|
-
return ok(numLit(obj.shape.elements.length), phi, env);
|
|
772
|
-
}
|
|
773
|
-
if (obj.shape.k === "arr") {
|
|
774
|
-
return ok(abs({ k: "prim", type: "number" }, undefined, undefined, "path"), phi, env);
|
|
775
|
-
}
|
|
776
|
-
if (isStrPrim(obj)) {
|
|
777
|
-
const lv = litValue(obj);
|
|
778
|
-
if (typeof lv === "string") return ok(numLit(lv.length), phi, env);
|
|
779
|
-
return ok(abs({ k: "prim", type: "number" }, undefined, undefined, "partial"), phi, env);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
// 静态属性访问
|
|
783
|
-
if (!m.computed && m.property.type === "Identifier" && obj.shape.k === "obj") {
|
|
784
|
-
const key = (m.property as Identifier).name;
|
|
785
|
-
const slot = (obj.shape as { slots: Record<string, { value: Abs }> }).slots[key];
|
|
786
|
-
if (slot) return ok(slot.value, phi, env);
|
|
787
|
-
}
|
|
788
|
-
// 计算属性 obj[key]
|
|
789
|
-
if (m.computed) {
|
|
790
|
-
const key = evalNode(m.property, env, phi, budget).value;
|
|
791
|
-
const kl = litValue(key);
|
|
792
|
-
if (typeof kl === "string" && obj.shape.k === "obj") {
|
|
793
|
-
const slot = (obj.shape as { slots: Record<string, { value: Abs }> }).slots[kl];
|
|
794
|
-
if (slot) return ok(slot.value, phi, env);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
// unknown 上的裸属性 → unknown-recv($get 同口径;obj open 不记)
|
|
798
|
-
if (!m.computed && m.property.type === "Identifier") {
|
|
799
|
-
const loc = node.loc
|
|
800
|
-
? ([node.loc.start.line, node.loc.start.column] as [number, number])
|
|
801
|
-
: undefined;
|
|
802
|
-
noteUnknownMemberMissing(obj, (m.property as Identifier).name, "property", loc);
|
|
803
|
-
}
|
|
804
|
-
return ok(unknown, phi, env);
|
|
805
|
-
}
|
|
806
|
-
case "ObjectExpression": {
|
|
807
|
-
const o = node as {
|
|
808
|
-
properties: Array<
|
|
809
|
-
| { type: "ObjectProperty"; key?: Node; value?: Node }
|
|
810
|
-
| { type: "SpreadElement"; argument: Node }
|
|
811
|
-
>;
|
|
812
|
-
};
|
|
813
|
-
// 先收集字面量属性,再按书写顺序应用 spread
|
|
814
|
-
let acc: Abs = abs({ k: "obj", slots: {} }, undefined, undefined, "exact");
|
|
815
|
-
let pending: Record<string, { value: Abs }> = {};
|
|
816
|
-
|
|
817
|
-
const flushPending = (base: Abs): Abs => {
|
|
818
|
-
if (Object.keys(pending).length === 0) return base;
|
|
819
|
-
const slots =
|
|
820
|
-
base.shape.k === "obj"
|
|
821
|
-
? { ...(base.shape as { slots: Record<string, { value: Abs }> }).slots }
|
|
822
|
-
: {};
|
|
823
|
-
for (const [k, s] of Object.entries(pending)) slots[k] = s;
|
|
824
|
-
pending = {};
|
|
825
|
-
return abs({ k: "obj", slots }, undefined, undefined, "exact");
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
for (const p of o.properties) {
|
|
829
|
-
if (p.type === "SpreadElement") {
|
|
830
|
-
acc = flushPending(acc);
|
|
831
|
-
const sp = evalNode(p.argument, env, phi, budget).value;
|
|
832
|
-
acc = spread(acc, sp);
|
|
833
|
-
continue;
|
|
834
|
-
}
|
|
835
|
-
const keyNode = p.key;
|
|
836
|
-
let key: string | undefined;
|
|
837
|
-
if (keyNode && keyNode.type === "Identifier") key = (keyNode as Identifier).name;
|
|
838
|
-
if (keyNode && keyNode.type === "StringLiteral") key = (keyNode as StringLiteral).value;
|
|
839
|
-
if (!key || !p.value) continue;
|
|
840
|
-
pending[key] = { value: evalNode(p.value, env, phi, budget).value };
|
|
841
|
-
}
|
|
842
|
-
acc = flushPending(acc);
|
|
843
|
-
return ok(acc, phi, env);
|
|
844
|
-
}
|
|
845
|
-
case "ArrayExpression": {
|
|
846
|
-
const a = node as { elements: Array<Node | null> };
|
|
847
|
-
const els = a.elements
|
|
848
|
-
.filter((e): e is Node => e != null && e.type !== "SpreadElement")
|
|
849
|
-
.map((e) => evalNode(e, env, phi, budget).value);
|
|
850
|
-
if (els.length === 0) {
|
|
851
|
-
return ok(abs({ k: "arr", element: unknown }, undefined, undefined, "exact"), phi, env);
|
|
852
|
-
}
|
|
853
|
-
// 小数组字面量 → tuple(保逐元素精确,map/reduce 可展开)
|
|
854
|
-
if (els.length <= 8) {
|
|
855
|
-
return ok(abs({ k: "tuple", elements: els }, undefined, undefined, "exact"), phi, env);
|
|
856
|
-
}
|
|
857
|
-
const elem = els.reduce((x, y) => joinAbs(x, y));
|
|
858
|
-
return ok(abs({ k: "arr", element: elem }, undefined, undefined, "path"), phi, env);
|
|
859
|
-
}
|
|
860
|
-
default:
|
|
861
|
-
return ok(unknown, phi, env);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
function ok(value: Abs, phi: Phi, env: AstEnv): EvalResult {
|
|
866
|
-
return { value, phi, env };
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function evalBinary(
|
|
870
|
-
node: BinaryExpression,
|
|
871
|
-
env: AstEnv,
|
|
872
|
-
phi: Phi,
|
|
873
|
-
budget: LeakBudget,
|
|
874
|
-
): EvalResult {
|
|
875
|
-
const l = evalNode(node.left, env, phi, budget).value;
|
|
876
|
-
const r = evalNode(node.right, env, phi, budget).value;
|
|
877
|
-
const op = node.operator;
|
|
878
|
-
|
|
879
|
-
switch (op) {
|
|
880
|
-
case "+":
|
|
881
|
-
return ok(leakIfNeeded(add(l, r, phi), budget, "add"), phi, env);
|
|
882
|
-
case "-":
|
|
883
|
-
return ok(leakIfNeeded(sub(l, r, phi), budget, "sub"), phi, env);
|
|
884
|
-
case "*":
|
|
885
|
-
return ok(leakIfNeeded(mul(l, r, phi), budget, "mul"), phi, env);
|
|
886
|
-
case "<":
|
|
887
|
-
return ok(cmp("lt", l, r, phi), phi, env);
|
|
888
|
-
case "<=":
|
|
889
|
-
return ok(cmp("le", l, r, phi), phi, env);
|
|
890
|
-
case ">":
|
|
891
|
-
return ok(cmp("gt", l, r, phi), phi, env);
|
|
892
|
-
case ">=":
|
|
893
|
-
return ok(cmp("ge", l, r, phi), phi, env);
|
|
894
|
-
case "===": {
|
|
895
|
-
const eq = strictEqAbs(l, r);
|
|
896
|
-
if (eq !== undefined) return ok(boolLit(eq), phi, env);
|
|
897
|
-
return ok(cmp("eq", l, r, phi), phi, env);
|
|
898
|
-
}
|
|
899
|
-
case "!==": {
|
|
900
|
-
const eq = strictEqAbs(l, r);
|
|
901
|
-
if (eq !== undefined) return ok(boolLit(!eq), phi, env);
|
|
902
|
-
return ok(cmp("ne", l, r, phi), phi, env);
|
|
903
|
-
}
|
|
904
|
-
case "/":
|
|
905
|
-
return ok(leakIfNeeded(div(l, r, phi), budget, "div"), phi, env);
|
|
906
|
-
case "%":
|
|
907
|
-
return ok(leakIfNeeded(mod(l, r, phi), budget, "mod"), phi, env);
|
|
908
|
-
case "instanceof": {
|
|
909
|
-
if (node.right.type !== "Identifier") return ok(unknown, phi, env);
|
|
910
|
-
const className = (node.right as Identifier).name;
|
|
911
|
-
return ok(instanceOf(l, className, env), phi, env);
|
|
912
|
-
}
|
|
913
|
-
default:
|
|
914
|
-
return ok(unknown, phi, env);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
function evalCall(
|
|
919
|
-
node: CallExpression,
|
|
920
|
-
env: AstEnv,
|
|
921
|
-
phi: Phi,
|
|
922
|
-
budget: LeakBudget,
|
|
923
|
-
): EvalResult {
|
|
924
|
-
const callee = node.callee;
|
|
925
|
-
|
|
926
|
-
// 方法调用:arr.map(fn) / Math.floor / Object.keys …
|
|
927
|
-
if (callee.type === "MemberExpression") {
|
|
928
|
-
const m = callee as { object: Node; property: Node; computed?: boolean };
|
|
929
|
-
if (!m.computed && m.property.type === "Identifier") {
|
|
930
|
-
const method = (m.property as Identifier).name;
|
|
931
|
-
|
|
932
|
-
// super.method() → 从当前方法所属类的父类派发
|
|
933
|
-
if (m.object.type === "Super") {
|
|
934
|
-
const thisVal = env.vars.get("this");
|
|
935
|
-
const sArgs = node.arguments.filter(
|
|
936
|
-
(a): a is Exclude<typeof a, { type: "SpreadElement" }> => a.type !== "SpreadElement",
|
|
937
|
-
);
|
|
938
|
-
if (thisVal && thisVal.shape.k === "brand") {
|
|
939
|
-
const fromClass = env.currentOwner ?? thisVal.shape.name;
|
|
940
|
-
const found = lookupSuperMethod(env, thisVal, fromClass, method);
|
|
941
|
-
if (found && found.def.kind !== "constructor") {
|
|
942
|
-
const margs = sArgs.map((a) => evalNode(a, env, phi, budget).value);
|
|
943
|
-
const ret = evalMethodBody(
|
|
944
|
-
found.def,
|
|
945
|
-
margs,
|
|
946
|
-
thisVal,
|
|
947
|
-
env,
|
|
948
|
-
phi,
|
|
949
|
-
budget,
|
|
950
|
-
found.owner,
|
|
951
|
-
);
|
|
952
|
-
if (found.def.async) return ok(coerceAsyncReturn(ret), phi, env);
|
|
953
|
-
return ok(ret, phi, env);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return ok(unknown, phi, env);
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
const obj = evalNode(m.object, env, phi, budget).value;
|
|
960
|
-
const rawArgs = node.arguments.filter(
|
|
961
|
-
(a): a is Exclude<typeof a, { type: "SpreadElement" }> => a.type !== "SpreadElement",
|
|
962
|
-
);
|
|
963
|
-
|
|
964
|
-
// 全局命名空间 builtin(Math/Object/JSON/Number/Array/Date/Promise)
|
|
965
|
-
if (m.object.type === "Identifier") {
|
|
966
|
-
const ns = (m.object as Identifier).name;
|
|
967
|
-
const margs = rawArgs.map((a) => evalNode(a, env, phi, budget).value);
|
|
968
|
-
const r = evalNamespaceCall(ns, method, margs);
|
|
969
|
-
if (r) return ok(r, phi, env);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// Abs 方法表(template startsWith 等)
|
|
973
|
-
{
|
|
974
|
-
const margs = rawArgs.map((a) => evalNode(a, env, phi, budget).value);
|
|
975
|
-
const viaTable = callAbsMethod(obj, method, margs);
|
|
976
|
-
if (viaTable) return ok(viaTable, phi, env);
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
if (obj.shape.k === "brand") {
|
|
980
|
-
// 内置 brand 实例方法(Date/RegExp/Map/Set)
|
|
981
|
-
{
|
|
982
|
-
const margs = rawArgs.map((a) => evalNode(a, env, phi, budget).value);
|
|
983
|
-
const bi = evalBuiltinInstanceMethod(obj.shape.name, method, obj, margs);
|
|
984
|
-
if (bi) return ok(bi, phi, env);
|
|
985
|
-
}
|
|
986
|
-
// brand 方法:在 this=receiver 下求值方法体(含继承链)
|
|
987
|
-
const foundM = lookupMethodWithOwner(env, obj, method);
|
|
988
|
-
if (foundM && foundM.def.kind !== "constructor") {
|
|
989
|
-
const margs = rawArgs.map((a) => evalNode(a, env, phi, budget).value);
|
|
990
|
-
const ret = evalMethodBody(
|
|
991
|
-
foundM.def,
|
|
992
|
-
margs,
|
|
993
|
-
obj,
|
|
994
|
-
env,
|
|
995
|
-
phi,
|
|
996
|
-
budget,
|
|
997
|
-
foundM.owner,
|
|
998
|
-
);
|
|
999
|
-
if (foundM.def.async) return ok(coerceAsyncReturn(ret), phi, env);
|
|
1000
|
-
return ok(ret, phi, env);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// Promise:then/catch/finally → promise
|
|
1005
|
-
if (obj.shape.k === "eff" && obj.shape.eff === "promise") {
|
|
1006
|
-
if (method === "then" || method === "catch" || method === "finally") {
|
|
1007
|
-
const fnNode = rawArgs[0];
|
|
1008
|
-
if (fnNode && method === "then") {
|
|
1009
|
-
const inner = applyUnaryCallback(fnNode, obj.shape.inner, env, phi, budget);
|
|
1010
|
-
return ok(
|
|
1011
|
-
abs({ k: "eff", eff: "promise", inner }, undefined, undefined, confJoin(obj.conf, inner.conf)),
|
|
1012
|
-
phi,
|
|
1013
|
-
env,
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
return ok(obj, phi, env);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// 字符串方法(字面量可折叠)——仅 string prim / string 字面量,勿把 number lit 误判
|
|
1021
|
-
if (isStrPrim(obj) || (obj.term?.op === "lit" && typeof litValue(obj) === "string")) {
|
|
1022
|
-
const sv = litValue(obj);
|
|
1023
|
-
const arg0 = rawArgs[0] ? evalNode(rawArgs[0], env, phi, budget).value : undefined;
|
|
1024
|
-
const a0 = arg0 ? litValue(arg0) : undefined;
|
|
1025
|
-
if (typeof sv === "string") {
|
|
1026
|
-
switch (method) {
|
|
1027
|
-
case "startsWith":
|
|
1028
|
-
if (typeof a0 === "string") return ok(boolLit(sv.startsWith(a0)), phi, env);
|
|
1029
|
-
break;
|
|
1030
|
-
case "endsWith":
|
|
1031
|
-
if (typeof a0 === "string") return ok(boolLit(sv.endsWith(a0)), phi, env);
|
|
1032
|
-
break;
|
|
1033
|
-
case "includes":
|
|
1034
|
-
if (typeof a0 === "string") return ok(boolLit(sv.includes(a0)), phi, env);
|
|
1035
|
-
break;
|
|
1036
|
-
case "charAt":
|
|
1037
|
-
if (typeof a0 === "number") return ok(strLit(sv.charAt(a0)), phi, env);
|
|
1038
|
-
break;
|
|
1039
|
-
case "slice": {
|
|
1040
|
-
const a1 = rawArgs[1] ? litValue(evalNode(rawArgs[1], env, phi, budget).value) : undefined;
|
|
1041
|
-
return ok(strLit(sv.slice(a0 as number | undefined, a1 as number | undefined)), phi, env);
|
|
1042
|
-
}
|
|
1043
|
-
case "toUpperCase":
|
|
1044
|
-
return ok(strLit(sv.toUpperCase()), phi, env);
|
|
1045
|
-
case "toLowerCase":
|
|
1046
|
-
return ok(strLit(sv.toLowerCase()), phi, env);
|
|
1047
|
-
case "trim":
|
|
1048
|
-
return ok(strLit(sv.trim()), phi, env);
|
|
1049
|
-
case "toString":
|
|
1050
|
-
case "valueOf":
|
|
1051
|
-
return ok(strLit(sv), phi, env);
|
|
1052
|
-
case "concat": {
|
|
1053
|
-
const rest = rawArgs.map((a) => litValue(evalNode(a, env, phi, budget).value));
|
|
1054
|
-
return ok(strLit(sv + rest.map((x) => String(x)).join("")), phi, env);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
// 抽象 string 上的谓词方法 → boolean
|
|
1059
|
-
if (
|
|
1060
|
-
method === "startsWith" || method === "endsWith" || method === "includes"
|
|
1061
|
-
) {
|
|
1062
|
-
return ok(abs({ k: "prim", type: "boolean" }, undefined, undefined, "partial"), phi, env);
|
|
1063
|
-
}
|
|
1064
|
-
if (method === "toUpperCase" || method === "toLowerCase" || method === "trim" || method === "slice") {
|
|
1065
|
-
return ok(abs({ k: "prim", type: "string" }, undefined, undefined, "path"), phi, env);
|
|
1066
|
-
}
|
|
1067
|
-
if (method === "charAt") {
|
|
1068
|
-
return ok(abs({ k: "prim", type: "string" }, undefined, undefined, "path"), phi, env);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// 数组 join / at / includes
|
|
1073
|
-
if (obj.shape.k === "arr" || obj.shape.k === "tuple") {
|
|
1074
|
-
if (method === "join") {
|
|
1075
|
-
return ok(abs({ k: "prim", type: "string" }, undefined, undefined, "path"), phi, env);
|
|
1076
|
-
}
|
|
1077
|
-
if (method === "includes") {
|
|
1078
|
-
return ok(abs({ k: "prim", type: "boolean" }, undefined, undefined, "partial"), phi, env);
|
|
1079
|
-
}
|
|
1080
|
-
if (method === "at" || method === "pop" || method === "shift") {
|
|
1081
|
-
if (obj.shape.k === "tuple" && obj.shape.elements.length > 0) {
|
|
1082
|
-
const idx =
|
|
1083
|
-
method === "at"
|
|
1084
|
-
? Number(litValue(evalNode(rawArgs[0] ?? { type: "NumericLiteral", value: 0 } as Node, env, phi, budget).value) ?? 0)
|
|
1085
|
-
: 0;
|
|
1086
|
-
const els = obj.shape.elements;
|
|
1087
|
-
const pick = method === "pop" || method === "shift" ? els[0] : els[Math.max(0, Math.min(els.length - 1, idx))];
|
|
1088
|
-
return ok(pick ?? unknown, phi, env);
|
|
1089
|
-
}
|
|
1090
|
-
if (obj.shape.k === "arr") {
|
|
1091
|
-
return ok((obj.shape as { element: Abs }).element, phi, env);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
if (method === "map" && rawArgs.length >= 1) {
|
|
1097
|
-
const fnNode = rawArgs[0]!;
|
|
1098
|
-
// tuple:逐元素 map,保精确
|
|
1099
|
-
if (obj.shape.k === "tuple") {
|
|
1100
|
-
const mapped = obj.shape.elements.map((el) =>
|
|
1101
|
-
applyUnaryCallback(fnNode, el, env, phi, budget),
|
|
1102
|
-
);
|
|
1103
|
-
return ok(
|
|
1104
|
-
abs({ k: "tuple", elements: mapped }, undefined, undefined, confJoin(obj.conf, "path")),
|
|
1105
|
-
phi,
|
|
1106
|
-
env,
|
|
1107
|
-
);
|
|
1108
|
-
}
|
|
1109
|
-
const elem =
|
|
1110
|
-
obj.shape.k === "arr"
|
|
1111
|
-
? (obj.shape as { element: Abs }).element
|
|
1112
|
-
: unknown;
|
|
1113
|
-
const out = applyUnaryCallback(fnNode, elem, env, phi, budget);
|
|
1114
|
-
return ok(
|
|
1115
|
-
abs({ k: "arr", element: out }, undefined, undefined, confJoin(obj.conf, out.conf)),
|
|
1116
|
-
phi,
|
|
1117
|
-
env,
|
|
1118
|
-
);
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
if (method === "reduce" && rawArgs.length >= 2) {
|
|
1122
|
-
const fnNode = rawArgs[0]!;
|
|
1123
|
-
let acc = evalNode(rawArgs[1]!, env, phi, budget).value;
|
|
1124
|
-
if (obj.shape.k === "tuple") {
|
|
1125
|
-
for (const el of obj.shape.elements) {
|
|
1126
|
-
acc = applyBinaryCallback(fnNode, acc, el, env, phi, budget);
|
|
1127
|
-
}
|
|
1128
|
-
return ok(acc, phi, env);
|
|
1129
|
-
}
|
|
1130
|
-
const item =
|
|
1131
|
-
obj.shape.k === "arr"
|
|
1132
|
-
? (obj.shape as { element: Abs }).element
|
|
1133
|
-
: unknown;
|
|
1134
|
-
// 不动点
|
|
1135
|
-
for (let i = 0; i < 6; i++) {
|
|
1136
|
-
const next = applyBinaryCallback(fnNode, acc, item, env, phi, budget);
|
|
1137
|
-
if (absIdentical(acc, next)) {
|
|
1138
|
-
return ok(next, phi, env);
|
|
1139
|
-
}
|
|
1140
|
-
acc = joinAbs(acc, next);
|
|
1141
|
-
}
|
|
1142
|
-
return ok(acc, phi, env);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (method === "filter" && rawArgs.length >= 1) {
|
|
1146
|
-
const fnNode = rawArgs[0]!;
|
|
1147
|
-
if (obj.shape.k === "tuple") {
|
|
1148
|
-
const kept: Abs[] = [];
|
|
1149
|
-
for (const el of obj.shape.elements) {
|
|
1150
|
-
const p = applyUnaryCallback(fnNode, el, env, phi, budget);
|
|
1151
|
-
const lv = litValue(p);
|
|
1152
|
-
if (lv === false) continue;
|
|
1153
|
-
kept.push(el);
|
|
1154
|
-
}
|
|
1155
|
-
if (kept.length === 0) {
|
|
1156
|
-
return ok(abs({ k: "arr", element: unknown }, undefined, undefined, "path"), phi, env);
|
|
1157
|
-
}
|
|
1158
|
-
if (kept.length === 1) return ok(kept[0]!, phi, env);
|
|
1159
|
-
// 多元素:tuple(保精确)或 join 成 arr
|
|
1160
|
-
return ok(
|
|
1161
|
-
abs({ k: "tuple", elements: kept }, undefined, undefined, confJoin(obj.conf, "path")),
|
|
1162
|
-
phi,
|
|
1163
|
-
env,
|
|
1164
|
-
);
|
|
1165
|
-
}
|
|
1166
|
-
// arr:filter 保持元素类型
|
|
1167
|
-
return ok(obj, phi, env);
|
|
1168
|
-
}
|
|
1169
|
-
// 分派失败:prim/unknown 上记 method-missing(跨文件 import 函数体走此路径)
|
|
1170
|
-
{
|
|
1171
|
-
const loc = node.loc
|
|
1172
|
-
? ([node.loc.start.line, node.loc.start.column] as [number, number])
|
|
1173
|
-
: undefined;
|
|
1174
|
-
noteMemberDispatchMiss(obj, method, "method", loc);
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
return ok(unknown, phi, env);
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
if (callee.type !== "Identifier") {
|
|
1181
|
-
const args = node.arguments.map((a) =>
|
|
1182
|
-
a.type === "SpreadElement" ? unknown : evalNode(a as Node, env, phi, budget).value,
|
|
1183
|
-
);
|
|
1184
|
-
const calleeVal = evalNode(callee, env, phi, budget).value;
|
|
1185
|
-
return ok(applyAbsFn(calleeVal, args, env, phi, budget), phi, env);
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
const name = (callee as Identifier).name;
|
|
1189
|
-
const args = node.arguments.map((a) =>
|
|
1190
|
-
a.type === "SpreadElement" ? unknown : evalNode(a as Node, env, phi, budget).value,
|
|
1191
|
-
);
|
|
1192
|
-
|
|
1193
|
-
// 全局函数 builtin
|
|
1194
|
-
const g = evalGlobalFn(name, args);
|
|
1195
|
-
if (g) return ok(g, phi, env);
|
|
1196
|
-
|
|
1197
|
-
// 变量上的 Abs 一等函数
|
|
1198
|
-
const bound = env.vars.get(name);
|
|
1199
|
-
if (bound && getFnImpl(bound)) {
|
|
1200
|
-
const v = applyAbsFn(bound, args, env, phi, budget);
|
|
1201
|
-
const loc0 = node.loc;
|
|
1202
|
-
recordAbsCall(name, args, v, loc0 ? { line: loc0.start.line, column: loc0.start.column } : undefined);
|
|
1203
|
-
return ok(v, phi, env);
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
const value = callFunction(env, name, args, phi, budget);
|
|
1207
|
-
if (env.fns.has(name)) {
|
|
1208
|
-
const loc = node.loc;
|
|
1209
|
-
recordAbsCall(
|
|
1210
|
-
name,
|
|
1211
|
-
args,
|
|
1212
|
-
value,
|
|
1213
|
-
loc ? { line: loc.start.line, column: loc.start.column } : undefined,
|
|
1214
|
-
);
|
|
1215
|
-
}
|
|
1216
|
-
return ok(value, phi, env);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
/** 应用 Abs 一等函数(有 impl 时) */
|
|
1220
|
-
function applyAbsFn(
|
|
1221
|
-
fnVal: Abs,
|
|
1222
|
-
args: Abs[],
|
|
1223
|
-
env: AstEnv,
|
|
1224
|
-
phi: Phi,
|
|
1225
|
-
budget: LeakBudget,
|
|
1226
|
-
): Abs {
|
|
1227
|
-
const impl = getFnImpl(fnVal);
|
|
1228
|
-
if (!impl) return unknown;
|
|
1229
|
-
// WeakMap 旁路:impl.body 对象作稳定身份
|
|
1230
|
-
const implId = stableCallId(impl.body as unknown as object);
|
|
1231
|
-
const key = callBudgetKey("absfn", implId, args);
|
|
1232
|
-
const label = (fnVal.shape as { name?: string }).name ?? "anonymous";
|
|
1233
|
-
if (!enterCall(key, label)) return truncatedAbs();
|
|
1234
|
-
try {
|
|
1235
|
-
// mock withArgs 等:有 apply 钩子时按实参派发,不经 body
|
|
1236
|
-
if (impl.apply) return impl.apply(args);
|
|
1237
|
-
const base = impl.env ?? env;
|
|
1238
|
-
let local: AstEnv = { vars: new Map(base.vars), fns: base.fns };
|
|
1239
|
-
if ((base as { classes?: unknown }).classes) {
|
|
1240
|
-
(local as { classes?: unknown }).classes = (base as { classes?: unknown }).classes;
|
|
1241
|
-
}
|
|
1242
|
-
impl.params.forEach((p, i) => {
|
|
1243
|
-
local.vars.set(p, args[i] ?? unknown);
|
|
1244
|
-
});
|
|
1245
|
-
const result = evalNode(impl.body, local, phi, budget);
|
|
1246
|
-
if (impl.async) return coerceAsyncReturn(result.value);
|
|
1247
|
-
return result.value;
|
|
1248
|
-
} finally {
|
|
1249
|
-
exitCall();
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
/** 把 (x) => body 或命名函数用给定实参求值一次 */
|
|
1254
|
-
function applyUnaryCallback(
|
|
1255
|
-
fnNode: Node,
|
|
1256
|
-
arg: Abs,
|
|
1257
|
-
env: AstEnv,
|
|
1258
|
-
phi: Phi,
|
|
1259
|
-
budget: LeakBudget,
|
|
1260
|
-
): Abs {
|
|
1261
|
-
if (fnNode.type === "Identifier") {
|
|
1262
|
-
const name = (fnNode as Identifier).name;
|
|
1263
|
-
const bound = env.vars.get(name);
|
|
1264
|
-
if (bound && getFnImpl(bound)) {
|
|
1265
|
-
return applyAbsFn(bound, [arg], env, phi, budget);
|
|
1266
|
-
}
|
|
1267
|
-
if (env.fns.has(name)) {
|
|
1268
|
-
return callFunction(env, name, [arg], phi, budget);
|
|
1269
|
-
}
|
|
1270
|
-
return unknown;
|
|
1271
|
-
}
|
|
1272
|
-
const { params, body } = extractCallback(fnNode);
|
|
1273
|
-
if (!params || !body) return unknown;
|
|
1274
|
-
let local: AstEnv = { vars: new Map(env.vars), fns: env.fns };
|
|
1275
|
-
local.vars.set(params[0]!, arg);
|
|
1276
|
-
return evalNode(body, local, phi, budget).value;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
function applyBinaryCallback(
|
|
1280
|
-
fnNode: Node,
|
|
1281
|
-
a: Abs,
|
|
1282
|
-
b: Abs,
|
|
1283
|
-
env: AstEnv,
|
|
1284
|
-
phi: Phi,
|
|
1285
|
-
budget: LeakBudget,
|
|
1286
|
-
): Abs {
|
|
1287
|
-
if (fnNode.type === "Identifier") {
|
|
1288
|
-
const name = (fnNode as Identifier).name;
|
|
1289
|
-
const bound = env.vars.get(name);
|
|
1290
|
-
if (bound && getFnImpl(bound)) {
|
|
1291
|
-
return applyAbsFn(bound, [a, b], env, phi, budget);
|
|
1292
|
-
}
|
|
1293
|
-
if (env.fns.has(name)) {
|
|
1294
|
-
return callFunction(env, name, [a, b], phi, budget);
|
|
1295
|
-
}
|
|
1296
|
-
return unknown;
|
|
1297
|
-
}
|
|
1298
|
-
const { params, body } = extractCallback(fnNode);
|
|
1299
|
-
if (!params || params.length < 2 || !body) return unknown;
|
|
1300
|
-
let local: AstEnv = { vars: new Map(env.vars), fns: env.fns };
|
|
1301
|
-
local.vars.set(params[0]!, a);
|
|
1302
|
-
local.vars.set(params[1]!, b);
|
|
1303
|
-
return evalNode(body, local, phi, budget).value;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
function extractCallback(node: Node): { params?: string[]; body?: Node } {
|
|
1307
|
-
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") {
|
|
1308
|
-
const fn = node as ArrowFunctionExpression;
|
|
1309
|
-
const params = fn.params.map(paramName);
|
|
1310
|
-
// 简体箭头 (x) => x+1 的 body 是表达式;块体是 BlockStatement
|
|
1311
|
-
return { params, body: fn.body };
|
|
1312
|
-
}
|
|
1313
|
-
if (node.type === "Identifier") {
|
|
1314
|
-
// 传入的是已定义函数名 —— 由 callFunction 处理,这里返回空
|
|
1315
|
-
return {};
|
|
1316
|
-
}
|
|
1317
|
-
return {};
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
function absIdentical(a: Abs, b: Abs): boolean {
|
|
1321
|
-
if (a.shape.k !== b.shape.k) return false;
|
|
1322
|
-
if (a.term !== b.term) {
|
|
1323
|
-
if (!a.term || !b.term || termToString(a.term) !== termToString(b.term)) return false;
|
|
1324
|
-
}
|
|
1325
|
-
if ((a.pred?.op ?? "true") !== (b.pred?.op ?? "true")) return false;
|
|
1326
|
-
return true;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
function evalBlock(
|
|
1330
|
-
node: BlockStatement,
|
|
1331
|
-
env: AstEnv,
|
|
1332
|
-
phi: Phi,
|
|
1333
|
-
budget: LeakBudget,
|
|
1334
|
-
): EvalResult {
|
|
1335
|
-
let local = env;
|
|
1336
|
-
let curPhi = phi;
|
|
1337
|
-
let last: Abs = unknown;
|
|
1338
|
-
|
|
1339
|
-
for (const stmt of node.body) {
|
|
1340
|
-
const r = evalNode(stmt, local, curPhi, budget);
|
|
1341
|
-
local = r.env;
|
|
1342
|
-
curPhi = r.phi;
|
|
1343
|
-
last = r.value;
|
|
1344
|
-
if (r.returned || r.brk || r.cont || r.threw) {
|
|
1345
|
-
return { value: r.value, phi: curPhi, env: local, returned: r.returned, brk: r.brk, cont: r.cont, threw: r.threw };
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
return { value: last, phi: curPhi, env: local };
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
/** 循环固定点:有限次展开 + join(收敛即停) */
|
|
1352
|
-
const MAX_LOOP_ITERS = 8;
|
|
1353
|
-
|
|
1354
|
-
function evalFor(
|
|
1355
|
-
node: ForStatement,
|
|
1356
|
-
env: AstEnv,
|
|
1357
|
-
phi: Phi,
|
|
1358
|
-
budget: LeakBudget,
|
|
1359
|
-
): EvalResult {
|
|
1360
|
-
let local = env;
|
|
1361
|
-
if (node.init) {
|
|
1362
|
-
const r = evalNode(node.init as Node, local, phi, budget);
|
|
1363
|
-
local = r.env;
|
|
1364
|
-
}
|
|
1365
|
-
let acc: Abs = unknown;
|
|
1366
|
-
for (let i = 0; i < MAX_LOOP_ITERS; i++) {
|
|
1367
|
-
if (node.test) {
|
|
1368
|
-
const t = evalNode(node.test, local, phi, budget);
|
|
1369
|
-
const lv = litValue(t.value);
|
|
1370
|
-
if (lv === false || lv === null || lv === undefined) break;
|
|
1371
|
-
}
|
|
1372
|
-
const bodyR = evalNode(node.body, local, phi, budget);
|
|
1373
|
-
if (bodyR.returned) return bodyR;
|
|
1374
|
-
if (bodyR.threw) return bodyR;
|
|
1375
|
-
if (bodyR.brk) {
|
|
1376
|
-
local = bodyR.env;
|
|
1377
|
-
break;
|
|
1378
|
-
}
|
|
1379
|
-
local = bodyR.env;
|
|
1380
|
-
acc = joinAbs(acc, bodyR.value);
|
|
1381
|
-
if (node.update) {
|
|
1382
|
-
const u = evalNode(node.update, local, phi, budget);
|
|
1383
|
-
local = u.env;
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
return ok(acc, phi, local);
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
function evalWhile(
|
|
1390
|
-
node: WhileStatement,
|
|
1391
|
-
env: AstEnv,
|
|
1392
|
-
phi: Phi,
|
|
1393
|
-
budget: LeakBudget,
|
|
1394
|
-
): EvalResult {
|
|
1395
|
-
let local = env;
|
|
1396
|
-
let acc: Abs = unknown;
|
|
1397
|
-
for (let i = 0; i < MAX_LOOP_ITERS; i++) {
|
|
1398
|
-
const t = evalNode(node.test, local, phi, budget);
|
|
1399
|
-
const lv = litValue(t.value);
|
|
1400
|
-
if (lv === false || lv === null || lv === undefined) break;
|
|
1401
|
-
const bodyR = evalNode(node.body, local, phi, budget);
|
|
1402
|
-
if (bodyR.returned || bodyR.threw) return bodyR;
|
|
1403
|
-
if (bodyR.brk) {
|
|
1404
|
-
local = bodyR.env;
|
|
1405
|
-
break;
|
|
1406
|
-
}
|
|
1407
|
-
local = bodyR.env;
|
|
1408
|
-
acc = joinAbs(acc, bodyR.value);
|
|
1409
|
-
}
|
|
1410
|
-
return ok(acc, phi, local);
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
function evalDoWhile(
|
|
1414
|
-
node: DoWhileStatement,
|
|
1415
|
-
env: AstEnv,
|
|
1416
|
-
phi: Phi,
|
|
1417
|
-
budget: LeakBudget,
|
|
1418
|
-
): EvalResult {
|
|
1419
|
-
let local = env;
|
|
1420
|
-
let acc: Abs = unknown;
|
|
1421
|
-
for (let i = 0; i < MAX_LOOP_ITERS; i++) {
|
|
1422
|
-
const bodyR = evalNode(node.body, local, phi, budget);
|
|
1423
|
-
if (bodyR.returned || bodyR.threw) return bodyR;
|
|
1424
|
-
if (bodyR.brk) {
|
|
1425
|
-
local = bodyR.env;
|
|
1426
|
-
break;
|
|
1427
|
-
}
|
|
1428
|
-
local = bodyR.env;
|
|
1429
|
-
acc = joinAbs(acc, bodyR.value);
|
|
1430
|
-
const t = evalNode(node.test, local, phi, budget);
|
|
1431
|
-
const lv = litValue(t.value);
|
|
1432
|
-
if (lv === false || lv === null || lv === undefined) break;
|
|
1433
|
-
}
|
|
1434
|
-
return ok(acc, phi, local);
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
function evalForOf(
|
|
1438
|
-
node: ForOfStatement,
|
|
1439
|
-
env: AstEnv,
|
|
1440
|
-
phi: Phi,
|
|
1441
|
-
budget: LeakBudget,
|
|
1442
|
-
): EvalResult {
|
|
1443
|
-
const iterVal = evalNode(node.right, env, phi, budget).value;
|
|
1444
|
-
const elements: Abs[] =
|
|
1445
|
-
iterVal.shape.k === "arr"
|
|
1446
|
-
? [iterVal.shape.element]
|
|
1447
|
-
: iterVal.shape.k === "tuple"
|
|
1448
|
-
? iterVal.shape.elements
|
|
1449
|
-
: iterVal.shape.k === "sum"
|
|
1450
|
-
? iterVal.shape.members.flatMap((m) =>
|
|
1451
|
-
m.shape.k === "arr"
|
|
1452
|
-
? [m.shape.element]
|
|
1453
|
-
: m.shape.k === "tuple"
|
|
1454
|
-
? m.shape.elements
|
|
1455
|
-
: [],
|
|
1456
|
-
)
|
|
1457
|
-
: [unknown];
|
|
1458
|
-
if (elements.length === 0) elements.push(unknown);
|
|
1459
|
-
|
|
1460
|
-
const left = node.left;
|
|
1461
|
-
const bindName =
|
|
1462
|
-
left.type === "Identifier"
|
|
1463
|
-
? (left as Identifier).name
|
|
1464
|
-
: left.type === "VariableDeclaration"
|
|
1465
|
-
? ((left as VariableDeclaration).declarations[0]?.id as Identifier | undefined)?.name
|
|
1466
|
-
: undefined;
|
|
1467
|
-
if (!bindName) return ok(unknown, phi, env);
|
|
1468
|
-
|
|
1469
|
-
let local = env;
|
|
1470
|
-
let acc: Abs = unknown;
|
|
1471
|
-
const n = Math.min(elements.length, MAX_LOOP_ITERS);
|
|
1472
|
-
for (let i = 0; i < n; i++) {
|
|
1473
|
-
local = withVar(local, bindName, elements[i]!);
|
|
1474
|
-
const bodyR = evalNode(node.body, local, phi, budget);
|
|
1475
|
-
if (bodyR.returned || bodyR.threw) return bodyR;
|
|
1476
|
-
if (bodyR.brk) {
|
|
1477
|
-
local = bodyR.env;
|
|
1478
|
-
break;
|
|
1479
|
-
}
|
|
1480
|
-
local = bodyR.env;
|
|
1481
|
-
acc = joinAbs(acc, bodyR.value);
|
|
1482
|
-
}
|
|
1483
|
-
return ok(acc, phi, local);
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
function evalTry(
|
|
1487
|
-
node: TryStatement,
|
|
1488
|
-
env: AstEnv,
|
|
1489
|
-
phi: Phi,
|
|
1490
|
-
budget: LeakBudget,
|
|
1491
|
-
): EvalResult {
|
|
1492
|
-
const tryR = evalNode(node.block, env, phi, budget);
|
|
1493
|
-
let value = tryR.value;
|
|
1494
|
-
let local = tryR.env;
|
|
1495
|
-
let curPhi = tryR.phi;
|
|
1496
|
-
|
|
1497
|
-
if (tryR.threw && node.handler) {
|
|
1498
|
-
const param =
|
|
1499
|
-
node.handler.param && node.handler.param.type === "Identifier"
|
|
1500
|
-
? (node.handler.param as Identifier).name
|
|
1501
|
-
: undefined;
|
|
1502
|
-
let catchEnv = local;
|
|
1503
|
-
if (param) catchEnv = withVar(local, param, tryR.value);
|
|
1504
|
-
const catchR = evalNode(node.handler.body, catchEnv, curPhi, budget);
|
|
1505
|
-
value = catchR.value;
|
|
1506
|
-
local = catchR.env;
|
|
1507
|
-
curPhi = catchR.phi;
|
|
1508
|
-
if (catchR.returned) {
|
|
1509
|
-
if (node.finalizer) evalNode(node.finalizer, local, curPhi, budget);
|
|
1510
|
-
return { ...catchR, env: local };
|
|
1511
|
-
}
|
|
1512
|
-
if (catchR.threw) {
|
|
1513
|
-
if (node.finalizer) evalNode(node.finalizer, local, curPhi, budget);
|
|
1514
|
-
return { value: catchR.value, phi: curPhi, env: local, threw: true };
|
|
1515
|
-
}
|
|
1516
|
-
} else if (tryR.returned) {
|
|
1517
|
-
if (node.finalizer) evalNode(node.finalizer, local, curPhi, budget);
|
|
1518
|
-
return tryR;
|
|
1519
|
-
} else if (tryR.threw && !node.handler) {
|
|
1520
|
-
if (node.finalizer) evalNode(node.finalizer, local, curPhi, budget);
|
|
1521
|
-
return tryR;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
if (node.finalizer) {
|
|
1525
|
-
const fR = evalNode(node.finalizer, local, curPhi, budget);
|
|
1526
|
-
if (fR.returned || fR.threw) return fR;
|
|
1527
|
-
local = fR.env;
|
|
1528
|
-
}
|
|
1529
|
-
return { value, phi: curPhi, env: local, returned: tryR.returned };
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
function evalVarDecl(
|
|
1533
|
-
node: VariableDeclaration,
|
|
1534
|
-
env: AstEnv,
|
|
1535
|
-
phi: Phi,
|
|
1536
|
-
budget: LeakBudget,
|
|
1537
|
-
): EvalResult {
|
|
1538
|
-
let local = env;
|
|
1539
|
-
for (const d of node.declarations) {
|
|
1540
|
-
if (d.id.type !== "Identifier" || !d.init) continue;
|
|
1541
|
-
const r = evalNode(d.init, local, phi, budget);
|
|
1542
|
-
local = withVar(local, d.id.name, r.value);
|
|
1543
|
-
}
|
|
1544
|
-
return { value: unknown, phi, env: local };
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
function evalIf(
|
|
1548
|
-
node: IfStatement,
|
|
1549
|
-
env: AstEnv,
|
|
1550
|
-
phi: Phi,
|
|
1551
|
-
budget: LeakBudget,
|
|
1552
|
-
): EvalResult {
|
|
1553
|
-
const t = evalNode(node.test, env, phi, budget).value;
|
|
1554
|
-
const tv = litValue(t);
|
|
1555
|
-
|
|
1556
|
-
if (tv === true) {
|
|
1557
|
-
return evalNode(node.consequent, env, phi, budget);
|
|
1558
|
-
}
|
|
1559
|
-
if (tv === false) {
|
|
1560
|
-
if (node.alternate) return evalNode(node.alternate, env, phi, budget);
|
|
1561
|
-
return ok(unknown, phi, env);
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
const tCons = trueConstraint(t);
|
|
1565
|
-
const fCons = falseConstraint(t);
|
|
1566
|
-
const a = evalNode(node.consequent, env, tCons ? and(phi, tCons) : phi, budget);
|
|
1567
|
-
if (node.alternate) {
|
|
1568
|
-
const b = evalNode(node.alternate, env, fCons ? and(phi, fCons) : phi, budget);
|
|
1569
|
-
return { value: joinAbs(a.value, b.value), phi, env, returned: a.returned || b.returned };
|
|
1570
|
-
}
|
|
1571
|
-
// if 无 else:与 fall-through join
|
|
1572
|
-
return { value: joinAbs(a.value, unknown), phi, env };
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
// --- 便捷 API ---
|
|
1576
|
-
|
|
1577
|
-
/**
|
|
1578
|
-
* 从源码分析指定函数在给定实参下的抽象结果。
|
|
1579
|
-
*
|
|
1580
|
-
* @example
|
|
1581
|
-
* analyzeFn(`
|
|
1582
|
-
* const add = (a, b) => a + b;
|
|
1583
|
-
* function scale(x) { return add(x, 1); }
|
|
1584
|
-
* `, "scale", [numVar("x", gtNum(v("x"), 0))], gtNum(v("x"), 0))
|
|
1585
|
-
*/
|
|
1586
|
-
export function analyzeFn(
|
|
1587
|
-
source: string,
|
|
1588
|
-
fnName: string,
|
|
1589
|
-
args: Abs[],
|
|
1590
|
-
phi: Phi = pTrue,
|
|
1591
|
-
budget?: LeakBudget,
|
|
1592
|
-
file?: File,
|
|
1593
|
-
modules?: Record<string, AbsModuleExports>,
|
|
1594
|
-
): Abs {
|
|
1595
|
-
return evalSource(source, { fn: fnName, args }, { phi, budget, file, modules }).value;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
/**
|
|
1599
|
-
* 程序级 Abs 求值:注册全部顶层函数/class,再顺序执行语句。
|
|
1600
|
-
* 返回最终 env(vars 含导出绑定)。TypeValue 仅在调用方 bridge 时出现。
|
|
1601
|
-
*
|
|
1602
|
-
* seedVars / seedFns:host 注入(@nudo:mock 等)在求值前绑定。
|
|
1603
|
-
* modules:host 已求值的依赖导出表(specifier → AbsModuleExports)。
|
|
1604
|
-
*/
|
|
1605
|
-
export function evalProgramAbs(
|
|
1606
|
-
source: string,
|
|
1607
|
-
opts: EvalOptions & {
|
|
1608
|
-
seedVars?: Record<string, Abs>;
|
|
1609
|
-
seedFns?: Record<string, { params: string[]; body: Node; async?: boolean }>;
|
|
1610
|
-
modules?: Record<string, AbsModuleExports>;
|
|
1611
|
-
} = {},
|
|
1612
|
-
): { env: AstEnv; last: Abs; phi: Phi } {
|
|
1613
|
-
resetAbsCallBudget();
|
|
1614
|
-
const file = opts.file ?? parse(source);
|
|
1615
|
-
const env = emptyEnv();
|
|
1616
|
-
if (opts.seedVars) {
|
|
1617
|
-
for (const [k, v] of Object.entries(opts.seedVars)) {
|
|
1618
|
-
env.vars.set(k, v);
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
if (opts.seedFns) {
|
|
1622
|
-
for (const [k, fn] of Object.entries(opts.seedFns)) {
|
|
1623
|
-
env.fns.set(k, fn);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
// import 先绑定(ESM 提升)
|
|
1627
|
-
for (const stmt of file.program.body) {
|
|
1628
|
-
if (stmt.type === "ImportDeclaration" && opts.modules) {
|
|
1629
|
-
bindImports(stmt, env, opts.modules);
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
let phi = opts.phi ?? pTrue;
|
|
1633
|
-
let last: Abs = unknown;
|
|
1634
|
-
const budget = opts.budget ?? defaultLeakBudget;
|
|
1635
|
-
|
|
1636
|
-
// 第一遍:函数与 class
|
|
1637
|
-
for (const stmt of file.program.body) {
|
|
1638
|
-
if (stmt.type === "FunctionDeclaration" && stmt.id) {
|
|
1639
|
-
registerFunction(env, stmt);
|
|
1640
|
-
}
|
|
1641
|
-
if (stmt.type === "ClassDeclaration") {
|
|
1642
|
-
registerClassDecl(env, stmt);
|
|
1643
|
-
}
|
|
1644
|
-
if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
1645
|
-
const d = stmt.declaration;
|
|
1646
|
-
if (d.type === "FunctionDeclaration" && d.id) registerFunction(env, d);
|
|
1647
|
-
if (d.type === "ClassDeclaration") registerClassDecl(env, d);
|
|
1648
|
-
}
|
|
1649
|
-
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration) {
|
|
1650
|
-
const d = stmt.declaration;
|
|
1651
|
-
if (d.type === "FunctionDeclaration") {
|
|
1652
|
-
if (d.id) registerFunction(env, d);
|
|
1653
|
-
else {
|
|
1654
|
-
env.fns.set("default", {
|
|
1655
|
-
params: d.params.map(paramName),
|
|
1656
|
-
body: d.body,
|
|
1657
|
-
async: d.async === true,
|
|
1658
|
-
});
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
if (d.type === "ClassDeclaration") registerClassDecl(env, d);
|
|
1662
|
-
}
|
|
1663
|
-
if (stmt.type === "VariableDeclaration") {
|
|
1664
|
-
for (const d of stmt.declarations) {
|
|
1665
|
-
if (
|
|
1666
|
-
d.id.type === "Identifier" &&
|
|
1667
|
-
(d.init?.type === "ArrowFunctionExpression" ||
|
|
1668
|
-
d.init?.type === "FunctionExpression")
|
|
1669
|
-
) {
|
|
1670
|
-
const init = d.init as ArrowFunctionExpression;
|
|
1671
|
-
env.fns.set(d.id.name, {
|
|
1672
|
-
params: init.params.map(paramName),
|
|
1673
|
-
body: init.body,
|
|
1674
|
-
async: init.async === true,
|
|
1675
|
-
});
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
// 第二遍:执行顶层语句(跳过已注册的声明)
|
|
1682
|
-
let local: AstEnv = env;
|
|
1683
|
-
for (const stmt of file.program.body) {
|
|
1684
|
-
if (stmt.type === "ImportDeclaration") continue;
|
|
1685
|
-
if (stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration") continue;
|
|
1686
|
-
if (
|
|
1687
|
-
stmt.type === "ExportNamedDeclaration" ||
|
|
1688
|
-
stmt.type === "ExportDefaultDeclaration"
|
|
1689
|
-
) {
|
|
1690
|
-
const decl = (stmt as { declaration?: Node }).declaration;
|
|
1691
|
-
if (decl && (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration")) {
|
|
1692
|
-
continue;
|
|
1693
|
-
}
|
|
1694
|
-
if (decl) {
|
|
1695
|
-
const r = evalNode(decl, local, phi, budget);
|
|
1696
|
-
local = r.env;
|
|
1697
|
-
last = r.value;
|
|
1698
|
-
phi = r.phi;
|
|
1699
|
-
}
|
|
1700
|
-
continue;
|
|
1701
|
-
}
|
|
1702
|
-
const r = evalNode(stmt, local, phi, budget);
|
|
1703
|
-
local = r.env;
|
|
1704
|
-
last = r.value;
|
|
1705
|
-
phi = r.phi;
|
|
1706
|
-
if (r.returned || r.threw) break;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
return { env: local, last, phi };
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
/**
|
|
1713
|
-
* 收集整文件的节点 → Abs 表(LSP 无损信息源)。
|
|
1714
|
-
* 自包含源码上一次 evalProgramAbs 即可;失败返回空表。
|
|
1715
|
-
*/
|
|
1716
|
-
export function collectAbsNodeTypes(
|
|
1717
|
-
source: string,
|
|
1718
|
-
opts: EvalOptions & {
|
|
1719
|
-
seedVars?: Record<string, Abs>;
|
|
1720
|
-
seedFns?: Record<string, { params: string[]; body: Node; async?: boolean }>;
|
|
1721
|
-
} = {},
|
|
1722
|
-
): Map<Node, Abs> {
|
|
1723
|
-
const map = new Map<Node, Abs>();
|
|
1724
|
-
setAbsNodeCollector((node, value) => {
|
|
1725
|
-
map.set(node, value);
|
|
1726
|
-
});
|
|
1727
|
-
try {
|
|
1728
|
-
evalProgramAbs(source, opts);
|
|
1729
|
-
} catch {
|
|
1730
|
-
// ignore
|
|
1731
|
-
} finally {
|
|
1732
|
-
setAbsNodeCollector(null);
|
|
1733
|
-
}
|
|
1734
|
-
return map;
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
/** 按 loc 找最紧的节点 Abs(hover 用) */
|
|
1738
|
-
export function findAbsAtPosition(
|
|
1739
|
-
nodeTypes: Map<Node, Abs>,
|
|
1740
|
-
line: number,
|
|
1741
|
-
column: number,
|
|
1742
|
-
): Abs | undefined {
|
|
1743
|
-
let best: Abs | undefined;
|
|
1744
|
-
let bestSize = Infinity;
|
|
1745
|
-
for (const [node, absVal] of nodeTypes) {
|
|
1746
|
-
const loc = node.loc;
|
|
1747
|
-
if (!loc) continue;
|
|
1748
|
-
if (loc.start.line !== line && loc.end.line !== line) continue;
|
|
1749
|
-
// 1-based line, 0-based column(与 Babel loc 一致;调用方注意)
|
|
1750
|
-
const inRange =
|
|
1751
|
-
(loc.start.line === line && column >= loc.start.column && column <= loc.end.column) ||
|
|
1752
|
-
(loc.end.line === line && column >= loc.start.column && column <= loc.end.column) ||
|
|
1753
|
-
(loc.start.line < line && loc.end.line > line);
|
|
1754
|
-
if (!inRange) continue;
|
|
1755
|
-
const size =
|
|
1756
|
-
(loc.end.line - loc.start.line) * 10000 + (loc.end.column - loc.start.column);
|
|
1757
|
-
if (size < bestSize) {
|
|
1758
|
-
bestSize = size;
|
|
1759
|
-
best = absVal;
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
return best;
|
|
1763
|
-
}
|