compiler: Decode fixed data items in checked code

8d09b8cba394fe010dd34d05ad9f6a87fc6d3d5317c6f32d05c033cb0e137e53
Alexis Sellier committed ago 1 parent 603eae1b
lib/std/lang/il/binary/reader.rad +12 -10
346 346
}
347 347
348 348
/// Read an initializer with a repetition count.
349 349
export unsafe fn dataValue 'input (input: &mut Reader 'input) -> il::DataValue throws (binary::Error) {
350 350
    let tag = try integer(input, 1) as u8;
351 -
    let mut item: il::DataItem = il::DataItem::Undef;
351 +
    let item = il::DataItem::Str(try bytes(input)) if tag == super::DATA_STR
352 +
        else try fixedDataItem(input, tag);
353 +
    let n = try integer(input, 4) as u32;
354 +
    return il::DataValue { item, count: n };
355 +
}
356 +
357 +
/// Decode a data item whose payload needs no allocation.
358 +
fn fixedDataItem 'input (input: &mut Reader 'input, tag: u8) -> il::DataItem throws (binary::Error) {
352 359
    match tag {
353 360
        case super::DATA_VAL => {
354 361
            let t = try typ(input);
355 362
            let n = try integer(input, il::typeSize(t));
356 -
            set item = il::DataItem::Val { typ: t, val: n as i64 };
363 +
            return il::DataItem::Val { typ: t, val: n as i64 };
357 364
        },
358 365
        case super::DATA_SYM => {
359 -
            set item = il::DataItem::Sym(try symbol(input));
366 +
            return il::DataItem::Sym(try symbol(input));
360 367
        },
361 368
        case super::DATA_FN => {
362 -
            set item = il::DataItem::Fn(try symbol(input));
363 -
        },
364 -
        case super::DATA_STR => {
365 -
            set item = il::DataItem::Str(try bytes(input));
369 +
            return il::DataItem::Fn(try symbol(input));
366 370
        },
367 371
        case super::DATA_UNDEF => {
368 -
            set item = il::DataItem::Undef;
372 +
            return il::DataItem::Undef;
369 373
        },
370 374
        else => throw binary::Error::Invalid,
371 375
    }
372 -
    let n = try integer(input, 4) as u32;
373 -
    return il::DataValue { item, count: n };
374 376
}
lib/std/lang/il/binary/tests.rad +46 -0
10 10
/// Decode arena backing storage. Tests reset it before each use.
11 11
static MEMORY: [u8; 512] = [0; 512];
12 12
/// Marker for bytes beyond a writer's borrowed output extent.
13 13
constant OUTPUT_CANARY: u8 = 0xa5;
14 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 +
15 61
/// Scalar decoding uses only checked input bytes and numeric cursor bounds.
16 62
fn checkScalarReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) {
17 63
    set input.registers = 2;
18 64
    set input.blocks = 3;
19 65
    let reg = try reader::reg(input) catch { throw testing::TestError::Failed; };