Виртуальная машина (исполнение JavaScript)
Модуль vm предоставляет API для компиляции и выполнения кода в контекстах виртуальной машины V8. Модуль vm не является механизмом безопасности. Не используйте его для запуска ненадежного кода. Термин «песочница» используется в этих документах для обозначения отдельного контекста и не предоставляет никаких гарантий безопасности.
Код JavaScript может быть скомпилирован и запущен немедленно или скомпилирован, сохранён и запущен позже.
Распространённый случай использования — запуск кода в изолированной среде. Изолированный код использует другой контекст V8, что означает, что у него другой глобальный объект, чем у остального кода.
Контекст можно предоставить, «контекстуализируя» объект песочницы. Изолированный код рассматривает любые свойства в песочнице как глобальные переменные. Любые изменения глобальных переменных, вызванные изолированным кодом, отражаются в объекте песочницы.
const vm = require('vm');
const x = 1;
const sandbox = { x: 2 };
vm.createContext(sandbox); // Contextify the sandbox.
const code = 'x += 40; var y = 17;';
// x and y are global variables in the sandboxed environment.
// Initially, x has the value 2 because that is the value of sandbox.x.
vm.runInContext(code, sandbox);
console.log(sandbox.x); // 42
console.log(sandbox.y); // 17
console.log(x); // 1; y is not defined.
Класс: vm.SourceTextModule
Эта функция доступна только при включении флага командной строки --experimental-vm-modules.
Класс vm.SourceTextModule предоставляет низкоуровневый интерфейс для использования модулей ECMAScript в контекстах VM. Он является аналогом класса vm.Script, который тесно соответствует записям модулей исходного текста, как определено в спецификации ECMAScript.
В отличие от vm.Script, каждый объект vm.SourceTextModule привязан к контексту с момента своего создания. Операции с объектами vm.SourceTextModule по своей природе асинхронны, в отличие от синхронного характера объектов vm.Script. Однако с помощью асинхронных функций манипулирование объектами vm.SourceTextModule довольно просто.
Использование объекта vm.SourceTextModule требует четырёх этапов: создание/парсинг, связывание, создание экземпляра и оценка. Эти четыре шага проиллюстрированы в следующем примере.
Эта реализация находится на более низком уровне, чем загрузчик модулей ECMAScript. В настоящее время также нет способа взаимодействия с загрузчиком, хотя поддержка запланирована.
const vm = require('vm');
const contextifiedSandbox = vm.createContext({ secret: 42 });
(async () => {
// Step 1
//
// Create a Module by constructing a new `vm.SourceTextModule` object. This
// parses the provided source text, throwing a `SyntaxError` if anything goes
// wrong. By default, a Module is created in the top context. But here, we
// specify `contextifiedSandbox` as the context this Module belongs to.
//
// Here, we attempt to obtain the default export from the module "foo", and
// put it into local binding "secret".
const bar = new vm.SourceTextModule(`
import s from 'foo';
s;
`, { context: contextifiedSandbox });
// Step 2
//
// "Link" the imported dependencies of this Module to it.
//
// The provided linking callback (the "linker") accepts two arguments: the
// parent module (`bar` in this case) and the string that is the specifier of
// the imported module. The callback is expected to return a Module that
// corresponds to the provided specifier, with certain requirements documented
// in `module.link()`.
//
// If linking has not started for the returned Module, the same linker
// callback will be called on the returned Module.
//
// Even top-level Modules without dependencies must be explicitly linked. The
// callback provided would never be called, however.
//
// The link() method returns a Promise that will be resolved when all the
// Promises returned by the linker resolve.
//
// Note: This is a contrived example in that the linker function creates a new
// "foo" module every time it is called. In a full-fledged module system, a
// cache would probably be used to avoid duplicated modules.
async function linker(specifier, referencingModule) {
if (specifier === 'foo') {
return new vm.SourceTextModule(`
// The "secret" variable refers to the global variable we added to
// "contextifiedSandbox" when creating the context.
export default secret;
`, { context: referencingModule.context });
// Using `contextifiedSandbox` instead of `referencingModule.context`
// here would work as well.
}
throw new Error(`Unable to resolve dependency: ${specifier}`);
}
await bar.link(linker);
// Step 3
//
// Instantiate the top-level Module.
//
// Only the top-level Module needs to be explicitly instantiated; its
// dependencies will be recursively instantiated by instantiate().
bar.instantiate();
// Step 4
//
// Evaluate the Module. The evaluate() method returns a Promise with a single
// property "result" that contains the result of the very last statement
// executed in the Module. In the case of `bar`, it is `s;`, which refers to
// the default export of the `foo` module, the `secret` we set in the
// beginning to 42.
const { result } = await bar.evaluate();
console.log(result);
// Prints 42.
})();
Конструктор: new vm.SourceTextModule(code[, options])
-
code<строка> Код модуля JavaScript для парсинга -
options-
url<строка> URL, используемый в разрешении модулей и трассировках стека. По умолчанию:'vm:module(i)'гдеi— контекстно-специфичный возрастающий индекс. -
context<объект> Объект контекстуализации, возвращаемый методомvm.createContext(), для компиляции и оценки данногоModule. -
lineOffset<целое число> Указывает смещение номера строки, отображаемое в трассировках стека, созданных этимModule. -
columnOffset<целое число> Указывает смещение номера столбца, отображаемое в трассировках стека, созданных этимModule. -
initializeImportMeta<функция> Вызывается при оценке этогоModuleдля инициализацииimport.meta. Эта функция имеет сигнатуру(meta, module), гдеmeta— объектimport.metaвModule, аmodule— этот объектvm.SourceTextModule. -
importModuleDynamically<функция> Вызывается при оценке данного модуля при вызовеimport(). Эта функция имеет сигнатуру(specifier, module), гдеspecifier— спецификатор, переданныйimport(), аmodule— этот объектvm.SourceTextModule. Если этот параметр не указан, вызовыimport()отклонят запрос с ошибкойERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING. Этот метод может возвращать объект пространства имён модуля, но рекомендуется возвращатьvm.SourceTextModule, чтобы воспользоваться отслеживанием ошибок и избежать проблем с пространствами имён, содержащимиthenэкспорты функций.
-
Создаёт новый объект ES Module.
Свойства объекта import.meta, которые являются объектами, могут позволить Module получить доступ к информации за пределами указанного context, если объект создан в верхнем уровне контекста. Используйте vm.runInContext() для создания объектов в определённом контексте.
const vm = require('vm');
const contextifiedSandbox = vm.createContext({ secret: 42 });
(async () => {
const module = new vm.SourceTextModule(
'Object.getPrototypeOf(import.meta.prop).secret = secret;',
{
initializeImportMeta(meta) {
// Note: this object is created in the top context. As such,
// Object.getPrototypeOf(import.meta.prop) points to the
// Object.prototype in the top context rather than that in
// the sandbox.
meta.prop = {};
}
});
// Since module has no dependencies, the linker function will never be called.
await module.link(() => {});
module.instantiate();
await module.evaluate();
// Now, Object.prototype.secret will be equal to 42.
//
// To fix this problem, replace
// meta.prop = {};
// above with
// meta.prop = vm.runInContext('{}', contextifiedSandbox);
})();
module.dependencySpecifiers
Спецификаторы всех зависимостей данного модуля. Возвращаемый массив заморожен, чтобы запретить любые изменения в нём.
Соответствует полю [[RequestedModules]] записей модулей исходного текста в спецификации ECMAScript.
module.error
Если у module.status статус 'errored', это свойство содержит исключение, сгенерированное модулем во время оценки. Если статус отличается, обращение к этому свойству приведёт к исключению.
Значение undefined не может использоваться в случаях, когда исключение не возникает, из-за возможной неоднозначности с throw undefined;.
Соответствует полю [[EvaluationError]] записей модулей исходного текста в спецификации ECMAScript.
module.evaluate([options])
-
options<объект>-
timeout<целое число> Указывает количество миллисекунд для оценки перед завершением выполнения. Если выполнение прервано, будет выброшено исключениеError. Это значение должно быть строго положительным целым числом. -
breakOnSigint<логическое значение> Еслиtrue, выполнение будет завершено при полученииSIGINT(Ctrl+C). Существующие обработчики события, прикреплённые черезprocess.on('SIGINT')будут отключены во время выполнения скрипта, но продолжат работать после него. Если выполнение прервано, будет выброшено исключениеError.
-
- Возвращает: <Promise>
Оценивает модуль.
Этот метод должен быть вызван после создания экземпляра модуля; в противном случае будет выброшено исключение. Его также можно вызвать, когда модуль уже был оценён, в этом случае он сделает одно из двух:
- вернёт
undefined, если начальная оценка завершилась успешно (module.statusравен'evaluated') - перебросит то же исключение, которое было выброшено при начальной оценке, если начальная оценка завершилась ошибкой (
module.statusравен'errored')
Этот метод не может быть вызван, пока модуль оценивается (module.status равен 'evaluating') для предотвращения бесконечной рекурсии.
Соответствует методу Evaluate() записей модулей исходного текста в спецификации ECMAScript.
module.instantiate()
Создаёт экземпляр модуля. Этот метод должен быть вызван после завершения связывания (linkingStatus равен 'linked'); в противном случае будет выброшено исключение. Он также может выбросить исключение, если одна из зависимостей не предоставляет экспорт, необходимый родительскому модулю.
Однако, если эта функция выполнилась успешно, дальнейшие вызовы этой функции после первоначального создания экземпляра будут недействительными, чтобы соответствовать спецификации ECMAScript.
В отличие от других методов, работающих с объектами Module, эта функция завершается синхронно и не возвращает ничего.
Соответствует методу Instantiate() записей модулей исходного текста в спецификации ECMAScript.
module.link(linker)
Связывает зависимости модуля. Этот метод должен вызываться до создания экземпляра и может быть вызван только один раз для каждого модуля.
Функции linker будут переданы два параметра:
-
specifierСпецификатор запрошенного модуля:import foo from 'foo'; // ^^^^^ the module specifier
-
referencingModuleОбъектModule, на котором вызываетсяlink().
Ожидается, что функция вернёт объект Module или объект Promise, который в конечном итоге разрешится в объект Module. Возвращённый объект Module должен удовлетворять следующим двум инвариантам:
- Он должен принадлежать тому же контексту, что и родительский объект
Module. - Его свойство
linkingStatusне должно быть'errored'.
Если свойство linkingStatus возвращённого объекта Module равно 'unlinked', этот метод будет рекурсивно вызван на возвращённом объекте Module с той же предоставленной функцией linker.
Функция link() возвращает объект Promise, который будет разрешён, когда все связанные экземпляры разрешатся в допустимый объект Module, или отклонен, если функция-связующее либо сгенерирует исключение, либо вернёт недопустимый объект Module.
Функция-связующее в общих чертах соответствует абстрактной операции HostResolveImportedModule в спецификации ECMAScript, с несколькими ключевыми отличиями:
- Функция-связующее может быть асинхронной, в то время как HostResolveImportedModule — синхронная.
- Функция-связующее выполняется во время связывания, специфичном для Node.js этапе до создания экземпляра, в то время как HostResolveImportedModule вызывается во время создания экземпляра.
Фактическая реализация HostResolveImportedModule, используемая при создании экземпляра модуля, — это такая, которая возвращает модули, связанные во время связывания. Поскольку в этот момент все модули уже будут полностью связаны, реализация HostResolveImportedModule полностью синхронна в соответствии со спецификацией.
module.linkingStatus
Текущий статус связывания модуля module. Он может принимать одно из следующих значений:
-
'unlinked': Функцияmodule.link()ещё не была вызвана. -
'linking': Функцияmodule.link()была вызвана, но ещё не все обещания, возвращённые функцией-связующим, не были разрешены. -
'linked': Функцияmodule.link()была вызвана, и все её зависимости были успешно связаны. -
'errored': Функцияmodule.link()была вызвана, но хотя бы одна из её зависимостей не смогла связаться, либо потому, что обратный вызов вернул объектPromise, который отклоняется, либо потому, что объектModule, возвращённый обратным вызовом, недействителен.
module.namespace
Объект пространства имён модуля. Он доступен только после завершения создания экземпляра (module.instantiate()).
Соответствует абстрактной операции GetModuleNamespace в спецификации ECMAScript.
module.status
Текущий статус модуля. Может принимать одно из следующих значений:
-
'uninstantiated': Модуль не создан. Это может быть по следующим причинам:- Модуль был только что создан.
-
module.instantiate()была вызвана для этого модуля, но по какой-то причине потерпела неудачу.
Этот статус не содержит никакой информации о том, была ли вызвана
module.link(). Для этого см.module.linkingStatus. -
'instantiating': Модуль в настоящее время создаётся через вызовmodule.instantiate()на нём самом или родительском модуле. -
'instantiated': Модуль был успешно создан, ноmodule.evaluate()ещё не был вызван. -
'evaluating': Модуль оценивается через вызовmodule.evaluate()на нём самом или родительском модуле. -
'evaluated': Модуль был успешно оценён. -
'errored': Модуль был оценён, но было выброшено исключение.
Помимо 'errored', эта строка статуса соответствует полю [[Status]] записи модуля исходного текста в спецификации. 'errored' соответствует полю 'evaluated' в спецификации, но с [[EvaluationError]] установленным в значение, которое не является undefined.
module.url
URL текущего модуля, заданный в конструкторе.
Класс: vm.Script
Экземпляры класса vm.Script содержат предварительно скомпилированные скрипты, которые можно выполнить в определённых песочницах (или "контекстах").
new vm.Script(code, options)
-
code<строка> JavaScript-код для компиляции. -
options-
filename<строка> Указывает имя файла, используемое в отладке стека, созданной этим скриптом. -
lineOffset<число> Указывает смещение номера строки, отображаемое в отладке стека, созданной этим скриптом. -
columnOffset<число> Указывает смещение номера столбца, отображаемое в отладке стека, созданной этим скриптом. -
cachedData<Буфер> | <Массив_типов> | <DataView> Предоставляет необязательныйBufferилиTypedArray, илиDataViewс данными кэша кода V8 для предоставленного источника. При предоставлении значениеcachedDataRejectedбудет установлено вtrueилиfalseв зависимости от принятия данных V8. -
produceCachedData<логическое_значение> Когдаtrueи нетcachedData, V8 попытается создать данные кэша кода дляcode. При успехе будет созданBufferс данными кэша кода V8 и сохранён в свойствеcachedDataвозвращённого экземпляраvm.Script. ЗначениеcachedDataProducedбудет установлено вtrueилиfalseв зависимости от успешного создания данных кэша кода. Эта опция устарела в пользуscript.createCachedData(). -
importModuleDynamically<Функция> Вызывается во время оценки этого модуля, когда вызываетсяimport(). Эта функция имеет сигнатуру(specifier, module), гдеspecifier— спецификатор, переданныйimport(), аmodule— этот объектvm.SourceTextModule. Если эта опция не указана, вызовыimport()будут отклонены сERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING. Этот метод может возвращать объект Модуль Объект Пространства Имён, но рекомендуется возвращатьvm.SourceTextModule, чтобы воспользоваться отслеживанием ошибок и избежать проблем с пространствами имён, содержащими экспорт функцийthen.
-
Создание нового объекта vm.Script компилирует code, но не запускает его. Скомпилированный vm.Script можно запустить несколько раз позднее. code не связан ни с каким глобальным объектом; он связывается перед каждым запуском только для этого запуска.
script.createCachedData()
- Возвращает: <Буфер>
Создаёт кэш кода, который можно использовать с опцией cachedData конструктора Script. Возвращает буфер. Этот метод можно вызывать в любое время и любое количество раз.
const script = new vm.Script(`
function add(a, b) {
return a + b;
}
const x = add(1, 2);
`);
const cacheWithoutX = script.createCachedData();
script.runInThisContext();
const cacheWithX = script.createCachedData();
script.runInContext(contextifiedSandbox[, options])[src]
-
contextifiedSandbox<Объект> Объект, возвращаемый методомvm.createContext(), контекстуализированный (contextified). -
options<Объект>-
filename<строка> Указывает имя файла, используемое в отслеживании стека, создаваемом этим скриптом. -
lineOffset<число> Указывает смещение номера строки, отображаемое в отслеживании стека, создаваемом этим скриптом. -
columnOffset<число> Указывает смещение номера столбца, отображаемое в отслеживании стека, создаваемом этим скриптом. -
displayErrors<логическое> Еслиtrue, при возникновении ошибкиErrorво время компиляцииcode, строка кода, вызвавшая ошибку, будет добавлена к отслеживанию стека. -
timeout<целое> Указывает количество миллисекунд для выполненияcodeперед завершением выполнения. Если выполнение будет завершено, будет брошена ошибкаError. Это значение должно быть строго положительным целым числом. -
breakOnSigint: еслиtrue, выполнение будет завершено при получении сигнала прерывания (Ctrl+C). Существующие обработчики события, присоединенные с помощьюprocess.on('SIGINT'), будут отключены во время выполнения скрипта, но продолжат работать после него. Если выполнение будет завершено, будет брошена ошибкаError.
-
Выполняет скомпилированный код, содержащийся в объекте vm.Script в заданном contextifiedSandbox и возвращает результат. Выполняемый код не имеет доступа к локальной области видимости.
Следующий пример компилирует код, увеличивающий глобальную переменную, устанавливает значение другой глобальной переменной, а затем многократно выполняет код. Глобальные переменные содержатся в объекте sandbox.
const util = require('util');
const vm = require('vm');
const sandbox = {
animal: 'cat',
count: 2
};
const script = new vm.Script('count += 1; name = "kitty";');
const context = vm.createContext(sandbox);
for (let i = 0; i < 10; ++i) {
script.runInContext(context);
}
console.log(util.inspect(sandbox));
// { animal: 'cat', count: 12, name: 'kitty' }
Использование опций timeout или breakOnSigint приведет к запуску новых циклов событий и соответствующих потоков, что имеет ненулевой ухудшающий производительность.
script.runInNewContext([sandbox[, options]])[src]
-
sandbox<Объект> Объект, который будет контекстуализирован. Еслиundefined, будет создан новый объект. -
options<Объект>-
filename<строка> Указывает имя файла, используемое в отслеживании стека, создаваемом этим скриптом. -
lineOffset<число> Указывает смещение номера строки, отображаемое в отслеживании стека, создаваемом этим скриптом. -
columnOffset<число> Указывает смещение номера столбца, отображаемое в отслеживании стека, создаваемом этим скриптом. -
displayErrors<логическое> Еслиtrue, при возникновении ошибкиErrorво время компиляцииcode, строка кода, вызвавшая ошибку, будет добавлена к отслеживанию стека. -
timeout<целое> Указывает количество миллисекунд для выполненияcodeперед завершением выполнения. Если выполнение будет завершено, будет брошена ошибкаError. Это значение должно быть строго положительным целым числом. -
contextName<строка> Читаемое человеком имя нового контекста. По умолчанию:'VM Context i', гдеi— возрастающий числовой индекс созданного контекста. -
contextOrigin<строка> Происхождение соответствующего созданному контексту для отображения. Происхождение должно быть отформатировано как URL, но содержать только схему, хост и порт (при необходимости), подобно значению свойстваurl.originобъектаURL. Важно, что эта строка должна опускать конечный слеш, так как он обозначает путь. По умолчанию:''. -
contextCodeGeneration<Объект>-
strings<логическое> Если установлено в false, любые вызовыevalили конструкторов функций (Function,GeneratorFunction, и т.д.) будут вызывать ошибкуEvalError. По умолчанию:true. -
wasm<логическое> Если установлено в false, любая попытка скомпилировать модуль WebAssembly вызовет ошибкуWebAssembly.CompileError. По умолчанию:true.
-
-
Сначала контекстуализирует указанный sandbox, выполняет скомпилированный код, содержащийся в объекте vm.Script в созданном контейнере (sandbox), и возвращает результат. Выполняемый код не имеет доступа к локальной области видимости.
Следующий пример компилирует код, устанавливающий глобальную переменную, а затем многократно выполняет этот код в различных контекстах. Глобальные переменные задаются и содержатся в каждом отдельном sandbox.
const util = require('util');
const vm = require('vm');
const script = new vm.Script('globalVar = "set"');
const sandboxes = [{}, {}, {}];
sandboxes.forEach((sandbox) => {
script.runInNewContext(sandbox);
});
console.log(util.inspect(sandboxes));
// [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]
script.runInThisContext([options])[src]
-
options<Объект>-
filename<строка> Указывает имя файла, используемое в отслеживании стека, создаваемом этим скриптом. -
lineOffset<число> Указывает смещение номера строки, отображаемое в отслеживании стека, создаваемом этим скриптом. -
columnOffset<число> Указывает смещение номера столбца, отображаемое в отслеживании стека, создаваемом этим скриптом. -
displayErrors<логическое> Еслиtrue, при возникновении ошибкиErrorво время компиляцииcode, строка кода, вызвавшая ошибку, будет добавлена к отслеживанию стека. -
timeout<целое> Указывает количество миллисекунд для выполненияcodeперед завершением выполнения. Если выполнение будет завершено, будет брошена ошибкаError. Это значение должно быть строго положительным целым числом.
-
Выполняет скомпилированный код, содержащийся в vm.Script в контексте текущего объекта global. Выполняемый код не имеет доступа к локальной области видимости, но имеет доступ к текущему объекту global.
Следующий пример компилирует код, увеличивающий переменную global, а затем многократно выполняет этот код:
const vm = require('vm');
global.globalVar = 0;
const script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });
for (let i = 0; i < 1000; ++i) {
script.runInThisContext();
}
console.log(globalVar);
// 1000
vm.compileFunction(code[, params[, options]])[src]
-
code<string> Тело функции для компиляции. -
params<string[]> Массив строк, содержащий все параметры функции. -
options<Object>-
filename<string> Указывает имя файла, используемое в трассировках стека, созданных этим скриптом. По умолчанию:''. -
lineOffset<number> Указывает смещение номера строки, отображаемое в трассировках стека, созданных этим скриптом. По умолчанию:0. -
columnOffset<number> Указывает смещение номера столбца, отображаемое в трассировках стека, созданных этим скриптом. По умолчанию:0. -
cachedData<Buffer> | <TypedArray> | <DataView> Предоставляет необязательныеBufferилиTypedArray, илиDataView, содержащие данные кэша кода V8 для предоставленного исходного кода. -
produceCachedData<boolean> Указывает, нужно ли создавать новые данные кэша. По умолчанию:false. -
parsingContext<Object> Контекстуализированный песочница, в которой указанная функция должна быть скомпилирована. -
contextExtensions<Object[]> Массив, содержащий набор расширений контекста (объекты, оборачивающие текущую область видимости), которые должны быть применены во время компиляции. По умолчанию:[].
-
Компилирует предоставленный код в указанный контекст/песочницу (если контекст не указан, используется текущий контекст) и возвращает его, обернутый в функцию с указанным params.
vm.createContext([sandbox[, options]])[src]
-
sandbox<Object> -
options<Object>-
name<string> Читаемое человеком имя вновь созданного контекста. По умолчанию:'VM Context i', гдеi— возрастающий числовой индекс созданного контекста. -
origin<string> Происхождение, соответствующее вновь созданному контексту для отображения. Происхождение должно быть отформатировано как URL, но содержать только схему, хост и порт (если необходимо), подобно значению свойстваurl.originобъектаURL. Важно, что эта строка должна опускать заключительный слэш, так как он обозначает путь. По умолчанию:''. -
codeGeneration<Object>-
strings<boolean> Если установлено в false, любые вызовыevalили конструкторов функций (Function,GeneratorFunction, и т. д.) вызовут исключениеEvalError. По умолчанию:true. -
wasm<boolean> Если установлено в false, любая попытка скомпилировать модуль WebAssembly вызовет исключениеWebAssembly.CompileError. По умолчанию:true.
-
-
Если предоставлен объект sandbox, метод vm.createContext() подготовит эту песочницу, чтобы ее можно было использовать в вызовах vm.runInContext() или script.runInContext(). Внутри таких скриптов объект sandbox будет глобальным объектом, сохраняя все свои существующие свойства, а также встроенные объекты и функции, которыми обладает стандартный глобальный объект. Вне скриптов, выполняемых модулем vm, глобальные переменные останутся неизменными.
const util = require('util');
const vm = require('vm');
global.globalVar = 3;
const sandbox = { globalVar: 1 };
vm.createContext(sandbox);
vm.runInContext('globalVar *= 2;', sandbox);
console.log(util.inspect(sandbox)); // { globalVar: 2 }
console.log(util.inspect(globalVar)); // 3
Если sandbox опущен (или явно передан как undefined), будет возвращен новый пустой контекстуализированный объект песочницы.
Метод vm.createContext() главным образом полезен для создания одной песочницы, которую можно использовать для выполнения нескольких скриптов. Например, при эмуляции веб-браузера, метод можно использовать для создания одной песочницы, представляющей глобальный объект окна, а затем выполнить все <script> теги вместе в контексте этой песочницы.
Предоставленные name и origin контекста делаются видимыми через API инспектора.
vm.isContext(sandbox)[src]
Возвращает true, если предоставленный объект sandbox был контекстуализирован с помощью vm.createContext().
vm.runInContext(code, contextifiedSandbox[, options])[src]
-
code<string> JavaScript-код для компиляции и выполнения. -
contextifiedSandbox<Object> Объект контекстуализации, который будет использован в качествеglobalпри компиляции и выполненииcode. -
-
filename<string> Указывает имя файла, используемое в трассировках стека, созданных этим скриптом. -
lineOffset<number> Указывает смещение номера строки, отображаемое в трассировках стека, созданных этим скриптом. -
columnOffset<number> Указывает смещение номера столбца, отображаемое в трассировках стека, созданных этим скриптом. -
displayErrors<boolean> Приtrue, если при компиляцииcodeвозникает ошибкаError, строка кода, вызвавшая ошибку, добавляется к трассировке стека. -
timeout<integer> Указывает количество миллисекунд для выполненияcodeперед завершением выполнения. Если выполнение прерывается, будет выброшено исключениеError. Это значение должно быть целым положительным числом.
-
Метод vm.runInContext() компилирует code, выполняет его в контексте contextifiedSandbox, а затем возвращает результат. Выполнение кода не имеет доступа к локальной области видимости. Объект contextifiedSandbox должен быть предварительно контекстуализирован с помощью метода vm.createContext().
Если options — строка, она указывает имя файла.
В следующем примере компилируются и выполняются различные скрипты с использованием одного контекстуализированного объекта:
const util = require('util');
const vm = require('vm');
const sandbox = { globalVar: 1 };
vm.createContext(sandbox);
for (let i = 0; i < 10; ++i) {
vm.runInContext('globalVar *= 2;', sandbox);
}
console.log(util.inspect(sandbox));
// { globalVar: 1024 }
vm.runInNewContext(code[, sandbox[, options]])[src]
-
code<строка> JavaScript-код для компиляции и выполнения. -
sandbox<Объект> Объект, который будет контекстуализирован. Еслиundefined, будет создан новый объект. -
-
filename<строка> Указывает имя файла, используемое в отслеживании стека, созданном этим скриптом. -
lineOffset<число> Указывает смещение номера строки, отображаемое в отслеживании стека, созданном этим скриптом. -
columnOffset<число> Указывает смещение номера столбца, отображаемое в отслеживании стека, созданном этим скриптом. -
displayErrors<логическое значение> Приtrue, если при компиляцииcodeвозникает ошибкаError, строка кода, вызвавшая ошибку, добавляется в отслеживание стека. -
timeout<целое число> Указывает количество миллисекунд для выполненияcodeперед завершением выполнения. Если выполнение завершается, будет выброшена ошибкаError. Это значение должно быть строго положительным целым числом. -
contextName<строка> Читаемое человеком имя нового контекста. По умолчанию:'VM Context i', гдеi— возрастающий числовой индекс созданного контекста. -
contextOrigin<строка> Происхождение, соответствующее новому контексту для отображения. Происхождение должно быть отформатировано как URL, но содержать только схему, хост и порт (при необходимости), как значение свойстваurl.originобъектаURL. В первую очередь, эта строка должна опускать конечный слэш, поскольку он обозначает путь. По умолчанию:''.
-
Метод vm.runInNewContext() сначала контекстуализирует переданный sandbox объект (или создаёт новый sandbox объект, если передан как undefined), компилирует code, выполняет его в контексте созданного контекста, затем возвращает результат. Выполняемый код не имеет доступа к локальному объёму.
Если options — строка, то она указывает имя файла.
Следующий пример компилирует и выполняет код, увеличивающий глобальную переменную и устанавливающий новую. Эти глобальные переменные содержатся в sandbox.
const util = require('util');
const vm = require('vm');
const sandbox = {
animal: 'cat',
count: 2
};
vm.runInNewContext('count += 1; name = "kitty"', sandbox);
console.log(util.inspect(sandbox));
// { animal: 'cat', count: 3, name: 'kitty' }
vm.runInThisContext(code[, options])[src]
-
code<строка> JavaScript-код для компиляции и выполнения. -
-
filename<строка> Указывает имя файла, используемое в отслеживании стека, созданном этим скриптом. -
lineOffset<число> Указывает смещение номера строки, отображаемое в отслеживании стека, созданном этим скриптом. -
columnOffset<число> Указывает смещение номера столбца, отображаемое в отслеживании стека, созданном этим скриптом. -
displayErrors<логическое значение> Приtrue, если при компиляцииcodeвозникает ошибкаError, строка кода, вызвавшая ошибку, добавляется в отслеживание стека. -
timeout<целое число> Указывает количество миллисекунд для выполненияcodeперед завершением выполнения. Если выполнение завершается, будет выброшена ошибкаError. Это значение должно быть строго положительным целым числом.
-
vm.runInThisContext() компилирует code, выполняет его в контексте текущего global и возвращает результат. Выполняемый код не имеет доступа к локальному объёму, но имеет доступ к текущему global объекту.
Если options — строка, то она указывает имя файла.
Следующий пример иллюстрирует использование как vm.runInThisContext(), так и JavaScript-функции eval() для выполнения одного и того же кода:
const vm = require('vm');
let localVar = 'initial value';
const vmResult = vm.runInThisContext('localVar = "vm";');
console.log('vmResult:', vmResult);
console.log('localVar:', localVar);
const evalResult = eval('localVar = "eval";');
console.log('evalResult:', evalResult);
console.log('localVar:', localVar);
// vmResult: 'vm', localVar: 'initial value'
// evalResult: 'eval', localVar: 'eval'
Поскольку vm.runInThisContext() не имеет доступа к локальному объёму, значение localVar не изменяется. В отличие от eval(), которое имеет доступ к локальному объёму, поэтому значение localVar изменяется. Таким образом, vm.runInThisContext() очень похож на косвенный eval() вызов, например (0,eval)('code').
Пример: Запуск HTTP-сервера в виртуальной машине
При использовании как script.runInThisContext(), так и vm.runInThisContext(), код выполняется в текущем глобальном контексте V8. Код, переданный в этот контекст VM, будет иметь свой изолированный объём.
Для запуска простого веб-сервера с использованием модуля http код, переданный в контекст, должен либо вызвать require('http') самостоятельно, либо иметь ссылку на модуль http.
'use strict';
const vm = require('vm');
const code = `
((require) => {
const http = require('http');
http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Hello World\\n');
}).listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
})`;
vm.runInThisContext(code)(require);
В данном случае require() разделяет состояние с контекстом, из которого он передаётся. Это может создать риски при выполнении недоверенного кода, например, при нежелательном изменении объектов в контексте.
Что означает «контекстуализация» объекта?
Весь JavaScript, выполняемый в Node.js, выполняется в рамках «контекста». Согласно руководству для разработчиков V8:
В V8 контекст — это среда выполнения, которая позволяет отдельным, не связанным JavaScript-приложениям работать в одной инстанции V8. Вы должны явно указать контекст, в котором вы хотите выполнить любой JavaScript-код.
При вызове метода vm.createContext(), объект sandbox, переданный в метод (или новый объект, если sandbox — undefined), связывается внутренне с новой инстанцией контекста V8. Этот контекст V8 предоставляет code для использования методами модуля vm с изолированной глобальной средой, в которой он может работать. Процесс создания контекста V8 и его связывания с объектом sandbox и есть то, что в этом документе называется «контекстуализацией» объекта sandbox.
Ограничения таймаутов при использовании process.nextTick() и Promises
Из-за внутренней механики реализации очереди process.nextTick() и очереди микрозадач, на которой основаны Promises в V8 и Node.js, код, работающий в контексте, может «вырваться» из набора timeout с помощью vm.runInContext(), vm.runInNewContext() и vm.runInThisContext().
Например, следующий код, выполняемый vm.runInNewContext() с таймаутом 5 миллисекунд, планирует бесконечный цикл для выполнения после разрешения обещания. Запланированный цикл никогда не прерывается таймаутом:
const vm = require('vm');
function loop() {
while (1) console.log(Date.now());
}
vm.runInNewContext(
'Promise.resolve().then(loop);',
{ loop, console },
{ timeout: 5 }
);
Эта проблема также возникает, когда вызов loop() планируется с помощью функции process.nextTick().
Эта проблема возникает, потому что все контексты делят одну и ту же очередь микрозадач и nextTick.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v10.x/docs/api/vm.html