lib/std/lang/il/binary/tests.rad 26.5 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
/// Invalid values stop counted decoding at the failing tag or register index.
411
@test unsafe fn invalidValueSequences() throws (testing::TestError) {
412
    let namesTable: [*[u8]; 0] = [];
413
    for position in 0..2 {
414
        for invalidTag in [false, true] {
415
            let mut encoded: [u8; 14] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
416
            if invalidTag {
417
                set encoded[4 + position * 5] = 255;
418
            } else {
419
                set encoded[5 + position * 5] = 1;
420
            }
421
            let mut arena = alloc::new(&mut MEMORY[..128]);
422
            let source: 'input = &encoded[..], names = &namesTable[..] in {
423
                let mut input = reader::new(source, &mut arena, names);
424
                set input.registers = 1;
425
                let mut failed = false;
426
                try reader::values(&mut input) catch err {
427
                    assert err == binary::Error::Invalid;
428
                    set failed = true;
429
                };
430
                assert failed;
431
                assert input.offset == position * 5 + (5 if invalidTag else 9);
432
            }
433
        }
434
    }
435
}
436
437
/// Check every value tag and empty and nonempty sequences.
438
@test unsafe fn values() throws (testing::TestError) {
439
    let mut buffer: [u8; 64] = [0; 64];
440
    let namesTable: [*[u8]; 2] = ["data", "fn"];
441
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
442
        let mut out = writer::new(storage, names);
443
        try writer::values(&mut out, &[]) catch {
444
            throw testing::TestError::Failed;
445
        };
446
        try writer::values(&mut out, &[
447
            il::Val::Reg(il::Reg { n: 0x12345678 }),
448
            il::Val::Imm(-2), il::Val::DataSym("data"),
449
            il::Val::FnAddr("fn"), il::Val::Undef,
450
        ]) catch {
451
            throw testing::TestError::Failed;
452
        };
453
        try testing::expectBytesEq(&out.bytes[..out.offset], &[
454
            0, 0, 0, 0, 5, 0, 0, 0,
455
            0, 0x78, 0x56, 0x34, 0x12,
456
            1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
457
            2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4,
458
        ]);
459
        let memory = &mut MEMORY[..256];
460
        let mut arena = alloc::new(&mut memory[..]);
461
        let mut input = reader::new(&out.bytes[..out.offset], &mut arena, &out.symbols[..]);
462
        set input.registers = 0x12345679;
463
        let empty = try reader::values(&mut input) catch {
464
            throw testing::TestError::Failed;
465
        };
466
        let decoded = try reader::values(&mut input) catch {
467
            throw testing::TestError::Failed;
468
        };
469
        try testing::expect(empty.len == 0);
470
        try testing::expect(decoded.len == 5);
471
        try testing::expect(input.offset == out.offset);
472
        let mut repeated: [u8; 64] = [0; 64];
473
        let copyNamesTable: [*[u8]; 2] = ["data", "fn"];
474
        let copyStorage: 'copy = &mut repeated[..], copyNames = &copyNamesTable[..] in {
475
            let mut copy = writer::new(copyStorage, copyNames);
476
            try writer::values(&mut copy, empty) catch {
477
                throw testing::TestError::Failed;
478
            };
479
            try writer::values(&mut copy, decoded) catch {
480
                throw testing::TestError::Failed;
481
            };
482
            try testing::expectBytesEq(&out.bytes[..out.offset], &copy.bytes[..copy.offset]);
483
484
        }
485
    }
486
}
487
488
/// Check initializer bytes and repetition counts.
489
@test fn initializers() throws (testing::TestError) {
490
    let mut buffer: [u8; 128] = [0; 128];
491
    let namesTable: [*[u8]; 2] = ["data", "fn"];
492
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
493
        let mut out = writer::new(storage, names);
494
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W8,
495
            val: -1 }, count: 0 }) catch {
496
                throw testing::TestError::Failed;
497
            };
498
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W16,
499
            val: -2 }, count: 1 }) catch {
500
                throw testing::TestError::Failed;
501
            };
502
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W32,
503
            val: -3 }, count: 2 }) catch {
504
                throw testing::TestError::Failed;
505
            };
506
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W64,
507
            val: -4 }, count: 3 }) catch {
508
                throw testing::TestError::Failed;
509
            };
510
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Sym("data"), count: 4 })
511
            catch {
512
                throw testing::TestError::Failed;
513
            };
514
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Fn("fn"), count: 5 }) catch
515
            {
516
                throw testing::TestError::Failed;
517
            };
518
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str("ab"), count: 6 }) catch
519
            {
520
                throw testing::TestError::Failed;
521
            };
522
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str(""), count: 7 }) catch {
523
            throw testing::TestError::Failed; };
524
        try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Undef, count: 8 }) catch {
525
            throw testing::TestError::Failed; };
526
        try testing::expectBytesEq(&out.bytes[..out.offset], &[0, 1, 255, 0, 0, 0, 0, 0, 2, 254, 255, 1, 0,
527
            0, 0, 0, 4, 253, 255, 255, 255, 2, 0, 0, 0, 0, 8, 252, 255, 255, 255, 255, 255, 255, 255, 3,
528
            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,
529
            0, 0, 3, 0, 0, 0, 0, 7, 0, 0, 0, 4, 8, 0, 0, 0]);
530
    }
531
}
532
533
/// Reject invalid widths, insufficient storage, and absent symbols.
534
@test fn errors() throws (testing::TestError) {
535
    let mut buffer: [u8; 8] = [0; 8];
536
    let namesTable: [*[u8]; 0] = [];
537
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
538
        let mut out = writer::new(storage, names);
539
        let mut failures: u32 = 0;
540
        try writer::integer(&mut out, 1, 3) catch err {
541
            try testing::expect(err == binary::Error::Invalid);
542
            set failures += 1;
543
        };
544
        try writer::symbol(&mut out, "absent") catch err {
545
            try testing::expect(err == binary::Error::Symbol);
546
            set failures += 1;
547
        };
548
        try testing::expect(out.offset == 0);
549
        try writer::integer(&mut out, 0, 8) catch {
550
            throw testing::TestError::Failed;
551
        };
552
        try writer::integer(&mut out, 1, 1) catch err {
553
            try testing::expect(err == binary::Error::Capacity);
554
            set failures += 1;
555
        };
556
        try testing::expect(failures == 3);
557
        try testing::expect(out.offset == 8);
558
    }
559
}
560
561
/// Check operation tags independently of the native union representation.
562
@test unsafe fn operations() throws (testing::TestError) {
563
    let binaryOps = &[
564
        il::BinOp::Add, il::BinOp::Sub, il::BinOp::Mul,
565
        il::BinOp::Sdiv, il::BinOp::Udiv, il::BinOp::Srem,
566
        il::BinOp::Urem, il::BinOp::Eq, il::BinOp::Ne,
567
        il::BinOp::Slt, il::BinOp::Sge, il::BinOp::Ult,
568
        il::BinOp::Uge, il::BinOp::And, il::BinOp::Or,
569
        il::BinOp::Xor, il::BinOp::Shl, il::BinOp::Sshr,
570
        il::BinOp::Ushr,
571
    ];
572
    for op, tag in binaryOps {
573
        try instruction(il::Instr::BinOp {
574
            op, typ: il::Type::W64, dst: il::Reg { n: 1 },
575
            a: il::Val::Undef, b: il::Val::Undef,
576
        }, &[6, tag as u8, 8, 1, 0, 0, 0, 4, 4]);
577
    }
578
    for op, tag in &[il::UnOp::Neg, il::UnOp::Not] {
579
        try instruction(il::Instr::UnOp {
580
            op, typ: il::Type::W64, dst: il::Reg { n: 1 },
581
            a: il::Val::Undef,
582
        }, &[7, tag as u8, 8, 1, 0, 0, 0, 4]);
583
    }
584
    let mut empty: [il::Val; 0] = [];
585
    for op, tag in &[il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] {
586
        try instruction(il::Instr::Br {
587
            op, typ: il::Type::W8, a: il::Val::Undef, b: il::Val::Undef,
588
            thenTarget: 1, thenArgs: &mut empty[..],
589
            elseTarget: 2, elseArgs: &mut empty[..],
590
        }, &[13, tag as u8, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]);
591
    }
592
}
593
594
/// Check byte-sequence bounds at every output size.
595
@test fn byteCapacity() throws (testing::TestError) {
596
    let mut buffer: [u8; 7] = [0; 7];
597
    for capacity in 0..7 {
598
        let namesTable: [*[u8]; 0] = [];
599
        let storage: 'short = &mut buffer[..capacity], names = &namesTable[..] in {
600
            let mut out = writer::new(storage, names);
601
            let mut failed = false;
602
            try writer::bytes(&mut out, "abc") catch err {
603
                try testing::expect(err == binary::Error::Capacity);
604
                set failed = true;
605
            };
606
            try testing::expect(failed);
607
            try testing::expect(out.offset <= capacity);
608
        }
609
    }
610
    let namesTable: [*[u8]; 0] = [];
611
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
612
        let mut out = writer::new(storage, names);
613
        try writer::bytes(&mut out, "abc") catch {
614
            throw testing::TestError::Failed;
615
        };
616
        try testing::expectBytesEq(&out.bytes[..], &[3, 0, 0, 0, 97, 98, 99]);
617
    }
618
}