• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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