compiler: Decode scalar RIL fields through regional input borrows

df6afe112faa7b92be060f31202dd5d56d7cea3234cfa6ef0c985863fb52f8a7
Alexis Sellier committed ago 1 parent c46d379a
lib/std/lang/il/binary/decodeTests.rad +30 -24
52 52
        };
53 53
        try testing::expect(failed);
54 54
        try testing::expect(arena.offset == 8);
55 55
    }
56 56
    set arena.offset = 8;
57 -
    let mut input = reader::new(&[], &mut arena, &[]);
58 -
    let mut exhausted = false;
59 -
    try reader::storage(&mut input, 0xffffffff, 8, 0xffffffff) catch err {
60 -
        try testing::expect(err == binary::Error::Storage);
61 -
        set exhausted = true;
62 -
    };
63 -
    try testing::expect(exhausted);
64 -
    try testing::expect(arena.offset == 8);
57 +
    let source: 'input = &buffer[..0] in {
58 +
        let mut input = reader::new(source, &mut arena, &[]);
59 +
        let mut exhausted = false;
60 +
        try reader::storage(&mut input, 0xffffffff, 8, 0xffffffff) catch err {
61 +
            try testing::expect(err == binary::Error::Storage);
62 +
            set exhausted = true;
63 +
        };
64 +
        try testing::expect(exhausted);
65 +
        try testing::expect(arena.offset == 8);
66 +
    }
65 67
    // Exercise every smaller arena extent, including alignment boundaries.
66 68
    let mut usedArena = alloc::new(&mut memory[..]);
67 69
    let _ = try program::decode(&buffer[..length], &mut usedArena, LIMITS) catch {
68 70
        throw testing::TestError::Failed;
69 71
    };
149 151
150 152
/// Reject an invalid encoded instruction without a panic.
151 153
unsafe fn badInstruction(bytes: *[u8], expected: binary::Error) throws (testing::TestError) {
152 154
    let memory = &mut MEMORY[..64];
153 155
    let mut arena = alloc::new(&mut memory[..]);
154 -
    let mut input = reader::new(bytes, &mut arena, &["p"]);
155 -
    set input.registers = 2;
156 -
    set input.blocks = 1;
157 -
    let mut failed = false;
158 -
    try reader::instr(&mut input) catch err {
159 -
        try testing::expect(err == expected);
160 -
        set failed = true;
161 -
    };
162 -
    try testing::expect(failed);
156 +
    let source: 'input = &bytes[..] in {
157 +
        let mut input = reader::new(source, &mut arena, &["p"]);
158 +
        set input.registers = 2;
159 +
        set input.blocks = 1;
160 +
        let mut failed = false;
161 +
        try reader::instr(&mut input) catch err {
162 +
            try testing::expect(err == expected);
163 +
            set failed = true;
164 +
        };
165 +
        try testing::expect(failed);
166 +
    }
163 167
}
164 168
165 169
/// Check instruction tags, operand tags, types, optional flags, and indices.
166 170
@test unsafe fn malformedInstructions() throws (testing::TestError) {
167 171
    try badInstruction(&[255], binary::Error::Invalid);
293 297
@test unsafe fn malformedInitializers() throws (testing::TestError) {
294 298
    let memory = &mut MEMORY[..8];
295 299
    let mut arena = alloc::new(&mut memory[..]);
296 300
    let fixtures: [*[u8]; 2] = [&[255], &[0, 3]];
297 301
    for bytes in &fixtures[..] {
298 -
        let mut input = reader::new(bytes, &mut arena, &[]);
299 -
        let mut failed = false;
300 -
        try reader::dataValue(&mut input) catch err {
301 -
            try testing::expect(err == binary::Error::Invalid);
302 -
            set failed = true;
303 -
        };
304 -
        try testing::expect(failed);
302 +
        let source: 'input = &bytes[..] in {
303 +
            let mut input = reader::new(source, &mut arena, &[]);
304 +
            let mut failed = false;
305 +
            try reader::dataValue(&mut input) catch err {
306 +
                try testing::expect(err == binary::Error::Invalid);
307 +
                set failed = true;
308 +
            };
309 +
            try testing::expect(failed);
310 +
        }
305 311
    }
306 312
}
307 313
308 314
/// Check symbol collection capacity and package ownership failures.
309 315
@test unsafe fn collectionBounds() throws (testing::TestError) {
lib/std/lang/il/binary/program.rad +11 -9
84 84
        return out.offset;
85 85
    }
86 86
}
87 87
88 88
/// Read typed SSA parameters with checked register indices.
89 -
unsafe fn readParams(input: &mut reader::Reader) -> *unsafe [il::Param] throws (binary::Error) {
89 +
unsafe fn readParams 'input (input: &mut reader::Reader 'input) -> *unsafe [il::Param] throws (binary::Error) {
90 90
    let n = try reader::count(input, 5);
91 91
    let params = try reader::storage(input, @sizeOf(il::Param), @alignOf(il::Param), n)
92 92
        as *mut [il::Param];
93 93
    for i in 0..n {
94 94
        let value = try reader::reg(input);
97 97
    }
98 98
    return (&params[..]) as *unsafe [il::Param];
99 99
}
100 100
101 101
/// Read global data and check initializer extents against declared storage.
102 -
unsafe fn readData(input: &mut reader::Reader) -> *[il::Data] throws (binary::Error) {
102 +
unsafe fn readData 'input (input: &mut reader::Reader 'input) -> *[il::Data] throws (binary::Error) {
103 103
    let n = try reader::count(input, 18);
104 104
    let items = try reader::storage(input, @sizeOf(il::Data), @alignOf(il::Data), n)
105 105
        as *mut [il::Data];
106 106
    for i in 0..n {
107 107
        let name = try reader::symbol(input);
143 143
    }
144 144
    return items;
145 145
}
146 146
147 147
/// Read functions with checked block and register indices.
148 -
unsafe fn readFunctions(input: &mut reader::Reader, limits: binary::Limits)
148 +
unsafe fn readFunctions 'input (input: &mut reader::Reader 'input, limits: binary::Limits)
149 149
    -> *unsafe [*unsafe il::Fn] throws (binary::Error)
150 150
{
151 151
    let n = try reader::count(input, 14);
152 152
    let fns = try reader::storage(input, @sizeOf(*il::Fn), @alignOf(*il::Fn), n) as *mut [*unsafe il::Fn];
153 153
    set input.registers = limits.registers;
193 193
    }
194 194
    return (&fns[..]) as *unsafe [*unsafe il::Fn];
195 195
}
196 196
197 197
/// Read the package tables. The input must contain exactly one package.
198 -
unsafe fn readPackage(input: &mut reader::Reader, limits: binary::Limits)
198 +
unsafe fn readPackage 'input (input: &mut reader::Reader 'input, limits: binary::Limits)
199 199
    -> binary::Package throws (binary::Error)
200 200
{
201 201
    let magic = try reader::integer(input, 4) as u32;
202 202
    let version = try reader::integer(input, 4) as u32;
203 203
    if magic <> binary::MAGIC or version <> binary::VERSION {
250 250
/// The caller must retain the decoded allocations until the last package use.
251 251
export unsafe fn decode(bytes: &[u8], arena: &mut alloc::Arena, limits: binary::Limits)
252 252
    -> binary::Package throws (binary::Error)
253 253
{
254 254
    let saved = alloc::save(arena);
255 -
    let mut input = reader::new(bytes, arena, &[]);
256 -
    return try readPackage(&mut input, limits) catch err {
257 -
        alloc::restore(arena, saved);
258 -
        throw err;
259 -
    };
255 +
    let source: 'input = &bytes[..] in {
256 +
        let mut input = reader::new(source, &mut *arena, &[]);
257 +
        return try readPackage(&mut input, limits) catch err {
258 +
            alloc::restore(arena, saved);
259 +
            throw err;
260 +
        };
261 +
    }
260 262
}
lib/std/lang/il/binary/reader.rad +22 -21
4 4
use std::lang::il;
5 5
use std::lang::il::binary;
6 6
use std::lang::alloc;
7 7
8 8
/// Input cursor and reconstruction storage.
9 -
/// Input bytes, symbols, and the arena must remain valid during each read.
10 -
export record Reader: Copy {
9 +
/// Input bytes are borrowed for the reader's region. Allocation and symbol
10 +
/// access require valid raw storage.
11 +
export record Reader: 'input + Copy {
11 12
    /// Encoded bytes.
12 -
    bytes: *unsafe [u8],
13 +
    bytes: &'input [u8],
13 14
    /// Number of bytes consumed.
14 15
    offset: u32,
15 16
    /// Storage for decoded sequences and byte strings.
16 17
    arena: *unsafe mut alloc::Arena,
17 18
    /// Symbol names in wire-index order.
21 22
    /// Number of blocks in the current function.
22 23
    blocks: u32,
23 24
}
24 25
25 26
/// Create a cursor. Set function bounds before reading instructions.
26 -
export unsafe fn new(bytes: &[u8], arena: &mut alloc::Arena, symbols: *unsafe [*[u8]]) -> Reader {
27 -
    return Reader { bytes: bytes as *unsafe [u8], offset: 0, arena: arena as *unsafe mut alloc::Arena, symbols, registers: 0, blocks: 0 };
27 +
export fn new 'input (bytes: &'input [u8], arena: *unsafe mut alloc::Arena, symbols: *unsafe [*[u8]]) -> Reader 'input {
28 +
    return Reader 'input { bytes, offset: 0, arena, symbols, registers: 0, blocks: 0 };
28 29
}
29 30
30 31
/// Read an unsigned integer with width 1, 2, 4, or 8.
31 -
export unsafe fn integer(input: &mut Reader, width: u32) -> u64 throws (binary::Error) {
32 +
export fn integer 'input (input: &mut Reader 'input, width: u32) -> u64 throws (binary::Error) {
32 33
    if width <> 1 and width <> 2 and width <> 4 and width <> 8 {
33 34
        throw binary::Error::Invalid;
34 35
    }
35 36
    if input.offset > input.bytes.len or width > input.bytes.len - input.offset {
36 37
        throw binary::Error::Truncated;
42 43
    set input.offset += width;
43 44
    return result;
44 45
}
45 46
46 47
/// Read a zero-or-one optional-field or boolean marker.
47 -
export unsafe fn flag(input: &mut Reader) -> bool throws (binary::Error) {
48 +
export fn flag 'input (input: &mut Reader 'input) -> bool throws (binary::Error) {
48 49
    let n = try integer(input, 1);
49 50
    if n > 1 {
50 51
        throw binary::Error::Invalid;
51 52
    }
52 53
    return n == 1;
53 54
}
54 55
55 56
/// Read a count and check the minimum required input bytes before allocation.
56 -
export unsafe fn count(input: &mut Reader, minimum: u32) -> u32 throws (binary::Error) {
57 +
export fn count 'input (input: &mut Reader 'input, minimum: u32) -> u32 throws (binary::Error) {
57 58
    assert minimum > 0;
58 59
    let n = try integer(input, 4) as u32;
59 60
    if n > (input.bytes.len - input.offset) / minimum {
60 61
        throw binary::Error::Truncated;
61 62
    }
62 63
    return n;
63 64
}
64 65
65 66
/// Allocate a typed sequence after checking size and alignment arithmetic.
66 -
export unsafe fn storage(input: &mut Reader, size: u32, alignment: u32, count: u32)
67 +
export unsafe fn storage 'input (input: &mut Reader 'input, size: u32, alignment: u32, count: u32)
67 68
    -> *mut [opaque] throws (binary::Error)
68 69
{
69 70
    assert size > 0 and alignment > 0 and (alignment & (alignment - 1)) == 0;
70 71
    return try alloc::allocSlice(input.arena, size, alignment, count) catch {
71 72
        throw binary::Error::Storage;
72 73
    };
73 74
}
74 75
75 76
/// Read a length-prefixed byte string and copy it into the arena.
76 -
export unsafe fn bytes(input: &mut Reader) -> *[u8] throws (binary::Error) {
77 +
export unsafe fn bytes 'input (input: &mut Reader 'input) -> *[u8] throws (binary::Error) {
77 78
    let n = try count(input, 1);
78 79
    let result = try storage(input, @sizeOf(u8), @alignOf(u8), n) as *mut [u8];
79 80
    for i in 0..n {
80 81
        set result[i] = input.bytes[input.offset + i];
81 82
    }
82 83
    set input.offset += n;
83 84
    return result;
84 85
}
85 86
86 87
/// Resolve a checked symbol-table index.
87 -
export unsafe fn symbol(input: &mut Reader) -> *[u8] throws (binary::Error) {
88 +
export unsafe fn symbol 'input (input: &mut Reader 'input) -> *[u8] throws (binary::Error) {
88 89
    let index = try integer(input, 4) as u32;
89 90
    if index >= input.symbols.len {
90 91
        throw binary::Error::Symbol;
91 92
    }
92 93
    return input.symbols[index];
93 94
}
94 95
95 96
/// Read an IL type from its byte width.
96 -
export unsafe fn typ(input: &mut Reader) -> il::Type throws (binary::Error) {
97 +
export fn typ 'input (input: &mut Reader 'input) -> il::Type throws (binary::Error) {
97 98
    let width = try integer(input, 1);
98 99
    match width {
99 100
        case 1 => return il::Type::W8,
100 101
        case 2 => return il::Type::W16,
101 102
        case 4 => return il::Type::W32,
103 104
        else => throw binary::Error::Invalid,
104 105
    }
105 106
}
106 107
107 108
/// Read a register index within the current function's bound.
108 -
export unsafe fn reg(input: &mut Reader) -> il::Reg throws (binary::Error) {
109 +
export fn reg 'input (input: &mut Reader 'input) -> il::Reg throws (binary::Error) {
109 110
    let n = try integer(input, 4) as u32;
110 111
    if n >= input.registers {
111 112
        throw binary::Error::Invalid;
112 113
    }
113 114
    return il::Reg { n };
114 115
}
115 116
116 117
/// Read a target block index within the current function.
117 -
export unsafe fn target(input: &mut Reader) -> u32 throws (binary::Error) {
118 +
export fn target 'input (input: &mut Reader 'input) -> u32 throws (binary::Error) {
118 119
    let n = try integer(input, 4) as u32;
119 120
    if n >= input.blocks {
120 121
        throw binary::Error::Invalid;
121 122
    }
122 123
    return n;
123 124
}
124 125
125 126
/// Read a tagged value with checked register and symbol indices.
126 -
export unsafe fn val(input: &mut Reader) -> il::Val throws (binary::Error) {
127 +
export unsafe fn val 'input (input: &mut Reader 'input) -> il::Val throws (binary::Error) {
127 128
    let tag = try integer(input, 1) as u8;
128 129
    match tag {
129 130
        case super::VALUE_REG => return il::Val::Reg(try reg(input)),
130 131
        case super::VALUE_IMM => return il::Val::Imm(try integer(input, 8) as i64),
131 132
        case super::VALUE_DATASYM => return il::Val::DataSym(try symbol(input)),
134 135
        else => throw binary::Error::Invalid,
135 136
    }
136 137
}
137 138
138 139
/// Read a counted sequence of values.
139 -
export unsafe fn values(input: &mut Reader) -> *unsafe mut [il::Val] throws (binary::Error) {
140 +
export unsafe fn values 'input (input: &mut Reader 'input) -> *unsafe mut [il::Val] throws (binary::Error) {
140 141
    let n = try count(input, 1);
141 142
    let result = try storage(input, @sizeOf(il::Val), @alignOf(il::Val), n) as *mut [il::Val];
142 143
    for i in 0..n {
143 144
        set result[i] = try val(input);
144 145
    }
145 146
    return (&mut result[..]) as *unsafe mut [il::Val];
146 147
}
147 148
148 149
/// Read a checked bin operation tag.
149 -
unsafe fn binOp(input: &mut Reader) -> il::BinOp throws (binary::Error) {
150 +
fn binOp 'input (input: &mut Reader 'input) -> il::BinOp throws (binary::Error) {
150 151
    let tag = try integer(input, 1) as u8;
151 152
    match tag {
152 153
        case super::BIN_ADD => return il::BinOp::Add,
153 154
        case super::BIN_SUB => return il::BinOp::Sub,
154 155
        case super::BIN_MUL => return il::BinOp::Mul,
171 172
        else => throw binary::Error::Invalid,
172 173
    }
173 174
}
174 175
175 176
/// Read a checked un operation tag.
176 -
unsafe fn unOp(input: &mut Reader) -> il::UnOp throws (binary::Error) {
177 +
fn unOp 'input (input: &mut Reader 'input) -> il::UnOp throws (binary::Error) {
177 178
    let tag = try integer(input, 1) as u8;
178 179
    match tag {
179 180
        case super::UN_NEG => return il::UnOp::Neg,
180 181
        case super::UN_NOT => return il::UnOp::Not,
181 182
        else => throw binary::Error::Invalid,
182 183
    }
183 184
}
184 185
185 186
/// Read a checked cmp operation tag.
186 -
unsafe fn cmpOp(input: &mut Reader) -> il::CmpOp throws (binary::Error) {
187 +
fn cmpOp 'input (input: &mut Reader 'input) -> il::CmpOp throws (binary::Error) {
187 188
    let tag = try integer(input, 1) as u8;
188 189
    match tag {
189 190
        case super::CMP_EQ => return il::CmpOp::Eq,
190 191
        case super::CMP_NE => return il::CmpOp::Ne,
191 192
        case super::CMP_SLT => return il::CmpOp::Slt,
193 194
        else => throw binary::Error::Invalid,
194 195
    }
195 196
}
196 197
197 198
/// Read one instruction and reconstruct its operand sequences.
198 -
export unsafe fn instr(input: &mut Reader) -> il::Instr throws (binary::Error) {
199 +
export unsafe fn instr 'input (input: &mut Reader 'input) -> il::Instr throws (binary::Error) {
199 200
    let tag = try integer(input, 1) as u8;
200 201
    match tag {
201 202
        case super::INSTR_RESERVE => {
202 203
            let vdst = try reg(input);
203 204
            let vsize = try val(input);
336 337
        else => throw binary::Error::Invalid,
337 338
    }
338 339
}
339 340
340 341
/// Read an initializer with a repetition count.
341 -
export unsafe fn dataValue(input: &mut Reader) -> il::DataValue throws (binary::Error) {
342 +
export unsafe fn dataValue 'input (input: &mut Reader 'input) -> il::DataValue throws (binary::Error) {
342 343
    let tag = try integer(input, 1) as u8;
343 344
    let mut item: il::DataItem = il::DataItem::Undef;
344 345
    match tag {
345 346
        case super::DATA_VAL => {
346 347
            let t = try typ(input);
lib/std/lang/il/binary/tests.rad +61 -20
8 8
use std::lang::alloc;
9 9
10 10
/// Decode arena backing storage. Tests reset it before each use.
11 11
static MEMORY: [u8; 512] = [0; 512];
12 12
13 +
/// Scalar decoding uses only checked input bytes and numeric cursor bounds.
14 +
fn checkScalarReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) {
15 +
    set input.registers = 2;
16 +
    set input.blocks = 3;
17 +
    let reg = try reader::reg(input) catch { throw testing::TestError::Failed; };
18 +
    try testing::expect(reg.n == 1);
19 +
    let target = try reader::target(input) catch { throw testing::TestError::Failed; };
20 +
    try testing::expect(target == 2);
21 +
    let mut truncated = false;
22 +
    try reader::integer(input, 1) catch err {
23 +
        try testing::expect(err == binary::Error::Truncated);
24 +
        set truncated = true;
25 +
    };
26 +
    try testing::expect(truncated);
27 +
    set input.offset = 0;
28 +
    let flag = try reader::flag(input) catch { throw testing::TestError::Failed; };
29 +
    try testing::expect(flag);
30 +
    set input.offset = 0;
31 +
    let typ = try reader::typ(input) catch { throw testing::TestError::Failed; };
32 +
    try testing::expect(typ == il::Type::W8);
33 +
    set input.offset = 0;
34 +
    let count = try reader::count(input, 4) catch { throw testing::TestError::Failed; };
35 +
    try testing::expect(count == 1);
36 +
    set input.offset = 0;
37 +
    let wide = try reader::integer(input, 8) catch { throw testing::TestError::Failed; };
38 +
    try testing::expect(wide == 0x0000000200000001);
39 +
}
40 +
41 +
/// A regional input buffer supports safe scalar decoding.
42 +
@test unsafe fn scalarReader() throws (testing::TestError) {
43 +
    let memory = &mut MEMORY[..];
44 +
    let mut arena = alloc::new(memory);
45 +
    let bytes: [u8; 8] = [1, 0, 0, 0, 2, 0, 0, 0];
46 +
    let source: 'input = &bytes[..] in {
47 +
        let mut input = reader::new(source, &mut arena, &[]);
48 +
        try checkScalarReader(&mut input);
49 +
    }
50 +
}
51 +
13 52
/// Check little-endian encoding for every integer width.
14 53
@test fn integers() throws (testing::TestError) {
15 54
    let mut buffer: [u8; 15] = [0; 15];
16 55
    let namesTable: [*[u8]; 0] = [];
17 56
    let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in {
55 94
                try testing::expect(short.offset <= capacity);
56 95
            }
57 96
        }
58 97
        let memory = &mut MEMORY[..512];
59 98
        let mut arena = alloc::new(&mut memory[..]);
60 -
        let mut input = reader::new(expected, &mut arena, &["data", "fn"]);
61 -
        set input.registers = 16;
62 -
        set input.blocks = 4;
63 -
        let decoded = try reader::instr(&mut input) catch {
64 -
            throw testing::TestError::Failed;
65 -
        };
66 -
        try testing::expect(input.offset == expected.len);
67 -
        set out.offset = 0;
68 -
        try writer::instr(&mut out, decoded) catch {
69 -
            throw testing::TestError::Failed;
70 -
        };
71 -
        try testing::expectBytesEq(&out.bytes[..out.offset], expected);
72 -
        for length in 0..expected.len {
73 -
            alloc::reset(&mut arena);
74 -
            set input = reader::new(&expected[..length], &mut arena, &["data", "fn"]);
99 +
        let source: 'input = &expected[..] in {
100 +
            let mut input = reader::new(source, &mut arena, &["data", "fn"]);
75 101
            set input.registers = 16;
76 102
            set input.blocks = 4;
77 -
            let mut failed = false;
78 -
            try reader::instr(&mut input) catch err {
79 -
                try testing::expect(err == binary::Error::Truncated);
80 -
                set failed = true;
103 +
            let decoded = try reader::instr(&mut input) catch {
104 +
                throw testing::TestError::Failed;
81 105
            };
82 -
            try testing::expect(failed);
106 +
            try testing::expect(input.offset == expected.len);
107 +
            set out.offset = 0;
108 +
            try writer::instr(&mut out, decoded) catch {
109 +
                throw testing::TestError::Failed;
110 +
            };
111 +
            try testing::expectBytesEq(&out.bytes[..out.offset], expected);
112 +
            for length in 0..expected.len {
113 +
                alloc::reset(&mut arena);
114 +
                set input = reader::new(&source[..length], &mut arena, &["data", "fn"]);
115 +
                set input.registers = 16;
116 +
                set input.blocks = 4;
117 +
                let mut failed = false;
118 +
                try reader::instr(&mut input) catch err {
119 +
                    try testing::expect(err == binary::Error::Truncated);
120 +
                    set failed = true;
121 +
                };
122 +
                try testing::expect(failed);
123 +
            }
83 124
        }
84 125
85 126
    }
86 127
}
87 128