lib/std/lang/il/binary/tests.rad 28.0 KiB raw
1
//! Binary RIL encoding fixtures.
2
3
use std::testing;
4
use std::lang::il;
5
use std::lang::il::binary::writer;
6
use std::lang::il::binary;
7
use std::lang::il::binary::reader;
8
use std::lang::alloc;
9
10
/// Decode arena backing storage. Tests reset it before each use.
11
static MEMORY: [u8; 512] = [0; 512];
12
/// Marker for bytes beyond a writer's borrowed output extent.
13
constant OUTPUT_CANARY: u8 = 0xa5;
14
15
/// Fixed data items decode with no arena storage at every count boundary.
16
@test unsafe fn fixedDataItems() throws (testing::TestError) {
17
    for item in [
18
        il::DataItem::Val { typ: il::Type::W8, val: 0x12 },
19
        il::DataItem::Val { typ: il::Type::W16, val: 0x1234 },
20
        il::DataItem::Val { typ: il::Type::W32, val: 0x12345678 },
21
        il::DataItem::Val { typ: il::Type::W64, val: -1 },
22
        il::DataItem::Sym("data"), il::DataItem::Fn("fn"), il::DataItem::Undef,
23
    ] {
24
        for count in [0 as u32, 1, 0xffffffff] {
25
            try checkFixedDataItem(il::DataValue { item, count });
26
        }
27
    }
28
}
29
30
/// Check a fixed initializer and every truncated prefix without allocation.
31
unsafe fn checkFixedDataItem(item: il::DataValue) throws (testing::TestError) {
32
    let mut buffer: [u8; 32] = [0; 32];
33
    let symbols: [*[u8]; 2] = ["data", "fn"];
34
    let mut length: u32 = 0;
35
    let output: 'output = &mut buffer[..], names = &symbols[..] in {
36
        let mut out = writer::new(output, names);
37
        try writer::dataValue(&mut out, item) catch { throw testing::TestError::Failed; };
38
        set length = out.offset;
39
    }
40
    for end in 0..length + 1 {
41
        let mut arena = alloc::new(&mut MEMORY[..0]);
42
        let source: 'input = &buffer[..end], names = &symbols[..] in {
43
            let mut input = reader::new(source, &mut arena, names);
44
            let mut failed = false;
45
            let decoded = try reader::dataValue(&mut input) catch err {
46
                assert err == binary::Error::Truncated;
47
                set failed = true;
48
                il::DataValue { item: il::DataItem::Undef, count: 0 }
49
            };
50
            assert failed == (end < length);
51
            assert input.offset <= end;
52
            if not failed {
53
                assert decoded == item;
54
                assert input.offset == length;
55
            }
56
        }
57
        assert arena.offset == 0;
58
    }
59
}
60
61
/// Scalar decoding uses only checked input bytes and numeric cursor bounds.
62
fn checkScalarReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) {
63
    set input.registers = 2;
64
    set input.blocks = 3;
65
    let reg = try reader::reg(input) catch { throw testing::TestError::Failed; };
66
    try testing::expect(reg.n == 1);
67
    let target = try reader::target(input) catch { throw testing::TestError::Failed; };
68
    try testing::expect(target == 2);
69
    let mut truncated = false;
70
    try reader::integer(input, 1) catch err {
71
        try testing::expect(err == binary::Error::Truncated);
72
        set truncated = true;
73
    };
74
    try testing::expect(truncated);
75
    set input.offset = 0;
76
    let flag = try reader::flag(input) catch { throw testing::TestError::Failed; };
77
    try testing::expect(flag);
78
    set input.offset = 0;
79
    let typ = try reader::typ(input) catch { throw testing::TestError::Failed; };
80
    try testing::expect(typ == il::Type::W8);
81
    set input.offset = 0;
82
    let count = try reader::count(input, 4) catch { throw testing::TestError::Failed; };
83
    try testing::expect(count == 1);
84
    set input.offset = 0;
85
    let wide = try reader::integer(input, 8) catch { throw testing::TestError::Failed; };
86
    try testing::expect(wide == 0x0000000200000001);
87
}
88
89
/// A regional input buffer supports safe scalar decoding.
90
@test unsafe fn scalarReader() throws (testing::TestError) {
91
    let memory = &mut MEMORY[..];
92
    let mut arena = alloc::new(memory);
93
    let bytes: [u8; 8] = [1, 0, 0, 0, 2, 0, 0, 0];
94
    let namesTable: [*[u8]; 0] = [];
95
    let source: 'input = &bytes[..], names = &namesTable[..] in {
96
        let mut input = reader::new(source, &mut arena, names);
97
        try checkScalarReader(&mut input);
98
    }
99
}
100
101
/// Read valid symbol values and reject invalid or truncated wire indices.
102
fn checkSymbolReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) {
103
    let name = try reader::symbol(input) catch { throw testing::TestError::Failed; };
104
    try testing::expectBytesEq(name, "data");
105
    let data = try reader::val(input) catch { throw testing::TestError::Failed; };
106
    let case il::Val::DataSym(dataName) = data else throw testing::TestError::Failed;
107
    try testing::expectBytesEq(dataName, "data");
108
    let function = try reader::val(input) catch { throw testing::TestError::Failed; };
109
    let case il::Val::FnAddr(functionName) = function else throw testing::TestError::Failed;
110
    try testing::expectBytesEq(functionName, "fn");
111
    let mut invalid = false;
112
    try reader::symbol(input) catch err {
113
        assert err == binary::Error::Symbol;
114
        set invalid = true;
115
    };
116
    assert invalid;
117
    let mut truncated = false;
118
    try reader::val(input) catch err {
119
        assert err == binary::Error::Truncated;
120
        set truncated = true;
121
    };
122
    assert truncated;
123
}
124
125
/// Symbol and value decoding use checked table storage from a safe function.
126
@test unsafe fn symbolReader() throws (testing::TestError) {
127
    let memory = &mut MEMORY[..];
128
    let mut arena = alloc::new(memory);
129
    let bytes: [u8; 18] = [0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 2, 0, 0, 0];
130
    let namesTable: [*[u8]; 2] = ["data", "fn"];
131
    let source: 'input = &bytes[..], names = &namesTable[..] in {
132
        let mut input = reader::new(source, &mut arena, names);
133
        try checkSymbolReader(&mut input);
134
    }
135
}
136
137
/// Check little-endian encoding for every integer width.
138
@test fn integers() throws (testing::TestError) {
139
    let mut buffer: [u8; 15] = [0; 15];
140
    let namesTable: [*[u8]; 0] = [];
141
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
142
        let mut out = writer::new(storage, names);
143
        try writer::integer(&mut out, 0x12, 1) catch {
144
            throw testing::TestError::Failed;
145
        };
146
        try writer::integer(&mut out, 0x3456, 2) catch {
147
            throw testing::TestError::Failed;
148
        };
149
        try writer::integer(&mut out, 0x789abcde, 4) catch {
150
            throw testing::TestError::Failed;
151
        };
152
        try writer::integer(&mut out, 0x0123456789abcdef, 8) catch {
153
            throw testing::TestError::Failed;
154
        };
155
        try testing::expectBytesEq(&out.bytes[..], &[0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78, 0xef, 0xcd,
156
            0xab, 0x89, 0x67, 0x45, 0x23, 0x01]);
157
    }
158
}
159
160
/// Compare one instruction with its fixed wire representation.
161
unsafe fn instruction(item: il::Instr, expected: &[u8]) throws (testing::TestError) {
162
    let mut buffer: [u8; 256] = [0; 256];
163
    let namesTable: [*[u8]; 2] = ["data", "fn"];
164
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
165
        let mut out = writer::new(storage, names);
166
        try writer::instr(&mut out, item) catch {
167
            throw testing::TestError::Failed;
168
        };
169
        try testing::expectBytesEq(&out.bytes[..out.offset], expected);
170
        for capacity in 0..(expected.len + 1) {
171
            for i in 0..out.bytes.len {
172
                set out.bytes[i] = OUTPUT_CANARY;
173
            }
174
            let shortStorage: 'short = &mut out.bytes[..capacity], shortNames = &out.symbols[..] in {
175
                let mut short = writer::new(shortStorage, shortNames);
176
                let mut failed = false;
177
                try writer::instr(&mut short, item) catch err {
178
                    try testing::expect(err == binary::Error::Capacity);
179
                    set failed = true;
180
                };
181
                assert failed == (capacity < expected.len);
182
                try testing::expect(short.offset <= capacity);
183
                try testing::expectBytesEq(&short.bytes[..short.offset], &expected[..short.offset]);
184
            }
185
            for i in capacity..out.bytes.len {
186
                assert out.bytes[i] == OUTPUT_CANARY;
187
            }
188
        }
189
        // Inline operands use only the input bytes and symbol table.
190
        let mut arenaSize: u32 = 0;
191
        match item {
192
            case il::Instr::Call { .. }, il::Instr::Jmp { .. },
193
                 il::Instr::Br { .. }, il::Instr::Switch { .. } => set arenaSize = 512,
194
            else => {}
195
        }
196
        let memory = &mut MEMORY[..arenaSize];
197
        let mut arena = alloc::new(&mut memory[..]);
198
        let source: 'input = &expected[..], inputNames = &namesTable[..] in {
199
            let mut input = reader::new(source, &mut arena, inputNames);
200
            set input.registers = 16;
201
            set input.blocks = 4;
202
            let decoded = try reader::instr(&mut input) catch {
203
                throw testing::TestError::Failed;
204
            };
205
            try testing::expect(input.offset == expected.len);
206
            set out.offset = 0;
207
            try writer::instr(&mut out, decoded) catch {
208
                throw testing::TestError::Failed;
209
            };
210
            try testing::expectBytesEq(&out.bytes[..out.offset], expected);
211
            for length in 0..expected.len {
212
                alloc::reset(&mut arena);
213
                set input = reader::new(&source[..length], &mut arena, inputNames);
214
                set input.registers = 16;
215
                set input.blocks = 4;
216
                let mut failed = false;
217
                try reader::instr(&mut input) catch err {
218
                    try testing::expect(err == binary::Error::Truncated);
219
                    set failed = true;
220
                };
221
                try testing::expect(failed);
222
            }
223
        }
224
225
    }
226
}
227
228
/// Malformed inline operands retain bounded cursors and empty arena storage.
229
@test unsafe fn invalidFixedInstructions() throws (testing::TestError) {
230
    let packets: [*[u8]; 6] = [
231
        &[255],
232
        &[6, 255],
233
        &[1, 3],
234
        &[5, 16, 0, 0, 0, 4],
235
        &[11, 2],
236
        &[5, 0, 0, 0, 0, 2, 0, 0, 0, 0],
237
    ];
238
    let namesTable: [*[u8]; 0] = [];
239
    for packet, index in packets {
240
        let mut arena = alloc::new(&mut MEMORY[..0]);
241
        let source: 'input = &packet[..], names = &namesTable[..] in {
242
            let mut input = reader::new(source, &mut arena, names);
243
            set input.registers = 16;
244
            let mut failed = false;
245
            try reader::instr(&mut input) catch err {
246
                let expected = binary::Error::Symbol if index == 5 else binary::Error::Invalid;
247
                assert err == expected;
248
                set failed = true;
249
            };
250
            assert failed;
251
            assert input.offset <= source.len;
252
            assert arena.offset == 0;
253
        }
254
    }
255
}
256
257
/// Check all instruction tags and their field order.
258
@test unsafe fn instructions() throws (testing::TestError) {
259
    let mut args: [il::Val; 1] = [il::Val::Undef];
260
    let mut cases: [il::SwitchCase; 1] = [il::SwitchCase {
261
        value: -1, target: 2, args: &mut args[..],
262
    }];
263
    try instruction(il::Instr::Reserve {
264
        dst: il::Reg { n: 1 }, size: il::Val::Undef, alignment: 16,
265
    }, &[0, 1, 0, 0, 0, 4, 16, 0, 0, 0]);
266
    try instruction(il::Instr::Load {
267
        typ: il::Type::W8, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: -1,
268
    }, &[1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 255]);
269
    try instruction(il::Instr::Sload {
270
        typ: il::Type::W16, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: 3,
271
    }, &[2, 2, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
272
    try instruction(il::Instr::Store {
273
        typ: il::Type::W32, src: il::Val::Undef, dst: il::Reg { n: 2 }, offset: 3,
274
    }, &[3, 4, 4, 2, 0, 0, 0, 3, 0, 0, 0]);
275
    try instruction(il::Instr::Blit {
276
        dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, size: il::Val::Undef,
277
    }, &[4, 1, 0, 0, 0, 2, 0, 0, 0, 4]);
278
    try instruction(il::Instr::Copy {
279
        dst: il::Reg { n: 1 }, val: il::Val::Undef,
280
    }, &[5, 1, 0, 0, 0, 4]);
281
    try instruction(il::Instr::BinOp {
282
        op: il::BinOp::Add, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef,
283
        b: il::Val::Undef,
284
    }, &[6, 0, 8, 1, 0, 0, 0, 4, 4]);
285
    try instruction(il::Instr::UnOp {
286
        op: il::UnOp::Neg, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef,
287
    }, &[7, 0, 8, 1, 0, 0, 0, 4]);
288
    try instruction(il::Instr::Zext {
289
        typ: il::Type::W8, dst: il::Reg { n: 1 }, val: il::Val::Undef,
290
    }, &[8, 1, 1, 0, 0, 0, 4]);
291
    try instruction(il::Instr::Sext {
292
        typ: il::Type::W16, dst: il::Reg { n: 1 }, val: il::Val::Undef,
293
    }, &[9, 2, 1, 0, 0, 0, 4]);
294
    try instruction(il::Instr::Call {
295
        retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("fn"), args: &[],
296
    }, &[10, 8, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0]);
297
    try instruction(il::Instr::Call {
298
        retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("fn"), args: &args[..],
299
    }, &[10, 8, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 0, 0, 4]);
300
    try instruction(il::Instr::Ret {
301
        val: nil,
302
    }, &[11, 0]);
303
    try instruction(il::Instr::Ret {
304
        val: il::Val::Undef,
305
    }, &[11, 1, 4]);
306
    try instruction(il::Instr::Jmp {
307
        target: 2, args: &mut args[..],
308
    }, &[12, 2, 0, 0, 0, 1, 0, 0, 0, 4]);
309
    try instruction(il::Instr::Br {
310
        op: il::CmpOp::Eq, typ: il::Type::W32, a: il::Val::Undef, b: il::Val::Undef, thenTarget: 1,
311
        thenArgs: &mut args[..], elseTarget: 2, elseArgs: &mut args[..],
312
    }, &[13, 0, 4, 4, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 0, 0, 1, 0, 0, 0, 4]);
313
    try instruction(il::Instr::Switch {
314
        val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut args[..], cases: &mut cases[..],
315
    }, &[
316
        14, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 1,
317
        0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 2,
318
        0, 0, 0, 1, 0, 0, 0, 4,
319
    ]);
320
    try instruction(il::Instr::Switch {
321
        val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut [], cases: &mut [],
322
    }, &[14, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
323
    let mut mixedCases = [
324
        il::SwitchCase { value: -9223372036854775808, target: 0, args: &mut [] },
325
        il::SwitchCase { value: 9223372036854775807, target: 2, args: &mut args[..] },
326
    ];
327
    try instruction(il::Instr::Switch {
328
        val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut [], cases: &mut mixedCases[..],
329
    }, &[
330
        14, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
331
        0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0,
332
        255, 255, 255, 255, 255, 255, 255, 127, 2, 0, 0, 0, 1, 0, 0, 0, 4,
333
    ]);
334
    try instruction(il::Instr::Unreachable, &[15]);
335
    try instruction(il::Instr::Ecall {
336
        dst: il::Reg { n: 1 }, num: il::Val::Undef, a0: il::Val::Undef, a1: il::Val::Undef,
337
        a2: il::Val::Undef, a3: il::Val::Undef,
338
    }, &[16, 1, 0, 0, 0, 4, 4, 4, 4, 4]);
339
    try instruction(il::Instr::Ebreak, &[17]);
340
    try instruction(il::Instr::MemoryFence, &[18]);
341
    for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] {
342
        let mut read: [u8; 8] = [19, 0, 1, 0, 0, 0, 4, 4];
343
        let mut write: [u8; 5] = [20, 0, 4, 4, 4];
344
        set read[1] = il::typeSize(typ) as u8;
345
        set write[1] = il::typeSize(typ) as u8;
346
        try instruction(il::Instr::DeviceRead {
347
            typ, dst: il::Reg { n: 1 }, handle: il::Val::Undef, offset: il::Val::Undef,
348
        }, &read[..]);
349
        try instruction(il::Instr::DeviceWrite {
350
            typ, handle: il::Val::Undef, offset: il::Val::Undef, value: il::Val::Undef,
351
        }, &write[..]);
352
    }
353
}
354
355
/// Switch decoding stops at an invalid target or argument in either case.
356
@test unsafe fn invalidSwitchCases() throws (testing::TestError) {
357
    let namesTable: [*[u8]; 0] = [];
358
    let mut encoded: [u8; 48] = [0; 48];
359
    set encoded[0] = binary::INSTR_SWITCH;
360
    set encoded[1] = 4;
361
    set encoded[10] = 2;
362
    set encoded[26] = 1;
363
    set encoded[30] = 4;
364
    set encoded[43] = 1;
365
    set encoded[47] = 4;
366
    for offset in [22, 39, 30, 47] {
367
        let original = encoded[offset];
368
        set encoded[offset] = 255;
369
        let mut arena = alloc::new(&mut MEMORY[..]);
370
        let source: 'input = &encoded[..], names = &namesTable[..] in {
371
            let mut input = reader::new(source, &mut arena, names);
372
            set input.blocks = 1;
373
            let mut failed = false;
374
            try reader::instr(&mut input) catch err {
375
                assert err == binary::Error::Invalid;
376
                set failed = true;
377
            };
378
            assert failed;
379
            assert input.offset == offset + (4 if offset == 22 or offset == 39 else 1);
380
        }
381
        set encoded[offset] = original;
382
    }
383
}
384
385
/// Byte decoding retains exact allocation and cursor state on storage failure.
386
@test unsafe fn byteStorage() throws (testing::TestError) {
387
    let encoded: [u8; 11] = [0, 0, 0, 0, 3, 0, 0, 0, 65, 0, 255];
388
    let namesTable: [*[u8]; 0] = [];
389
    for capacity in 0..4 {
390
        let mut arena = alloc::new(&mut MEMORY[..capacity]);
391
        let source: 'input = &encoded[..], names = &namesTable[..] in {
392
            let mut input = reader::new(source, &mut arena, names);
393
            let empty = try! reader::bytes(&mut input);
394
            assert empty.len == 0;
395
            assert input.offset == 4;
396
            let mut failed = false;
397
            let result = try reader::bytes(&mut input) catch err {
398
                assert err == binary::Error::Storage;
399
                set failed = true;
400
                &[] as *[u8]
401
            };
402
            assert failed == (capacity < 3);
403
            assert input.offset == (8 if failed else 11);
404
            if not failed { try testing::expectBytesEq(result, &[65, 0, 255]); }
405
        }
406
        assert arena.offset == (3 if capacity == 3 else 0);
407
    }
408
}
409
410
/// Exact operand tables use one allocation and publish only complete prefixes.
411
@test unsafe fn exactValueStorage() throws (testing::TestError) {
412
    let namesTable: [*[u8]; 0] = [];
413
    let alignment = @alignOf(il::Val);
414
    let remainder = ((&MEMORY[0]) as u64) & (alignment as u64 - 1);
415
    let padding = ((alignment as u64 - remainder) & (alignment as u64 - 1)) as u32;
416
    for count in 0..4 {
417
        let required = 0 if count == 0 else padding + count * @sizeOf(il::Val);
418
        let mut encoded: [u8; 7] = [0, 0, 0, 0, 4, 4, 4];
419
        set encoded[0] = count as u8;
420
        for capacity in 0..required + 1 {
421
            let mut arena = alloc::new(&mut MEMORY[..capacity]);
422
            let source: 'input = &encoded[..4 + count], names = &namesTable[..] in {
423
                let mut input = reader::new(source, &mut arena, names);
424
                let mut failed = false;
425
                let result = try reader::values(&mut input) catch error {
426
                    assert error == binary::Error::Storage;
427
                    set failed = true;
428
                    &mut [] as *unsafe mut [il::Val]
429
                };
430
                assert failed == (capacity < required);
431
                assert input.offset == (4 if failed else 4 + count);
432
                if not failed {
433
                    assert result.len == count;
434
                    for value in result { assert value == il::Val::Undef; }
435
                }
436
            }
437
            assert arena.offset == (0 if capacity < required else required);
438
        }
439
    }
440
}
441
442
/// Invalid values stop counted decoding at the failing tag or register index.
443
@test unsafe fn invalidValueSequences() throws (testing::TestError) {
444
    let namesTable: [*[u8]; 0] = [];
445
    for position in 0..2 {
446
        for invalidTag in [false, true] {
447
            let mut encoded: [u8; 14] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
448
            if invalidTag {
449
                set encoded[4 + position * 5] = 255;
450
            } else {
451
                set encoded[5 + position * 5] = 1;
452
            }
453
            let mut arena = alloc::new(&mut MEMORY[..128]);
454
            let source: 'input = &encoded[..], names = &namesTable[..] in {
455
                let mut input = reader::new(source, &mut arena, names);
456
                set input.registers = 1;
457
                let mut failed = false;
458
                try reader::values(&mut input) catch err {
459
                    assert err == binary::Error::Invalid;
460
                    set failed = true;
461
                };
462
                assert failed;
463
                assert input.offset == position * 5 + (5 if invalidTag else 9);
464
            }
465
        }
466
    }
467
}
468
469
/// Check every value tag and empty and nonempty sequences.
470
@test unsafe fn values() throws (testing::TestError) {
471
    let mut buffer: [u8; 64] = [0; 64];
472
    let namesTable: [*[u8]; 2] = ["data", "fn"];
473
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
474
        let mut out = writer::new(storage, names);
475
        try writer::values(&mut out, &[]) catch {
476
            throw testing::TestError::Failed;
477
        };
478
        try writer::values(&mut out, &[
479
            il::Val::Reg(il::Reg { n: 0x12345678 }),
480
            il::Val::Imm(-2), il::Val::DataSym("data"),
481
            il::Val::FnAddr("fn"), il::Val::Undef,
482
        ]) catch {
483
            throw testing::TestError::Failed;
484
        };
485
        try testing::expectBytesEq(&out.bytes[..out.offset], &[
486
            0, 0, 0, 0, 5, 0, 0, 0,
487
            0, 0x78, 0x56, 0x34, 0x12,
488
            1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
489
            2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4,
490
        ]);
491
        let memory = &mut MEMORY[..256];
492
        let mut arena = alloc::new(&mut memory[..]);
493
        let mut input = reader::new(&out.bytes[..out.offset], &mut arena, &out.symbols[..]);
494
        set input.registers = 0x12345679;
495
        let empty = try reader::values(&mut input) catch {
496
            throw testing::TestError::Failed;
497
        };
498
        let decoded = try reader::values(&mut input) catch {
499
            throw testing::TestError::Failed;
500
        };
501
        try testing::expect(empty.len == 0);
502
        try testing::expect(decoded.len == 5);
503
        try testing::expect(input.offset == out.offset);
504
        let mut repeated: [u8; 64] = [0; 64];
505
        let copyNamesTable: [*[u8]; 2] = ["data", "fn"];
506
        let copyStorage: 'copy = &mut repeated[..], copyNames = &copyNamesTable[..] in {
507
            let mut copy = writer::new(copyStorage, copyNames);
508
            try writer::values(&mut copy, empty) catch {
509
                throw testing::TestError::Failed;
510
            };
511
            try writer::values(&mut copy, decoded) catch {
512
                throw testing::TestError::Failed;
513
            };
514
            try testing::expectBytesEq(&out.bytes[..out.offset], &copy.bytes[..copy.offset]);
515
516
        }
517
    }
518
}
519
520
/// Check initializer bytes and repetition counts.
521
@test fn initializers() throws (testing::TestError) {
522
    let mut buffer: [u8; 128] = [0; 128];
523
    let namesTable: [*[u8]; 2] = ["data", "fn"];
524
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
525
        let mut out = writer::new(storage, names);
526
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W8,
527
            val: -1 }, count: 0 }) catch {
528
                throw testing::TestError::Failed;
529
            };
530
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W16,
531
            val: -2 }, count: 1 }) catch {
532
                throw testing::TestError::Failed;
533
            };
534
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W32,
535
            val: -3 }, count: 2 }) catch {
536
                throw testing::TestError::Failed;
537
            };
538
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W64,
539
            val: -4 }, count: 3 }) catch {
540
                throw testing::TestError::Failed;
541
            };
542
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Sym("data"), count: 4 })
543
            catch {
544
                throw testing::TestError::Failed;
545
            };
546
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Fn("fn"), count: 5 }) catch
547
            {
548
                throw testing::TestError::Failed;
549
            };
550
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str("ab"), count: 6 }) catch
551
            {
552
                throw testing::TestError::Failed;
553
            };
554
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str(""), count: 7 }) catch {
555
            throw testing::TestError::Failed; };
556
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Undef, count: 8 }) catch {
557
            throw testing::TestError::Failed; };
558
        try testing::expectBytesEq(&out.bytes[..out.offset], &[0, 1, 255, 0, 0, 0, 0, 0, 2, 254, 255, 1, 0,
559
            0, 0, 0, 4, 253, 255, 255, 255, 2, 0, 0, 0, 0, 8, 252, 255, 255, 255, 255, 255, 255, 255, 3,
560
            0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, 0, 0, 0, 5, 0, 0, 0, 3, 2, 0, 0, 0, 97, 98, 6, 0,
561
            0, 0, 3, 0, 0, 0, 0, 7, 0, 0, 0, 4, 8, 0, 0, 0]);
562
    }
563
}
564
565
/// Reject invalid widths, insufficient storage, and absent symbols.
566
@test fn errors() throws (testing::TestError) {
567
    let mut buffer: [u8; 8] = [0; 8];
568
    let namesTable: [*[u8]; 0] = [];
569
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
570
        let mut out = writer::new(storage, names);
571
        let mut failures: u32 = 0;
572
        try writer::integer(&mut out, 1, 3) catch err {
573
            try testing::expect(err == binary::Error::Invalid);
574
            set failures += 1;
575
        };
576
        try writer::symbol(&mut out, "absent") catch err {
577
            try testing::expect(err == binary::Error::Symbol);
578
            set failures += 1;
579
        };
580
        try testing::expect(out.offset == 0);
581
        try writer::integer(&mut out, 0, 8) catch {
582
            throw testing::TestError::Failed;
583
        };
584
        try writer::integer(&mut out, 1, 1) catch err {
585
            try testing::expect(err == binary::Error::Capacity);
586
            set failures += 1;
587
        };
588
        try testing::expect(failures == 3);
589
        try testing::expect(out.offset == 8);
590
    }
591
}
592
593
/// Check operation tags independently of the native union representation.
594
@test unsafe fn operations() throws (testing::TestError) {
595
    let binaryOps = &[
596
        il::BinOp::Add, il::BinOp::Sub, il::BinOp::Mul,
597
        il::BinOp::Sdiv, il::BinOp::Udiv, il::BinOp::Srem,
598
        il::BinOp::Urem, il::BinOp::Eq, il::BinOp::Ne,
599
        il::BinOp::Slt, il::BinOp::Sge, il::BinOp::Ult,
600
        il::BinOp::Uge, il::BinOp::And, il::BinOp::Or,
601
        il::BinOp::Xor, il::BinOp::Shl, il::BinOp::Sshr,
602
        il::BinOp::Ushr,
603
    ];
604
    for op, tag in binaryOps {
605
        try instruction(il::Instr::BinOp {
606
            op, typ: il::Type::W64, dst: il::Reg { n: 1 },
607
            a: il::Val::Undef, b: il::Val::Undef,
608
        }, &[6, tag as u8, 8, 1, 0, 0, 0, 4, 4]);
609
    }
610
    for op, tag in &[il::UnOp::Neg, il::UnOp::Not] {
611
        try instruction(il::Instr::UnOp {
612
            op, typ: il::Type::W64, dst: il::Reg { n: 1 },
613
            a: il::Val::Undef,
614
        }, &[7, tag as u8, 8, 1, 0, 0, 0, 4]);
615
    }
616
    let mut empty: [il::Val; 0] = [];
617
    for op, tag in &[il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] {
618
        try instruction(il::Instr::Br {
619
            op, typ: il::Type::W8, a: il::Val::Undef, b: il::Val::Undef,
620
            thenTarget: 1, thenArgs: &mut empty[..],
621
            elseTarget: 2, elseArgs: &mut empty[..],
622
        }, &[13, tag as u8, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]);
623
    }
624
}
625
626
/// Check byte-sequence bounds at every output size.
627
@test fn byteCapacity() throws (testing::TestError) {
628
    let mut buffer: [u8; 7] = [0; 7];
629
    for capacity in 0..7 {
630
        let namesTable: [*[u8]; 0] = [];
631
        let storage: 'short = &mut buffer[..capacity], names = &namesTable[..] in {
632
            let mut out = writer::new(storage, names);
633
            let mut failed = false;
634
            try writer::bytes(&mut out, "abc") catch err {
635
                try testing::expect(err == binary::Error::Capacity);
636
                set failed = true;
637
            };
638
            try testing::expect(failed);
639
            try testing::expect(out.offset <= capacity);
640
        }
641
    }
642
    let namesTable: [*[u8]; 0] = [];
643
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
644
        let mut out = writer::new(storage, names);
645
        try writer::bytes(&mut out, "abc") catch {
646
            throw testing::TestError::Failed;
647
        };
648
        try testing::expectBytesEq(&out.bytes[..], &[3, 0, 0, 0, 97, 98, 99]);
649
    }
650
}