compiler/
kernel/
lib/
examples/
std/
arch/
char/
collections/
lang/
alloc/
ast/
gen/
il/
binary/
collect.rad
7.2 KiB
decodeTests.rad
14.9 KiB
program.rad
10.8 KiB
reader.rad
14.0 KiB
tests.rad
13.4 KiB
writer.rad
12.7 KiB
binary.rad
5.9 KiB
printer.rad
15.4 KiB
module/
parser/
resolver/
scanner/
alloc.rad
5.3 KiB
ast.rad
23.6 KiB
gen.rad
513 B
il.rad
17.0 KiB
lower.rad
278.5 KiB
module.rad
13.5 KiB
package.rad
1.3 KiB
parser.rad
79.6 KiB
resolver.rad
329.1 KiB
scanner.rad
17.6 KiB
sexpr.rad
6.4 KiB
strings.rad
2.2 KiB
types.rad
286 B
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.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
281 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
10.2 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
551 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 a package round trip and failure at every truncated byte length. |
| 18 | unsafe fn roundTrip(package: &binary::Package) throws (testing::TestError) { |
| 19 | let mut buffer: [u8; 2048] = [0; 2048]; |
| 20 | let length = try program::encode(&mut buffer[..], package) catch { |
| 21 | throw testing::TestError::Failed; |
| 22 | }; |
| 23 | let memory = &mut MEMORY[..2048]; |
| 24 | let mut arena = alloc::new(&mut memory[..]); |
| 25 | set arena.offset = 8; |
| 26 | let decoded = try program::decode(&buffer[..length], &mut arena, LIMITS) catch { |
| 27 | throw testing::TestError::Failed; |
| 28 | }; |
| 29 | try testing::expectBytesEq(decoded.name, package.name); |
| 30 | try testing::expect(decoded.dependencies.len == package.dependencies.len); |
| 31 | try testing::expect(decoded.exports.len == package.exports.len); |
| 32 | try testing::expect(decoded.program.fns.len == package.program.fns.len); |
| 33 | let mut encoded: [u8; 2048] = [0; 2048]; |
| 34 | let repeated = try program::encode(&mut encoded[..], &decoded) catch { |
| 35 | throw testing::TestError::Failed; |
| 36 | }; |
| 37 | try testing::expectBytesEq(&buffer[..length], &encoded[..repeated]); |
| 38 | // Decoded names and string initializers retain arena ownership. |
| 39 | for i in 0..length { |
| 40 | set buffer[i] = 0; |
| 41 | } |
| 42 | let copied = try program::encode(&mut buffer[..], &decoded) catch { |
| 43 | throw testing::TestError::Failed; |
| 44 | }; |
| 45 | try testing::expectBytesEq(&buffer[..copied], &encoded[..repeated]); |
| 46 | for end in 0..length { |
| 47 | set arena.offset = 8; |
| 48 | let mut failed = false; |
| 49 | try program::decode(&buffer[..end], &mut arena, LIMITS) catch err { |
| 50 | try testing::expect(err == binary::Error::Truncated); |
| 51 | set failed = true; |
| 52 | }; |
| 53 | try testing::expect(failed); |
| 54 | try testing::expect(arena.offset == 8); |
| 55 | } |
| 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); |
| 65 | // Exercise every smaller arena extent, including alignment boundaries. |
| 66 | let mut usedArena = alloc::new(&mut memory[..]); |
| 67 | let _ = try program::decode(&buffer[..length], &mut usedArena, LIMITS) catch { |
| 68 | throw testing::TestError::Failed; |
| 69 | }; |
| 70 | let required = usedArena.offset; |
| 71 | for capacity in 0..required { |
| 72 | let mut short = alloc::new(&mut MEMORY[..capacity]); |
| 73 | let mut failed = false; |
| 74 | try program::decode(&buffer[..length], &mut short, LIMITS) catch err { |
| 75 | try testing::expect(err == binary::Error::Storage); |
| 76 | set failed = true; |
| 77 | }; |
| 78 | try testing::expect(failed); |
| 79 | try testing::expect(short.offset == 0); |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | /// Check an empty program with a package identity. |
| 84 | @test unsafe fn emptyPackage() throws (testing::TestError) { |
| 85 | let package = binary::Package { |
| 86 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 87 | program: il::Program { data: &[], fns: &[] }, |
| 88 | }; |
| 89 | try roundTrip(&package); |
| 90 | let mut buffer: [u8; 64] = [0; 64]; |
| 91 | let length = try program::encode(&mut buffer[..], &package) catch { |
| 92 | throw testing::TestError::Failed; |
| 93 | }; |
| 94 | try testing::expectBytesEq(&buffer[..length], &[ |
| 95 | 82, 73, 76, 0, 1, 0, 0, 0, 1, 0, 0, 0, |
| 96 | 1, 0, 0, 0, 112, 0, 0, 0, 0, |
| 97 | 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 98 | 0, 0, 0, 0, 0, 0, 0, 0, |
| 99 | ]); |
| 100 | } |
| 101 | |
| 102 | /// Check functions, dependencies, exports, initializers, and block metadata. |
| 103 | @test unsafe fn fullPackage() throws (testing::TestError) { |
| 104 | let params: *unsafe [il::Param] = &[il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }]; |
| 105 | let mut instrs = [ |
| 106 | il::Instr::Call { |
| 107 | retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("dep::fn"), |
| 108 | args: &[il::Val::Reg(il::Reg { n: 1 })], |
| 109 | }, |
| 110 | il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) }, |
| 111 | ]; |
| 112 | let func = il::Fn { |
| 113 | name: "p::main", params, returnType: il::Type::W64, isExtern: false, isLeaf: false, |
| 114 | blocks: &[il::Block { |
| 115 | label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[0], loopDepth: 2, |
| 116 | }], |
| 117 | }; |
| 118 | let external = il::Fn { |
| 119 | name: "dep::fn", params, returnType: il::Type::W64, isExtern: true, isLeaf: true, blocks: &[], |
| 120 | }; |
| 121 | let data = il::Data { |
| 122 | name: "p::data", size: 64, alignment: 8, readOnly: false, isZeroInit: false, |
| 123 | values: &[ |
| 124 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, val: -1 }, count: 2 }, |
| 125 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, val: -2 }, count: 1 }, |
| 126 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, val: -3 }, count: 1 }, |
| 127 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: -4 }, count: 1 }, |
| 128 | il::DataValue { item: il::DataItem::Sym("p::data"), count: 1 }, |
| 129 | il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }, |
| 130 | il::DataValue { item: il::DataItem::Str("bytes"), count: 2 }, |
| 131 | il::DataValue { item: il::DataItem::Str(""), count: 0 }, |
| 132 | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
| 133 | ], |
| 134 | }; |
| 135 | let zero = il::Data { |
| 136 | name: "p::zero", size: 4096, alignment: 4096, readOnly: false, isZeroInit: true, values: &[], |
| 137 | }; |
| 138 | let package = binary::Package { |
| 139 | symbols: &["p", "dep", "p::main", "dep::fn", "p::data", "p::zero"], name: "p", |
| 140 | dependencies: &["dep"], |
| 141 | exports: &[ |
| 142 | binary::Export { name: "p::main", kind: binary::ExportKind::Function }, |
| 143 | binary::Export { name: "p::data", kind: binary::ExportKind::Data }, |
| 144 | ], |
| 145 | entry: "p::main", program: il::Program { data: retainData(&[data, zero]), fns: &[&func, &external] }, |
| 146 | }; |
| 147 | try roundTrip(&package); |
| 148 | } |
| 149 | |
| 150 | /// Reject an invalid encoded instruction without a panic. |
| 151 | unsafe fn badInstruction(bytes: *[u8], expected: binary::Error) throws (testing::TestError) { |
| 152 | let memory = &mut MEMORY[..64]; |
| 153 | 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); |
| 163 | } |
| 164 | |
| 165 | /// Check instruction tags, operand tags, types, optional flags, and indices. |
| 166 | @test unsafe fn malformedInstructions() throws (testing::TestError) { |
| 167 | try badInstruction(&[255], binary::Error::Invalid); |
| 168 | try badInstruction(&[5, 0, 0, 0, 0, 255], binary::Error::Invalid); |
| 169 | try badInstruction(&[1, 3], binary::Error::Invalid); |
| 170 | try badInstruction(&[6, 255], binary::Error::Invalid); |
| 171 | try badInstruction(&[7, 255], binary::Error::Invalid); |
| 172 | try badInstruction(&[13, 255], binary::Error::Invalid); |
| 173 | try badInstruction(&[11, 2], binary::Error::Invalid); |
| 174 | try badInstruction(&[10, 8, 2], binary::Error::Invalid); |
| 175 | try badInstruction(&[5, 2, 0, 0, 0], binary::Error::Invalid); |
| 176 | try badInstruction(&[5, 0, 0, 0, 0, 0, 2, 0, 0, 0], binary::Error::Invalid); |
| 177 | try badInstruction(&[12, 1, 0, 0, 0], binary::Error::Invalid); |
| 178 | try badInstruction(&[5, 0, 0, 0, 0, 2, 1, 0, 0, 0], binary::Error::Symbol); |
| 179 | try badInstruction(&[12, 0, 0, 0, 0, 255, 255, 255, 255], binary::Error::Truncated); |
| 180 | } |
| 181 | |
| 182 | /// Check header validation and package symbol indices with arena rollback. |
| 183 | @test unsafe fn malformedPackages() throws (testing::TestError) { |
| 184 | let package = binary::Package { |
| 185 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 186 | program: il::Program { data: &[], fns: &[] }, |
| 187 | }; |
| 188 | let mut buffer: [u8; 64] = [0; 64]; |
| 189 | let length = try program::encode(&mut buffer[..], &package) catch { |
| 190 | throw testing::TestError::Failed; |
| 191 | }; |
| 192 | let memory = &mut MEMORY[..64]; |
| 193 | let mut arena = alloc::new(&mut memory[..]); |
| 194 | for offset in &[0, 4, 17, 29] { |
| 195 | let original = buffer[offset]; |
| 196 | set buffer[offset] = 255; |
| 197 | set arena.offset = 8; |
| 198 | let mut failed = false; |
| 199 | try program::decode(&buffer[..length], &mut arena, LIMITS) catch { |
| 200 | set failed = true; |
| 201 | }; |
| 202 | try testing::expect(failed); |
| 203 | try testing::expect(arena.offset == 8); |
| 204 | set buffer[offset] = original; |
| 205 | } |
| 206 | let mut trailing = false; |
| 207 | try program::decode(&buffer[..length + 1], &mut arena, LIMITS) catch err { |
| 208 | try testing::expect(err == binary::Error::Invalid); |
| 209 | set trailing = true; |
| 210 | }; |
| 211 | try testing::expect(trailing); |
| 212 | try testing::expect(arena.offset == 8); |
| 213 | } |
| 214 | |
| 215 | /// Encode a malformed package and check rejection with arena rollback. |
| 216 | unsafe fn rejected(package: &binary::Package, limits: binary::Limits) throws (testing::TestError) { |
| 217 | let mut buffer: [u8; 512] = [0; 512]; |
| 218 | let length = try program::encode(&mut buffer[..], package) catch { |
| 219 | throw testing::TestError::Failed; |
| 220 | }; |
| 221 | let memory = &mut MEMORY[..256]; |
| 222 | let mut arena = alloc::new(&mut memory[..]); |
| 223 | set arena.offset = 8; |
| 224 | let mut failed = false; |
| 225 | try program::decode(&buffer[..length], &mut arena, limits) catch err { |
| 226 | try testing::expect(err == binary::Error::Invalid); |
| 227 | set failed = true; |
| 228 | }; |
| 229 | try testing::expect(failed); |
| 230 | try testing::expect(arena.offset == 8); |
| 231 | } |
| 232 | |
| 233 | /// Check initializer extent arithmetic, alignments, and duplicate symbols. |
| 234 | @test unsafe fn invalidData() throws (testing::TestError) { |
| 235 | let mut item = il::Data { |
| 236 | name: "p", size: 0xffffffff, alignment: 8, readOnly: false, isZeroInit: false, |
| 237 | values: &[ |
| 238 | il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 0 }, count: 0xffffffff }, |
| 239 | ], |
| 240 | }; |
| 241 | let mut package = binary::Package { |
| 242 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 243 | program: il::Program { data: retainData(&[item]), fns: &[] }, |
| 244 | }; |
| 245 | try rejected(&package, LIMITS); |
| 246 | set item.size = 1; |
| 247 | set item.values = &[ |
| 248 | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
| 249 | il::DataValue { item: il::DataItem::Undef, count: 1 }, |
| 250 | ]; |
| 251 | set package.program = il::Program { data: retainData(&[item]), fns: &[] }; |
| 252 | try rejected(&package, LIMITS); |
| 253 | set item.values = &[]; |
| 254 | for alignment in &[0, 3] { |
| 255 | set item.alignment = alignment; |
| 256 | set package.program = il::Program { data: retainData(&[item]), fns: &[] }; |
| 257 | try rejected(&package, LIMITS); |
| 258 | } |
| 259 | set package.program = il::Program { data: &[], fns: &[] }; |
| 260 | set package.symbols = &["p", "p"]; |
| 261 | try rejected(&package, LIMITS); |
| 262 | set package.symbols = &["p", ""]; |
| 263 | try rejected(&package, LIMITS); |
| 264 | } |
| 265 | |
| 266 | /// Check function block limits, parameter indices, and predecessor indices. |
| 267 | @test unsafe fn invalidFunctions() throws (testing::TestError) { |
| 268 | let mut instrs = [il::Instr::Ret { val: nil }]; |
| 269 | let mut block = il::Block { |
| 270 | label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[], loopDepth: 0, |
| 271 | }; |
| 272 | let mut func = il::Fn { |
| 273 | name: "p", params: &[], returnType: il::Type::W64, |
| 274 | isExtern: false, isLeaf: true, blocks: &[block], |
| 275 | }; |
| 276 | let package = binary::Package { |
| 277 | symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil, |
| 278 | program: il::Program { data: &[], fns: &[&func] }, |
| 279 | }; |
| 280 | try rejected(&package, binary::Limits { registers: 16, blocks: 0 }); |
| 281 | set func.isExtern = true; |
| 282 | try rejected(&package, LIMITS); |
| 283 | set func.isExtern = false; |
| 284 | set func.params = &[il::Param { value: il::Reg { n: 16 }, type: il::Type::W64 }]; |
| 285 | try rejected(&package, LIMITS); |
| 286 | set func.params = &[]; |
| 287 | set block.preds = &[1]; |
| 288 | set func.blocks = &[block]; |
| 289 | try rejected(&package, LIMITS); |
| 290 | } |
| 291 | |
| 292 | /// Reject unknown initializer tags and widths before reading their payloads. |
| 293 | @test unsafe fn malformedInitializers() throws (testing::TestError) { |
| 294 | let memory = &mut MEMORY[..8]; |
| 295 | let mut arena = alloc::new(&mut memory[..]); |
| 296 | let fixtures: [*[u8]; 2] = [&[255], &[0, 3]]; |
| 297 | 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); |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | /// Check symbol collection capacity and package ownership failures. |
| 309 | @test unsafe fn collectionBounds() throws (testing::TestError) { |
| 310 | let mut symbols: [*[u8]; 8] = undefined; |
| 311 | let mut dependencies: [*[u8]; 1] = undefined; |
| 312 | let empty = il::Program { data: &[], fns: &[] }; |
| 313 | let mut names = collect::new(&mut symbols[..0], &mut dependencies[..]); |
| 314 | let mut failed = false; |
| 315 | try collect::package(&mut names, "p", empty, &[], nil) catch err { |
| 316 | try testing::expect(err == binary::Error::Capacity); |
| 317 | set failed = true; |
| 318 | }; |
| 319 | try testing::expect(failed); |
| 320 | let item = il::Data { |
| 321 | name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false, |
| 322 | values: &[il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }], |
| 323 | }; |
| 324 | let local = il::Program { data: retainData(&[item]), fns: &[] }; |
| 325 | set names = collect::new(&mut symbols[..], &mut dependencies[..0]); |
| 326 | set failed = false; |
| 327 | try collect::package(&mut names, "p", local, &[], nil) catch err { |
| 328 | try testing::expect(err == binary::Error::Capacity); |
| 329 | set failed = true; |
| 330 | }; |
| 331 | try testing::expect(failed); |
| 332 | set names = collect::new(&mut symbols[..], &mut dependencies[..]); |
| 333 | set failed = false; |
| 334 | try collect::package(&mut names, "other", local, &[], nil) catch err { |
| 335 | try testing::expect(err == binary::Error::Invalid); |
| 336 | set failed = true; |
| 337 | }; |
| 338 | try testing::expect(failed); |
| 339 | set names = collect::new(&mut symbols[..], &mut dependencies[..]); |
| 340 | let package = try collect::package(&mut names, "p", local, &[], nil) catch { |
| 341 | throw testing::TestError::Failed; |
| 342 | }; |
| 343 | try testing::expect(package.dependencies.len == 1); |
| 344 | try testing::expectBytesEq(package.dependencies[0], "dep"); |
| 345 | try testing::expect(package.symbols.len == 4); |
| 346 | } |
| 347 | |
| 348 | /// Copy data fixtures into stable storage for the package descriptor. |
| 349 | unsafe fn retainData(items: &[il::Data]) -> *[il::Data] { |
| 350 | unsafe static DATA: [il::Data; 2] = undefined; |
| 351 | assert items.len <= DATA.len; |
| 352 | for item, i in items { set DATA[i] = item; } |
| 353 | return &DATA[..items.len]; |
| 354 | } |