1'use strict'; 2const { 3 ArrayPrototypeForEach, 4 ArrayPrototypeMap, 5 ArrayPrototypePush, 6 FunctionPrototypeBind, 7 ObjectEntries, 8 String, 9 Symbol, 10} = primordials; 11 12const { 13 ERR_INVALID_ARG_TYPE, 14 ERR_WASI_ALREADY_STARTED 15} = require('internal/errors').codes; 16const { emitExperimentalWarning } = require('internal/util'); 17const { isArrayBuffer } = require('internal/util/types'); 18const { 19 validateArray, 20 validateBoolean, 21 validateInt32, 22 validateObject, 23} = require('internal/validators'); 24const { WASI: _WASI } = internalBinding('wasi'); 25const kExitCode = Symbol('kExitCode'); 26const kSetMemory = Symbol('kSetMemory'); 27const kStarted = Symbol('kStarted'); 28const kInstance = Symbol('kInstance'); 29 30emitExperimentalWarning('WASI'); 31 32 33function setupInstance(self, instance) { 34 validateObject(instance, 'instance'); 35 validateObject(instance.exports, 'instance.exports'); 36 37 // WASI::_SetMemory() in src/node_wasi.cc only expects that |memory| is 38 // an object. It will try to look up the .buffer property when needed 39 // and fail with UVWASI_EINVAL when the property is missing or is not 40 // an ArrayBuffer. Long story short, we don't need much validation here 41 // but we type-check anyway because it helps catch bugs in the user's 42 // code early. 43 validateObject(instance.exports.memory, 'instance.exports.memory'); 44 if (!isArrayBuffer(instance.exports.memory.buffer)) { 45 throw new ERR_INVALID_ARG_TYPE( 46 'instance.exports.memory.buffer', 47 ['WebAssembly.Memory'], 48 instance.exports.memory.buffer); 49 } 50 51 self[kInstance] = instance; 52 self[kSetMemory](instance.exports.memory); 53} 54 55class WASI { 56 constructor(options = {}) { 57 validateObject(options, 'options'); 58 59 if (options.args !== undefined) 60 validateArray(options.args, 'options.args'); 61 const args = ArrayPrototypeMap(options.args || [], String); 62 63 const env = []; 64 if (options.env !== undefined) { 65 validateObject(options.env, 'options.env'); 66 ArrayPrototypeForEach( 67 ObjectEntries(options.env), 68 ({ 0: key, 1: value }) => { 69 if (value !== undefined) 70 ArrayPrototypePush(env, `${key}=${value}`); 71 }); 72 } 73 74 const preopens = []; 75 if (options.preopens !== undefined) { 76 validateObject(options.preopens, 'options.preopens'); 77 ArrayPrototypeForEach( 78 ObjectEntries(options.preopens), 79 ({ 0: key, 1: value }) => 80 ArrayPrototypePush(preopens, String(key), String(value)) 81 ); 82 } 83 84 const { stdin = 0, stdout = 1, stderr = 2 } = options; 85 validateInt32(stdin, 'options.stdin', 0); 86 validateInt32(stdout, 'options.stdout', 0); 87 validateInt32(stderr, 'options.stderr', 0); 88 const stdio = [stdin, stdout, stderr]; 89 90 const wrap = new _WASI(args, env, preopens, stdio); 91 92 for (const prop in wrap) { 93 wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap); 94 } 95 96 if (options.returnOnExit !== undefined) { 97 validateBoolean(options.returnOnExit, 'options.returnOnExit'); 98 if (options.returnOnExit) 99 wrap.proc_exit = FunctionPrototypeBind(wasiReturnOnProcExit, this); 100 } 101 102 this[kSetMemory] = wrap._setMemory; 103 delete wrap._setMemory; 104 this.wasiImport = wrap; 105 this[kStarted] = false; 106 this[kExitCode] = 0; 107 this[kInstance] = undefined; 108 } 109 110 // Must not export _initialize, must export _start 111 start(instance) { 112 if (this[kStarted]) { 113 throw new ERR_WASI_ALREADY_STARTED(); 114 } 115 this[kStarted] = true; 116 117 setupInstance(this, instance); 118 119 const { _start, _initialize } = this[kInstance].exports; 120 121 if (typeof _start !== 'function') { 122 throw new ERR_INVALID_ARG_TYPE( 123 'instance.exports._start', 'function', _start); 124 } 125 if (_initialize !== undefined) { 126 throw new ERR_INVALID_ARG_TYPE( 127 'instance.exports._initialize', 'undefined', _initialize); 128 } 129 130 try { 131 _start(); 132 } catch (err) { 133 if (err !== kExitCode) { 134 throw err; 135 } 136 } 137 138 return this[kExitCode]; 139 } 140 141 // Must not export _start, may optionally export _initialize 142 initialize(instance) { 143 if (this[kStarted]) { 144 throw new ERR_WASI_ALREADY_STARTED(); 145 } 146 this[kStarted] = true; 147 148 setupInstance(this, instance); 149 150 const { _start, _initialize } = this[kInstance].exports; 151 152 if (typeof _initialize !== 'function' && _initialize !== undefined) { 153 throw new ERR_INVALID_ARG_TYPE( 154 'instance.exports._initialize', 'function', _initialize); 155 } 156 if (_start !== undefined) { 157 throw new ERR_INVALID_ARG_TYPE( 158 'instance.exports._start', 'undefined', _initialize); 159 } 160 161 if (_initialize !== undefined) { 162 _initialize(); 163 } 164 } 165} 166 167 168module.exports = { WASI }; 169 170 171function wasiReturnOnProcExit(rval) { 172 // If __wasi_proc_exit() does not terminate the process, an assertion is 173 // triggered in the wasm runtime. Node can sidestep the assertion and return 174 // an exit code by recording the exit code, and throwing a JavaScript 175 // exception that WebAssembly cannot catch. 176 this[kExitCode] = rval; 177 throw kExitCode; 178} 179