compiler: Borrow binary decoder symbol tables safely
3e51617aad2f307942e0386a03f63f6a5b81f2e8cd4484680d6f6f35759069e7
1 parent
ed2fbb6c
lib/std/lang/il/binary/decodeTests.rad
+9 -6
| 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 source: 'input = &buffer[..0] in { |
|
| 58 | - | let mut input = reader::new(source, &mut arena, &[]); |
|
| 57 | + | let namesTable: [*[u8]; 0] = []; |
|
| 58 | + | let source: 'input = &buffer[..0], names = &namesTable[..] in { |
|
| 59 | + | let mut input = reader::new(source, &mut arena, names); |
|
| 59 | 60 | let mut exhausted = false; |
|
| 60 | 61 | try reader::storage(&mut input, 0xffffffff, 8, 0xffffffff) catch err { |
|
| 61 | 62 | try testing::expect(err == binary::Error::Storage); |
|
| 62 | 63 | set exhausted = true; |
|
| 63 | 64 | }; |
| 151 | 152 | ||
| 152 | 153 | /// Reject an invalid encoded instruction without a panic. |
|
| 153 | 154 | unsafe fn badInstruction(bytes: *[u8], expected: binary::Error) throws (testing::TestError) { |
|
| 154 | 155 | let memory = &mut MEMORY[..64]; |
|
| 155 | 156 | let mut arena = alloc::new(&mut memory[..]); |
|
| 156 | - | let source: 'input = &bytes[..] in { |
|
| 157 | - | let mut input = reader::new(source, &mut arena, &["p"]); |
|
| 157 | + | let namesTable: [*[u8]; 1] = ["p"]; |
|
| 158 | + | let source: 'input = &bytes[..], names = &namesTable[..] in { |
|
| 159 | + | let mut input = reader::new(source, &mut arena, names); |
|
| 158 | 160 | set input.registers = 2; |
|
| 159 | 161 | set input.blocks = 1; |
|
| 160 | 162 | let mut failed = false; |
|
| 161 | 163 | try reader::instr(&mut input) catch err { |
|
| 162 | 164 | try testing::expect(err == expected); |
| 297 | 299 | @test unsafe fn malformedInitializers() throws (testing::TestError) { |
|
| 298 | 300 | let memory = &mut MEMORY[..8]; |
|
| 299 | 301 | let mut arena = alloc::new(&mut memory[..]); |
|
| 300 | 302 | let fixtures: [*[u8]; 2] = [&[255], &[0, 3]]; |
|
| 301 | 303 | for bytes in &fixtures[..] { |
|
| 302 | - | let source: 'input = &bytes[..] in { |
|
| 303 | - | let mut input = reader::new(source, &mut arena, &[]); |
|
| 304 | + | let namesTable: [*[u8]; 0] = []; |
|
| 305 | + | let source: 'input = &bytes[..], names = &namesTable[..] in { |
|
| 306 | + | let mut input = reader::new(source, &mut arena, names); |
|
| 304 | 307 | let mut failed = false; |
|
| 305 | 308 | try reader::dataValue(&mut input) catch err { |
|
| 306 | 309 | try testing::expect(err == binary::Error::Invalid); |
|
| 307 | 310 | set failed = true; |
|
| 308 | 311 | }; |
lib/std/lang/il/binary/program.rad
+32 -27
| 216 | 216 | throw binary::Error::Invalid; |
|
| 217 | 217 | } |
|
| 218 | 218 | } |
|
| 219 | 219 | set symbols[i] = name; |
|
| 220 | 220 | } |
|
| 221 | - | set input.symbols = (&symbols[..]) as *unsafe [*[u8]]; |
|
| 222 | - | let name = try reader::symbol(input); |
|
| 223 | - | let depCount = try reader::count(input, 4); |
|
| 224 | - | let dependencies = try reader::storage(input, @sizeOf(*[u8]), @alignOf(*[u8]), depCount) |
|
| 225 | - | as *mut [*[u8]]; |
|
| 226 | - | for i in 0..depCount { |
|
| 227 | - | set dependencies[i] = try reader::symbol(input); |
|
| 228 | - | } |
|
| 229 | - | let exportCount = try reader::count(input, 5); |
|
| 230 | - | let exports = try reader::storage(input, @sizeOf(binary::Export), @alignOf(binary::Export), exportCount) |
|
| 231 | - | as *mut [binary::Export]; |
|
| 232 | - | for i in 0..exportCount { |
|
| 233 | - | let symbol = try reader::symbol(input); |
|
| 234 | - | let kind = binary::ExportKind::Data if try reader::flag(input) else binary::ExportKind::Function; |
|
| 235 | - | set exports[i] = binary::Export { name: symbol, kind }; |
|
| 236 | - | } |
|
| 237 | - | let mut entry: ?*[u8] = nil; |
|
| 238 | - | if try reader::flag(input) { |
|
| 239 | - | set entry = try reader::symbol(input); |
|
| 240 | - | } |
|
| 241 | - | let data = try readData(input); |
|
| 242 | - | let fns = try readFunctions(input, limits); |
|
| 243 | - | if input.offset <> input.bytes.len { |
|
| 244 | - | throw binary::Error::Invalid; |
|
| 221 | + | let source: 'tables = &input.bytes[..], names = &symbols[..] where 'input: 'tables in { |
|
| 222 | + | let mut cursor = reader::new(source, input.arena, names); |
|
| 223 | + | set cursor.offset = input.offset; |
|
| 224 | + | let name = try reader::symbol(&mut cursor); |
|
| 225 | + | let depCount = try reader::count(&mut cursor, 4); |
|
| 226 | + | let dependencies = try reader::storage(&mut cursor, @sizeOf(*[u8]), @alignOf(*[u8]), depCount) |
|
| 227 | + | as *mut [*[u8]]; |
|
| 228 | + | for i in 0..depCount { |
|
| 229 | + | set dependencies[i] = try reader::symbol(&mut cursor); |
|
| 230 | + | } |
|
| 231 | + | let exportCount = try reader::count(&mut cursor, 5); |
|
| 232 | + | let exports = try reader::storage(&mut cursor, @sizeOf(binary::Export), @alignOf(binary::Export), exportCount) |
|
| 233 | + | as *mut [binary::Export]; |
|
| 234 | + | for i in 0..exportCount { |
|
| 235 | + | let symbol = try reader::symbol(&mut cursor); |
|
| 236 | + | let kind = binary::ExportKind::Data if try reader::flag(&mut cursor) else binary::ExportKind::Function; |
|
| 237 | + | set exports[i] = binary::Export { name: symbol, kind }; |
|
| 238 | + | } |
|
| 239 | + | let mut entry: ?*[u8] = nil; |
|
| 240 | + | if try reader::flag(&mut cursor) { |
|
| 241 | + | set entry = try reader::symbol(&mut cursor); |
|
| 242 | + | } |
|
| 243 | + | let data = try readData(&mut cursor); |
|
| 244 | + | let fns = try readFunctions(&mut cursor, limits); |
|
| 245 | + | if cursor.offset <> cursor.bytes.len { |
|
| 246 | + | throw binary::Error::Invalid; |
|
| 247 | + | } |
|
| 248 | + | set input.offset = cursor.offset; |
|
| 249 | + | return binary::Package { symbols: (&symbols[..]) as *unsafe [*[u8]], name, dependencies: (&dependencies[..]) as *unsafe [*[u8]], exports: (&exports[..]) as *unsafe [binary::Export], entry, program: il::Program { data, fns } }; |
|
| 245 | 250 | } |
|
| 246 | - | return binary::Package { symbols: (&symbols[..]) as *unsafe [*[u8]], name, dependencies: (&dependencies[..]) as *unsafe [*[u8]], exports: (&exports[..]) as *unsafe [binary::Export], entry, program: il::Program { data, fns } }; |
|
| 247 | 251 | } |
|
| 248 | 252 | ||
| 249 | 253 | /// Decode one package. Restore the arena offset on every failure. |
|
| 250 | 254 | /// The caller must retain the decoded allocations until the last package use. |
|
| 251 | 255 | export unsafe fn decode(bytes: &[u8], arena: &mut alloc::Arena, limits: binary::Limits) |
|
| 252 | 256 | -> binary::Package throws (binary::Error) |
|
| 253 | 257 | { |
|
| 254 | 258 | let saved = alloc::save(arena); |
|
| 255 | - | let source: 'input = &bytes[..] in { |
|
| 256 | - | let mut input = reader::new(source, &mut *arena, &[]); |
|
| 259 | + | let namesTable: [*[u8]; 0] = []; |
|
| 260 | + | let source: 'input = &bytes[..], names = &namesTable[..] in { |
|
| 261 | + | let mut input = reader::new(source, &mut *arena, names); |
|
| 257 | 262 | return try readPackage(&mut input, limits) catch err { |
|
| 258 | 263 | alloc::restore(arena, saved); |
|
| 259 | 264 | throw err; |
|
| 260 | 265 | }; |
|
| 261 | 266 | } |
lib/std/lang/il/binary/reader.rad
+6 -6
| 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 are borrowed for the reader's region. Allocation and symbol |
|
| 10 | - | /// access require valid raw storage. |
|
| 9 | + | /// Input bytes and the symbol table are borrowed for the reader's region. |
|
| 10 | + | /// Allocation requires valid raw storage. |
|
| 11 | 11 | export record Reader: 'input + Copy { |
|
| 12 | 12 | /// Encoded bytes. |
|
| 13 | 13 | bytes: &'input [u8], |
|
| 14 | 14 | /// Number of bytes consumed. |
|
| 15 | 15 | offset: u32, |
|
| 16 | 16 | /// Storage for decoded sequences and byte strings. |
|
| 17 | 17 | arena: *unsafe mut alloc::Arena, |
|
| 18 | 18 | /// Symbol names in wire-index order. |
|
| 19 | - | symbols: *unsafe [*[u8]], |
|
| 19 | + | symbols: &'input [*[u8]], |
|
| 20 | 20 | /// Exclusive bound for SSA register numbers. |
|
| 21 | 21 | registers: u32, |
|
| 22 | 22 | /// Number of blocks in the current function. |
|
| 23 | 23 | blocks: u32, |
|
| 24 | 24 | } |
|
| 25 | 25 | ||
| 26 | 26 | /// Create a cursor. Set function bounds before reading instructions. |
|
| 27 | - | export fn new 'input (bytes: &'input [u8], arena: *unsafe mut alloc::Arena, symbols: *unsafe [*[u8]]) -> Reader 'input { |
|
| 27 | + | export fn new 'input (bytes: &'input [u8], arena: *unsafe mut alloc::Arena, symbols: &'input [*[u8]]) -> Reader 'input { |
|
| 28 | 28 | return Reader 'input { bytes, offset: 0, arena, symbols, registers: 0, blocks: 0 }; |
|
| 29 | 29 | } |
|
| 30 | 30 | ||
| 31 | 31 | /// Read an unsigned integer with width 1, 2, 4, or 8. |
|
| 32 | 32 | export fn integer 'input (input: &mut Reader 'input, width: u32) -> u64 throws (binary::Error) { |
| 83 | 83 | set input.offset += n; |
|
| 84 | 84 | return result; |
|
| 85 | 85 | } |
|
| 86 | 86 | ||
| 87 | 87 | /// Resolve a checked symbol-table index. |
|
| 88 | - | export unsafe fn symbol 'input (input: &mut Reader 'input) -> *[u8] throws (binary::Error) { |
|
| 88 | + | export fn symbol 'input (input: &mut Reader 'input) -> *[u8] throws (binary::Error) { |
|
| 89 | 89 | let index = try integer(input, 4) as u32; |
|
| 90 | 90 | if index >= input.symbols.len { |
|
| 91 | 91 | throw binary::Error::Symbol; |
|
| 92 | 92 | } |
|
| 93 | 93 | return input.symbols[index]; |
| 122 | 122 | } |
|
| 123 | 123 | return n; |
|
| 124 | 124 | } |
|
| 125 | 125 | ||
| 126 | 126 | /// Read a tagged value with checked register and symbol indices. |
|
| 127 | - | export unsafe fn val 'input (input: &mut Reader 'input) -> il::Val throws (binary::Error) { |
|
| 127 | + | export fn val 'input (input: &mut Reader 'input) -> il::Val throws (binary::Error) { |
|
| 128 | 128 | let tag = try integer(input, 1) as u8; |
|
| 129 | 129 | match tag { |
|
| 130 | 130 | case super::VALUE_REG => return il::Val::Reg(try reg(input)), |
|
| 131 | 131 | case super::VALUE_IMM => return il::Val::Imm(try integer(input, 8) as i64), |
|
| 132 | 132 | case super::VALUE_DATASYM => return il::Val::DataSym(try symbol(input)), |
lib/std/lang/il/binary/tests.rad
+43 -6
| 41 | 41 | /// A regional input buffer supports safe scalar decoding. |
|
| 42 | 42 | @test unsafe fn scalarReader() throws (testing::TestError) { |
|
| 43 | 43 | let memory = &mut MEMORY[..]; |
|
| 44 | 44 | let mut arena = alloc::new(memory); |
|
| 45 | 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, &[]); |
|
| 46 | + | let namesTable: [*[u8]; 0] = []; |
|
| 47 | + | let source: 'input = &bytes[..], names = &namesTable[..] in { |
|
| 48 | + | let mut input = reader::new(source, &mut arena, names); |
|
| 48 | 49 | try checkScalarReader(&mut input); |
|
| 49 | 50 | } |
|
| 50 | 51 | } |
|
| 51 | 52 | ||
| 53 | + | /// Read valid symbol values and reject invalid or truncated wire indices. |
|
| 54 | + | fn checkSymbolReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) { |
|
| 55 | + | let name = try reader::symbol(input) catch { throw testing::TestError::Failed; }; |
|
| 56 | + | try testing::expectBytesEq(name, "data"); |
|
| 57 | + | let data = try reader::val(input) catch { throw testing::TestError::Failed; }; |
|
| 58 | + | let case il::Val::DataSym(dataName) = data else throw testing::TestError::Failed; |
|
| 59 | + | try testing::expectBytesEq(dataName, "data"); |
|
| 60 | + | let function = try reader::val(input) catch { throw testing::TestError::Failed; }; |
|
| 61 | + | let case il::Val::FnAddr(functionName) = function else throw testing::TestError::Failed; |
|
| 62 | + | try testing::expectBytesEq(functionName, "fn"); |
|
| 63 | + | let mut invalid = false; |
|
| 64 | + | try reader::symbol(input) catch err { |
|
| 65 | + | assert err == binary::Error::Symbol; |
|
| 66 | + | set invalid = true; |
|
| 67 | + | }; |
|
| 68 | + | assert invalid; |
|
| 69 | + | let mut truncated = false; |
|
| 70 | + | try reader::val(input) catch err { |
|
| 71 | + | assert err == binary::Error::Truncated; |
|
| 72 | + | set truncated = true; |
|
| 73 | + | }; |
|
| 74 | + | assert truncated; |
|
| 75 | + | } |
|
| 76 | + | ||
| 77 | + | /// Symbol and value decoding use checked table storage from a safe function. |
|
| 78 | + | @test unsafe fn symbolReader() throws (testing::TestError) { |
|
| 79 | + | let memory = &mut MEMORY[..]; |
|
| 80 | + | let mut arena = alloc::new(memory); |
|
| 81 | + | let bytes: [u8; 18] = [0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 2, 0, 0, 0]; |
|
| 82 | + | let namesTable: [*[u8]; 2] = ["data", "fn"]; |
|
| 83 | + | let source: 'input = &bytes[..], names = &namesTable[..] in { |
|
| 84 | + | let mut input = reader::new(source, &mut arena, names); |
|
| 85 | + | try checkSymbolReader(&mut input); |
|
| 86 | + | } |
|
| 87 | + | } |
|
| 88 | + | ||
| 52 | 89 | /// Check little-endian encoding for every integer width. |
|
| 53 | 90 | @test fn integers() throws (testing::TestError) { |
|
| 54 | 91 | let mut buffer: [u8; 15] = [0; 15]; |
|
| 55 | 92 | let namesTable: [*[u8]; 0] = []; |
|
| 56 | 93 | let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in { |
| 94 | 131 | try testing::expect(short.offset <= capacity); |
|
| 95 | 132 | } |
|
| 96 | 133 | } |
|
| 97 | 134 | let memory = &mut MEMORY[..512]; |
|
| 98 | 135 | let mut arena = alloc::new(&mut memory[..]); |
|
| 99 | - | let source: 'input = &expected[..] in { |
|
| 100 | - | let mut input = reader::new(source, &mut arena, &["data", "fn"]); |
|
| 136 | + | let source: 'input = &expected[..], inputNames = &namesTable[..] in { |
|
| 137 | + | let mut input = reader::new(source, &mut arena, inputNames); |
|
| 101 | 138 | set input.registers = 16; |
|
| 102 | 139 | set input.blocks = 4; |
|
| 103 | 140 | let decoded = try reader::instr(&mut input) catch { |
|
| 104 | 141 | throw testing::TestError::Failed; |
|
| 105 | 142 | }; |
| 109 | 146 | throw testing::TestError::Failed; |
|
| 110 | 147 | }; |
|
| 111 | 148 | try testing::expectBytesEq(&out.bytes[..out.offset], expected); |
|
| 112 | 149 | for length in 0..expected.len { |
|
| 113 | 150 | alloc::reset(&mut arena); |
|
| 114 | - | set input = reader::new(&source[..length], &mut arena, &["data", "fn"]); |
|
| 151 | + | set input = reader::new(&source[..length], &mut arena, inputNames); |
|
| 115 | 152 | set input.registers = 16; |
|
| 116 | 153 | set input.blocks = 4; |
|
| 117 | 154 | let mut failed = false; |
|
| 118 | 155 | try reader::instr(&mut input) catch err { |
|
| 119 | 156 | try testing::expect(err == binary::Error::Truncated); |
| 232 | 269 | 1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
|
| 233 | 270 | 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4, |
|
| 234 | 271 | ]); |
|
| 235 | 272 | let memory = &mut MEMORY[..256]; |
|
| 236 | 273 | let mut arena = alloc::new(&mut memory[..]); |
|
| 237 | - | let mut input = reader::new(&out.bytes[..out.offset], &mut arena, &["data", "fn"]); |
|
| 274 | + | let mut input = reader::new(&out.bytes[..out.offset], &mut arena, &out.symbols[..]); |
|
| 238 | 275 | set input.registers = 0x12345679; |
|
| 239 | 276 | let empty = try reader::values(&mut input) catch { |
|
| 240 | 277 | throw testing::TestError::Failed; |
|
| 241 | 278 | }; |
|
| 242 | 279 | let decoded = try reader::values(&mut input) catch { |