1'use strict'; 2const { 3 ArrayPrototypeForEach, 4 ArrayPrototypeMap, 5 ArrayPrototypePush, 6 FunctionPrototypeBind, 7 ObjectEntries, 8 String, 9 Symbol, 10} = primordials; 11 12const { 13 ERR_WASI_ALREADY_STARTED, 14} = require('internal/errors').codes; 15const { 16 emitExperimentalWarning, 17 kEmptyObject, 18} = require('internal/util'); 19const { 20 validateArray, 21 validateBoolean, 22 validateFunction, 23 validateInt32, 24 validateObject, 25 validateUndefined, 26} = require('internal/validators'); 27const { WASI: _WASI } = internalBinding('wasi'); 28const kExitCode = Symbol('kExitCode'); 29const kSetMemory = Symbol('kSetMemory'); 30const kStarted = Symbol('kStarted'); 31const kInstance = Symbol('kInstance'); 32 33emitExperimentalWarning('WASI'); 34 35 36function setupInstance(self, instance) { 37 validateObject(instance, 'instance'); 38 validateObject(instance.exports, 'instance.exports'); 39 40 self[kInstance] = instance; 41 self[kSetMemory](instance.exports.memory); 42} 43 44class WASI { 45 constructor(options = kEmptyObject) { 46 validateObject(options, 'options'); 47 48 if (options.args !== undefined) 49 validateArray(options.args, 'options.args'); 50 const args = ArrayPrototypeMap(options.args || [], String); 51 52 const env = []; 53 if (options.env !== undefined) { 54 validateObject(options.env, 'options.env'); 55 ArrayPrototypeForEach( 56 ObjectEntries(options.env), 57 ({ 0: key, 1: value }) => { 58 if (value !== undefined) 59 ArrayPrototypePush(env, `${key}=${value}`); 60 }); 61 } 62 63 const preopens = []; 64 if (options.preopens !== undefined) { 65 validateObject(options.preopens, 'options.preopens'); 66 ArrayPrototypeForEach( 67 ObjectEntries(options.preopens), 68 ({ 0: key, 1: value }) => 69 ArrayPrototypePush(preopens, String(key), String(value)), 70 ); 71 } 72 73 const { stdin = 0, stdout = 1, stderr = 2 } = options; 74 validateInt32(stdin, 'options.stdin', 0); 75 validateInt32(stdout, 'options.stdout', 0); 76 validateInt32(stderr, 'options.stderr', 0); 77 const stdio = [stdin, stdout, stderr]; 78 79 const wrap = new _WASI(args, env, preopens, stdio); 80 81 for (const prop in wrap) { 82 wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap); 83 } 84 85 if (options.returnOnExit !== undefined) { 86 validateBoolean(options.returnOnExit, 'options.returnOnExit'); 87 if (options.returnOnExit) 88 wrap.proc_exit = FunctionPrototypeBind(wasiReturnOnProcExit, this); 89 } 90 91 this[kSetMemory] = wrap._setMemory; 92 delete wrap._setMemory; 93 this.wasiImport = wrap; 94 this[kStarted] = false; 95 this[kExitCode] = 0; 96 this[kInstance] = undefined; 97 } 98 99 // Must not export _initialize, must export _start 100 start(instance) { 101 if (this[kStarted]) { 102 throw new ERR_WASI_ALREADY_STARTED(); 103 } 104 this[kStarted] = true; 105 106 setupInstance(this, instance); 107 108 const { _start, _initialize } = this[kInstance].exports; 109 110 validateFunction(_start, 'instance.exports._start'); 111 validateUndefined(_initialize, 'instance.exports._initialize'); 112 113 try { 114 _start(); 115 } catch (err) { 116 if (err !== kExitCode) { 117 throw err; 118 } 119 } 120 121 return this[kExitCode]; 122 } 123 124 // Must not export _start, may optionally export _initialize 125 initialize(instance) { 126 if (this[kStarted]) { 127 throw new ERR_WASI_ALREADY_STARTED(); 128 } 129 this[kStarted] = true; 130 131 setupInstance(this, instance); 132 133 const { _start, _initialize } = this[kInstance].exports; 134 135 validateUndefined(_start, 'instance.exports._start'); 136 if (_initialize !== undefined) { 137 validateFunction(_initialize, 'instance.exports._initialize'); 138 _initialize(); 139 } 140 } 141} 142 143 144module.exports = { WASI }; 145 146 147function wasiReturnOnProcExit(rval) { 148 // If __wasi_proc_exit() does not terminate the process, an assertion is 149 // triggered in the wasm runtime. Node can sidestep the assertion and return 150 // an exit code by recording the exit code, and throwing a JavaScript 151 // exception that WebAssembly cannot catch. 152 this[kExitCode] = rval; 153 throw kExitCode; 154} 155