Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 27c6ceb

Browse files
committed
Allow dynamic code generation in Jest environment
1 parent 9b5d0f2 commit 27c6ceb

File tree

6 files changed

+65
-42
lines changed

6 files changed

+65
-42
lines changed

packages/jest-environment-miniflare/src/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "@miniflare/durable-objects";
1212
import { HTMLRewriterPlugin } from "@miniflare/html-rewriter";
1313
import { KVPlugin } from "@miniflare/kv";
14-
import { VMScriptRunner, proxiedGlobals } from "@miniflare/runner-vm";
14+
import { VMScriptRunner, makeProxiedGlobals } from "@miniflare/runner-vm";
1515
import { Context, NoOpLog } from "@miniflare/shared";
1616
import { SitesPlugin } from "@miniflare/sites";
1717
import { WebSocketPlugin } from "@miniflare/web-sockets";
@@ -66,11 +66,12 @@ export default class MiniflareEnvironment implements JestEnvironment {
6666

6767
constructor(config: Config.ProjectConfig) {
6868
this.config = config;
69-
this.context = vm.createContext(
70-
{},
71-
{ codeGeneration: { strings: false, wasm: false } }
69+
// Intentionally allowing code generation as some coverage tools require it
70+
this.context = vm.createContext({});
71+
this.scriptRunner = new VMScriptRunner(
72+
this.context,
73+
/* blockCodeGeneration */ false
7274
);
73-
this.scriptRunner = new VMScriptRunner(this.context);
7475

7576
const global = (this.global = vm.runInContext("this", this.context));
7677
global.global = global;
@@ -168,7 +169,7 @@ export default class MiniflareEnvironment implements JestEnvironment {
168169
// Make sure Miniflare's global scope is assigned to Jest's global context,
169170
// even if we didn't run a script because we had no Durable Objects
170171
Object.assign(global, mfGlobalScope);
171-
Object.assign(global, proxiedGlobals);
172+
Object.assign(global, makeProxiedGlobals(/* blockCodeGeneration */ false));
172173

173174
// Add a way of getting bindings in modules mode to allow seeding data.
174175
// These names are intentionally verbose so they don't collide with anything

packages/jest-environment-miniflare/test/fixtures/core.worker.spec.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ test("includes custom globals", () => {
1010
expect(KEY).toBe("value");
1111
});
1212

13-
test("dynamic code generation disallowed", () => {
14-
expect(() => eval("console.log('evil')")).toThrow(EvalError);
15-
});
16-
1713
test("uses Jest console", () => {
1814
console.log("hello!");
1915
});

packages/runner-vm/src/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ import {
99
} from "@miniflare/shared";
1010
import { VMScriptRunnerError } from "./error";
1111
import { ModuleLinker } from "./linker";
12-
import { proxiedGlobals } from "./proxied";
12+
import { makeProxiedGlobals } from "./proxied";
1313

1414
export * from "./error";
1515
export * from "./proxied";
1616

1717
// noinspection JSMethodCanBeStatic
1818
export class VMScriptRunner implements ScriptRunner {
19-
constructor(private context?: vm.Context) {}
19+
constructor(
20+
private context?: vm.Context,
21+
private blockCodeGeneration = true
22+
) {}
2023

2124
private runAsScript(context: vm.Context, blueprint: ScriptBlueprint) {
2225
const script = new vm.Script(blueprint.code, {
@@ -58,7 +61,7 @@ export class VMScriptRunner implements ScriptRunner {
5861

5962
// Add proxied globals so cross-realm instanceof works correctly.
6063
// globalScope will be fresh for each call of run so it's fine to mutate it.
61-
Object.assign(globalScope, proxiedGlobals);
64+
Object.assign(globalScope, makeProxiedGlobals(this.blockCodeGeneration));
6265

6366
let context = this.context;
6467
if (context) {
@@ -67,8 +70,9 @@ export class VMScriptRunner implements ScriptRunner {
6770
Object.assign(context, globalScope);
6871
} else {
6972
// Create a new context with eval/new Function/WASM compile disabled
73+
const allow = !this.blockCodeGeneration;
7074
context = vm.createContext(globalScope, {
71-
codeGeneration: { strings: false, wasm: false },
75+
codeGeneration: { strings: allow, wasm: allow },
7276
});
7377
}
7478

packages/runner-vm/src/proxied.ts

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,30 +38,38 @@ function proxyHasInstance<T extends object>(
3838
});
3939
}
4040

41-
export const proxiedGlobals = {
42-
Object: proxyHasInstance(Object, isObject),
43-
Array: proxyHasInstance(Array, Array.isArray),
44-
Promise: proxyHasInstance(Promise, types.isPromise),
45-
RegExp: proxyHasInstance(RegExp, types.isRegExp),
46-
Error: proxyHasInstance(Error, isError(Error)),
47-
EvalError: proxyHasInstance(EvalError, isError(EvalError)),
48-
RangeError: proxyHasInstance(RangeError, isError(RangeError)),
49-
ReferenceError: proxyHasInstance(ReferenceError, isError(ReferenceError)),
50-
SyntaxError: proxyHasInstance(SyntaxError, isError(SyntaxError)),
51-
TypeError: proxyHasInstance(TypeError, isError(TypeError)),
52-
URIError: proxyHasInstance(URIError, isError(URIError)),
53-
Function: proxyHasInstance(Function, isFunction, {
54-
construct() {
55-
// We set `codeGeneration: { strings: false }` in our vm context, but
56-
// because we're passing the Function constructor from outside the realm,
57-
// we have to prevent construction ourselves.
58-
//
59-
// Constructing with the Function constructor obtained from
60-
// `Object.getPrototypeOf(function(){}).constructor` will still throw
61-
// though because of `strings: false`.
62-
throw new EvalError(
63-
"Code generation from strings disallowed for this context"
64-
);
65-
},
66-
}),
67-
};
41+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
42+
export function makeProxiedGlobals(blockCodeGeneration?: boolean) {
43+
if (!blockCodeGeneration) blockCodeGeneration = undefined;
44+
return {
45+
Object: proxyHasInstance(Object, isObject),
46+
Array: proxyHasInstance(Array, Array.isArray),
47+
Promise: proxyHasInstance(Promise, types.isPromise),
48+
RegExp: proxyHasInstance(RegExp, types.isRegExp),
49+
Error: proxyHasInstance(Error, isError(Error)),
50+
EvalError: proxyHasInstance(EvalError, isError(EvalError)),
51+
RangeError: proxyHasInstance(RangeError, isError(RangeError)),
52+
ReferenceError: proxyHasInstance(ReferenceError, isError(ReferenceError)),
53+
SyntaxError: proxyHasInstance(SyntaxError, isError(SyntaxError)),
54+
TypeError: proxyHasInstance(TypeError, isError(TypeError)),
55+
URIError: proxyHasInstance(URIError, isError(URIError)),
56+
Function: proxyHasInstance(
57+
Function,
58+
isFunction,
59+
blockCodeGeneration && {
60+
construct() {
61+
// We set `codeGeneration: { strings: false }` in our vm context, but
62+
// because we're passing the Function constructor from outside the
63+
// realm, we have to prevent construction ourselves.
64+
//
65+
// Constructing with the Function constructor obtained from
66+
// `Object.getPrototypeOf(function(){}).constructor` will still throw
67+
// though because of `strings: false`.
68+
throw new EvalError(
69+
"Code generation from strings disallowed for this context"
70+
);
71+
},
72+
}
73+
),
74+
};
75+
}

packages/runner-vm/test/index.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ test("VMScriptRunner: run: disallows WebAssembly compilation", async (t) => {
100100
"WebAssembly.compile(): Wasm code generation disallowed by embedder",
101101
});
102102
});
103+
test("VMScriptRunner: run: allows dynamic code generation if enabled", async (t) => {
104+
const runner = new VMScriptRunner(undefined, false);
105+
const addModule = await fs.readFile(path.join(fixturesPath, "add.wasm"));
106+
await runner.run({}, { code: 'eval("1 + 1")', filePath: "test.js" });
107+
await runner.run({}, { code: 'new Function("1 + 1")', filePath: "test.js" });
108+
await runner.run(
109+
{ addModule },
110+
{ code: "await WebAssembly.compile(addModule)", filePath: "test.js" },
111+
[]
112+
);
113+
t.pass();
114+
});
103115
test("VMScriptRunner: run: supports cross-realm instanceof", async (t) => {
104116
const result = await runner.run(
105117
{ outsideRegexp: /a/ },

packages/runner-vm/test/proxied.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import vm from "vm";
2-
import { proxiedGlobals } from "@miniflare/runner-vm";
2+
import { makeProxiedGlobals } from "@miniflare/runner-vm";
33
import test, { Macro } from "ava";
44

5+
const proxiedGlobals = makeProxiedGlobals(false);
6+
57
const instanceOfMacro: Macro<
68
[type: string, create: () => any, invert?: boolean]
79
> = (t, type, create, invert) => {

0 commit comments

Comments
 (0)