@pleger/esa-js 0.2.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/GITHUB_USAGE.md +121 -0
- package/LICENSE +21 -0
- package/NPM_PUBLISH.md +51 -0
- package/PATTERNS.md +85 -0
- package/README.md +160 -0
- package/ROADMAP_10_STEPS.md +14 -0
- package/STEP1_TRIAGE.md +139 -0
- package/aspectscript-cli.js +61 -0
- package/aspectscript.js +1227 -0
- package/conformance/01-noBR-base-reentrancy.js +15 -0
- package/conformance/02-level-sensitive-cflow.js +19 -0
- package/conformance/03-scoping-strategy-d.js +20 -0
- package/conformance/04-jp-isolation-before-order.js +17 -0
- package/esa.js +904 -0
- package/index.d.ts +123 -0
- package/instrument.js +668 -0
- package/package.json +68 -0
- package/run-conformance.js +51 -0
- package/run-script.js +193 -0
- package/run-tests.js +236 -0
- package/transform-cache.js +54 -0
package/aspectscript.js
ADDED
|
@@ -0,0 +1,1227 @@
|
|
|
1
|
+
let createESAExtension = null;
|
|
2
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.createESA === "function") {
|
|
3
|
+
createESAExtension = globalThis.createESA;
|
|
4
|
+
}
|
|
5
|
+
if (typeof require === "function") {
|
|
6
|
+
try {
|
|
7
|
+
({ createESA: createESAExtension } = require("./esa"));
|
|
8
|
+
} catch (error) {
|
|
9
|
+
// keep global fallback if available
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createAspectScript(runtimeGlobal = globalThis) {
|
|
14
|
+
const globalObject = runtimeGlobal;
|
|
15
|
+
const state = {
|
|
16
|
+
currentJP: null,
|
|
17
|
+
currentLevel: 0,
|
|
18
|
+
currentAspect: null,
|
|
19
|
+
globalDeployments: [],
|
|
20
|
+
frameStack: [],
|
|
21
|
+
pendingCalls: [],
|
|
22
|
+
downStack: [],
|
|
23
|
+
nextAspectId: 1,
|
|
24
|
+
nextScopeId: 1,
|
|
25
|
+
traceEnabled: false,
|
|
26
|
+
traceEntries: [],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function pushTrace(entry) {
|
|
30
|
+
if (!state.traceEnabled) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
state.traceEntries.push(entry);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function rootFrame() {
|
|
37
|
+
return {
|
|
38
|
+
lexicalObjects: [globalObject],
|
|
39
|
+
lexicalFunctions: [],
|
|
40
|
+
dynamicAspects: [],
|
|
41
|
+
receiver: globalObject,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
state.frameStack.push(rootFrame());
|
|
46
|
+
|
|
47
|
+
function currentFrame() {
|
|
48
|
+
return state.frameStack[state.frameStack.length - 1];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ensureAspectList(target) {
|
|
52
|
+
if (!target) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
if (!Object.prototype.hasOwnProperty.call(target, "__asDeployments")) {
|
|
56
|
+
Object.defineProperty(target, "__asDeployments", {
|
|
57
|
+
value: [],
|
|
58
|
+
writable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return target.__asDeployments;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeFn(value) {
|
|
66
|
+
if (typeof value === "function") {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
return function constantFn() {
|
|
70
|
+
return value;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeStrategy(strategy) {
|
|
75
|
+
const ss = strategy || [false, false, true];
|
|
76
|
+
return [normalizeFn(ss[0]), normalizeFn(ss[1]), normalizeFn(ss[2])];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function uniqueById(items) {
|
|
80
|
+
const out = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const item of items) {
|
|
83
|
+
if (!item || seen.has(item.id)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
seen.add(item.id);
|
|
87
|
+
out.push(item);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function sameValue(a, b) {
|
|
93
|
+
return a === b || (Number.isNaN(a) && Number.isNaN(b));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
class Env {
|
|
97
|
+
constructor(map) {
|
|
98
|
+
this.map = map || new Map();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
bind(key, value) {
|
|
102
|
+
const next = new Map(this.map);
|
|
103
|
+
if (Array.isArray(key)) {
|
|
104
|
+
next.set(key[0], key[1]);
|
|
105
|
+
} else {
|
|
106
|
+
next.set(key, value);
|
|
107
|
+
}
|
|
108
|
+
return new Env(next);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
unbind(...keys) {
|
|
112
|
+
const next = new Map(this.map);
|
|
113
|
+
for (const key of keys) {
|
|
114
|
+
next.delete(key);
|
|
115
|
+
}
|
|
116
|
+
return new Env(next);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get(key) {
|
|
120
|
+
if (!this.map.has(key)) {
|
|
121
|
+
throw new Error("Unbound key: " + key);
|
|
122
|
+
}
|
|
123
|
+
return this.map.get(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function cloneArray(value) {
|
|
128
|
+
return Array.isArray(value) ? value.slice() : value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function jpLabel(kind) {
|
|
132
|
+
return {
|
|
133
|
+
call: "call",
|
|
134
|
+
exec: "exec",
|
|
135
|
+
init: "init",
|
|
136
|
+
creation: "creation",
|
|
137
|
+
get: "pr",
|
|
138
|
+
set: "pw",
|
|
139
|
+
varGet: "vr",
|
|
140
|
+
varSet: "vw",
|
|
141
|
+
event: "event",
|
|
142
|
+
}[kind] || kind;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function nameFromInternal(internal) {
|
|
146
|
+
if (internal.kind === "event") {
|
|
147
|
+
return internal.eventType || "";
|
|
148
|
+
}
|
|
149
|
+
if (internal.name != null) {
|
|
150
|
+
return String(internal.name);
|
|
151
|
+
}
|
|
152
|
+
if (internal.methods && internal.methods.length > 0) {
|
|
153
|
+
return String(internal.methods[0]);
|
|
154
|
+
}
|
|
155
|
+
const fun = internal.fun;
|
|
156
|
+
if (fun && fun.__asDeclaredName) {
|
|
157
|
+
return fun.__asDeclaredName;
|
|
158
|
+
}
|
|
159
|
+
if (typeof fun === "function" && fun.name && fun.name !== "wrapped") {
|
|
160
|
+
return fun.name;
|
|
161
|
+
}
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function publicJoinPoint(internal, proceedImpl, metaLevel) {
|
|
166
|
+
const clone = {
|
|
167
|
+
target: internal.target,
|
|
168
|
+
fun: internal.fun,
|
|
169
|
+
name: internal.name,
|
|
170
|
+
value: internal.value,
|
|
171
|
+
args: cloneArray(internal.args) || [],
|
|
172
|
+
methods: cloneArray(internal.methods) || [],
|
|
173
|
+
finalResult: internal.finalResult,
|
|
174
|
+
metaLevel,
|
|
175
|
+
ctx: internal.ctx,
|
|
176
|
+
eventType: internal.eventType,
|
|
177
|
+
proceed: function proceed(...args) {
|
|
178
|
+
return proceedImpl(...args);
|
|
179
|
+
},
|
|
180
|
+
clone: function cloneJP() {
|
|
181
|
+
return publicJoinPoint(internal, proceedImpl, metaLevel);
|
|
182
|
+
},
|
|
183
|
+
isCall: function isCall() {
|
|
184
|
+
return internal.kind === "call";
|
|
185
|
+
},
|
|
186
|
+
isExec: function isExec() {
|
|
187
|
+
return internal.kind === "exec";
|
|
188
|
+
},
|
|
189
|
+
isInit: function isInit() {
|
|
190
|
+
return internal.kind === "init";
|
|
191
|
+
},
|
|
192
|
+
isCreation: function isCreation() {
|
|
193
|
+
return internal.kind === "creation";
|
|
194
|
+
},
|
|
195
|
+
isPropRead: function isPropRead() {
|
|
196
|
+
return internal.kind === "get";
|
|
197
|
+
},
|
|
198
|
+
isPropWrite: function isPropWrite() {
|
|
199
|
+
return internal.kind === "set" || internal.kind === "varSet";
|
|
200
|
+
},
|
|
201
|
+
isEvent: function isEvent() {
|
|
202
|
+
return internal.kind === "event";
|
|
203
|
+
},
|
|
204
|
+
toString: function toString() {
|
|
205
|
+
return "[" + jpLabel(internal.kind) + ": " + nameFromInternal(internal) + "]";
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
Object.defineProperty(clone, "__asInternal", {
|
|
209
|
+
value: internal,
|
|
210
|
+
enumerable: false,
|
|
211
|
+
configurable: true,
|
|
212
|
+
});
|
|
213
|
+
if (internal.kind === "event" && internal.ctx && typeof internal.ctx === "object") {
|
|
214
|
+
Object.assign(clone, internal.ctx);
|
|
215
|
+
}
|
|
216
|
+
return Object.freeze(clone);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function annotateErrorWithJoinPoint(error, internal) {
|
|
220
|
+
if (!error || (typeof error !== "object" && typeof error !== "function")) {
|
|
221
|
+
return error;
|
|
222
|
+
}
|
|
223
|
+
if (error.__asAnnotated) {
|
|
224
|
+
return error;
|
|
225
|
+
}
|
|
226
|
+
const jpLabelWithMeta = publicJoinPoint(internal, () => internal.finalResult, state.currentLevel).toString();
|
|
227
|
+
const receiver = currentFrame() && currentFrame().receiver;
|
|
228
|
+
const receiverName = receiver && receiver.constructor && receiver.constructor.name
|
|
229
|
+
? receiver.constructor.name
|
|
230
|
+
: (receiver === globalObject ? "global" : typeof receiver);
|
|
231
|
+
const contextLine = "[AspectScript] join point: " + jpLabelWithMeta +
|
|
232
|
+
" | level: " + state.currentLevel +
|
|
233
|
+
" | receiver: " + receiverName;
|
|
234
|
+
if (typeof error.message === "string" && !error.message.includes(contextLine)) {
|
|
235
|
+
error.message += "\n" + contextLine;
|
|
236
|
+
}
|
|
237
|
+
if (typeof error.stack === "string" && !error.stack.includes(contextLine)) {
|
|
238
|
+
error.stack += "\n" + contextLine;
|
|
239
|
+
}
|
|
240
|
+
Object.defineProperty(error, "__asAnnotated", {
|
|
241
|
+
value: true,
|
|
242
|
+
configurable: true,
|
|
243
|
+
});
|
|
244
|
+
return error;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isSuppressed(aspect) {
|
|
248
|
+
if (aspect.allowReentrance) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
for (let i = state.downStack.length - 1; i >= 0; i -= 1) {
|
|
252
|
+
const frame = state.downStack[i];
|
|
253
|
+
if (frame.level !== state.currentLevel) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (frame.aspectIds.has(aspect.id)) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function pointcutMatch(result, env) {
|
|
264
|
+
if (result === false || result == null) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (result === true) {
|
|
268
|
+
return env;
|
|
269
|
+
}
|
|
270
|
+
return result instanceof Env ? result : env;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function makePointcut(fn) {
|
|
274
|
+
if (fn && fn.__asPointcut) {
|
|
275
|
+
return fn;
|
|
276
|
+
}
|
|
277
|
+
const pc = function pointcut(jp, env) {
|
|
278
|
+
return fn(jp, env || AspectScript.emptyEnv);
|
|
279
|
+
};
|
|
280
|
+
Object.defineProperty(pc, "__asPointcut", { value: true });
|
|
281
|
+
Object.defineProperty(pc, "__asRaw", { value: fn });
|
|
282
|
+
pc.and = function and(other) {
|
|
283
|
+
const rhs = makePointcut(other);
|
|
284
|
+
return makePointcut(function andPC(jp, env) {
|
|
285
|
+
const first = pointcutMatch(pc(jp, env), env);
|
|
286
|
+
if (!first) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return rhs(jp, first);
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
pc.or = function or(other) {
|
|
293
|
+
const rhs = makePointcut(other);
|
|
294
|
+
return makePointcut(function orPC(jp, env) {
|
|
295
|
+
const first = pointcutMatch(pc(jp, env), env);
|
|
296
|
+
if (first) {
|
|
297
|
+
return first;
|
|
298
|
+
}
|
|
299
|
+
return rhs(jp, env);
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
pc.not = function not() {
|
|
303
|
+
return makePointcut(function notPC(jp, env) {
|
|
304
|
+
return pc(jp, env) ? false : env;
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
pc.inCFlowOf = function inCFlowOf(other) {
|
|
308
|
+
return pc.and(Pointcuts.cflow(other));
|
|
309
|
+
};
|
|
310
|
+
return pc;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function functionNameMatch(jp, expected) {
|
|
314
|
+
if (typeof expected === "function") {
|
|
315
|
+
return jp.fun === expected;
|
|
316
|
+
}
|
|
317
|
+
return (jp.methods || []).includes(expected) ||
|
|
318
|
+
nameFromInternal(jp) === expected ||
|
|
319
|
+
(jp.fun && jp.fun.__asDeclaredName === expected);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const Pointcuts = {
|
|
323
|
+
call(fun) {
|
|
324
|
+
return makePointcut(function callPC(jp, env) {
|
|
325
|
+
const internal = jp.__asInternal || jp;
|
|
326
|
+
return internal.kind === "call" && functionNameMatch(jp, fun) ? env : false;
|
|
327
|
+
});
|
|
328
|
+
},
|
|
329
|
+
exec(fun) {
|
|
330
|
+
return makePointcut(function execPC(jp, env) {
|
|
331
|
+
const internal = jp.__asInternal || jp;
|
|
332
|
+
return internal.kind === "exec" && functionNameMatch(jp, fun) ? env : false;
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
init(fun) {
|
|
336
|
+
return makePointcut(function initPC(jp, env) {
|
|
337
|
+
const internal = jp.__asInternal || jp;
|
|
338
|
+
return internal.kind === "init" && functionNameMatch(jp, fun) ? env : false;
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
creation(fun) {
|
|
342
|
+
return makePointcut(function creationPC(jp, env) {
|
|
343
|
+
const internal = jp.__asInternal || jp;
|
|
344
|
+
return internal.kind === "creation" && functionNameMatch(jp, fun) ? env : false;
|
|
345
|
+
});
|
|
346
|
+
},
|
|
347
|
+
get(target, name) {
|
|
348
|
+
const byNameOnly = arguments.length === 1;
|
|
349
|
+
if (arguments.length === 1) {
|
|
350
|
+
name = target;
|
|
351
|
+
target = null;
|
|
352
|
+
}
|
|
353
|
+
return makePointcut(function getPC(jp, env) {
|
|
354
|
+
const internal = jp.__asInternal || jp;
|
|
355
|
+
if (byNameOnly) {
|
|
356
|
+
if (internal.kind !== "varGet") {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
} else if (internal.kind !== "get" && internal.kind !== "varGet") {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
if (name != null && name !== "*" && internal.name !== name) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
if (target == null || target === "*") {
|
|
366
|
+
return env;
|
|
367
|
+
}
|
|
368
|
+
return internal.target === target ? env : false;
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
set(target, name) {
|
|
372
|
+
const byNameOnly = arguments.length === 1;
|
|
373
|
+
if (arguments.length === 1) {
|
|
374
|
+
name = target;
|
|
375
|
+
target = null;
|
|
376
|
+
}
|
|
377
|
+
return makePointcut(function setPC(jp, env) {
|
|
378
|
+
const internal = jp.__asInternal || jp;
|
|
379
|
+
if (byNameOnly) {
|
|
380
|
+
if (internal.kind !== "varSet") {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
} else if (internal.kind !== "set" && internal.kind !== "varSet") {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
if (name != null && name !== "*" && internal.name !== name) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
if (target == null || target === "*") {
|
|
390
|
+
return env;
|
|
391
|
+
}
|
|
392
|
+
return internal.target === target ? env : false;
|
|
393
|
+
});
|
|
394
|
+
},
|
|
395
|
+
event(type) {
|
|
396
|
+
return makePointcut(function eventPC(jp, env) {
|
|
397
|
+
const internal = jp.__asInternal || jp;
|
|
398
|
+
return internal.kind === "event" && internal.eventType === type ? env : false;
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
cflow(other) {
|
|
402
|
+
const pc = makePointcut(other);
|
|
403
|
+
return makePointcut(function cflowPC(jp, env) {
|
|
404
|
+
let parent = (jp.__asInternal || jp).parent;
|
|
405
|
+
while (parent) {
|
|
406
|
+
if (pc(publicJoinPoint(parent, () => parent.finalResult, state.currentLevel), env)) {
|
|
407
|
+
return env;
|
|
408
|
+
}
|
|
409
|
+
parent = parent.parent;
|
|
410
|
+
}
|
|
411
|
+
return false;
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
within(target) {
|
|
415
|
+
return makePointcut(function withinPC(jp, env) {
|
|
416
|
+
const internal = jp.__asInternal || jp;
|
|
417
|
+
return internal.withinObjects && internal.withinObjects.includes(target) ? env : false;
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
noBR(other, ctxFn) {
|
|
421
|
+
const pc = makePointcut(other);
|
|
422
|
+
const ctx = ctxFn || function defaultCtx() {
|
|
423
|
+
return null;
|
|
424
|
+
};
|
|
425
|
+
return makePointcut(function noBRPC(jp, env) {
|
|
426
|
+
const internal = jp.__asInternal || jp;
|
|
427
|
+
let parent = internal.parent;
|
|
428
|
+
const token = ctx(jp);
|
|
429
|
+
while (parent) {
|
|
430
|
+
const records = parent.__asNoBRMatches || [];
|
|
431
|
+
for (const record of records) {
|
|
432
|
+
if (record.pointcut === pc && sameValue(record.token, token)) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
parent = parent.parent;
|
|
437
|
+
}
|
|
438
|
+
const matched = pointcutMatch(pc(jp, env), env);
|
|
439
|
+
if (!matched) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
if (!internal.__asNoBRMatches) {
|
|
443
|
+
internal.__asNoBRMatches = [];
|
|
444
|
+
}
|
|
445
|
+
internal.__asNoBRMatches.push({ pointcut: pc, token });
|
|
446
|
+
return matched;
|
|
447
|
+
});
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
function makeAspect(kind, pointcut, advice, option) {
|
|
452
|
+
return {
|
|
453
|
+
kind,
|
|
454
|
+
pointcut: makePointcut(pointcut),
|
|
455
|
+
advice,
|
|
456
|
+
option,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function createDeployment(spec) {
|
|
461
|
+
const [c, d, f] = normalizeStrategy(spec.strategy);
|
|
462
|
+
return {
|
|
463
|
+
id: state.nextAspectId++,
|
|
464
|
+
kind: spec.kind,
|
|
465
|
+
pointcut: makePointcut(spec.pointcut),
|
|
466
|
+
advice: spec.advice,
|
|
467
|
+
level: spec.level,
|
|
468
|
+
allowReentrance: spec.allowReentrance === true,
|
|
469
|
+
c,
|
|
470
|
+
d,
|
|
471
|
+
f,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function addDeployment(target, deployment) {
|
|
476
|
+
if (!deployment.__asTarget) {
|
|
477
|
+
deployment.__asTarget = target;
|
|
478
|
+
}
|
|
479
|
+
ensureAspectList(target).push(deployment);
|
|
480
|
+
return deployment;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function removeDeployment(target, deployment) {
|
|
484
|
+
const list = ensureAspectList(target);
|
|
485
|
+
const index = list.indexOf(deployment);
|
|
486
|
+
if (index >= 0) {
|
|
487
|
+
list.splice(index, 1);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function deploy(...args) {
|
|
492
|
+
let strategy = null;
|
|
493
|
+
let aspect = null;
|
|
494
|
+
let thunk = null;
|
|
495
|
+
if (Array.isArray(args[0])) {
|
|
496
|
+
strategy = args[0];
|
|
497
|
+
aspect = args[1];
|
|
498
|
+
thunk = args[2];
|
|
499
|
+
} else {
|
|
500
|
+
aspect = args[0];
|
|
501
|
+
if (typeof args[1] === "function") {
|
|
502
|
+
thunk = args[1];
|
|
503
|
+
strategy = args[2] || [true, false, true];
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const deployment = createDeployment({
|
|
507
|
+
kind: aspect.kind,
|
|
508
|
+
pointcut: aspect.pointcut,
|
|
509
|
+
advice: aspect.advice,
|
|
510
|
+
allowReentrance: aspect.option === true,
|
|
511
|
+
level: state.currentLevel,
|
|
512
|
+
strategy,
|
|
513
|
+
});
|
|
514
|
+
if (thunk) {
|
|
515
|
+
currentFrame().dynamicAspects = currentFrame().dynamicAspects.concat([deployment]);
|
|
516
|
+
try {
|
|
517
|
+
return thunk();
|
|
518
|
+
} finally {
|
|
519
|
+
currentFrame().dynamicAspects = currentFrame().dynamicAspects.filter((item) => item !== deployment);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
state.globalDeployments.push(deployment);
|
|
523
|
+
return deployment;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function undeploy(deployment) {
|
|
527
|
+
const index = state.globalDeployments.indexOf(deployment);
|
|
528
|
+
if (index >= 0) {
|
|
529
|
+
state.globalDeployments.splice(index, 1);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const frame = currentFrame();
|
|
533
|
+
frame.dynamicAspects = frame.dynamicAspects.filter((item) => item !== deployment);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function deployOn(...args) {
|
|
537
|
+
let strategy = null;
|
|
538
|
+
let aspect = null;
|
|
539
|
+
let target = null;
|
|
540
|
+
if (Array.isArray(args[0])) {
|
|
541
|
+
strategy = args[0];
|
|
542
|
+
aspect = args[1];
|
|
543
|
+
target = args[2];
|
|
544
|
+
} else {
|
|
545
|
+
aspect = args[0];
|
|
546
|
+
target = args[1];
|
|
547
|
+
strategy = args[2];
|
|
548
|
+
}
|
|
549
|
+
const deployment = createDeployment({
|
|
550
|
+
kind: aspect.kind,
|
|
551
|
+
pointcut: aspect.pointcut,
|
|
552
|
+
advice: aspect.advice,
|
|
553
|
+
allowReentrance: aspect.option === true,
|
|
554
|
+
level: state.currentLevel,
|
|
555
|
+
strategy,
|
|
556
|
+
});
|
|
557
|
+
addDeployment(target, deployment);
|
|
558
|
+
deployment.__asTarget = target;
|
|
559
|
+
return deployment;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function aspectsFromLexicalContexts() {
|
|
563
|
+
const frame = currentFrame();
|
|
564
|
+
const collected = [];
|
|
565
|
+
for (const obj of frame.lexicalObjects) {
|
|
566
|
+
collected.push(...ensureAspectList(obj));
|
|
567
|
+
}
|
|
568
|
+
for (const fn of frame.lexicalFunctions) {
|
|
569
|
+
collected.push(...ensureAspectList(fn));
|
|
570
|
+
}
|
|
571
|
+
return collected;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function currentVisibleAspects() {
|
|
575
|
+
const frame = currentFrame();
|
|
576
|
+
return uniqueById([
|
|
577
|
+
...state.globalDeployments,
|
|
578
|
+
...frame.dynamicAspects,
|
|
579
|
+
...aspectsFromLexicalContexts(),
|
|
580
|
+
]).filter((aspect) => {
|
|
581
|
+
if (aspect.__asTarget === globalObject &&
|
|
582
|
+
frame.receiver &&
|
|
583
|
+
frame.receiver !== globalObject) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
return aspect.level === state.currentLevel && !isSuppressed(aspect);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function evaluatePointcut(aspect, internal, env) {
|
|
591
|
+
if (!aspect.f(internal)) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
const previousAspect = state.currentAspect;
|
|
595
|
+
const previousLevel = state.currentLevel;
|
|
596
|
+
state.currentAspect = aspect;
|
|
597
|
+
state.currentLevel = aspect.level + 1;
|
|
598
|
+
try {
|
|
599
|
+
return pointcutMatch(aspect.pointcut(publicJoinPoint(internal, () => internal.finalResult, aspect.level + 1), env), env);
|
|
600
|
+
} finally {
|
|
601
|
+
state.currentAspect = previousAspect;
|
|
602
|
+
state.currentLevel = previousLevel;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function invokeAdvice(aspect, internal, env, proceedImpl) {
|
|
607
|
+
const previousAspect = state.currentAspect;
|
|
608
|
+
const previousLevel = state.currentLevel;
|
|
609
|
+
state.currentAspect = aspect;
|
|
610
|
+
state.currentLevel = aspect.level + 1;
|
|
611
|
+
try {
|
|
612
|
+
const proceedAtJoinPointLevel = (...args) => {
|
|
613
|
+
const levelBeforeProceed = state.currentLevel;
|
|
614
|
+
state.currentLevel = aspect.level;
|
|
615
|
+
try {
|
|
616
|
+
return proceedImpl(...args);
|
|
617
|
+
} finally {
|
|
618
|
+
state.currentLevel = levelBeforeProceed;
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
return aspect.advice(publicJoinPoint(internal, proceedAtJoinPointLevel, aspect.level + 1), env);
|
|
622
|
+
} finally {
|
|
623
|
+
state.currentAspect = previousAspect;
|
|
624
|
+
state.currentLevel = previousLevel;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function runAfterAdvice(aspect, internal, proceedImpl) {
|
|
629
|
+
const env = evaluatePointcut(aspect, internal, AspectScript.emptyEnv);
|
|
630
|
+
if (!env) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
invokeAdvice(aspect, internal, env, proceedImpl);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function weave(internal, baseProceed) {
|
|
637
|
+
const applicable = currentVisibleAspects();
|
|
638
|
+
const beforeAndAround = [];
|
|
639
|
+
const afters = [];
|
|
640
|
+
for (const aspect of applicable) {
|
|
641
|
+
if (aspect.kind === AspectScript.AFTER) {
|
|
642
|
+
afters.push(aspect);
|
|
643
|
+
} else {
|
|
644
|
+
const env = evaluatePointcut(aspect, internal, AspectScript.emptyEnv);
|
|
645
|
+
if (env) {
|
|
646
|
+
beforeAndAround.push({ aspect, env });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
pushTrace({
|
|
651
|
+
label: publicJoinPoint(internal, () => internal.finalResult, state.currentLevel).toString(),
|
|
652
|
+
kind: internal.kind,
|
|
653
|
+
matched: beforeAndAround.length + afters.length > 0,
|
|
654
|
+
beforeOrAroundMatches: beforeAndAround.length,
|
|
655
|
+
afterMatches: afters.length,
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
const joinPointFrame = currentFrame();
|
|
659
|
+
const executeBase = (...args) => {
|
|
660
|
+
const previousJP = state.currentJP;
|
|
661
|
+
const activeFrame = currentFrame();
|
|
662
|
+
const switchedFrame = activeFrame !== joinPointFrame;
|
|
663
|
+
if (switchedFrame) {
|
|
664
|
+
state.frameStack.push(joinPointFrame);
|
|
665
|
+
}
|
|
666
|
+
state.currentJP = internal;
|
|
667
|
+
try {
|
|
668
|
+
return baseProceed(...args);
|
|
669
|
+
} finally {
|
|
670
|
+
state.currentJP = previousJP;
|
|
671
|
+
if (switchedFrame) {
|
|
672
|
+
state.frameStack.pop();
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
let chain = function base(...args) {
|
|
678
|
+
return executeBase(...args);
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
let orderedBeforeAndAround = beforeAndAround;
|
|
682
|
+
if (beforeAndAround.length > 1 &&
|
|
683
|
+
beforeAndAround.every((item) => item.aspect.kind === AspectScript.BEFORE)) {
|
|
684
|
+
const noArgBefore = [];
|
|
685
|
+
const withArgBefore = [];
|
|
686
|
+
for (const item of beforeAndAround) {
|
|
687
|
+
const rawAdvice = item.aspect.advice && item.aspect.advice.__asOriginal
|
|
688
|
+
? item.aspect.advice.__asOriginal
|
|
689
|
+
: item.aspect.advice;
|
|
690
|
+
if (!rawAdvice || rawAdvice.length === 0) {
|
|
691
|
+
noArgBefore.push(item);
|
|
692
|
+
} else {
|
|
693
|
+
withArgBefore.push(item);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
orderedBeforeAndAround = noArgBefore.concat(withArgBefore);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
for (const item of orderedBeforeAndAround) {
|
|
700
|
+
const next = chain;
|
|
701
|
+
const aspect = item.aspect;
|
|
702
|
+
const env = item.env;
|
|
703
|
+
if (aspect.kind === AspectScript.BEFORE) {
|
|
704
|
+
chain = function beforeChain(...args) {
|
|
705
|
+
invokeAdvice(aspect, internal, env, (...proceedArgs) => next(...(proceedArgs.length ? proceedArgs : args)));
|
|
706
|
+
return next(...args);
|
|
707
|
+
};
|
|
708
|
+
} else {
|
|
709
|
+
chain = function aroundChain(...args) {
|
|
710
|
+
return invokeAdvice(aspect, internal, env, (...proceedArgs) => next(...(proceedArgs.length ? proceedArgs : args)));
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
const result = chain(...(internal.args || []));
|
|
717
|
+
internal.finalResult = result;
|
|
718
|
+
internal.value = internal.kind === "get" || internal.kind === "varGet" ? result : internal.value;
|
|
719
|
+
for (const aspect of afters) {
|
|
720
|
+
runAfterAdvice(aspect, internal, (...proceedArgs) => executeBase(...(proceedArgs.length ? proceedArgs : (internal.args || []))));
|
|
721
|
+
}
|
|
722
|
+
return result;
|
|
723
|
+
} catch (error) {
|
|
724
|
+
throw annotateErrorWithJoinPoint(error, internal);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function captureLexicalObjects() {
|
|
729
|
+
return uniqueById(currentFrame().lexicalObjects.map((obj, index) => ({ id: index + 1, value: obj })))
|
|
730
|
+
.map((entry) => entry.value);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function captureLexicalFunctions() {
|
|
734
|
+
return currentFrame().lexicalFunctions.slice();
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function markFunctionMetadata(fn, declaredName) {
|
|
738
|
+
if (!fn || typeof fn !== "function") {
|
|
739
|
+
return fn;
|
|
740
|
+
}
|
|
741
|
+
if (!fn.__asWrapped) {
|
|
742
|
+
Object.defineProperty(fn, "__asWrapped", { value: true });
|
|
743
|
+
Object.defineProperty(fn, "__asDeclaredName", {
|
|
744
|
+
value: declaredName || fn.name || "",
|
|
745
|
+
writable: true,
|
|
746
|
+
configurable: true,
|
|
747
|
+
});
|
|
748
|
+
Object.defineProperty(fn, "__asCapturedObjects", {
|
|
749
|
+
value: captureLexicalObjects(),
|
|
750
|
+
writable: true,
|
|
751
|
+
configurable: true,
|
|
752
|
+
});
|
|
753
|
+
Object.defineProperty(fn, "__asCapturedFunctions", {
|
|
754
|
+
value: captureLexicalFunctions(),
|
|
755
|
+
writable: true,
|
|
756
|
+
configurable: true,
|
|
757
|
+
});
|
|
758
|
+
Object.defineProperty(fn, "__asMethodNames", {
|
|
759
|
+
value: declaredName ? [declaredName] : [],
|
|
760
|
+
writable: true,
|
|
761
|
+
configurable: true,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return fn;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function setFunctionOwner(target, key, value) {
|
|
768
|
+
if (typeof value === "function" && target !== globalObject) {
|
|
769
|
+
markFunctionMetadata(value, value.__asDeclaredName || (typeof key === "string" ? key : ""));
|
|
770
|
+
value.__asOwner = target;
|
|
771
|
+
if (typeof key === "string" && !value.__asMethodNames.includes(key)) {
|
|
772
|
+
value.__asMethodNames = [key].concat(value.__asMethodNames.filter((name) => name !== key));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return value;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function wrap(fn, declaredName) {
|
|
779
|
+
markFunctionMetadata(fn, declaredName);
|
|
780
|
+
function wrapped(...args) {
|
|
781
|
+
const pending = state.pendingCalls[state.pendingCalls.length - 1];
|
|
782
|
+
const methods = pending && pending.fun === wrapped ? pending.methods : wrapped.__asMethodNames || [];
|
|
783
|
+
const target = pending && pending.fun === wrapped ? pending.target : this;
|
|
784
|
+
const parent = state.currentJP;
|
|
785
|
+
const internal = {
|
|
786
|
+
kind: "exec",
|
|
787
|
+
target,
|
|
788
|
+
fun: wrapped,
|
|
789
|
+
args: args.slice(),
|
|
790
|
+
methods: methods.slice(),
|
|
791
|
+
parent,
|
|
792
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
793
|
+
};
|
|
794
|
+
const nextDynamicAspects = pending && pending.fun === wrapped ? pending.dynamicAspects : currentFrame().dynamicAspects.slice();
|
|
795
|
+
const frame = {
|
|
796
|
+
lexicalObjects: uniqueById([
|
|
797
|
+
...wrapped.__asCapturedObjects.map((value, index) => ({ id: index + 1, value })),
|
|
798
|
+
...(wrapped.__asOwner ? [{ id: 1000000, value: wrapped.__asOwner }] : []),
|
|
799
|
+
]).map((entry) => entry.value),
|
|
800
|
+
lexicalFunctions: uniqueById([
|
|
801
|
+
...wrapped.__asCapturedFunctions.map((value, index) => ({ id: index + 1, value })),
|
|
802
|
+
{ id: 2000000, value: wrapped },
|
|
803
|
+
]).map((entry) => entry.value),
|
|
804
|
+
dynamicAspects: uniqueById(nextDynamicAspects),
|
|
805
|
+
receiver: target,
|
|
806
|
+
};
|
|
807
|
+
const original = fn;
|
|
808
|
+
const previousPending = state.pendingCalls[state.pendingCalls.length - 1];
|
|
809
|
+
if (previousPending && previousPending.fun === wrapped) {
|
|
810
|
+
state.pendingCalls.pop();
|
|
811
|
+
}
|
|
812
|
+
state.frameStack.push(frame);
|
|
813
|
+
try {
|
|
814
|
+
return weave(internal, (...overrideArgs) => original.apply(target, overrideArgs.length ? overrideArgs : args));
|
|
815
|
+
} finally {
|
|
816
|
+
state.frameStack.pop();
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
Object.defineProperty(wrapped, "__asWrapped", { value: true });
|
|
820
|
+
Object.defineProperty(wrapped, "__asOriginal", {
|
|
821
|
+
value: fn,
|
|
822
|
+
writable: true,
|
|
823
|
+
configurable: true,
|
|
824
|
+
});
|
|
825
|
+
Object.defineProperty(wrapped, "__asDeclaredName", {
|
|
826
|
+
value: declaredName || fn.name || "",
|
|
827
|
+
writable: true,
|
|
828
|
+
configurable: true,
|
|
829
|
+
});
|
|
830
|
+
Object.defineProperty(wrapped, "__asCapturedObjects", {
|
|
831
|
+
value: fn.__asCapturedObjects || captureLexicalObjects(),
|
|
832
|
+
writable: true,
|
|
833
|
+
configurable: true,
|
|
834
|
+
});
|
|
835
|
+
Object.defineProperty(wrapped, "__asCapturedFunctions", {
|
|
836
|
+
value: fn.__asCapturedFunctions || captureLexicalFunctions(),
|
|
837
|
+
writable: true,
|
|
838
|
+
configurable: true,
|
|
839
|
+
});
|
|
840
|
+
Object.defineProperty(wrapped, "__asMethodNames", {
|
|
841
|
+
value: fn.__asMethodNames || (declaredName ? [declaredName] : []),
|
|
842
|
+
writable: true,
|
|
843
|
+
configurable: true,
|
|
844
|
+
});
|
|
845
|
+
wrapped.prototype = fn.prototype;
|
|
846
|
+
if (wrapped.prototype && wrapped.prototype.constructor === fn) {
|
|
847
|
+
wrapped.prototype.constructor = wrapped;
|
|
848
|
+
}
|
|
849
|
+
return wrapped;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function computePropagation(callJP) {
|
|
853
|
+
return currentVisibleAspects().filter((aspect) => aspect.c(callJP));
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function call(target, fun, args, methods) {
|
|
857
|
+
const internal = {
|
|
858
|
+
kind: "call",
|
|
859
|
+
target,
|
|
860
|
+
fun,
|
|
861
|
+
args: args.slice(),
|
|
862
|
+
methods: methods.slice(),
|
|
863
|
+
parent: state.currentJP,
|
|
864
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
865
|
+
};
|
|
866
|
+
return weave(internal, (...overrideArgs) => {
|
|
867
|
+
const actualArgs = overrideArgs.length ? overrideArgs : args;
|
|
868
|
+
state.pendingCalls.push({
|
|
869
|
+
fun,
|
|
870
|
+
target,
|
|
871
|
+
methods: methods.slice(),
|
|
872
|
+
dynamicAspects: computePropagation(internal),
|
|
873
|
+
});
|
|
874
|
+
try {
|
|
875
|
+
return fun.apply(target, actualArgs);
|
|
876
|
+
} finally {
|
|
877
|
+
if (state.pendingCalls[state.pendingCalls.length - 1] && state.pendingCalls[state.pendingCalls.length - 1].fun === fun) {
|
|
878
|
+
state.pendingCalls.pop();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function callProp(target, name, args, methods) {
|
|
885
|
+
const fun = getProp(target, name);
|
|
886
|
+
return call(target, fun, args, methods);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function explicitCall(fun, target, args, methods) {
|
|
890
|
+
return call(target, fun, args, methods);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function explicitApply(fun, target, args, methods) {
|
|
894
|
+
return call(target, fun, Array.isArray(args) ? args : [], methods);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function newObject(constructor, args, methods) {
|
|
898
|
+
const creationJP = {
|
|
899
|
+
kind: "creation",
|
|
900
|
+
target: null,
|
|
901
|
+
fun: constructor,
|
|
902
|
+
args: args.slice(),
|
|
903
|
+
methods: methods.slice(),
|
|
904
|
+
parent: state.currentJP,
|
|
905
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
906
|
+
};
|
|
907
|
+
return weave(creationJP, (...overrideArgs) => {
|
|
908
|
+
const actualArgs = overrideArgs.length ? overrideArgs : args;
|
|
909
|
+
if (!constructor.__asOriginal) {
|
|
910
|
+
const nativeResult = Reflect.construct(constructor, actualArgs);
|
|
911
|
+
return typeof nativeResult === "function" ? wrap(nativeResult, nativeResult.name || "") : nativeResult;
|
|
912
|
+
}
|
|
913
|
+
const originalConstructor = constructor.__asOriginal;
|
|
914
|
+
const target = Object.create(constructor.prototype || Object.prototype);
|
|
915
|
+
const initJP = {
|
|
916
|
+
kind: "init",
|
|
917
|
+
target,
|
|
918
|
+
fun: constructor,
|
|
919
|
+
args: actualArgs.slice(),
|
|
920
|
+
methods: methods.slice(),
|
|
921
|
+
parent: state.currentJP,
|
|
922
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
923
|
+
};
|
|
924
|
+
for (const aspect of currentVisibleAspects()) {
|
|
925
|
+
if (aspect.d(initJP)) {
|
|
926
|
+
addDeployment(target, createDeployment({
|
|
927
|
+
kind: aspect.kind,
|
|
928
|
+
pointcut: aspect.pointcut,
|
|
929
|
+
advice: aspect.advice,
|
|
930
|
+
allowReentrance: aspect.allowReentrance,
|
|
931
|
+
level: aspect.level,
|
|
932
|
+
strategy: [aspect.c, aspect.d, aspect.f],
|
|
933
|
+
}));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
const result = weave(initJP, (...proceedArgs) => {
|
|
937
|
+
const usedArgs = proceedArgs.length ? proceedArgs : actualArgs;
|
|
938
|
+
state.pendingCalls.push({
|
|
939
|
+
fun: constructor,
|
|
940
|
+
target,
|
|
941
|
+
methods: methods.slice(),
|
|
942
|
+
dynamicAspects: computePropagation({
|
|
943
|
+
kind: "call",
|
|
944
|
+
target,
|
|
945
|
+
fun: constructor,
|
|
946
|
+
args: usedArgs,
|
|
947
|
+
methods: methods.slice(),
|
|
948
|
+
parent: state.currentJP,
|
|
949
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
950
|
+
}),
|
|
951
|
+
});
|
|
952
|
+
try {
|
|
953
|
+
const returned = originalConstructor.apply(target, usedArgs);
|
|
954
|
+
const finalValue = returned && (typeof returned === "object" || typeof returned === "function") ? returned : target;
|
|
955
|
+
if (typeof finalValue === "function") {
|
|
956
|
+
return wrap(finalValue, finalValue.name || "");
|
|
957
|
+
}
|
|
958
|
+
return finalValue;
|
|
959
|
+
} finally {
|
|
960
|
+
state.pendingCalls.pop();
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
creationJP.target = result;
|
|
964
|
+
return result;
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function getProp(target, name) {
|
|
969
|
+
const internal = {
|
|
970
|
+
kind: "get",
|
|
971
|
+
target,
|
|
972
|
+
name,
|
|
973
|
+
methods: [],
|
|
974
|
+
args: [],
|
|
975
|
+
parent: state.currentJP,
|
|
976
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
977
|
+
};
|
|
978
|
+
return weave(internal, () => target[name]);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function setProp(target, name, value) {
|
|
982
|
+
const internal = {
|
|
983
|
+
kind: "set",
|
|
984
|
+
target,
|
|
985
|
+
name,
|
|
986
|
+
value,
|
|
987
|
+
methods: [],
|
|
988
|
+
args: [],
|
|
989
|
+
parent: state.currentJP,
|
|
990
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
991
|
+
};
|
|
992
|
+
return weave(internal, (overrideValue) => {
|
|
993
|
+
const assigned = overrideValue !== undefined ? overrideValue : value;
|
|
994
|
+
target[name] = setFunctionOwner(target, name, assigned);
|
|
995
|
+
internal.value = assigned;
|
|
996
|
+
return assigned;
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function getVar(scope, name, getter, mode) {
|
|
1001
|
+
const kind = mode === "global" ? "get" : "varGet";
|
|
1002
|
+
const internal = {
|
|
1003
|
+
kind,
|
|
1004
|
+
target: scope,
|
|
1005
|
+
name,
|
|
1006
|
+
methods: [],
|
|
1007
|
+
args: [],
|
|
1008
|
+
parent: state.currentJP,
|
|
1009
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
1010
|
+
};
|
|
1011
|
+
return weave(internal, getter);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function setVar(scope, name, value, setter, mode) {
|
|
1015
|
+
const kind = mode === "global" ? "set" : "varSet";
|
|
1016
|
+
const internal = {
|
|
1017
|
+
kind,
|
|
1018
|
+
target: scope,
|
|
1019
|
+
name,
|
|
1020
|
+
value,
|
|
1021
|
+
methods: [],
|
|
1022
|
+
args: [],
|
|
1023
|
+
parent: state.currentJP,
|
|
1024
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
1025
|
+
};
|
|
1026
|
+
return weave(internal, (overrideValue) => {
|
|
1027
|
+
const assigned = overrideValue !== undefined ? overrideValue : value;
|
|
1028
|
+
internal.value = assigned;
|
|
1029
|
+
return setter(assigned);
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function updateVar(scope, name, getter, setter, delta, prefix, mode) {
|
|
1034
|
+
const current = getVar(scope, name, getter, mode);
|
|
1035
|
+
const next = current + delta;
|
|
1036
|
+
setVar(scope, name, next, setter, mode);
|
|
1037
|
+
return prefix ? next : current;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function updateProp(target, name, delta, prefix) {
|
|
1041
|
+
const current = getProp(target, name);
|
|
1042
|
+
const next = current + delta;
|
|
1043
|
+
setProp(target, name, next);
|
|
1044
|
+
return prefix ? next : current;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function deleteProp(target, name) {
|
|
1048
|
+
return delete target[name];
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function makeObjectLiteral(entries) {
|
|
1052
|
+
const target = {};
|
|
1053
|
+
const initJP = {
|
|
1054
|
+
kind: "init",
|
|
1055
|
+
target,
|
|
1056
|
+
fun: Object,
|
|
1057
|
+
args: [],
|
|
1058
|
+
methods: [],
|
|
1059
|
+
parent: state.currentJP,
|
|
1060
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
1061
|
+
};
|
|
1062
|
+
for (const aspect of currentVisibleAspects()) {
|
|
1063
|
+
if (aspect.d(initJP)) {
|
|
1064
|
+
addDeployment(target, createDeployment({
|
|
1065
|
+
kind: aspect.kind,
|
|
1066
|
+
pointcut: aspect.pointcut,
|
|
1067
|
+
advice: aspect.advice,
|
|
1068
|
+
allowReentrance: aspect.allowReentrance,
|
|
1069
|
+
level: aspect.level,
|
|
1070
|
+
strategy: [aspect.c, aspect.d, aspect.f],
|
|
1071
|
+
}));
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
for (const entry of entries) {
|
|
1075
|
+
setProp(target, entry.key, entry.value);
|
|
1076
|
+
}
|
|
1077
|
+
return target;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function scope(name) {
|
|
1081
|
+
return { __asScope: true, __asScopeId: state.nextScopeId++, __asName: name || "" };
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function up(thunk) {
|
|
1085
|
+
state.currentLevel += 1;
|
|
1086
|
+
try {
|
|
1087
|
+
return thunk();
|
|
1088
|
+
} finally {
|
|
1089
|
+
state.currentLevel -= 1;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function down(thunk) {
|
|
1094
|
+
const previousLevel = state.currentLevel;
|
|
1095
|
+
const lowered = Math.max(0, previousLevel - 1);
|
|
1096
|
+
const aspectIds = new Set();
|
|
1097
|
+
if (state.currentAspect && !state.currentAspect.allowReentrance) {
|
|
1098
|
+
aspectIds.add(state.currentAspect.id);
|
|
1099
|
+
}
|
|
1100
|
+
state.currentLevel = lowered;
|
|
1101
|
+
state.downStack.push({ level: lowered, aspectIds });
|
|
1102
|
+
try {
|
|
1103
|
+
return thunk();
|
|
1104
|
+
} finally {
|
|
1105
|
+
state.downStack.pop();
|
|
1106
|
+
state.currentLevel = previousLevel;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function event(type, ctx, block) {
|
|
1111
|
+
const internal = {
|
|
1112
|
+
kind: "event",
|
|
1113
|
+
eventType: type,
|
|
1114
|
+
ctx,
|
|
1115
|
+
target: globalObject,
|
|
1116
|
+
methods: [],
|
|
1117
|
+
args: [],
|
|
1118
|
+
parent: state.currentJP,
|
|
1119
|
+
withinObjects: currentFrame().lexicalObjects.slice(),
|
|
1120
|
+
};
|
|
1121
|
+
return weave(internal, (...overrideArgs) => block.apply(globalObject, overrideArgs));
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const tracer = {
|
|
1125
|
+
enable() {
|
|
1126
|
+
state.traceEnabled = true;
|
|
1127
|
+
state.traceEntries = [];
|
|
1128
|
+
},
|
|
1129
|
+
disable() {
|
|
1130
|
+
state.traceEnabled = false;
|
|
1131
|
+
},
|
|
1132
|
+
reset() {
|
|
1133
|
+
state.traceEntries = [];
|
|
1134
|
+
},
|
|
1135
|
+
getEntries() {
|
|
1136
|
+
return state.traceEntries.slice();
|
|
1137
|
+
},
|
|
1138
|
+
dump() {
|
|
1139
|
+
return state.traceEntries.slice();
|
|
1140
|
+
},
|
|
1141
|
+
toJSON(pretty) {
|
|
1142
|
+
return JSON.stringify(state.traceEntries, null, pretty ? 2 : 0);
|
|
1143
|
+
},
|
|
1144
|
+
saveToFile(filePath, pretty) {
|
|
1145
|
+
if (typeof require !== "function") {
|
|
1146
|
+
throw new Error("saveToFile is only available in Node.js environments");
|
|
1147
|
+
}
|
|
1148
|
+
const fs = require("fs");
|
|
1149
|
+
fs.writeFileSync(String(filePath), JSON.stringify(state.traceEntries, null, pretty === false ? 0 : 2), "utf8");
|
|
1150
|
+
},
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const i13n = {};
|
|
1154
|
+
for (const key of ["wrap", "call", "call2", "propWrite", "propWrite2", "withPush", "withPop", "objectInWithHasProperty", "uncertainWrite"]) {
|
|
1155
|
+
i13n[key] = function notExposed() {
|
|
1156
|
+
throw new Error("Instrumentation helper is internal");
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const AspectScript = {
|
|
1161
|
+
BEFORE: "before",
|
|
1162
|
+
AROUND: "around",
|
|
1163
|
+
AFTER: "after",
|
|
1164
|
+
Pointcuts,
|
|
1165
|
+
emptyEnv: new Env(),
|
|
1166
|
+
globalObject,
|
|
1167
|
+
tracer,
|
|
1168
|
+
i13n,
|
|
1169
|
+
Env,
|
|
1170
|
+
aspect(kind, pointcut, advice, option) {
|
|
1171
|
+
return makeAspect(kind, pointcut, advice, option);
|
|
1172
|
+
},
|
|
1173
|
+
before(pointcut, advice, option) {
|
|
1174
|
+
return deploy(makeAspect(AspectScript.BEFORE, pointcut, advice, option));
|
|
1175
|
+
},
|
|
1176
|
+
around(pointcut, advice, option) {
|
|
1177
|
+
return deploy(makeAspect(AspectScript.AROUND, pointcut, advice, option));
|
|
1178
|
+
},
|
|
1179
|
+
after(pointcut, advice, option) {
|
|
1180
|
+
return deploy(makeAspect(AspectScript.AFTER, pointcut, advice, option));
|
|
1181
|
+
},
|
|
1182
|
+
deploy,
|
|
1183
|
+
undeploy,
|
|
1184
|
+
deployOn,
|
|
1185
|
+
up,
|
|
1186
|
+
down,
|
|
1187
|
+
event,
|
|
1188
|
+
__wrapFunction: wrap,
|
|
1189
|
+
__call: call,
|
|
1190
|
+
__callProp: callProp,
|
|
1191
|
+
__explicitCall: explicitCall,
|
|
1192
|
+
__explicitApply: explicitApply,
|
|
1193
|
+
__new: newObject,
|
|
1194
|
+
__getProp: getProp,
|
|
1195
|
+
__setProp: setProp,
|
|
1196
|
+
__getVar: getVar,
|
|
1197
|
+
__setVar: setVar,
|
|
1198
|
+
__updateVar: updateVar,
|
|
1199
|
+
__updateProp: updateProp,
|
|
1200
|
+
__deleteProp: deleteProp,
|
|
1201
|
+
__makeObjectLiteral: makeObjectLiteral,
|
|
1202
|
+
__scope: scope,
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
if (typeof createESAExtension === "function") {
|
|
1206
|
+
const ESA = createESAExtension(AspectScript);
|
|
1207
|
+
AspectScript.ESA = ESA;
|
|
1208
|
+
AspectScript.PTs = ESA.Pointcuts;
|
|
1209
|
+
if (runtimeGlobal && (typeof runtimeGlobal === "object" || typeof runtimeGlobal === "function")) {
|
|
1210
|
+
runtimeGlobal.ESA = ESA;
|
|
1211
|
+
runtimeGlobal.PTs = ESA.Pointcuts;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
AspectScript.createAspectScript = createAspectScript;
|
|
1216
|
+
|
|
1217
|
+
return AspectScript;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const AspectScript = createAspectScript(globalThis);
|
|
1221
|
+
|
|
1222
|
+
globalThis.AspectScript = AspectScript;
|
|
1223
|
+
|
|
1224
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
1225
|
+
module.exports = AspectScript;
|
|
1226
|
+
module.exports.createAspectScript = createAspectScript;
|
|
1227
|
+
}
|