469 lines
11 KiB
JavaScript
469 lines
11 KiB
JavaScript
/* global host, data, VMError */
|
|
|
|
'use strict';
|
|
|
|
const LocalError = Error;
|
|
const LocalTypeError = TypeError;
|
|
const LocalWeakMap = WeakMap;
|
|
|
|
const {
|
|
apply: localReflectApply,
|
|
defineProperty: localReflectDefineProperty
|
|
} = Reflect;
|
|
|
|
const {
|
|
set: localWeakMapSet,
|
|
get: localWeakMapGet
|
|
} = LocalWeakMap.prototype;
|
|
|
|
const {
|
|
isArray: localArrayIsArray
|
|
} = Array;
|
|
|
|
function uncurryThis(func) {
|
|
return (thiz, ...args) => localReflectApply(func, thiz, args);
|
|
}
|
|
|
|
const localArrayPrototypeSlice = uncurryThis(Array.prototype.slice);
|
|
const localArrayPrototypeIncludes = uncurryThis(Array.prototype.includes);
|
|
const localArrayPrototypePush = uncurryThis(Array.prototype.push);
|
|
const localArrayPrototypeIndexOf = uncurryThis(Array.prototype.indexOf);
|
|
const localArrayPrototypeSplice = uncurryThis(Array.prototype.splice);
|
|
const localStringPrototypeStartsWith = uncurryThis(String.prototype.startsWith);
|
|
const localStringPrototypeSlice = uncurryThis(String.prototype.slice);
|
|
const localStringPrototypeIndexOf = uncurryThis(String.prototype.indexOf);
|
|
|
|
const {
|
|
argv: optionArgv,
|
|
env: optionEnv,
|
|
console: optionConsole,
|
|
vm,
|
|
resolver,
|
|
extensions
|
|
} = data;
|
|
|
|
function ensureSandboxArray(a) {
|
|
return localArrayPrototypeSlice(a);
|
|
}
|
|
|
|
const globalPaths = ensureSandboxArray(resolver.globalPaths);
|
|
|
|
class Module {
|
|
|
|
constructor(id, path, parent) {
|
|
this.id = id;
|
|
this.filename = id;
|
|
this.path = path;
|
|
this.parent = parent;
|
|
this.loaded = false;
|
|
this.paths = path ? ensureSandboxArray(resolver.genLookupPaths(path)) : [];
|
|
this.children = [];
|
|
this.exports = {};
|
|
}
|
|
|
|
_updateChildren(child, isNew) {
|
|
const children = this.children;
|
|
if (children && (isNew || !localArrayPrototypeIncludes(children, child))) {
|
|
localArrayPrototypePush(children, child);
|
|
}
|
|
}
|
|
|
|
require(id) {
|
|
return requireImpl(this, id, false);
|
|
}
|
|
|
|
}
|
|
|
|
const originalRequire = Module.prototype.require;
|
|
const cacheBuiltins = {__proto__: null};
|
|
|
|
function requireImpl(mod, id, direct) {
|
|
if (direct && mod.require !== originalRequire) {
|
|
return mod.require(id);
|
|
}
|
|
const filename = resolver.resolve(mod, id, undefined, Module._extensions, direct);
|
|
if (localStringPrototypeStartsWith(filename, 'node:')) {
|
|
id = localStringPrototypeSlice(filename, 5);
|
|
let nmod = cacheBuiltins[id];
|
|
if (!nmod) {
|
|
nmod = resolver.loadBuiltinModule(vm, id);
|
|
if (!nmod) throw new VMError(`Cannot find module '${filename}'`, 'ENOTFOUND');
|
|
cacheBuiltins[id] = nmod;
|
|
}
|
|
return nmod;
|
|
}
|
|
|
|
const cachedModule = Module._cache[filename];
|
|
if (cachedModule !== undefined) {
|
|
mod._updateChildren(cachedModule, false);
|
|
return cachedModule.exports;
|
|
}
|
|
|
|
let nmod = cacheBuiltins[id];
|
|
if (nmod) return nmod;
|
|
nmod = resolver.loadBuiltinModule(vm, id);
|
|
if (nmod) {
|
|
cacheBuiltins[id] = nmod;
|
|
return nmod;
|
|
}
|
|
|
|
const path = resolver.pathDirname(filename);
|
|
const module = new Module(filename, path, mod);
|
|
resolver.registerModule(module, filename, path, mod, direct);
|
|
mod._updateChildren(module, true);
|
|
try {
|
|
Module._cache[filename] = module;
|
|
const handler = findBestExtensionHandler(filename);
|
|
handler(module, filename);
|
|
module.loaded = true;
|
|
} catch (e) {
|
|
delete Module._cache[filename];
|
|
const children = mod.children;
|
|
if (localArrayIsArray(children)) {
|
|
const index = localArrayPrototypeIndexOf(children, module);
|
|
if (index !== -1) {
|
|
localArrayPrototypeSplice(children, index, 1);
|
|
}
|
|
}
|
|
throw e;
|
|
}
|
|
|
|
return module.exports;
|
|
}
|
|
|
|
Module.builtinModules = ensureSandboxArray(resolver.getBuiltinModulesList());
|
|
Module.globalPaths = globalPaths;
|
|
Module._extensions = {__proto__: null};
|
|
Module._cache = {__proto__: null};
|
|
|
|
{
|
|
const keys = Object.getOwnPropertyNames(extensions);
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i];
|
|
const handler = extensions[key];
|
|
Module._extensions[key] = (mod, filename) => handler(mod, filename);
|
|
}
|
|
}
|
|
|
|
function findBestExtensionHandler(filename) {
|
|
const name = resolver.pathBasename(filename);
|
|
for (let i = 0; (i = localStringPrototypeIndexOf(name, '.', i + 1)) !== -1;) {
|
|
const ext = localStringPrototypeSlice(name, i);
|
|
const handler = Module._extensions[ext];
|
|
if (handler) return handler;
|
|
}
|
|
const js = Module._extensions['.js'];
|
|
if (js) return js;
|
|
const keys = Object.getOwnPropertyNames(Module._extensions);
|
|
if (keys.length === 0) throw new VMError(`Failed to load '${filename}': Unknown type.`, 'ELOADFAIL');
|
|
return Module._extensions[keys[0]];
|
|
}
|
|
|
|
function createRequireForModule(mod) {
|
|
// eslint-disable-next-line no-shadow
|
|
function require(id) {
|
|
return requireImpl(mod, id, true);
|
|
}
|
|
function resolve(id, options) {
|
|
return resolver.resolve(mod, id, options, Module._extensions, true);
|
|
}
|
|
require.resolve = resolve;
|
|
function paths(id) {
|
|
return ensureSandboxArray(resolver.lookupPaths(mod, id));
|
|
}
|
|
resolve.paths = paths;
|
|
|
|
require.extensions = Module._extensions;
|
|
|
|
require.cache = Module._cache;
|
|
|
|
return require;
|
|
}
|
|
|
|
/**
|
|
* Prepare sandbox.
|
|
*/
|
|
|
|
const TIMERS = new LocalWeakMap();
|
|
|
|
class Timeout {
|
|
}
|
|
|
|
class Interval {
|
|
}
|
|
|
|
class Immediate {
|
|
}
|
|
|
|
function clearTimer(timer) {
|
|
const obj = localReflectApply(localWeakMapGet, TIMERS, [timer]);
|
|
if (obj) {
|
|
obj.clear(obj.value);
|
|
}
|
|
}
|
|
|
|
// This is a function and not an arrow function, since the original is also a function
|
|
// eslint-disable-next-line no-shadow
|
|
global.setTimeout = function setTimeout(callback, delay, ...args) {
|
|
if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function');
|
|
const obj = new Timeout(callback, args);
|
|
const cb = () => {
|
|
localReflectApply(callback, null, args);
|
|
};
|
|
const tmr = host.setTimeout(cb, delay);
|
|
|
|
const ref = {
|
|
__proto__: null,
|
|
clear: host.clearTimeout,
|
|
value: tmr
|
|
};
|
|
|
|
localReflectApply(localWeakMapSet, TIMERS, [obj, ref]);
|
|
return obj;
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
global.setInterval = function setInterval(callback, interval, ...args) {
|
|
if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function');
|
|
const obj = new Interval();
|
|
const cb = () => {
|
|
localReflectApply(callback, null, args);
|
|
};
|
|
const tmr = host.setInterval(cb, interval);
|
|
|
|
const ref = {
|
|
__proto__: null,
|
|
clear: host.clearInterval,
|
|
value: tmr
|
|
};
|
|
|
|
localReflectApply(localWeakMapSet, TIMERS, [obj, ref]);
|
|
return obj;
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
global.setImmediate = function setImmediate(callback, ...args) {
|
|
if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function');
|
|
const obj = new Immediate();
|
|
const cb = () => {
|
|
localReflectApply(callback, null, args);
|
|
};
|
|
const tmr = host.setImmediate(cb);
|
|
|
|
const ref = {
|
|
__proto__: null,
|
|
clear: host.clearImmediate,
|
|
value: tmr
|
|
};
|
|
|
|
localReflectApply(localWeakMapSet, TIMERS, [obj, ref]);
|
|
return obj;
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
global.clearTimeout = function clearTimeout(timeout) {
|
|
clearTimer(timeout);
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
global.clearInterval = function clearInterval(interval) {
|
|
clearTimer(interval);
|
|
};
|
|
|
|
// eslint-disable-next-line no-shadow
|
|
global.clearImmediate = function clearImmediate(immediate) {
|
|
clearTimer(immediate);
|
|
};
|
|
|
|
const localProcess = host.process;
|
|
|
|
function vmEmitArgs(event, args) {
|
|
const allargs = [event];
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (!localReflectDefineProperty(allargs, i + 1, {
|
|
__proto__: null,
|
|
value: args[i],
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true
|
|
})) throw new LocalError('Unexpected');
|
|
}
|
|
return localReflectApply(vm.emit, vm, allargs);
|
|
}
|
|
|
|
const LISTENERS = new LocalWeakMap();
|
|
const LISTENER_HANDLER = new LocalWeakMap();
|
|
|
|
/**
|
|
*
|
|
* @param {*} name
|
|
* @param {*} handler
|
|
* @this process
|
|
* @return {this}
|
|
*/
|
|
function addListener(name, handler) {
|
|
if (name !== 'beforeExit' && name !== 'exit') {
|
|
throw new LocalError(`Access denied to listen for '${name}' event.`);
|
|
}
|
|
|
|
let cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]);
|
|
if (!cb) {
|
|
cb = () => {
|
|
handler();
|
|
};
|
|
localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]);
|
|
localReflectApply(localWeakMapSet, LISTENERS, [handler, cb]);
|
|
}
|
|
|
|
localProcess.on(name, cb);
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @this process
|
|
* @return {this}
|
|
*/
|
|
// eslint-disable-next-line no-shadow
|
|
function process() {
|
|
return this;
|
|
}
|
|
|
|
const baseUptime = localProcess.uptime();
|
|
|
|
// FIXME wrong class structure
|
|
global.process = {
|
|
__proto__: process.prototype,
|
|
argv: optionArgv !== undefined ? optionArgv : [],
|
|
title: localProcess.title,
|
|
version: localProcess.version,
|
|
versions: localProcess.versions,
|
|
arch: localProcess.arch,
|
|
platform: localProcess.platform,
|
|
env: optionEnv !== undefined ? optionEnv : {},
|
|
pid: localProcess.pid,
|
|
features: localProcess.features,
|
|
nextTick: function nextTick(callback, ...args) {
|
|
if (typeof callback !== 'function') {
|
|
throw new LocalError('Callback must be a function.');
|
|
}
|
|
|
|
localProcess.nextTick(()=>{
|
|
localReflectApply(callback, null, args);
|
|
});
|
|
},
|
|
hrtime: function hrtime(time) {
|
|
return localProcess.hrtime(time);
|
|
},
|
|
uptime: function uptime() {
|
|
return localProcess.uptime() - baseUptime;
|
|
},
|
|
cwd: function cwd() {
|
|
return localProcess.cwd();
|
|
},
|
|
addListener,
|
|
on: addListener,
|
|
|
|
once: function once(name, handler) {
|
|
if (name !== 'beforeExit' && name !== 'exit') {
|
|
throw new LocalError(`Access denied to listen for '${name}' event.`);
|
|
}
|
|
|
|
let triggered = false;
|
|
const cb = () => {
|
|
if (triggered) return;
|
|
triggered = true;
|
|
localProcess.removeListener(name, cb);
|
|
handler();
|
|
};
|
|
localReflectApply(localWeakMapSet, LISTENER_HANDLER, [cb, handler]);
|
|
|
|
localProcess.on(name, cb);
|
|
|
|
return this;
|
|
},
|
|
|
|
listeners: function listeners(name) {
|
|
if (name !== 'beforeExit' && name !== 'exit') {
|
|
// Maybe add ({__proto__:null})[name] to throw when name fails in https://tc39.es/ecma262/#sec-topropertykey.
|
|
return [];
|
|
}
|
|
|
|
// Filter out listeners, which were not created in this sandbox
|
|
const all = localProcess.listeners(name);
|
|
const filtered = [];
|
|
let j = 0;
|
|
for (let i = 0; i < all.length; i++) {
|
|
const h = localReflectApply(localWeakMapGet, LISTENER_HANDLER, [all[i]]);
|
|
if (h) {
|
|
if (!localReflectDefineProperty(filtered, j, {
|
|
__proto__: null,
|
|
value: h,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true
|
|
})) throw new LocalError('Unexpected');
|
|
j++;
|
|
}
|
|
}
|
|
return filtered;
|
|
},
|
|
|
|
removeListener: function removeListener(name, handler) {
|
|
if (name !== 'beforeExit' && name !== 'exit') {
|
|
return this;
|
|
}
|
|
|
|
const cb = localReflectApply(localWeakMapGet, LISTENERS, [handler]);
|
|
if (cb) localProcess.removeListener(name, cb);
|
|
|
|
return this;
|
|
},
|
|
|
|
umask: function umask() {
|
|
if (arguments.length) {
|
|
throw new LocalError('Access denied to set umask.');
|
|
}
|
|
|
|
return localProcess.umask();
|
|
}
|
|
};
|
|
|
|
if (optionConsole === 'inherit') {
|
|
global.console = host.console;
|
|
} else if (optionConsole === 'redirect') {
|
|
global.console = {
|
|
debug(...args) {
|
|
vmEmitArgs('console.debug', args);
|
|
},
|
|
log(...args) {
|
|
vmEmitArgs('console.log', args);
|
|
},
|
|
info(...args) {
|
|
vmEmitArgs('console.info', args);
|
|
},
|
|
warn(...args) {
|
|
vmEmitArgs('console.warn', args);
|
|
},
|
|
error(...args) {
|
|
vmEmitArgs('console.error', args);
|
|
},
|
|
dir(...args) {
|
|
vmEmitArgs('console.dir', args);
|
|
},
|
|
time() {},
|
|
timeEnd() {},
|
|
trace(...args) {
|
|
vmEmitArgs('console.trace', args);
|
|
}
|
|
};
|
|
}
|
|
|
|
return {
|
|
__proto__: null,
|
|
Module,
|
|
jsonParse: JSON.parse,
|
|
createRequireForModule,
|
|
requireImpl
|
|
};
|