compiler/
lib/
examples/
std/
arch/
char/
collections/
graph/
lang/
alloc/
ast/
gen/
il/
binary/
collect.rad
9.5 KiB
decodeTests.rad
32.7 KiB
program.rad
15.3 KiB
reader.rad
17.2 KiB
tests.rad
28.0 KiB
writer.rad
13.9 KiB
binary.rad
5.9 KiB
printer.rad
16.0 KiB
published.rad
13.4 KiB
publishedTests.rad
8.7 KiB
tests.rad
14.7 KiB
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.9 KiB
gen.rad
513 B
il.rad
20.4 KiB
lower.rad
321.7 KiB
module.rad
17.3 KiB
package.rad
1.3 KiB
parser.rad
92.2 KiB
resolver.rad
511.1 KiB
scanner.rad
17.9 KiB
sexpr.rad
6.7 KiB
strings.rad
2.2 KiB
types.rad
1.6 KiB
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
graph.rad
4.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
299 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CELL_PERMISSIONS
6.8 KiB
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
5.4 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
808 B
lib/std/lang/il/binary/decodeTests.rad
raw
| 1 | //! Package reconstruction, malformed input, and arena rollback tests. |
| 2 | |
| 3 | use std::testing; |
| 4 | use std::lang::alloc; |
| 5 | use std::lang::il; |
| 6 | use std::lang::il::binary; |
| 7 | use std::lang::il::binary::reader; |
| 8 | use std::lang::il::binary::program; |
| 9 | use std::lang::il::binary::collect; |
| 10 | |
| 11 | /// Decode arena backing storage. Tests reset it before each use. |
| 12 | static MEMORY: [u8; 2048] = [0; 2048]; |
| 13 | |
| 14 | /// Structural limits for small test programs. |
| 15 | constant LIMITS: binary::Limits = binary::Limits { registers: 16, blocks: 4 }; |
| 16 | |
| 17 | /// Check exact-fit package encoding and every shorter output window. |
| 18 | unsafe fn encodingCapacity(package: &binary::Package, expected: &[u8]) throws (testing::TestError) { |
| 19 | for capacity in 0..expected.len + 1 { |
| 20 | let mut buffer: [u8; 2050] = [0xa5; 2050]; |
| 21 | let mut failed = false; |
| 22 | let count = try program::encode(&mut buffer[1..capacity + 1], package) catch err { |
| 23 | assert err == binary::Error::Capacity; |
| 24 | set failed = true; |
| 25 | 0 |
| 26 | }; |
| 27 | assert failed == (capacity < expected.len); |
| 28 | assert buffer[0] == 0xa5; |
| 29 | for i in capacity + 1..buffer.len { assert buffer[i] == 0xa5; } |
| 30 | if not failed { |
| 31 | assert count == expected.len; |
| 32 | try testing::expectBytesEq(&buffer[1..count + 1], expected); |
| 33 | } |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | /// Check a package round trip and failure at every truncated byte length. |
| 38 | unsafe fn roundTrip(package: &binary::Package) throws (testing::TestError) { |
| 39 | let mut buffer: [u8; 2048] = [0; 2048]; |
| 40 | let length = try program::encode(&mut buffer[..], package) catch { |
| 41 | throw testing::TestError::Failed; |
| 42 | }; |
| 43 | try encodingCapacity(package, &buffer[..length]); |
| 44 | let memory = &mut MEMORY[..2048]; |
| 45 | let mut arena = alloc::new(&mut memory[..]); |
| 46 | set arena.offset = 8; |
| 47 | let decoded = try program::decode(&buffer[..length], &mut arena, LIMITS) catch { |
| 48 | throw testing::TestError::Failed; |
| 49 | }; |
| 50 | try testing::expectBytesEq(decoded.name, package.name); |
| 51 | try testing::expect(decoded.dependencies.len == package.dependencies.len); |
| 52 | try testing::expect(decoded.exports.len == package.exports.len); |
| 53 | try testing::expect(decoded.program.fns.len == package.program.fns.len); |
| 54 | for original, i in package.program.fns { |
| 55 | let restored = decoded.program.fns[i]; |
| 56 | assert restored.params.len == original.params.len; |
| 57 | for param, j in original.params { |
| 58 | assert restored.params[j].value.n == param.value.n; |
| 59 | assert restored.params[j].type == param.type; |
| 60 | } |
| 61 | assert restored.blocks.len == original.blocks.len; |
| 62 | let mut isLeaf = true; |
| 63 | for block, j in original.blocks { |
| 64 | for instr in block.instrs { |
| 65 | if il::isCall(instr) { set isLeaf = false; } |
| 66 | } |
| 67 | let rebuilt = &restored.blocks[j]; |
| 68 | try testing::expectBytesEq(rebuilt.label, block.label); |
| 69 | assert rebuilt.loopDepth == block.loopDepth; |
| 70 | assert rebuilt.params.len == block.params.len; |
| 71 | for param, k in block.params { |
| 72 | assert rebuilt.params[k].value.n == param.value.n; |
| 73 | assert rebuilt.params[k].type == param.type; |
| 74 | } |
| 75 | assert rebuilt.preds.len == block.preds.len; |
| 76 | for pred, k in block.preds { assert rebuilt.preds[k] == pred; } |
| 77 | } |
| 78 | assert restored.isLeaf == isLeaf; |
| 79 | } |
| 80 | let mut encoded: [u8; 2048] = [0; 2048]; |
| 81 | let repeated = try program::encode(&mut encoded[..], &decoded) catch { |
| 82 | throw testing::TestError::Failed; |
| 83 | }; |
| 84 | try testing::expectBytesEq(&buffer[..length], &encoded[..repeated]); |
| 85 | // Decoded names and string initializers retain arena ownership. |
| 86 | for i in 0..length { |
| 87 | set buffer[i] = 0; |
| 88 | } |
| 89 | let copied = try program::encode(&mut buffer[..], &decoded) catch { |
| 90 | throw testing::TestError::Failed; |
| 91 | }; |
| 92 | try testing::expectBytesEq(&buffer[..copied], &encoded[..repeated]); |
| 93 | for end in 0..length { |
| 94 | set arena.offset = 8; |
| 95 | let mut failed = false; |
| 96 | try program::decode(&buffer[..end], &mut arena, LIMITS) catch err { |
| 97 | try testing::expect(err == binary::Error::Truncated); |
| 98 | set failed = true; |
| 99 | }; |
| 100 | try testing::expect(failed); |
| 101 | try testing::expect(arena.offset == 8); |
| 102 | } |
| 103 | set arena.offset = 8; |
| 104 | let namesTable: [*[u8]; 0] = []; |
| 105 | let source: 'input = &buffer[..0], names = &namesTable[..] in { |
| 106 | let mut input = reader::new(source, &mut arena, names); |
| 107 | let mut exhausted = false; |
| 108 | try reader::storage(&mut input, 0xffffffff, 8, 0xffffffff) catch err { |
| 109 | try testing::expect(err == binary::Error::Storage); |
| 110 | set exhausted = true; |
| 111 | }; |
| 112 | try testing::expect(exhausted); |
| 113 | try testing::expect(arena.offset == 8); |
| 114 | } |
| 115 | // Exercise every smaller arena extent, including alignment boundaries. |
| 116 | let mut usedArena = alloc::new(&mut memory[..]); |
| 117 | let _ = try program::decode(&buffer[..length], &mut usedArena, LIMITS) catch { |
| 118 | throw testing::TestError::Failed; |
| 119 | }; |
| 120 | let required = usedArena.offset; |
| 121 | for capacity in 0..required { |
| 122 | let mut short = alloc::new(&mut MEMORY[..capacity]); |
| 123 | let mut failed = false; |
| 124 | try program::decode(&buffer[..length], &mut short, LIMITS) catch err { |
| 125 | try testing::expect(err == binary::Error::Storage); |
| 126 | set failed = true; |
| 127 | }; |
| 128 | try testing::expect(failed); |
| 129 | try testing::expect(short.offset == 0); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// Check an empty program with a package identity. |
| 134 | @test unsafe fn emptyPackage() throws (testing::TestError) { |
| 135 | let package = binary::Package { |
| 136 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 137 | program: il::Program { data: &[], fns: &[] }, |
| 138 | }; |
| 139 | try roundTrip(&package); |
| 140 | let mut buffer: [u8; 64] = [0; 64]; |
| 141 | let length = try program::encode(&mut buffer[..], &package) catch { |
| 142 | throw testing::TestError::Failed; |
| 143 | }; |
| 144 | try testing::expectBytesEq(&buffer[..length], &[ |
| 145 | 82, 73, 76, 0, 1, 0, 0, 0, 1, 0, 0, 0, |
| 146 | 1, 0, 0, 0, 112, 0, 0, 0, 0, |
| 147 | 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 148 | 0, 0, 0, 0, 0, 0, 0, 0, |
| 149 | ]); |
| 150 | } |
| 151 | |
| 152 | /// Check functions, dependencies, exports, initializers, and block metadata. |
| 153 | @test unsafe fn fullPackage() throws (testing::TestError) { |
| 154 | let params: *unsafe [il::Param] = &[ |
| 155 | il::Param { value: il::Reg { n: 1 }, type: il::Type::W8 }, |
| 156 | il::Param { value: il::Reg { n: 4 }, type: il::Type::W16 }, |
| 157 | il::Param { value: il::Reg { n: 5 }, type: il::Type::W32 }, |
| 158 | il::Param { value: il::Reg { n: 6 }, type: il::Type::W64 }, |
| 159 | ]; |
| 160 | let mut instrs = [ |
| 161 | il::Instr::Call { |
| 162 | retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("dep::fn"), |
| 163 | args: &[il::Val::Reg(il::Reg { n: 1 })], |
| 164 | }, |
| 165 | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) }, |
| 166 | ]; |
| 167 | let func = il::Fn { |
| 168 | name: "p::main", params, returnType: il::Type::W64, isExtern: false, isLeaf: false, |
| 169 | blocks: &[il::Block { |
| 170 | label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[0], loopDepth: 2, |
| 171 | }, il::Block { |
| 172 | label: "empty", params: &[il::Param { value: il::Reg { n: 3 }, type: il::Type::W32 }], |
| 173 | instrs: &mut [], locs: &[], preds: &[0, 1], loopDepth: 0, |
| 174 | }], |
| 175 | }; |
| 176 | let external = il::Fn { |
| 177 | name: "dep::fn", params, returnType: il::Type::W64, isExtern: true, isLeaf: true, blocks: &[], |
| 178 | }; |
| 179 | let data = il::Data { |
| 180 | name: "p::data", size: 64, alignment: 8, readOnly: false, isZeroInit: false, |
| 181 | values: &[ |
| 182 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, val: -1 }, count: 2 }, |
| 183 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, val: -2 }, count: 1 }, |
| 184 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, val: -3 }, count: 1 }, |
| 185 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: -4 }, count: 1 }, |
| 186 | il::DataValue { item: il::DataItem::Sym("p::data"), count: 1 }, |
| 187 | il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }, |
| 188 | il::DataValue { item: il::DataItem::Str("bytes"), count: 2 }, |
| 189 | il::DataValue { item: il::DataItem::Str(""), count: 0 }, |
| 190 | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
| 191 | ], |
| 192 | }; |
| 193 | let zero = il::Data { |
| 194 | name: "p::zero", size: 4096, alignment: 4096, readOnly: false, isZeroInit: true, values: &[], |
| 195 | }; |
| 196 | let package = binary::Package { |
| 197 | symbols: &["p", "dep", "p::main", "dep::fn", "p::data", "p::zero"], name: "p", |
| 198 | dependencies: &["dep"], |
| 199 | exports: &[ |
| 200 | binary::Export { name: "p::main", kind: binary::ExportKind::Function }, |
| 201 | binary::Export { name: "p::data", kind: binary::ExportKind::Data }, |
| 202 | ], |
| 203 | entry: "p::main", program: il::Program { data: retainData(&[data, zero]), fns: &[&func, &external] }, |
| 204 | }; |
| 205 | try roundTrip(&package); |
| 206 | } |
| 207 | |
| 208 | /// Reject an invalid encoded instruction without a panic. |
| 209 | unsafe fn badInstruction(bytes: *[u8], expected: binary::Error) throws (testing::TestError) { |
| 210 | let memory = &mut MEMORY[..64]; |
| 211 | let mut arena = alloc::new(&mut memory[..]); |
| 212 | let namesTable: [*[u8]; 1] = ["p"]; |
| 213 | let source: 'input = &bytes[..], names = &namesTable[..] in { |
| 214 | let mut input = reader::new(source, &mut arena, names); |
| 215 | set input.registers = 2; |
| 216 | set input.blocks = 1; |
| 217 | let mut failed = false; |
| 218 | try reader::instr(&mut input) catch err { |
| 219 | try testing::expect(err == expected); |
| 220 | set failed = true; |
| 221 | }; |
| 222 | try testing::expect(failed); |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | /// Check instruction tags, operand tags, types, optional flags, and indices. |
| 227 | @test unsafe fn malformedInstructions() throws (testing::TestError) { |
| 228 | try badInstruction(&[255], binary::Error::Invalid); |
| 229 | try badInstruction(&[5, 0, 0, 0, 0, 255], binary::Error::Invalid); |
| 230 | try badInstruction(&[1, 3], binary::Error::Invalid); |
| 231 | try badInstruction(&[6, 255], binary::Error::Invalid); |
| 232 | try badInstruction(&[7, 255], binary::Error::Invalid); |
| 233 | try badInstruction(&[13, 255], binary::Error::Invalid); |
| 234 | try badInstruction(&[11, 2], binary::Error::Invalid); |
| 235 | try badInstruction(&[10, 8, 2], binary::Error::Invalid); |
| 236 | try badInstruction(&[5, 2, 0, 0, 0], binary::Error::Invalid); |
| 237 | try badInstruction(&[5, 0, 0, 0, 0, 0, 2, 0, 0, 0], binary::Error::Invalid); |
| 238 | try badInstruction(&[12, 1, 0, 0, 0], binary::Error::Invalid); |
| 239 | try badInstruction(&[5, 0, 0, 0, 0, 2, 1, 0, 0, 0], binary::Error::Symbol); |
| 240 | try badInstruction(&[12, 0, 0, 0, 0, 255, 255, 255, 255], binary::Error::Truncated); |
| 241 | } |
| 242 | |
| 243 | /// Check header validation and package symbol indices with arena rollback. |
| 244 | @test unsafe fn malformedPackages() throws (testing::TestError) { |
| 245 | let package = binary::Package { |
| 246 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 247 | program: il::Program { data: &[], fns: &[] }, |
| 248 | }; |
| 249 | let mut buffer: [u8; 64] = [0; 64]; |
| 250 | let length = try program::encode(&mut buffer[..], &package) catch { |
| 251 | throw testing::TestError::Failed; |
| 252 | }; |
| 253 | let memory = &mut MEMORY[..64]; |
| 254 | let mut arena = alloc::new(&mut memory[..]); |
| 255 | for offset in &[0, 4, 17, 29] { |
| 256 | let original = buffer[offset]; |
| 257 | set buffer[offset] = 255; |
| 258 | set arena.offset = 8; |
| 259 | let mut failed = false; |
| 260 | try program::decode(&buffer[..length], &mut arena, LIMITS) catch { |
| 261 | set failed = true; |
| 262 | }; |
| 263 | try testing::expect(failed); |
| 264 | try testing::expect(arena.offset == 8); |
| 265 | set buffer[offset] = original; |
| 266 | } |
| 267 | let mut trailing = false; |
| 268 | try program::decode(&buffer[..length + 1], &mut arena, LIMITS) catch err { |
| 269 | try testing::expect(err == binary::Error::Invalid); |
| 270 | set trailing = true; |
| 271 | }; |
| 272 | try testing::expect(trailing); |
| 273 | try testing::expect(arena.offset == 8); |
| 274 | } |
| 275 | |
| 276 | /// Reject invalid dependency and export entries after partial table decoding. |
| 277 | @test unsafe fn malformedHeaderTables() throws (testing::TestError) { |
| 278 | let package = binary::Package { |
| 279 | symbols: &["p"], name: "p", dependencies: &["p", "p"], |
| 280 | exports: &[ |
| 281 | binary::Export { name: "p", kind: binary::ExportKind::Function }, |
| 282 | binary::Export { name: "p", kind: binary::ExportKind::Data }, |
| 283 | ], |
| 284 | entry: nil, program: il::Program { data: &[], fns: &[] }, |
| 285 | }; |
| 286 | let mut buffer: [u8; 64] = [0; 64]; |
| 287 | let length = try program::encode(&mut buffer[..], &package) catch { throw testing::TestError::Failed; }; |
| 288 | for offset in [25, 29, 37, 42, 41, 46] { |
| 289 | let original = buffer[offset]; |
| 290 | set buffer[offset] = 2; |
| 291 | let mut arena = alloc::new(&mut MEMORY[..]); |
| 292 | set arena.offset = 8; |
| 293 | let mut failed = false; |
| 294 | try program::decode(&buffer[..length], &mut arena, LIMITS) catch err { |
| 295 | assert err == (binary::Error::Invalid if offset == 41 or offset == 46 else binary::Error::Symbol); |
| 296 | set failed = true; |
| 297 | }; |
| 298 | assert failed; |
| 299 | assert arena.offset == 8; |
| 300 | set buffer[offset] = original; |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | /// Encode a malformed package and check rejection with arena rollback. |
| 305 | unsafe fn rejected(package: &binary::Package, limits: binary::Limits) throws (testing::TestError) { |
| 306 | let mut buffer: [u8; 512] = [0; 512]; |
| 307 | let length = try program::encode(&mut buffer[..], package) catch { |
| 308 | throw testing::TestError::Failed; |
| 309 | }; |
| 310 | let memory = &mut MEMORY[..256]; |
| 311 | let mut arena = alloc::new(&mut memory[..]); |
| 312 | set arena.offset = 8; |
| 313 | let mut failed = false; |
| 314 | try program::decode(&buffer[..length], &mut arena, limits) catch err { |
| 315 | try testing::expect(err == binary::Error::Invalid); |
| 316 | set failed = true; |
| 317 | }; |
| 318 | try testing::expect(failed); |
| 319 | try testing::expect(arena.offset == 8); |
| 320 | } |
| 321 | |
| 322 | /// Check initializer extent arithmetic, alignments, and duplicate symbols. |
| 323 | @test unsafe fn invalidData() throws (testing::TestError) { |
| 324 | let mut item = il::Data { |
| 325 | name: "p", size: 0xffffffff, alignment: 8, readOnly: false, isZeroInit: false, |
| 326 | values: &[ |
| 327 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 0 }, count: 0xffffffff }, |
| 328 | ], |
| 329 | }; |
| 330 | let mut package = binary::Package { |
| 331 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 332 | program: il::Program { data: retainData(&[item]), fns: &[] }, |
| 333 | }; |
| 334 | try rejected(&package, LIMITS); |
| 335 | set item.size = 1; |
| 336 | set item.values = &[ |
| 337 | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
| 338 | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
| 339 | ]; |
| 340 | set package.program = il::Program { data: retainData(&[item]), fns: &[] }; |
| 341 | try rejected(&package, LIMITS); |
| 342 | set item.values = &[]; |
| 343 | for alignment in &[0, 3] { |
| 344 | set item.alignment = alignment; |
| 345 | set package.program = il::Program { data: retainData(&[item]), fns: &[] }; |
| 346 | try rejected(&package, LIMITS); |
| 347 | } |
| 348 | set package.program = il::Program { data: &[], fns: &[] }; |
| 349 | set package.symbols = &["p", "p"]; |
| 350 | try rejected(&package, LIMITS); |
| 351 | set package.symbols = &["p", ""]; |
| 352 | try rejected(&package, LIMITS); |
| 353 | } |
| 354 | |
| 355 | /// Initializer extents include every item kind and cumulative size boundaries. |
| 356 | @test unsafe fn initializerExtents() throws (testing::TestError) { |
| 357 | for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] { |
| 358 | try checkInitializerExtents(il::DataItem::Val { typ, val: 0 }, il::typeSize(typ)); |
| 359 | } |
| 360 | try checkInitializerExtents(il::DataItem::Sym("p"), 8); |
| 361 | try checkInitializerExtents(il::DataItem::Fn("p"), 8); |
| 362 | try checkInitializerExtents(il::DataItem::Str("abc"), 3); |
| 363 | try checkInitializerExtents(il::DataItem::Str(""), 0); |
| 364 | try checkInitializerExtents(il::DataItem::Undef, 1); |
| 365 | } |
| 366 | |
| 367 | /// Test one repeated item followed by a byte at exact-fit and shorter sizes. |
| 368 | unsafe fn checkInitializerExtents(item: il::DataItem, width: u32) throws (testing::TestError) { |
| 369 | /// Stable initialized values retained by the fixture's data definition. |
| 370 | static values: [il::DataValue; 2] = [il::DataValue { item: il::DataItem::Undef, count: 1 }; 2]; |
| 371 | for count in [0 as u32, 1, 3, 0xffffffff] { |
| 372 | set values[0] = il::DataValue { item, count }; |
| 373 | let extent = width as u64 * count as u64 + 1; |
| 374 | let size = extent as u32 if extent <= 0xffffffff else 0xffffffff; |
| 375 | for declared in [size, size - 1] { |
| 376 | let definition = il::Data { |
| 377 | name: "p", size: declared, alignment: 1, readOnly: false, isZeroInit: false, |
| 378 | values: &values[..], |
| 379 | }; |
| 380 | let package = binary::Package { |
| 381 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 382 | program: il::Program { data: retainData(&[definition]), fns: &[] }, |
| 383 | }; |
| 384 | let mut bytes: [u8; 256] = [0; 256]; |
| 385 | let length = try program::encode(&mut bytes[..], &package) catch { throw testing::TestError::Failed; }; |
| 386 | let mut arena = alloc::new(&mut MEMORY[..]); |
| 387 | set arena.offset = 8; |
| 388 | let mut failed = false; |
| 389 | try program::decode(&bytes[..length], &mut arena, LIMITS) catch err { |
| 390 | assert err == binary::Error::Invalid; |
| 391 | assert arena.offset == 8; |
| 392 | set failed = true; |
| 393 | }; |
| 394 | assert failed == (extent > declared as u64); |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | /// Check function block limits, parameter indices, and predecessor indices. |
| 400 | @test unsafe fn invalidFunctions() throws (testing::TestError) { |
| 401 | let mut instrs = [il::Instr::Ret { val: nil }]; |
| 402 | let mut block = il::Block { |
| 403 | label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[], loopDepth: 0, |
| 404 | }; |
| 405 | let mut func = il::Fn { |
| 406 | name: "p", params: &[], returnType: il::Type::W64, |
| 407 | isExtern: false, isLeaf: true, blocks: &[block], |
| 408 | }; |
| 409 | let package = binary::Package { |
| 410 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 411 | program: il::Program { data: &[], fns: &[&func] }, |
| 412 | }; |
| 413 | try rejected(&package, binary::Limits { registers: 16, blocks: 0 }); |
| 414 | set func.isExtern = true; |
| 415 | try rejected(&package, LIMITS); |
| 416 | set func.isExtern = false; |
| 417 | set func.params = &[il::Param { value: il::Reg { n: 16 }, type: il::Type::W64 }]; |
| 418 | try rejected(&package, LIMITS); |
| 419 | set func.params = &[]; |
| 420 | set block.params = &[il::Param { value: il::Reg { n: 16 }, type: il::Type::W32 }]; |
| 421 | set func.blocks = &[block]; |
| 422 | try rejected(&package, LIMITS); |
| 423 | set block.params = &[]; |
| 424 | set block.preds = &[1]; |
| 425 | set func.blocks = &[block]; |
| 426 | try rejected(&package, LIMITS); |
| 427 | } |
| 428 | |
| 429 | /// Reject unknown initializer tags and widths before reading their payloads. |
| 430 | @test unsafe fn malformedInitializers() throws (testing::TestError) { |
| 431 | let memory = &mut MEMORY[..8]; |
| 432 | let mut arena = alloc::new(&mut memory[..]); |
| 433 | let fixtures: [*[u8]; 2] = [&[255], &[0, 3]]; |
| 434 | for bytes in &fixtures[..] { |
| 435 | let namesTable: [*[u8]; 0] = []; |
| 436 | let source: 'input = &bytes[..], names = &namesTable[..] in { |
| 437 | let mut input = reader::new(source, &mut arena, names); |
| 438 | let mut failed = false; |
| 439 | try reader::dataValue(&mut input) catch err { |
| 440 | try testing::expect(err == binary::Error::Invalid); |
| 441 | set failed = true; |
| 442 | }; |
| 443 | try testing::expect(failed); |
| 444 | } |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | /// Check symbol collection capacity and package ownership failures. |
| 449 | @test unsafe fn collectionBounds() throws (testing::TestError) { |
| 450 | let mut symbols: [*[u8]; 8] = [""; 8]; |
| 451 | let mut dependencies: [*[u8]; 1] = [""; 1]; |
| 452 | let empty = il::Program { data: &[], fns: &[] }; |
| 453 | let symbolTable: 'empty = &mut symbols[..0], dependencyTable = &mut dependencies[..] in { |
| 454 | let mut names = collect::new(symbolTable, dependencyTable); |
| 455 | let mut failed = false; |
| 456 | try collect::package(&mut names, "p", empty, &[], nil) catch err { |
| 457 | try testing::expect(err == binary::Error::Capacity); |
| 458 | set failed = true; |
| 459 | }; |
| 460 | try testing::expect(failed); |
| 461 | } |
| 462 | let item = il::Data { |
| 463 | name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false, |
| 464 | values: &[il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }], |
| 465 | }; |
| 466 | let local = il::Program { data: retainData(&[item]), fns: &[] }; |
| 467 | let symbolTable: 'capacity = &mut symbols[..], dependencyTable = &mut dependencies[..0] in { |
| 468 | let mut names = collect::new(symbolTable, dependencyTable); |
| 469 | let mut failed = false; |
| 470 | try collect::package(&mut names, "p", local, &[], nil) catch err { |
| 471 | try testing::expect(err == binary::Error::Capacity); |
| 472 | set failed = true; |
| 473 | }; |
| 474 | try testing::expect(failed); |
| 475 | } |
| 476 | let symbolTable: 'owner = &mut symbols[..], dependencyTable = &mut dependencies[..] in { |
| 477 | let mut names = collect::new(symbolTable, dependencyTable); |
| 478 | let mut failed = false; |
| 479 | try collect::package(&mut names, "other", local, &[], nil) catch err { |
| 480 | try testing::expect(err == binary::Error::Invalid); |
| 481 | set failed = true; |
| 482 | }; |
| 483 | try testing::expect(failed); |
| 484 | } |
| 485 | let symbolTable: 'valid = &mut symbols[..], dependencyTable = &mut dependencies[..] in { |
| 486 | let mut names = collect::new(symbolTable, dependencyTable); |
| 487 | let package = try collect::package(&mut names, "p", local, &[], nil) catch { |
| 488 | throw testing::TestError::Failed; |
| 489 | }; |
| 490 | try testing::expect(package.dependencies.len == 1); |
| 491 | try testing::expectBytesEq(package.dependencies[0], "dep"); |
| 492 | try testing::expect(package.symbols.len == 4); |
| 493 | } |
| 494 | } |
| 495 | |
| 496 | /// Fixed instruction operands retain symbol order and bounded table writes. |
| 497 | @test unsafe fn fixedInstructionCollection() throws (testing::TestError) { |
| 498 | let r = il::Reg { n: 0 }; |
| 499 | let a = il::Val::DataSym("z::data"); |
| 500 | let b = il::Val::FnAddr("a::fn"); |
| 501 | let c = il::Val::DataSym("c::data"); |
| 502 | let d = il::Val::FnAddr("d::fn"); |
| 503 | let e = il::Val::DataSym("e::data"); |
| 504 | let single: [*[u8]; 2] = ["z::data", "z"]; |
| 505 | let pair: [*[u8]; 4] = ["z::data", "z", "a::fn", "a"]; |
| 506 | for instr in [ |
| 507 | il::Instr::Reserve { dst: r, size: a, alignment: 8 }, |
| 508 | il::Instr::Blit { dst: r, src: r, size: a }, |
| 509 | il::Instr::Store { typ: il::Type::W64, src: a, dst: r, offset: 0 }, |
| 510 | il::Instr::Copy { dst: r, val: a }, |
| 511 | il::Instr::Zext { typ: il::Type::W8, dst: r, val: a }, |
| 512 | il::Instr::Sext { typ: il::Type::W8, dst: r, val: a }, |
| 513 | il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W64, dst: r, a }, |
| 514 | il::Instr::Ret { val: a }, |
| 515 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b: a }, |
| 516 | ] { try checkInstructionNames(instr, &single[..]); } |
| 517 | for instr in [ |
| 518 | il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b }, |
| 519 | il::Instr::DeviceRead { typ: il::Type::W8, dst: r, handle: a, offset: b }, |
| 520 | ] { try checkInstructionNames(instr, &pair[..]); } |
| 521 | try checkInstructionNames(il::Instr::Ecall { dst: r, num: a, a0: b, a1: c, a2: d, a3: e }, |
| 522 | &["z::data", "z", "a::fn", "a", "c::data", "c", "d::fn", "d", "e::data", "e"]); |
| 523 | try checkInstructionNames(il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: c }, |
| 524 | &["z::data", "z", "a::fn", "a", "c::data", "c"]); |
| 525 | for instr in [ |
| 526 | il::Instr::Load { typ: il::Type::W8, dst: r, src: r, offset: 0 }, |
| 527 | il::Instr::Sload { typ: il::Type::W8, dst: r, src: r, offset: 0 }, |
| 528 | il::Instr::Ret { val: nil }, |
| 529 | il::Instr::Copy { dst: r, val: il::Val::Imm(9) }, |
| 530 | il::Instr::Unreachable, il::Instr::Ebreak, il::Instr::MemoryFence, |
| 531 | ] { try checkInstructionNames(instr, &[]); } |
| 532 | } |
| 533 | |
| 534 | /// Variable operand groups retain first-use order and deduplicate names. |
| 535 | @test unsafe fn variableInstructionCollection() throws (testing::TestError) { |
| 536 | let r = il::Reg { n: 0 }; |
| 537 | let a = il::Val::DataSym("z::data"); |
| 538 | let b = il::Val::FnAddr("a::fn"); |
| 539 | let c = il::Val::DataSym("c::data"); |
| 540 | let imm = il::Val::Imm(0); |
| 541 | let mut args = [imm, a, a, b]; |
| 542 | let mut fallback = [c, a]; |
| 543 | try checkInstructionNames(il::Instr::Call { retTy: il::Type::W64, dst: r, |
| 544 | func: b, args: &args[..] }, &["a::fn", "a", "z::data", "z"]); |
| 545 | try checkInstructionNames(il::Instr::Call { retTy: il::Type::W64, dst: nil, |
| 546 | func: il::Val::Reg(r), args: &[] }, &[]); |
| 547 | try checkInstructionNames(il::Instr::Jmp { target: 0, args: &mut args[..] }, |
| 548 | &["z::data", "z", "a::fn", "a"]); |
| 549 | try checkInstructionNames(il::Instr::Br { op: il::CmpOp::Eq, typ: il::Type::W64, |
| 550 | a, b: imm, thenTarget: 0, thenArgs: &mut [], elseTarget: 0, elseArgs: &mut args[..] }, |
| 551 | &["z::data", "z", "a::fn", "a"]); |
| 552 | try checkInstructionNames(il::Instr::Br { op: il::CmpOp::Eq, typ: il::Type::W64, |
| 553 | a: imm, b: imm, thenTarget: 0, thenArgs: &mut args[..], elseTarget: 0, elseArgs: &mut fallback[..] }, |
| 554 | &["z::data", "z", "a::fn", "a", "c::data", "c"]); |
| 555 | let mut cases = [il::SwitchCase { value: 0, target: 0, args: &mut [] }, |
| 556 | il::SwitchCase { value: 1, target: 0, args: &mut args[..] }]; |
| 557 | try checkInstructionNames(il::Instr::Switch { val: b, defaultTarget: 0, |
| 558 | defaultArgs: &mut fallback[..], cases: &mut cases[..] }, |
| 559 | &["a::fn", "a", "c::data", "c", "z::data", "z"]); |
| 560 | try checkInstructionNames(il::Instr::Switch { val: imm, defaultTarget: 0, |
| 561 | defaultArgs: &mut [], cases: &mut [] }, &[]); |
| 562 | } |
| 563 | |
| 564 | /// Collect one instruction at exact-fit and every shorter symbol capacity. |
| 565 | unsafe fn checkInstructionNames(instr: il::Instr, expected: &[*[u8]]) throws (testing::TestError) { |
| 566 | let mut body = [instr]; |
| 567 | let function = il::Fn { |
| 568 | name: "p::f", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true, |
| 569 | blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..], locs: &[], preds: &[], loopDepth: 0 }], |
| 570 | }; |
| 571 | let local = il::Program { data: &[], fns: &[&function] }; |
| 572 | for capacity in 0..expected.len + 3 { |
| 573 | let mut symbols: [*[u8]; 16] = [""; 16]; |
| 574 | let mut dependencies: [*[u8]; 5] = [""; 5]; |
| 575 | let symbolTable: 'tables = &mut symbols[..capacity], dependencyTable = &mut dependencies[..] in { |
| 576 | let mut names = collect::new(symbolTable, dependencyTable); |
| 577 | let mut failed = false; |
| 578 | try collect::package(&mut names, "p", local, &[], nil) catch err { |
| 579 | assert err == binary::Error::Capacity; |
| 580 | set failed = true; |
| 581 | }; |
| 582 | assert failed == (capacity < expected.len + 2); |
| 583 | assert names.symbolCount <= capacity; |
| 584 | if not failed { |
| 585 | assert names.symbolCount == expected.len + 2; |
| 586 | try testing::expectBytesEq(names.symbols[0], "p"); |
| 587 | try testing::expectBytesEq(names.symbols[1], "p::f"); |
| 588 | for name, i in expected { try testing::expectBytesEq(names.symbols[i + 2], name); } |
| 589 | assert names.dependencyCount == expected.len / 2; |
| 590 | } |
| 591 | } |
| 592 | for i in capacity..symbols.len { assert symbols[i].len == 0; } |
| 593 | } |
| 594 | } |
| 595 | |
| 596 | /// Function definitions precede their block references in table order. |
| 597 | @test unsafe fn functionCollectionOrder() throws (testing::TestError) { |
| 598 | let external = il::Fn { name: "p::external", params: &[], returnType: il::Type::W64, |
| 599 | isExtern: true, isLeaf: true, blocks: &[] }; |
| 600 | let mut first = [il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::DataSym("z::data") }]; |
| 601 | let mut second = [il::Instr::Ret { val: il::Val::FnAddr("a::fn") }]; |
| 602 | let blocks = [ |
| 603 | il::Block { label: "first", params: &[], instrs: &mut first[..], locs: &[], preds: &[], loopDepth: 0 }, |
| 604 | il::Block { label: "empty", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }, |
| 605 | il::Block { label: "second", params: &[], instrs: &mut second[..], locs: &[], preds: &[], loopDepth: 0 }, |
| 606 | ]; |
| 607 | let function = il::Fn { name: "p::main", params: &[], returnType: il::Type::W64, |
| 608 | isExtern: false, isLeaf: true, blocks: &blocks[..] }; |
| 609 | let local = il::Program { data: &[], fns: &[&external, &function] }; |
| 610 | let expected: [*[u8]; 7] = ["p", "p::external", "p::main", "z::data", "z", "a::fn", "a"]; |
| 611 | for capacity in 0..expected.len + 1 { |
| 612 | let mut symbols: [*[u8]; 8] = [""; 8]; |
| 613 | let mut dependencies: [*[u8]; 2] = [""; 2]; |
| 614 | let symbolTable: 'tables = &mut symbols[..capacity], dependencyTable = &mut dependencies[..] in { |
| 615 | let mut names = collect::new(symbolTable, dependencyTable); |
| 616 | let mut failed = false; |
| 617 | try collect::package(&mut names, "p", local, &[], nil) catch err { |
| 618 | assert err == binary::Error::Capacity; |
| 619 | set failed = true; |
| 620 | }; |
| 621 | assert failed == (capacity < expected.len); |
| 622 | assert names.symbolCount <= capacity; |
| 623 | if not failed { |
| 624 | assert names.symbolCount == expected.len; |
| 625 | for name, i in expected { try testing::expectBytesEq(names.symbols[i], name); } |
| 626 | assert names.dependencyCount == 2; |
| 627 | try testing::expectBytesEq(names.dependencies[0], "z"); |
| 628 | try testing::expectBytesEq(names.dependencies[1], "a"); |
| 629 | } |
| 630 | } |
| 631 | for i in capacity..symbols.len { assert symbols[i].len == 0; } |
| 632 | } |
| 633 | } |
| 634 | |
| 635 | /// Exports precede the entry name, and entry definitions belong to the package. |
| 636 | @test unsafe fn entryCollectionOrder() throws (testing::TestError) { |
| 637 | let local = il::Program { data: &[], fns: &[] }; |
| 638 | let exports = [ |
| 639 | binary::Export { name: "p::second", kind: binary::ExportKind::Function }, |
| 640 | binary::Export { name: "p::first", kind: binary::ExportKind::Function }, |
| 641 | binary::Export { name: "p::second", kind: binary::ExportKind::Function }, |
| 642 | ]; |
| 643 | let entries: [?*[u8]; 5] = [nil, "p::first", "p::start", "other::start", "p::"]; |
| 644 | for entry, index in entries { |
| 645 | let mut symbols: [*[u8]; 4] = [""; 4]; |
| 646 | let mut dependencies: [*[u8]; 1] = [""; 1]; |
| 647 | let symbolTable: 'tables = &mut symbols[..], dependencyTable = &mut dependencies[..] in { |
| 648 | let mut names = collect::new(symbolTable, dependencyTable); |
| 649 | let package = try collect::package(&mut names, "p", local, &exports[..], entry) catch err { |
| 650 | assert index >= 3; |
| 651 | assert err == binary::Error::Invalid; |
| 652 | continue; |
| 653 | }; |
| 654 | assert index < 3; |
| 655 | assert package.symbols.len == (4 if index == 2 else 3); |
| 656 | for name, i in ["p", "p::second", "p::first"] { |
| 657 | try testing::expectBytesEq(package.symbols[i], name); |
| 658 | } |
| 659 | if index == 2 { try testing::expectBytesEq(package.symbols[3], "p::start"); } |
| 660 | assert package.dependencies.len == 0; |
| 661 | assert package.exports.len == 3; |
| 662 | assert package.entry == entry; |
| 663 | } |
| 664 | } |
| 665 | } |
| 666 | |
| 667 | /// Data collection retains first-use order and unique dependencies. |
| 668 | @test unsafe fn dataCollectionOrder() throws (testing::TestError) { |
| 669 | let item = il::Data { |
| 670 | name: "p::data", size: 64, alignment: 8, readOnly: true, isZeroInit: false, |
| 671 | values: &[ |
| 672 | il::DataValue { item: il::DataItem::Fn("z::fn"), count: 1 }, |
| 673 | il::DataValue { item: il::DataItem::Sym("a::data"), count: 1 }, |
| 674 | il::DataValue { item: il::DataItem::Fn("z::fn"), count: 1 }, |
| 675 | il::DataValue { item: il::DataItem::Sym("p::data"), count: 1 }, |
| 676 | il::DataValue { item: il::DataItem::Str("not::a::symbol"), count: 1 }, |
| 677 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, val: 9 }, count: 2 }, |
| 678 | ], |
| 679 | }; |
| 680 | let zero = il::Data { |
| 681 | name: "p::zero", size: 32, alignment: 8, readOnly: false, isZeroInit: true, values: &[], |
| 682 | }; |
| 683 | let local = il::Program { data: retainData(&[item, zero]), fns: &[] }; |
| 684 | let mut symbols: [*[u8]; 7] = [""; 7]; |
| 685 | let mut dependencies: [*[u8]; 2] = [""; 2]; |
| 686 | let symbolTable: 'tables = &mut symbols[..], dependencyTable = &mut dependencies[..] in { |
| 687 | let mut names = collect::new(symbolTable, dependencyTable); |
| 688 | let package = try collect::package(&mut names, "p", local, |
| 689 | &[binary::Export { name: "p::data", kind: binary::ExportKind::Data }], nil |
| 690 | ) catch { throw testing::TestError::Failed; }; |
| 691 | assert package.symbols.len == 7; |
| 692 | for expected, index in ["p", "p::data", "z::fn", "z", "a::data", "a", "p::zero"] { |
| 693 | try testing::expectBytesEq(package.symbols[index], expected); |
| 694 | } |
| 695 | assert package.dependencies.len == 2; |
| 696 | try testing::expectBytesEq(package.dependencies[0], "z"); |
| 697 | try testing::expectBytesEq(package.dependencies[1], "a"); |
| 698 | try roundTrip(&package); |
| 699 | } |
| 700 | } |
| 701 | |
| 702 | /// Copy data fixtures into stable storage for the package descriptor. |
| 703 | unsafe fn retainData(items: &[il::Data]) -> *[il::Data] { |
| 704 | unsafe static DATA: [il::Data; 2] = undefined; |
| 705 | assert items.len <= DATA.len; |
| 706 | for item, i in items { |
| 707 | set DATA[i] = item; |
| 708 | } |
| 709 | return &DATA[..items.len]; |
| 710 | } |