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.9 KiB
reader.rad
14.1 KiB
tests.rad
13.7 KiB
writer.rad
12.7 KiB
binary.rad
5.9 KiB
printer.rad
15.4 KiB
tests.rad
3.8 KiB
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.7 KiB
gen.rad
513 B
il.rad
19.5 KiB
lower.rad
308.1 KiB
module.rad
14.9 KiB
package.rad
1.3 KiB
parser.rad
89.5 KiB
resolver.rad
439.6 KiB
scanner.rad
18.0 KiB
sexpr.rad
6.4 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
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
9.2 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
662 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 { |
| 353 | set DATA[i] = item; |
| 354 | } |
| 355 | return &DATA[..items.len]; |
| 356 | } |