Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/jsc/bindings/NodeVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1619,8 +1619,6 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject
}
}

options.parsingContext->setGlobalScopeExtension(functionScope);

// Create the function using constructAnonymousFunction with the appropriate scope chain
JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, WTF::move(options), JSC::SourceTaintedOrigin::Untainted, functionScope);
RETURN_IF_EXCEPTION(scope, {});
Expand Down
57 changes: 57 additions & 0 deletions test/js/node/vm/vm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,63 @@ describe("vm", () => {
expect(e).toBeTruthy();
}
});

describe("contextExtensions", () => {
// Node (V8's ScriptCompiler::CompileFunction) wraps the extensions around
// the compiled function only. Nothing else running in the realm may see them.
const probe = "[typeof fromFirstExtension, typeof fromSecondExtension]";
const extensions = () => [{ fromFirstExtension: "first" }, { fromSecondExtension: "second" }];

test("are visible to the compiled function, later extensions shadow earlier ones", () => {
const fn = compileFunction(
"return [fromFirstExtension, fromSecondExtension, shadowed, () => shadowed, eval('shadowed')]",
[],
{
contextExtensions: [
{ fromFirstExtension: "first", shadowed: "from first" },
{ fromSecondExtension: "second", shadowed: "from second" },
],
},
);
const [first, second, shadowed, closure, evaluated] = fn();
expect([first, second, shadowed, closure(), evaluated]).toEqual([
"first",
"second",
"from second",
"from second",
"from second",
]);
});

test("are not visible to code that runs in the realm afterwards", () => {
const compiledEarlier = compileFunction(`return ${probe}`);
const fn = compileFunction(`return ${probe}`, [], { contextExtensions: extensions() });
expect(fn()).toEqual(["string", "string"]);

const notVisible = ["undefined", "undefined"];
// @ts-expect-error these identifiers are deliberately undeclared
expect([typeof fromFirstExtension, typeof fromSecondExtension]).toEqual(notVisible);
expect(runInThisContext(probe)).toEqual(notVisible);
expect(new Function(`return ${probe}`)()).toEqual(notVisible);
expect((0, eval)(probe)).toEqual(notVisible);
expect(compiledEarlier()).toEqual(notVisible);
expect(compileFunction(`return ${probe}`)()).toEqual(notVisible);
});

test("are not visible to code that runs in parsingContext afterwards", () => {
const parsingContext = createContext({});
const compiledEarlier = compileFunction(`return ${probe}`, [], { parsingContext });
const fn = compileFunction(`return ${probe}`, [], { parsingContext, contextExtensions: extensions() });
expect(fn()).toEqual(["string", "string"]);

const notVisible = ["undefined", "undefined"];
expect(runInContext(probe, parsingContext)).toEqual(notVisible);
expect(runInContext(`new Function("return ${probe}")()`, parsingContext)).toEqual(notVisible);
expect(compiledEarlier()).toEqual(notVisible);
expect(compileFunction(`return ${probe}`, [], { parsingContext })()).toEqual(notVisible);
expect(runInThisContext(probe)).toEqual(notVisible);
});
});
});
});

Expand Down
Loading