summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/passive-segs-boundary.js
blob: 18dee4a303e53be1dd8f540d93c4c4e45cccf99b (plain)
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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
// Perform a test which,
//
// * if errKind is defined, is expected to fail with an exception
//   characterised by errKind and errText.
//
// * if errKind is undefined, is expected to succeed, in which case errKind
//   and errText are ignored.
//
// The function body will be [insn1, insn2] and is constructed according to
// four booleans:
//
// * isMem controls whether the module is constructed with memory or
//   table initializers.
//
// * haveStorage determines whether there is actually a memory or table to
//   work with.
//
// * haveInitA controls whether active initializers are added.
//
// * haveInitP controls whether passive initializers are added.

function do_test(insn1, insn2, errKind, errText,
                 isMem, haveStorage, haveInitA, haveInitP)
{
    let preamble;
    if (isMem) {
        let mem_def  = haveStorage ? "(memory 1 1)" : "";
        let mem_ia1  = `(data (i32.const 2) "\\03\\01\\04\\01")`;
        let mem_ia2  = `(data (i32.const 12) "\\07\\05\\02\\03\\06")`;
        let mem_ip1  = `(data "\\02\\07\\01\\08")`;
        let mem_ip2  = `(data "\\05\\09\\02\\07\\06")`;
        let mem_init = ``;
        if (haveInitA && haveInitP)
            mem_init = `${mem_ia1} ${mem_ip1} ${mem_ia2} ${mem_ip2}`;
        else if (haveInitA && !haveInitP) mem_init = `${mem_ia1} ${mem_ia2}`;
        else if (!haveInitA && haveInitP) mem_init = `${mem_ip1} ${mem_ip2}`;
        preamble
            = `;; -------- Memories --------
               ${mem_def}
               ;; -------- Memory initialisers --------
               ${mem_init}
              `;
    } else {
        let tab_def  = haveStorage ? "(table 30 30 funcref)" : "";
        let tab_ia1  = `(elem (i32.const 2) 3 1 4 1)`;
        let tab_ia2  = `(elem (i32.const 12) 7 5 2 3 6)`;
        let tab_ip1  = `(elem func 2 7 1 8)`;
        let tab_ip2  = `(elem func 5 9 2 7 6)`;
        let tab_init = ``;
        if (haveInitA && haveInitP)
            tab_init = `${tab_ia1} ${tab_ip1} ${tab_ia2} ${tab_ip2}`;
        else if (haveInitA && !haveInitP) tab_init = `${tab_ia1} ${tab_ia2}`;
        else if (!haveInitA && haveInitP) tab_init = `${tab_ip1} ${tab_ip2}`;
        preamble
            = `;; -------- Tables --------
               ${tab_def}
               ;; -------- Table initialisers --------
               ${tab_init}
               ;; ------ Functions (0..9) referred by the table/esegs ------
               (func (result i32) (i32.const 0))
               (func (result i32) (i32.const 1))
               (func (result i32) (i32.const 2))
               (func (result i32) (i32.const 3))
               (func (result i32) (i32.const 4))
               (func (result i32) (i32.const 5))
               (func (result i32) (i32.const 6))
               (func (result i32) (i32.const 7))
               (func (result i32) (i32.const 8))
               (func (result i32) (i32.const 9))
              `;
    }

    let txt = "(module\n" + preamble +
              `;; -------- testfn --------
               (func (export "testfn")
                 ${insn1}
                 ${insn2}
               )
               )`;

    if (!!errKind) {
        assertErrorMessage(
            () => {
                let inst = wasmEvalText(txt);
                inst.exports.testfn();
            },
            errKind,
            errText
        );
    } else {
        let inst = wasmEvalText(txt);
        assertEq(undefined, inst.exports.testfn());
    }
}


// Some handy specialisations of do_test().

function mem_test(insn1, insn2, errKind, errText,
                  haveStorage=true, haveInitA=true, haveInitP=true) {
    do_test(insn1, insn2, errKind, errText,
            /*isMem=*/true, haveStorage, haveInitA, haveInitP);
}

function mem_test_nofail(insn1, insn2,
                         haveStorage=true, haveInitA=true, haveInitP=true) {
    do_test(insn1, insn2, undefined, undefined,
            /*isMem=*/true, haveStorage, haveInitA, haveInitP);
}

function tab_test(insn1, insn2, errKind, errText,
                  haveStorage=true, haveInitA=true, haveInitP=true) {
    do_test(insn1, insn2, errKind, errText,
            /*isMem=*/false, haveStorage, haveInitA, haveInitP);
}

function tab_test_nofail(insn1, insn2,
                         haveStorage=true, haveInitA=true, haveInitP=true) {
    do_test(insn1, insn2, undefined, undefined,
            /*isMem=*/false, haveStorage, haveInitA, haveInitP);
}


//---- memory.{drop,init,copy} -------------------------------------------------

// The tested semantics for memory.drop, in the case where there's no memory,
// are as follows.  table.drop is analogous.
//
// no memory, no data segments:
//    drop with any args -> fail OOB
//                          [because there's nothing to drop]
//
// no memory, data segments, at least one of which is active:
//    -> always fails, regardless of insns
//       [because active segments implicitly reference memory 0]
//
// no memory, data segments, all of which are passive:
//    drop, segment index is OOB -> fail OOB
//                                  [because it refers to non existent segment]
//
//    drop, segment index is IB -> OK

// drop with no memory and no data segments
mem_test("data.drop 0", "",
         WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 0)/,
         /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false);

// drop with no memory but with both passive and active segments, ix in range
// and refers to a passive segment
mem_test("data.drop 3", "",
         WebAssembly.CompileError,
         /active data segment requires a memory section/,
         /*haveStorage=*/false, /*haveInitA=*/true, /*haveInitP=*/true);

// drop with no memory but with passive segments only, ix out of range
mem_test("data.drop 2", "",
         WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 2)/,
         /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true);

// drop with no memory but with passive segments only, ix in range
mem_test_nofail("data.drop 1", "",
                /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true);


// init with no memory and no data segs
mem_test("(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
         WebAssembly.CompileError, /(can't touch memory without memory)|(unknown memory 0)/,
         /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false);

// drop with data seg ix out of range
mem_test("data.drop 4", "",
         WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 4)/);

// init with data seg ix out of range
mem_test("(memory.init 4 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
         WebAssembly.CompileError, /(memory.init segment index out of range)|(unknown data segment 4)/);

// drop with data seg ix indicating an active segment
mem_test("data.drop 2", "");

// init with data seg ix indicating an active segment
mem_test("(memory.init 2 (i32.const 1234) (i32.const 1) (i32.const 1))", "",
         WebAssembly.RuntimeError, /index out of bounds/);

// init, using a data seg ix more than once is OK
mem_test_nofail(
    "(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))",
    "(memory.init 1 (i32.const 4321) (i32.const 1) (i32.const 1))");

// drop, then drop
mem_test("data.drop 1",
         "data.drop 1");

// drop, then init
mem_test("data.drop 1",
         "(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, but length to copy > len of seg
mem_test("",
         "(memory.init 1 (i32.const 1234) (i32.const 0) (i32.const 5))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, but implies copying beyond end of seg
mem_test("",
         "(memory.init 1 (i32.const 1234) (i32.const 2) (i32.const 3))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, but implies copying beyond end of dst
mem_test("",
         "(memory.init 1 (i32.const 0xFFFE) (i32.const 1) (i32.const 3))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, zero len, but src offset out of bounds at the
// edge
mem_test("",
         "(memory.init 1 (i32.const 1234) (i32.const 4) (i32.const 0))");

// init: seg ix is valid passive, zero len, but src offset out of bounds one
// past the edge
mem_test("",
         "(memory.init 1 (i32.const 1234) (i32.const 5) (i32.const 0))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, zero len, but dst offset out of bounds at the
// edge
mem_test("",
         "(memory.init 1 (i32.const 0x10000) (i32.const 2) (i32.const 0))");

// init: seg ix is valid passive, zero len, but dst offset out of bounds one
// past the edge
mem_test("",
         "(memory.init 1 (i32.const 0x10001) (i32.const 2) (i32.const 0))",
         WebAssembly.RuntimeError, /index out of bounds/);

// drop: too many args
mem_test("data.drop 1 (i32.const 42)", "",
         WebAssembly.CompileError,
         /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/);

// init: too many args
mem_test("(memory.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))",
         "",
         WebAssembly.CompileError, /(unused values)|(values remaining on stack at end of block)/);

// init: too few args
mem_test("(memory.init 1 (i32.const 1) (i32.const 1))", "",
         WebAssembly.CompileError,
         /(popping value from empty stack)|(expected i32 but nothing on stack)/);

// invalid argument types
{
    const tys  = ['i32', 'f32', 'i64', 'f64'];

    for (let ty1 of tys) {
    for (let ty2 of tys) {
    for (let ty3 of tys) {
        if (ty1 == 'i32' && ty2 == 'i32' && ty3 == 'i32')
            continue;  // this is the only valid case
        let i1 = `(memory.init 1 (${ty1}.const 1) (${ty2}.const 1) (${ty3}.const 1))`;
        mem_test(i1, "", WebAssembly.CompileError, /type mismatch/);
    }}}
}

//
//---- table.{drop,init} --------------------------------------------------

// drop with no tables and no elem segments
tab_test("elem.drop 0", "",
         WebAssembly.CompileError,
         /(element segment index out of range for elem.drop)|(segment index out of bounds)/,
         /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false);

// drop with no tables but with both passive and active segments, ix in range
// and refers to a passive segment
tab_test("elem.drop 3", "",
         WebAssembly.CompileError,
         /active elem segment requires a table/,
         /*haveStorage=*/false, /*haveInitA=*/true, /*haveInitP=*/true);

// drop with no tables but with passive segments only, ix out of range
tab_test("elem.drop 2", "",
         WebAssembly.CompileError,
         /(element segment index out of range for elem.drop)|(segment index out of bounds)/,
         /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true);

// drop with no tables but with passive segments only, ix in range
tab_test_nofail("elem.drop 1", "",
                /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true);


// init with no table
tab_test("(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", "",
         WebAssembly.CompileError, /(table index out of range)|(table index out of bounds)/,
         /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false);

// drop with elem seg ix out of range
tab_test("elem.drop 4", "",
         WebAssembly.CompileError, /(element segment index out of range for elem.drop)|(segment index out of bounds)/);

// init with elem seg ix out of range
tab_test("(table.init 4 (i32.const 12) (i32.const 1) (i32.const 1))", "",
         WebAssembly.CompileError, /(table.init segment index out of range)|(segment index out of bounds)/);

// drop with elem seg ix indicating an active segment
tab_test("elem.drop 2", "");

// init with elem seg ix indicating an active segment
tab_test("(table.init 2 (i32.const 12) (i32.const 1) (i32.const 1))", "",
         WebAssembly.RuntimeError, /index out of bounds/);

// init, using an elem seg ix more than once is OK
tab_test_nofail(
    "(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))",
    "(table.init 1 (i32.const 21) (i32.const 1) (i32.const 1))");

// drop, then drop
tab_test("elem.drop 1",
         "elem.drop 1");

// drop, then init
tab_test("elem.drop 1",
         "(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, but length to copy > len of seg
tab_test("",
         "(table.init 1 (i32.const 12) (i32.const 0) (i32.const 5))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, but implies copying beyond end of seg
tab_test("",
         "(table.init 1 (i32.const 12) (i32.const 2) (i32.const 3))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, but implies copying beyond end of dst
tab_test("",
         "(table.init 1 (i32.const 28) (i32.const 1) (i32.const 3))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, zero len, but src offset out of bounds at the
// edge
tab_test("",
         "(table.init 1 (i32.const 12) (i32.const 4) (i32.const 0))");

// init: seg ix is valid passive, zero len, but src offset out of bounds one
// past the edge
tab_test("",
         "(table.init 1 (i32.const 12) (i32.const 5) (i32.const 0))",
         WebAssembly.RuntimeError, /index out of bounds/);

// init: seg ix is valid passive, zero len, but dst offset out of bounds
tab_test("",
         "(table.init 1 (i32.const 30) (i32.const 2) (i32.const 0))");

// init: seg ix is valid passive, zero len, but dst offset out of bounds one
// past the edge
tab_test("",
         "(table.init 1 (i32.const 31) (i32.const 2) (i32.const 0))",
         WebAssembly.RuntimeError, /index out of bounds/);

// drop: too many args
tab_test("elem.drop 1 (i32.const 42)", "",
         WebAssembly.CompileError,
         /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/);

// init: too many args
tab_test("(table.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))",
         "",
         WebAssembly.CompileError, /(unused values)|(values remaining on stack at end of block)/);

// init: too few args
tab_test("(table.init 1 (i32.const 1) (i32.const 1))", "",
         WebAssembly.CompileError,
         /(popping value from empty stack)|(expected i32 but nothing on stack)/);

// invalid argument types
{
    const tys  = ['i32', 'f32', 'i64', 'f64'];

    const ops = ['table.init 1', 'table.copy'];
    for (let ty1 of tys) {
    for (let ty2 of tys) {
    for (let ty3 of tys) {
    for (let op of ops) {
        if (ty1 == 'i32' && ty2 == 'i32' && ty3 == 'i32')
            continue;  // this is the only valid case
        let i1 = `(${op} (${ty1}.const 1) (${ty2}.const 1) (${ty3}.const 1))`;
        tab_test(i1, "", WebAssembly.CompileError, /type mismatch/);
    }}}}
}


//---- table.copy ---------------------------------------------------------

// There are no immediates here, only 3 dynamic args.  So we're limited to
// runtime boundary checks.

// passive-segs-smoketest.js tests the normal, non-exception cases of
// table.copy.  Here we just test the boundary-failure cases.  The
// table's valid indices are 0 .. 29 inclusive.

// copy: dst range invalid
tab_test("(table.copy (i32.const 28) (i32.const 1) (i32.const 3))",
         "",
         WebAssembly.RuntimeError, /index out of bounds/);

// copy: dst wraparound end of 32 bit offset space
tab_test("(table.copy (i32.const 0xFFFFFFFE) (i32.const 1) (i32.const 2))",
         "",
         WebAssembly.RuntimeError, /index out of bounds/);

// copy: src range invalid
tab_test("(table.copy (i32.const 15) (i32.const 25) (i32.const 6))",
         "",
         WebAssembly.RuntimeError, /index out of bounds/);

// copy: src wraparound end of 32 bit offset space
tab_test("(table.copy (i32.const 15) (i32.const 0xFFFFFFFE) (i32.const 2))",
         "",
         WebAssembly.RuntimeError, /index out of bounds/);

// copy: zero length with both offsets in-bounds is OK
tab_test_nofail(
    "(table.copy (i32.const 15) (i32.const 25) (i32.const 0))",
    "");

// copy: zero length with dst offset out of bounds at the edge
tab_test("(table.copy (i32.const 30) (i32.const 15) (i32.const 0))",
         "");

// copy: zero length with dst offset out of bounds one past the edge
tab_test("(table.copy (i32.const 31) (i32.const 15) (i32.const 0))",
         "", WebAssembly.RuntimeError, /index out of bounds/);

// copy: zero length with src offset out of bounds at the edge
tab_test("(table.copy (i32.const 15) (i32.const 30) (i32.const 0))",
         "");

// copy: zero length with src offset out of bounds one past the edge
tab_test("(table.copy (i32.const 15) (i32.const 31) (i32.const 0))",
         "", WebAssembly.RuntimeError, /index out of bounds/);