Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasi: update start() to better match spec #33073

Merged
merged 2 commits into from Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions doc/api/wasi.md
Expand Up @@ -76,14 +76,15 @@ added:

* `instance` {WebAssembly.Instance}

Attempt to begin execution of `instance` by invoking its `_start()` export.
If `instance` does not contain a `_start()` export, then `start()` attempts to
invoke the `__wasi_unstable_reactor_start()` export. If neither of those exports
is present on `instance`, then `start()` does nothing.
Attempt to begin execution of `instance` as a WASI command by invoking its
`_start()` export. If `instance` does not contain a `_start()` export, or if
`instance` contains an `_initialize()` export, then an exception is thrown.

`start()` requires that `instance` exports a [`WebAssembly.Memory`][] named
`memory`. If `instance` does not have a `memory` export an exception is thrown.

If `start()` is called more than once, an exception is thrown.

### `wasi.wasiImport`
<!-- YAML
added:
Expand Down
17 changes: 12 additions & 5 deletions lib/wasi.js
Expand Up @@ -80,7 +80,17 @@ class WASI {

validateObject(exports, 'instance.exports');

const { memory } = exports;
const { _initialize, _start, memory } = exports;

if (typeof _start !== 'function') {
throw new ERR_INVALID_ARG_TYPE(
'instance.exports._start', 'function', _start);
}

if (_initialize !== undefined) {
throw new ERR_INVALID_ARG_TYPE(
'instance.exports._initialize', 'undefined', _initialize);
}

if (!(memory instanceof WebAssembly.Memory)) {
throw new ERR_INVALID_ARG_TYPE(
Expand All @@ -95,10 +105,7 @@ class WASI {
this[kSetMemory](memory);

try {
if (exports._start)
exports._start();
else if (exports.__wasi_unstable_reactor_start)
exports.__wasi_unstable_reactor_start();
exports._start();
} catch (err) {
if (err !== kExitCode) {
throw err;
Expand Down
162 changes: 94 additions & 68 deletions test/wasi/test-wasi-start-validation.js
Expand Up @@ -8,85 +8,111 @@ const { WASI } = require('wasi');
const fixtures = require('../common/fixtures');
const bufferSource = fixtures.readSync('simple.wasm');

{
const wasi = new WASI();
assert.throws(
() => {
wasi.start();
},
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bWebAssembly\.Instance\b/ }
);
}

(async () => {
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
{
// Verify that a WebAssembly.Instance is passed in.
const wasi = new WASI();

assert.throws(
() => { wasi.start(instance); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bWebAssembly\.Memory\b/ }
);
})();
assert.throws(
() => { wasi.start(); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bWebAssembly\.Instance\b/ }
);
}

(async () => {
const wasi = new WASI();
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
const values = [undefined, null, 'foo', 42, true, false, () => {}];
let cnt = 0;
{
// Verify that the passed instance has an exports objects.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);

Object.defineProperty(instance, 'exports', { get() { return null; } });
assert.throws(
() => { wasi.start(instance); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports" property must be of type object/
}
);
}

// Mock instance.exports to trigger start() validation.
Object.defineProperty(instance, 'exports', {
get() { return values[cnt++]; }
});
{
// Verify that a _start() export was passed.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);

values.forEach((val) => {
Object.defineProperty(instance, 'exports', { get() { return {}; } });
assert.throws(
() => { wasi.start(instance); },
{ code: 'ERR_INVALID_ARG_TYPE', message: /\binstance\.exports\b/ }
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports\._start" property must be of type function/
}
);
});
})();
}

(async () => {
const wasi = new WASI();
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
{
// Verify that an _initialize export was not passed.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);

// Mock instance.exports.memory to bypass start() validation.
Object.defineProperty(instance, 'exports', {
get() {
return {
memory: new WebAssembly.Memory({ initial: 1 })
};
}
});
Object.defineProperty(instance, 'exports', {
get() {
return {
_start() {},
_initialize() {}
};
}
});
assert.throws(
() => { wasi.start(instance); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports\._initialize" property must be undefined/
}
);
}

wasi.start(instance);
assert.throws(
() => { wasi.start(instance); },
{
code: 'ERR_WASI_ALREADY_STARTED',
message: /^WASI instance has already started$/
}
);
})();
{
// Verify that a memory export was passed.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);

(async () => {
const wasi = new WASI();
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
Object.defineProperty(instance, 'exports', {
get() { return { _start() {} }; }
});
assert.throws(
() => { wasi.start(instance); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports\.memory" property .+ WebAssembly\.Memory/
}
);
}

// Mock instance.exports to bypass start() validation.
Object.defineProperty(instance, 'exports', {
get() {
return {
memory: new WebAssembly.Memory({ initial: 1 }),
__wasi_unstable_reactor_start: common.mustCall()
};
}
});
{
// Verify that start() can only be called once.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);

wasi.start(instance);
})();
Object.defineProperty(instance, 'exports', {
get() {
return {
_start() {},
memory: new WebAssembly.Memory({ initial: 1 })
};
}
});
wasi.start(instance);
assert.throws(
() => { wasi.start(instance); },
{
code: 'ERR_WASI_ALREADY_STARTED',
message: /^WASI instance has already started$/
}
);
}
})().then(common.mustCall());