1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
|
load(libdir + "wasm.js");
load(libdir + "wasm-binary.js");
const CompileError = WebAssembly.CompileError;
const magicError = /failed to match magic number/;
const unknownSection = /expected user-defined section/;
function sectionError(section) {
return RegExp(`failed to start ${section} section`);
}
function versionError(actual) {
var expect = encodingVersion;
var str = `binary version 0x${actual.toString(16)} does not match expected version 0x${expect.toString(16)}`;
return RegExp(str);
}
function toU8(array) {
for (let b of array)
assertEq(b < 256, true);
return Uint8Array.from(array);
}
function varU32(u32) {
assertEq(u32 >= 0, true);
assertEq(u32 < Math.pow(2,32), true);
var bytes = [];
do {
var byte = u32 & 0x7f;
u32 >>>= 7;
if (u32 != 0)
byte |= 0x80;
bytes.push(byte);
} while (u32 != 0);
return bytes;
}
function varS32(s32) {
assertEq(s32 >= -Math.pow(2,31), true);
assertEq(s32 < Math.pow(2,31), true);
var bytes = [];
do {
var byte = s32 & 0x7f;
s32 >>= 7;
if (s32 != 0 && s32 != -1)
byte |= 0x80;
bytes.push(byte);
} while (s32 != 0 && s32 != -1);
return bytes;
}
const U32MAX_LEB = [255, 255, 255, 255, 15];
const wasmEval = (code, imports) => new WebAssembly.Instance(new WebAssembly.Module(code), imports).exports;
assertErrorMessage(() => wasmEval(toU8([])), CompileError, magicError);
assertErrorMessage(() => wasmEval(toU8([42])), CompileError, magicError);
assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2])), CompileError, magicError);
assertErrorMessage(() => wasmEval(toU8([1,2,3,4])), CompileError, magicError);
assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3])), CompileError, versionError(0x6d736100));
assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, 1])), CompileError, versionError(0x6d736100));
assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0])), CompileError, versionError(0x6d736100));
assertErrorMessage(() => wasmEval(toU8([magic0, magic1, magic2, magic3, ver0, ver1, ver2])), CompileError, versionError(0x6d736100));
// This test should be removed shortly.
assertEq(WebAssembly.validate(toU8([magic0, magic1, magic2, magic3, 0xd, 0x0, 0x0, 0x0])), true);
function moduleHeaderThen(...rest) {
return [magic0, magic1, magic2, magic3, ver0, ver1, ver2, ver3, ...rest];
}
var o = wasmEval(toU8(moduleHeaderThen()));
assertEq(Object.getOwnPropertyNames(o).length, 0);
// unfinished known sections
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(typeId))), CompileError, sectionError("type"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(importId))), CompileError, sectionError("import"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(functionId))), CompileError, sectionError("function"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(tableId))), CompileError, sectionError("table"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(memoryId))), CompileError, sectionError("memory"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(globalId))), CompileError, sectionError("global"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(exportId))), CompileError, sectionError("export"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(startId))), CompileError, sectionError("start"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(elemId))), CompileError, sectionError("elem"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(codeId))), CompileError, sectionError("code"));
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(dataId))), CompileError, sectionError("data"));
// unknown sections are unconditionally rejected
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42))), CompileError, unknownSection);
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 0))), CompileError, unknownSection);
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(42, 1, 0))), CompileError, unknownSection);
// user sections have special rules
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0))), CompileError, sectionError("user-defined")); // no length
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0))), CompileError, sectionError("user-defined")); // no id
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 0, 0))), CompileError, sectionError("user-defined")); // payload too small to have id length
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1))), CompileError, sectionError("user-defined")); // id not present
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 1, 65))), CompileError, sectionError("user-defined")); // id length doesn't fit in section
assertErrorMessage(() => wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0))), CompileError, sectionError("user-defined")); // second, unfinished user-defined section
wasmEval(toU8(moduleHeaderThen(0, 1, 0))); // empty id
wasmEval(toU8(moduleHeaderThen(0, 1, 0, 0, 1, 0))); // 2x empty id
wasmEval(toU8(moduleHeaderThen(0, 2, 1, 65))); // id = "A"
function string(name) {
var nameBytes = name.split('').map(c => {
var code = c.charCodeAt(0);
assertEq(code < 128, true); // TODO
return code
});
return varU32(nameBytes.length).concat(nameBytes);
}
function encodedString(name, len) {
var name = unescape(encodeURIComponent(name)); // break into string of utf8 code points
var nameBytes = name.split('').map(c => c.charCodeAt(0)); // map to array of numbers
return varU32(len === undefined ? nameBytes.length : len).concat(nameBytes);
}
function moduleWithSections(sectionArray) {
var bytes = moduleHeaderThen();
for (let section of sectionArray) {
bytes.push(section.name);
bytes.push(...varU32(section.body.length));
bytes.push(...section.body);
}
return toU8(bytes);
}
function sigSection(sigs) {
var body = [];
body.push(...varU32(sigs.length));
for (let sig of sigs) {
body.push(...varU32(FuncCode));
body.push(...varU32(sig.args.length));
for (let arg of sig.args)
body.push(...varU32(arg));
body.push(...varU32(sig.ret == VoidCode ? 0 : 1));
if (sig.ret != VoidCode)
body.push(...varU32(sig.ret));
}
return { name: typeId, body };
}
function declSection(decls) {
var body = [];
body.push(...varU32(decls.length));
for (let decl of decls)
body.push(...varU32(decl));
return { name: functionId, body };
}
function funcBody(func) {
var body = varU32(func.locals.length);
for (let local of func.locals)
body.push(...varU32(local));
body = body.concat(...func.body);
body.push(EndCode);
body.splice(0, 0, ...varU32(body.length));
return body;
}
function bodySection(bodies) {
var body = varU32(bodies.length).concat(...bodies);
return { name: codeId, body };
}
function importSection(imports) {
var body = [];
body.push(...varU32(imports.length));
for (let imp of imports) {
body.push(...string(imp.module));
body.push(...string(imp.func));
body.push(...varU32(FunctionCode));
body.push(...varU32(imp.sigIndex));
}
return { name: importId, body };
}
function exportSection(exports) {
var body = [];
body.push(...varU32(exports.length));
for (let exp of exports) {
body.push(...string(exp.name));
body.push(...varU32(FunctionCode));
body.push(...varU32(exp.funcIndex));
}
return { name: exportId, body };
}
function tableSection(initialSize) {
var body = [];
body.push(...varU32(1)); // number of tables
body.push(...varU32(AnyFuncCode));
body.push(...varU32(0x0)); // for now, no maximum
body.push(...varU32(initialSize));
return { name: tableId, body };
}
function memorySection(initialSize) {
var body = [];
body.push(...varU32(1)); // number of memories
body.push(...varU32(0x0)); // for now, no maximum
body.push(...varU32(initialSize));
return { name: memoryId, body };
}
function elemSection(elemArrays) {
var body = [];
body.push(...varU32(elemArrays.length));
for (let array of elemArrays) {
body.push(...varU32(0)); // table index
body.push(...varU32(I32ConstCode));
body.push(...varS32(array.offset));
body.push(...varU32(EndCode));
body.push(...varU32(array.elems.length));
for (let elem of array.elems)
body.push(...varU32(elem));
}
return { name: elemId, body };
}
function nameSection(elems) {
var body = [];
body.push(...string(nameName));
body.push(...varU32(elems.length));
for (let fn of elems) {
body.push(...encodedString(fn.name, fn.nameLen));
if (!fn.locals) {
body.push(...varU32(0));
continue;
}
body.push(...varU32(fn.locals.length));
for (let local of fn.locals)
body.push(...encodedString(local.name, local.nameLen));
}
return { name: userDefinedId, body };
}
function userDefinedSection(name, ...body) {
return { name: userDefinedId, body: [...string(name), ...body] };
}
const v2vSig = {args:[], ret:VoidCode};
const i2vSig = {args:[I32Code], ret:VoidCode};
const v2vBody = funcBody({locals:[], body:[]});
assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: U32MAX_LEB } ])), CompileError, /too many signatures/);
assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, 0], } ])), CompileError, /expected function form/);
assertErrorMessage(() => wasmEval(moduleWithSections([ {name: typeId, body: [1, FuncCode, ...U32MAX_LEB], } ])), CompileError, /too many arguments in signature/);
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1]}])), CompileError);
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([{name: typeId, body: [1, 1, 0]}])), CompileError);
wasmEval(moduleWithSections([sigSection([])]));
wasmEval(moduleWithSections([sigSection([v2vSig])]));
wasmEval(moduleWithSections([sigSection([i2vSig])]));
wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])]));
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), CompileError, /bad type/);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), CompileError, /bad type/);
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([]), declSection([0])])), CompileError, /signature index out of range/);
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([1])])), CompileError, /signature index out of range/);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0])])), CompileError, /expected function bodies/);
wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([v2vBody])]));
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([v2vBody.concat(v2vBody)])])), CompileError, /byte size mismatch in code section/);
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), {name: importId, body:[]}])), CompileError);
assertErrorMessage(() => wasmEval(moduleWithSections([importSection([{sigIndex:0, module:"a", func:"b"}])])), CompileError, /signature index out of range/);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), importSection([{sigIndex:1, module:"a", func:"b"}])])), CompileError, /signature index out of range/);
wasmEval(moduleWithSections([sigSection([v2vSig]), importSection([])]));
wasmEval(moduleWithSections([sigSection([v2vSig]), importSection([{sigIndex:0, module:"a", func:""}])]), {a:{"":()=>{}}});
wasmEval(moduleWithSections([
sigSection([v2vSig]),
importSection([{sigIndex:0, module:"a", func:""}]),
declSection([0]),
bodySection([v2vBody])
]), {a:{"":()=>{}}});
assertErrorMessage(() => wasmEval(moduleWithSections([ {name: dataId, body: [], } ])), CompileError, /data section requires a memory section/);
wasmEval(moduleWithSections([tableSection(0)]));
wasmEval(moduleWithSections([elemSection([])]));
wasmEval(moduleWithSections([tableSection(0), elemSection([])]));
wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:1, elems:[]}])]));
assertErrorMessage(() => wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:0, elems:[0]}])])), CompileError, /table element out of range/);
wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), tableSection(1), elemSection([{offset:0, elems:[0]}]), bodySection([v2vBody])]));
wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,0]}]), bodySection([v2vBody])]));
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,1]}]), bodySection([v2vBody])])), CompileError, /table element out of range/);
wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0,0,0]), tableSection(4), elemSection([{offset:0, elems:[0,1,0,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
wasmEval(moduleWithSections([sigSection([v2vSig,i2vSig]), declSection([0,0,1]), tableSection(3), elemSection([{offset:0,elems:[0,1,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
function invalidTableSection0() {
var body = [];
body.push(...varU32(0)); // number of tables
return { name: tableId, body };
}
assertErrorMessage(() => wasmEval(moduleWithSections([invalidTableSection0()])), CompileError, /number of tables must be exactly one/);
wasmEval(moduleWithSections([memorySection(0)]));
function invalidMemorySection0() {
var body = [];
body.push(...varU32(0)); // number of memories
return { name: memoryId, body };
}
assertErrorMessage(() => wasmEval(moduleWithSections([invalidMemorySection0()])), CompileError, /number of memories must be exactly one/);
// Test early 'end'
const bodyMismatch = /function body length mismatch/;
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[EndCode]})])])), CompileError, bodyMismatch);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[UnreachableCode,EndCode]})])])), CompileError, bodyMismatch);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[EndCode,UnreachableCode]})])])), CompileError, bodyMismatch);
// Deep nesting shouldn't crash or even throw.
var manyBlocks = [];
for (var i = 0; i < 20000; i++)
manyBlocks.push(BlockCode, VoidCode, EndCode);
wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:manyBlocks})])]));
// Ignore errors in name section.
var tooBigNameSection = {
name: userDefinedId,
body: [...string(nameName), ...varU32(Math.pow(2, 31))] // declare 2**31 functions.
};
wasmEval(moduleWithSections([tooBigNameSection]));
// Skip user-defined sections before any expected section
var userDefSec = userDefinedSection("wee", 42, 13);
var sigSec = sigSection([v2vSig]);
var declSec = declSection([0]);
var bodySec = bodySection([v2vBody]);
wasmEval(moduleWithSections([userDefSec, sigSec, declSec, bodySec]));
wasmEval(moduleWithSections([sigSec, userDefSec, declSec, bodySec]));
wasmEval(moduleWithSections([sigSec, declSec, userDefSec, bodySec]));
wasmEval(moduleWithSections([sigSec, declSec, bodySec, userDefSec]));
wasmEval(moduleWithSections([userDefSec, userDefSec, sigSec, declSec, bodySec]));
wasmEval(moduleWithSections([userDefSec, userDefSec, sigSec, userDefSec, declSec, userDefSec, bodySec]));
// Diagnose nonstandard block signature types.
for (var bad of [0xff, 0, 1, 0x3f])
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid inline block type/);
// Checking stack trace.
function runStackTraceTest(namesContent, expectedName) {
var sections = [
sigSection([v2vSig]),
importSection([{sigIndex:0, module:"env", func:"callback"}]),
declSection([0]),
exportSection([{funcIndex:1, name: "run"}]),
bodySection([funcBody({locals: [], body: [CallCode, varU32(0)]})]),
userDefinedSection("whoa"),
userDefinedSection("wee", 42),
];
if (namesContent)
sections.push(nameSection(namesContent));
sections.push(userDefinedSection("yay", 13));
var result = "";
var callback = () => {
var prevFrameEntry = new Error().stack.split('\n')[1];
result = prevFrameEntry.split('@')[0];
};
wasmEval(moduleWithSections(sections), {"env": { callback }}).run();
assertEq(result, expectedName);
};
runStackTraceTest(null, 'wasm-function[1]');
runStackTraceTest([{name:'blah'}, {name: 'test'}], 'test');
runStackTraceTest([{name:'blah'}, {name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
runStackTraceTest([{name:'blah'}, {name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
runStackTraceTest([{name:'blah'}, {name: 'test1'}, {name: 'test2'}], 'test1');
runStackTraceTest([{name:'blah'}, {name: 'test☃'}], 'test☃');
runStackTraceTest([{name:'blah'}, {name: 'te\xE0\xFF'}], 'te\xE0\xFF');
runStackTraceTest([{name:'blah'}], 'wasm-function[1]');
runStackTraceTest([], 'wasm-function[1]');
// Notice that invalid names section content shall not fail the parsing
runStackTraceTest([{name:'blah'}, {nameLen: 100, name: 'test'}], 'wasm-function[1]'); // invalid name size
runStackTraceTest([{name:'blah'}, {name: 'test', locals: [{nameLen: 40, name: 'var1'}]}], 'wasm-function[1]'); // invalid variable name size
runStackTraceTest([{name:'blah'}, {name: ''}], 'wasm-function[1]'); // empty name
|