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