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