Skip to content

Commit 14cb982

Browse files
fraxkenCopilot
andauthored
fix(js-x-ray): properly deep clone and reset probe context in ProbeRunner (#400)
Update workspaces/js-x-ray/test/ProbeRunner.spec.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 283d5b6 commit 14cb982

File tree

3 files changed

+84
-2
lines changed

3 files changed

+84
-2
lines changed

.changeset/ninety-adults-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nodesecure/js-x-ray": patch
3+
---
4+
5+
Properly deep clone and reset probe context

workspaces/js-x-ray/src/ProbeRunner.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import isSerializeEnv from "./probes/isSerializeEnv.js";
2323
import type { SourceFile } from "./SourceFile.js";
2424
import type { OptionalWarningName } from "./warnings.js";
2525

26+
// CONSTANTS
27+
const kProbeOriginalContext = Symbol.for("ProbeOriginalContext");
28+
2629
export type ProbeReturn = void | null | symbol;
2730
export type ProbeContextDef = Record<string, any>;
2831
export type ProbeContext<T extends ProbeContextDef = ProbeContextDef> = {
@@ -50,7 +53,7 @@ export interface Probe<T extends ProbeContextDef = ProbeContextDef> {
5053
teardown?: (ctx: ProbeContext<T>) => void;
5154
breakOnMatch?: boolean;
5255
breakGroup?: string;
53-
context?: ProbeContext<T>;
56+
context?: T;
5457
}
5558

5659
export class ProbeRunner {
@@ -107,9 +110,18 @@ export class ProbeRunner {
107110
`Invalid probe ${probe.name}: initialize must be a function or undefined`
108111
);
109112
if (probe.initialize) {
113+
const isDefined = Reflect.defineProperty(probe, kProbeOriginalContext, {
114+
enumerable: false,
115+
value: structuredClone(probe.context),
116+
writable: false
117+
});
118+
if (!isDefined) {
119+
throw new Error(`Failed to define original context for probe '${probe.name}'`);
120+
}
121+
110122
const context = probe.initialize(this.#getProbeContext(probe));
111123
if (context) {
112-
probe.context = context;
124+
probe.context = structuredClone(context);
113125
}
114126
}
115127
}
@@ -193,6 +205,7 @@ export class ProbeRunner {
193205
finalize(): void {
194206
for (const probe of this.probes) {
195207
probe.finalize?.(this.#getProbeContext(probe));
208+
probe.context = probe[kProbeOriginalContext];
196209
}
197210
}
198211
}

workspaces/js-x-ray/test/ProbeRunner.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { ESTree } from "meriyah";
77

88
// Import Internal Dependencies
99
import {
10+
ProbeContext,
1011
ProbeRunner
1112
} from "../src/ProbeRunner.js";
1213
import { SourceFile } from "../src/SourceFile.js";
@@ -89,6 +90,29 @@ describe("ProbeRunner", () => {
8990

9091
assert.throws(instantiateProbeRunner, Error, "Invalid probe");
9192
});
93+
94+
it("should throw if one the provided probe is sealed or frozen", () => {
95+
const methods = ["seal", "freeze"];
96+
for (const method of methods) {
97+
const fakeProbe = Object[method]({
98+
name: "frozen-probe",
99+
initialize() {
100+
return {};
101+
},
102+
validateNode: mock.fn((_: ESTree.Node) => [true]),
103+
main: () => ProbeRunner.Signals.Skip
104+
});
105+
106+
assert.throws(() => {
107+
new ProbeRunner(
108+
new SourceFile(),
109+
[fakeProbe]
110+
);
111+
}, {
112+
message: "Failed to define original context for probe 'frozen-probe'"
113+
});
114+
}
115+
});
92116
});
93117

94118
describe("walk", () => {
@@ -299,5 +323,45 @@ describe("ProbeRunner", () => {
299323
expectedContext
300324
]);
301325
});
326+
327+
it("should deep clone initialization context and clear context when the probe is fully executed", () => {
328+
const fakeCtx = {};
329+
const fakeProbe: any = {
330+
initialize() {
331+
return fakeCtx;
332+
},
333+
validateNode: mock.fn((_: ESTree.Node) => [true]),
334+
main(_node: ESTree.Node, ctx: Required<ProbeContext>) {
335+
ctx.context.hello = "world";
336+
337+
return ProbeRunner.Signals.Skip;
338+
}
339+
};
340+
341+
const sourceFile = new SourceFile();
342+
343+
const pr = new ProbeRunner(
344+
sourceFile,
345+
[fakeProbe]
346+
);
347+
348+
const astNode: ESTree.Literal = {
349+
type: "Literal",
350+
value: "test"
351+
};
352+
pr.walk(astNode);
353+
assert.deepEqual(fakeProbe.context, {
354+
hello: "world"
355+
});
356+
357+
pr.finalize();
358+
359+
assert.strictEqual(fakeProbe.context, undefined);
360+
const { context = null } = fakeProbe.validateNode.mock.calls.at(0)?.arguments[1] ?? {};
361+
assert.deepEqual(context, {
362+
hello: "world"
363+
});
364+
assert.notStrictEqual(context, fakeCtx);
365+
});
302366
});
303367
});

0 commit comments

Comments
 (0)