Skip to content

Commit

Permalink
test(xsnap): size limits (#2681)
Browse files Browse the repository at this point in the history
* test(xsnap): property name space exhaustion

  - lint: globals, ava devDependencies

* test(xsnap): test orderly fail-stop of large sizes

* test(xsnap): parse long strings

* feat(xsnap): use exit code to signal memory full etc.

 - enable promise loop test
 - check exit codes in tests

* test(xsnap): resource exhaustion is not catchable (x2)

* feat(xsnap): parserBufferSize run-time option

* test(xsnap): don't skip name check in infinite loop test

* test(xsnap): distinguish failure modes for property namespace
  • Loading branch information
dckc authored Mar 29, 2021
1 parent bbb2de8 commit 96b55eb
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 25 deletions.
48 changes: 30 additions & 18 deletions packages/xsnap/src/xsnap.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,19 +204,7 @@ int main(int argc, char* argv[])
int error = 0;
int interval = 0;
int freeze = 0;
xsCreation _creation = {
16 * 1024 * 1024, /* initialChunkSize */
16 * 1024 * 1024, /* incrementalChunkSize */
1 * 1024 * 1024, /* initialHeapCount */
1 * 1024 * 1024, /* incrementalHeapCount */
4096, /* stackCount */
32000, /* keyCount */
1993, /* nameModulo */
127, /* symbolModulo */
8192 * 1024, /* parserBufferSize */
1993, /* parserTableModulo */
};
xsCreation* creation = &_creation;
int parserBufferSize = 8192 * 1024;

txSnapshot snapshot = {
SNAPSHOT_SIGNATURE,
Expand Down Expand Up @@ -279,6 +267,15 @@ int main(int argc, char* argv[])
return 1;
}
}
else if (!strcmp(argv[argi], "-s")) {
argi++;
if (argi < argc)
parserBufferSize = 1024 * atoi(argv[argi]);
else {
fxPrintUsage();
return 1;
}
}
else if (!strcmp(argv[argi], "-v")) {
printf("xsnap %s (XS %d.%d.%d)\n", XSNAP_VERSION, XS_MAJOR_VERSION, XS_MINOR_VERSION, XS_PATCH_VERSION);
return 0;
Expand All @@ -287,6 +284,20 @@ int main(int argc, char* argv[])
return 1;
}
}
xsCreation _creation = {
16 * 1024 * 1024, /* initialChunkSize */
16 * 1024 * 1024, /* incrementalChunkSize */
1 * 1024 * 1024, /* initialHeapCount */
1 * 1024 * 1024, /* incrementalHeapCount */
4096, /* stackCount */
32000, /* keyCount */
1993, /* nameModulo */
127, /* symbolModulo */
parserBufferSize, /* parserBufferSize */
1993, /* parserTableModulo */
};
xsCreation* creation = &_creation;

if (gxCrankMeteringLimit) {
if (interval == 0)
interval = 1;
Expand Down Expand Up @@ -850,11 +861,12 @@ void fxPatchBuiltIns(txMachine* machine)

void fxPrintUsage()
{
printf("xsnap [-h] [-f] [-i <interval>] [-l <limit>] [-m] [-r <snapshot>] [-s] [-v]\n");
printf("xsnap [-h] [-f] [-i <interval>] [-l <limit>] [-s <size>] [-m] [-r <snapshot>] [-s] [-v]\n");
printf("\t-f: freeze the XS machine\n");
printf("\t-h: print this help message\n");
printf("\t-i <interval>: metering interval (default to 1)\n");
printf("\t-l <limit>: metering limit (default to none)\n");
printf("\t-s <size>: parser buffer size, in kB (default to 8192)\n");
printf("\t-r <snapshot>: read snapshot to create the XS machine\n");
printf("\t-v: print XS version\n");
}
Expand Down Expand Up @@ -992,28 +1004,28 @@ void fxAbort(txMachine* the, int status)
#ifdef mxDebug
fxDebugger(the, (char *)__FILE__, __LINE__);
#endif
fxExitToHost(the);
c_exit(status);
break;
case XS_NOT_ENOUGH_MEMORY_EXIT:
xsLog("memory full\n");
#ifdef mxDebug
fxDebugger(the, (char *)__FILE__, __LINE__);
#endif
fxExitToHost(the);
c_exit(status);
break;
case XS_NO_MORE_KEYS_EXIT:
xsLog("not enough keys\n");
#ifdef mxDebug
fxDebugger(the, (char *)__FILE__, __LINE__);
#endif
fxExitToHost(the);
c_exit(status);
break;
case XS_TOO_MUCH_COMPUTATION_EXIT:
xsLog("too much computation\n");
#ifdef mxDebug
fxDebugger(the, (char *)__FILE__, __LINE__);
#endif
fxExitToHost(the);
c_exit(status);
break;
case XS_UNHANDLED_EXCEPTION_EXIT:
case XS_UNHANDLED_REJECTION_EXIT:
Expand Down
10 changes: 9 additions & 1 deletion packages/xsnap/src/xsnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function echoCommand(arg) {
* @param {(request:Uint8Array) => Promise<Uint8Array>} [options.handleCommand]
* @param {string=} [options.name]
* @param {boolean=} [options.debug]
* @param {number=} [options.parserBufferSize] in kB (must be an integer)
* @param {string=} [options.snapshot]
* @param {'ignore' | 'inherit'} [options.stdout]
* @param {'ignore' | 'inherit'} [options.stderr]
Expand All @@ -58,6 +59,7 @@ export function xsnap(options) {
name = '<unnamed xsnap worker>',
handleCommand = echoCommand,
debug = false,
parserBufferSize = undefined,
snapshot = undefined,
stdout = 'ignore',
stderr = 'ignore',
Expand All @@ -82,10 +84,16 @@ export function xsnap(options) {
/** @type {Deferred<void>} */
const vatExit = defer();

const args = snapshot ? ['-r', snapshot] : [];
const args = [];
if (snapshot) {
args.push('-r', snapshot);
}
if (meteringLimit) {
args.push('-l', `${meteringLimit}`);
}
if (parserBufferSize) {
args.push('-s', `${parserBufferSize}`);
}

const xsnapProcess = spawn(bin, args, {
stdio: ['ignore', stdout, stderr, 'pipe', 'pipe'],
Expand Down
111 changes: 105 additions & 6 deletions packages/xsnap/test/test-xsnap.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* global setTimeout, __filename */
// eslint-disable-next-line import/no-extraneous-dependencies
import test from 'ava';
import * as childProcess from 'child_process';
import * as os from 'os';
Expand Down Expand Up @@ -48,18 +50,19 @@ test('evaluate until idle', async t => {
test('evaluate infinite loop', async t => {
const opts = options();
const vat = xsnap(opts);
t.teardown(vat.terminate);
await t.throwsAsync(vat.evaluate(`for (;;) {}`), {
message: 'xsnap test worker exited',
message: /xsnap test worker exited with code 7/,
instanceOf: Error,
});
await vat.close();
t.deepEqual([], opts.messages);
});

// TODO: Reenable when this doesn't take 3.6 seconds.
test.skip('evaluate promise loop', async t => {
test('evaluate promise loop', async t => {
const opts = options();
const vat = xsnap(opts);
t.teardown(vat.terminate);
await t.throwsAsync(
vat.evaluate(`
function f() {
Expand All @@ -68,11 +71,10 @@ test.skip('evaluate promise loop', async t => {
f();
`),
{
message: 'xsnap test worker exited',
message: /exited with code 7/,
instanceOf: Error,
},
);
await vat.close();
t.deepEqual([], opts.messages);
});

Expand Down Expand Up @@ -400,6 +402,103 @@ test('heap exhaustion: orderly fail-stop', async t => {
const vat = xsnap({ ...xsnapOptions, meteringLimit: 0, debug });
t.teardown(() => vat.terminate());
// eslint-disable-next-line no-await-in-loop
await t.throwsAsync(vat.evaluate(grow));
await t.throwsAsync(vat.evaluate(grow), { message: /exited with code 1$/ });
}
});

test('property name space exhaustion: orderly fail-stop', async t => {
const grow = qty => `
const objmap = {};
try {
for (let ix = 0; ix < ${qty}; ix += 1) {
const key = \`k\${ix}\`;
objmap[key] = 1;
if (!(key in objmap)) {
throw Error(key);
}
}
} catch (err) {
// name space exhaustion should not be catchable!
// spin and fail with "too much computation"
for (;;) {}
}
`;
for (const debug of [false, true]) {
const vat = xsnap({ ...xsnapOptions, meteringLimit: 0, debug });
t.teardown(() => vat.terminate());
console.log({ debug, qty: 31000 });
// eslint-disable-next-line no-await-in-loop
await t.notThrowsAsync(vat.evaluate(grow(31000)));
console.log({ debug, qty: 4000000000 });
// eslint-disable-next-line no-await-in-loop
await t.throwsAsync(vat.evaluate(grow(4000000000)), {
message: /exited with code 6/,
});
}
});

(() => {
const grow = qty => `
const send = it => issueCommand(ArrayBuffer.fromString(JSON.stringify(it)));
let expr = \`"\${Array(${qty}).fill('abcd').join('')}"\`;
try {
eval(expr);
send(expr.length);
} catch (err) {
send(err.message);
}
`;
for (const debug of [false, true]) {
for (const [parserBufferSize, qty, failure] of [
[undefined, 100, null],
[undefined, 8192 * 1024 + 100, 'buffer overflow'],
[2, 10, null],
[2, 50000, 'buffer overflow'],
]) {
test(`parser buffer size ${parserBufferSize ||
'default'}k; rep ${qty}; debug ${debug}`, async t => {
const opts = options();
const vat = xsnap({ ...opts, debug, parserBufferSize });
t.teardown(() => vat.terminate());
const expected = failure ? [failure] : [qty * 4 + 2];
// eslint-disable-next-line no-await-in-loop
await t.notThrowsAsync(vat.evaluate(grow(qty)));
t.deepEqual(
expected,
opts.messages.map(txt => JSON.parse(txt)),
);
});
}
}
})();

(() => {
const challenges = [
'new Uint8Array(2_130_706_417)',
'new Uint16Array(1_065_353_209)',
'new Uint32Array(532_676_605)',
'new BigUint64Array(266_338_303);',
'new Array(66_584_576).fill(0)',
'(new Array(66_584_575).fill(0))[66_584_575] = 0;',
];

for (const statement of challenges) {
test(`large sizes - abort cluster: ${statement}`, async t => {
const vat = xsnap(xsnapOptions);
t.teardown(() => vat.terminate());
// eslint-disable-next-line no-await-in-loop
await t.throwsAsync(
vat.evaluate(`
(() => {
try {
// can't catch memory full
${statement}\n
} catch (ignore) {
// ignore
}
})()`),
{ message: /exited with code 1$/ },
);
});
}
})();

0 comments on commit 96b55eb

Please sign in to comment.