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/tests.rad
raw
| 1 | //! Binary RIL encoding fixtures. |
| 2 | |
| 3 | use std::testing; |
| 4 | use std::lang::il; |
| 5 | use std::lang::il::binary::writer; |
| 6 | use std::lang::il::binary; |
| 7 | use std::lang::il::binary::reader; |
| 8 | use std::lang::alloc; |
| 9 | |
| 10 | /// Decode arena backing storage. Tests reset it before each use. |
| 11 | static MEMORY: [u8; 512] = [0; 512]; |
| 12 | /// Marker for bytes beyond a writer's borrowed output extent. |
| 13 | constant OUTPUT_CANARY: u8 = 0xa5; |
| 14 | |
| 15 | /// Fixed data items decode with no arena storage at every count boundary. |
| 16 | @test unsafe fn fixedDataItems() throws (testing::TestError) { |
| 17 | for item in [ |
| 18 | il::DataItem::Val { typ: il::Type::W8, val: 0x12 }, |
| 19 | il::DataItem::Val { typ: il::Type::W16, val: 0x1234 }, |
| 20 | il::DataItem::Val { typ: il::Type::W32, val: 0x12345678 }, |
| 21 | il::DataItem::Val { typ: il::Type::W64, val: -1 }, |
| 22 | il::DataItem::Sym("data"), il::DataItem::Fn("fn"), il::DataItem::Undef, |
| 23 | ] { |
| 24 | for count in [0 as u32, 1, 0xffffffff] { |
| 25 | try checkFixedDataItem(il::DataValue { item, count }); |
| 26 | } |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | /// Check a fixed initializer and every truncated prefix without allocation. |
| 31 | unsafe fn checkFixedDataItem(item: il::DataValue) throws (testing::TestError) { |
| 32 | let mut buffer: [u8; 32] = [0; 32]; |
| 33 | let symbols: [*[u8]; 2] = ["data", "fn"]; |
| 34 | let mut length: u32 = 0; |
| 35 | let output: 'output = &mut buffer[..], names = &symbols[..] in { |
| 36 | let mut out = writer::new(output, names); |
| 37 | try writer::dataValue(&mut out, item) catch { throw testing::TestError::Failed; }; |
| 38 | set length = out.offset; |
| 39 | } |
| 40 | for end in 0..length + 1 { |
| 41 | let mut arena = alloc::new(&mut MEMORY[..0]); |
| 42 | let source: 'input = &buffer[..end], names = &symbols[..] in { |
| 43 | let mut input = reader::new(source, &mut arena, names); |
| 44 | let mut failed = false; |
| 45 | let decoded = try reader::dataValue(&mut input) catch err { |
| 46 | assert err == binary::Error::Truncated; |
| 47 | set failed = true; |
| 48 | il::DataValue { item: il::DataItem::Undef, count: 0 } |
| 49 | }; |
| 50 | assert failed == (end < length); |
| 51 | assert input.offset <= end; |
| 52 | if not failed { |
| 53 | assert decoded == item; |
| 54 | assert input.offset == length; |
| 55 | } |
| 56 | } |
| 57 | assert arena.offset == 0; |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | /// Scalar decoding uses only checked input bytes and numeric cursor bounds. |
| 62 | fn checkScalarReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) { |
| 63 | set input.registers = 2; |
| 64 | set input.blocks = 3; |
| 65 | let reg = try reader::reg(input) catch { throw testing::TestError::Failed; }; |
| 66 | try testing::expect(reg.n == 1); |
| 67 | let target = try reader::target(input) catch { throw testing::TestError::Failed; }; |
| 68 | try testing::expect(target == 2); |
| 69 | let mut truncated = false; |
| 70 | try reader::integer(input, 1) catch err { |
| 71 | try testing::expect(err == binary::Error::Truncated); |
| 72 | set truncated = true; |
| 73 | }; |
| 74 | try testing::expect(truncated); |
| 75 | set input.offset = 0; |
| 76 | let flag = try reader::flag(input) catch { throw testing::TestError::Failed; }; |
| 77 | try testing::expect(flag); |
| 78 | set input.offset = 0; |
| 79 | let typ = try reader::typ(input) catch { throw testing::TestError::Failed; }; |
| 80 | try testing::expect(typ == il::Type::W8); |
| 81 | set input.offset = 0; |
| 82 | let count = try reader::count(input, 4) catch { throw testing::TestError::Failed; }; |
| 83 | try testing::expect(count == 1); |
| 84 | set input.offset = 0; |
| 85 | let wide = try reader::integer(input, 8) catch { throw testing::TestError::Failed; }; |
| 86 | try testing::expect(wide == 0x0000000200000001); |
| 87 | } |
| 88 | |
| 89 | /// A regional input buffer supports safe scalar decoding. |
| 90 | @test unsafe fn scalarReader() throws (testing::TestError) { |
| 91 | let memory = &mut MEMORY[..]; |
| 92 | let mut arena = alloc::new(memory); |
| 93 | let bytes: [u8; 8] = [1, 0, 0, 0, 2, 0, 0, 0]; |
| 94 | let namesTable: [*[u8]; 0] = []; |
| 95 | let source: 'input = &bytes[..], names = &namesTable[..] in { |
| 96 | let mut input = reader::new(source, &mut arena, names); |
| 97 | try checkScalarReader(&mut input); |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | /// Read valid symbol values and reject invalid or truncated wire indices. |
| 102 | fn checkSymbolReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) { |
| 103 | let name = try reader::symbol(input) catch { throw testing::TestError::Failed; }; |
| 104 | try testing::expectBytesEq(name, "data"); |
| 105 | let data = try reader::val(input) catch { throw testing::TestError::Failed; }; |
| 106 | let case il::Val::DataSym(dataName) = data else throw testing::TestError::Failed; |
| 107 | try testing::expectBytesEq(dataName, "data"); |
| 108 | let function = try reader::val(input) catch { throw testing::TestError::Failed; }; |
| 109 | let case il::Val::FnAddr(functionName) = function else throw testing::TestError::Failed; |
| 110 | try testing::expectBytesEq(functionName, "fn"); |
| 111 | let mut invalid = false; |
| 112 | try reader::symbol(input) catch err { |
| 113 | assert err == binary::Error::Symbol; |
| 114 | set invalid = true; |
| 115 | }; |
| 116 | assert invalid; |
| 117 | let mut truncated = false; |
| 118 | try reader::val(input) catch err { |
| 119 | assert err == binary::Error::Truncated; |
| 120 | set truncated = true; |
| 121 | }; |
| 122 | assert truncated; |
| 123 | } |
| 124 | |
| 125 | /// Symbol and value decoding use checked table storage from a safe function. |
| 126 | @test unsafe fn symbolReader() throws (testing::TestError) { |
| 127 | let memory = &mut MEMORY[..]; |
| 128 | let mut arena = alloc::new(memory); |
| 129 | let bytes: [u8; 18] = [0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 2, 0, 0, 0]; |
| 130 | let namesTable: [*[u8]; 2] = ["data", "fn"]; |
| 131 | let source: 'input = &bytes[..], names = &namesTable[..] in { |
| 132 | let mut input = reader::new(source, &mut arena, names); |
| 133 | try checkSymbolReader(&mut input); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | /// Check little-endian encoding for every integer width. |
| 138 | @test fn integers() throws (testing::TestError) { |
| 139 | let mut buffer: [u8; 15] = [0; 15]; |
| 140 | let namesTable: [*[u8]; 0] = []; |
| 141 | let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in { |
| 142 | let mut out = writer::new(storage, names); |
| 143 | try writer::integer(&mut out, 0x12, 1) catch { |
| 144 | throw testing::TestError::Failed; |
| 145 | }; |
| 146 | try writer::integer(&mut out, 0x3456, 2) catch { |
| 147 | throw testing::TestError::Failed; |
| 148 | }; |
| 149 | try writer::integer(&mut out, 0x789abcde, 4) catch { |
| 150 | throw testing::TestError::Failed; |
| 151 | }; |
| 152 | try writer::integer(&mut out, 0x0123456789abcdef, 8) catch { |
| 153 | throw testing::TestError::Failed; |
| 154 | }; |
| 155 | try testing::expectBytesEq(&out.bytes[..], &[0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78, 0xef, 0xcd, |
| 156 | 0xab, 0x89, 0x67, 0x45, 0x23, 0x01]); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /// Compare one instruction with its fixed wire representation. |
| 161 | unsafe fn instruction(item: il::Instr, expected: &[u8]) throws (testing::TestError) { |
| 162 | let mut buffer: [u8; 256] = [0; 256]; |
| 163 | let namesTable: [*[u8]; 2] = ["data", "fn"]; |
| 164 | let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in { |
| 165 | let mut out = writer::new(storage, names); |
| 166 | try writer::instr(&mut out, item) catch { |
| 167 | throw testing::TestError::Failed; |
| 168 | }; |
| 169 | try testing::expectBytesEq(&out.bytes[..out.offset], expected); |
| 170 | for capacity in 0..(expected.len + 1) { |
| 171 | for i in 0..out.bytes.len { |
| 172 | set out.bytes[i] = OUTPUT_CANARY; |
| 173 | } |
| 174 | let shortStorage: 'short = &mut out.bytes[..capacity], shortNames = &out.symbols[..] in { |
| 175 | let mut short = writer::new(shortStorage, shortNames); |
| 176 | let mut failed = false; |
| 177 | try writer::instr(&mut short, item) catch err { |
| 178 | try testing::expect(err == binary::Error::Capacity); |
| 179 | set failed = true; |
| 180 | }; |
| 181 | assert failed == (capacity < expected.len); |
| 182 | try testing::expect(short.offset <= capacity); |
| 183 | try testing::expectBytesEq(&short.bytes[..short.offset], &expected[..short.offset]); |
| 184 | } |
| 185 | for i in capacity..out.bytes.len { |
| 186 | assert out.bytes[i] == OUTPUT_CANARY; |
| 187 | } |
| 188 | } |
| 189 | // Inline operands use only the input bytes and symbol table. |
| 190 | let mut arenaSize: u32 = 0; |
| 191 | match item { |
| 192 | case il::Instr::Call { .. }, il::Instr::Jmp { .. }, |
| 193 | il::Instr::Br { .. }, il::Instr::Switch { .. } => set arenaSize = 512, |
| 194 | else => {} |
| 195 | } |
| 196 | let memory = &mut MEMORY[..arenaSize]; |
| 197 | let mut arena = alloc::new(&mut memory[..]); |
| 198 | let source: 'input = &expected[..], inputNames = &namesTable[..] in { |
| 199 | let mut input = reader::new(source, &mut arena, inputNames); |
| 200 | set input.registers = 16; |
| 201 | set input.blocks = 4; |
| 202 | let decoded = try reader::instr(&mut input) catch { |
| 203 | throw testing::TestError::Failed; |
| 204 | }; |
| 205 | try testing::expect(input.offset == expected.len); |
| 206 | set out.offset = 0; |
| 207 | try writer::instr(&mut out, decoded) catch { |
| 208 | throw testing::TestError::Failed; |
| 209 | }; |
| 210 | try testing::expectBytesEq(&out.bytes[..out.offset], expected); |
| 211 | for length in 0..expected.len { |
| 212 | alloc::reset(&mut arena); |
| 213 | set input = reader::new(&source[..length], &mut arena, inputNames); |
| 214 | set input.registers = 16; |
| 215 | set input.blocks = 4; |
| 216 | let mut failed = false; |
| 217 | try reader::instr(&mut input) catch err { |
| 218 | try testing::expect(err == binary::Error::Truncated); |
| 219 | set failed = true; |
| 220 | }; |
| 221 | try testing::expect(failed); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | /// Malformed inline operands retain bounded cursors and empty arena storage. |
| 229 | @test unsafe fn invalidFixedInstructions() throws (testing::TestError) { |
| 230 | let packets: [*[u8]; 6] = [ |
| 231 | &[255], |
| 232 | &[6, 255], |
| 233 | &[1, 3], |
| 234 | &[5, 16, 0, 0, 0, 4], |
| 235 | &[11, 2], |
| 236 | &[5, 0, 0, 0, 0, 2, 0, 0, 0, 0], |
| 237 | ]; |
| 238 | let namesTable: [*[u8]; 0] = []; |
| 239 | for packet, index in packets { |
| 240 | let mut arena = alloc::new(&mut MEMORY[..0]); |
| 241 | let source: 'input = &packet[..], names = &namesTable[..] in { |
| 242 | let mut input = reader::new(source, &mut arena, names); |
| 243 | set input.registers = 16; |
| 244 | let mut failed = false; |
| 245 | try reader::instr(&mut input) catch err { |
| 246 | let expected = binary::Error::Symbol if index == 5 else binary::Error::Invalid; |
| 247 | assert err == expected; |
| 248 | set failed = true; |
| 249 | }; |
| 250 | assert failed; |
| 251 | assert input.offset <= source.len; |
| 252 | assert arena.offset == 0; |
| 253 | } |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | /// Check all instruction tags and their field order. |
| 258 | @test unsafe fn instructions() throws (testing::TestError) { |
| 259 | let mut args: [il::Val; 1] = [il::Val::Undef]; |
| 260 | let mut cases: [il::SwitchCase; 1] = [il::SwitchCase { |
| 261 | value: -1, target: 2, args: &mut args[..], |
| 262 | }]; |
| 263 | try instruction(il::Instr::Reserve { |
| 264 | dst: il::Reg { n: 1 }, size: il::Val::Undef, alignment: 16, |
| 265 | }, &[0, 1, 0, 0, 0, 4, 16, 0, 0, 0]); |
| 266 | try instruction(il::Instr::Load { |
| 267 | typ: il::Type::W8, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: -1, |
| 268 | }, &[1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 255]); |
| 269 | try instruction(il::Instr::Sload { |
| 270 | typ: il::Type::W16, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: 3, |
| 271 | }, &[2, 2, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); |
| 272 | try instruction(il::Instr::Store { |
| 273 | typ: il::Type::W32, src: il::Val::Undef, dst: il::Reg { n: 2 }, offset: 3, |
| 274 | }, &[3, 4, 4, 2, 0, 0, 0, 3, 0, 0, 0]); |
| 275 | try instruction(il::Instr::Blit { |
| 276 | dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, size: il::Val::Undef, |
| 277 | }, &[4, 1, 0, 0, 0, 2, 0, 0, 0, 4]); |
| 278 | try instruction(il::Instr::Copy { |
| 279 | dst: il::Reg { n: 1 }, val: il::Val::Undef, |
| 280 | }, &[5, 1, 0, 0, 0, 4]); |
| 281 | try instruction(il::Instr::BinOp { |
| 282 | op: il::BinOp::Add, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef, |
| 283 | b: il::Val::Undef, |
| 284 | }, &[6, 0, 8, 1, 0, 0, 0, 4, 4]); |
| 285 | try instruction(il::Instr::UnOp { |
| 286 | op: il::UnOp::Neg, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef, |
| 287 | }, &[7, 0, 8, 1, 0, 0, 0, 4]); |
| 288 | try instruction(il::Instr::Zext { |
| 289 | typ: il::Type::W8, dst: il::Reg { n: 1 }, val: il::Val::Undef, |
| 290 | }, &[8, 1, 1, 0, 0, 0, 4]); |
| 291 | try instruction(il::Instr::Sext { |
| 292 | typ: il::Type::W16, dst: il::Reg { n: 1 }, val: il::Val::Undef, |
| 293 | }, &[9, 2, 1, 0, 0, 0, 4]); |
| 294 | try instruction(il::Instr::Call { |
| 295 | retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("fn"), args: &[], |
| 296 | }, &[10, 8, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0]); |
| 297 | try instruction(il::Instr::Call { |
| 298 | retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("fn"), args: &args[..], |
| 299 | }, &[10, 8, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 0, 0, 4]); |
| 300 | try instruction(il::Instr::Ret { |
| 301 | val: nil, |
| 302 | }, &[11, 0]); |
| 303 | try instruction(il::Instr::Ret { |
| 304 | val: il::Val::Undef, |
| 305 | }, &[11, 1, 4]); |
| 306 | try instruction(il::Instr::Jmp { |
| 307 | target: 2, args: &mut args[..], |
| 308 | }, &[12, 2, 0, 0, 0, 1, 0, 0, 0, 4]); |
| 309 | try instruction(il::Instr::Br { |
| 310 | op: il::CmpOp::Eq, typ: il::Type::W32, a: il::Val::Undef, b: il::Val::Undef, thenTarget: 1, |
| 311 | thenArgs: &mut args[..], elseTarget: 2, elseArgs: &mut args[..], |
| 312 | }, &[13, 0, 4, 4, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 0, 0, 1, 0, 0, 0, 4]); |
| 313 | try instruction(il::Instr::Switch { |
| 314 | val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut args[..], cases: &mut cases[..], |
| 315 | }, &[ |
| 316 | 14, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 1, |
| 317 | 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 2, |
| 318 | 0, 0, 0, 1, 0, 0, 0, 4, |
| 319 | ]); |
| 320 | try instruction(il::Instr::Switch { |
| 321 | val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut [], cases: &mut [], |
| 322 | }, &[14, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); |
| 323 | let mut mixedCases = [ |
| 324 | il::SwitchCase { value: -9223372036854775808, target: 0, args: &mut [] }, |
| 325 | il::SwitchCase { value: 9223372036854775807, target: 2, args: &mut args[..] }, |
| 326 | ]; |
| 327 | try instruction(il::Instr::Switch { |
| 328 | val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut [], cases: &mut mixedCases[..], |
| 329 | }, &[ |
| 330 | 14, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, |
| 331 | 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, |
| 332 | 255, 255, 255, 255, 255, 255, 255, 127, 2, 0, 0, 0, 1, 0, 0, 0, 4, |
| 333 | ]); |
| 334 | try instruction(il::Instr::Unreachable, &[15]); |
| 335 | try instruction(il::Instr::Ecall { |
| 336 | dst: il::Reg { n: 1 }, num: il::Val::Undef, a0: il::Val::Undef, a1: il::Val::Undef, |
| 337 | a2: il::Val::Undef, a3: il::Val::Undef, |
| 338 | }, &[16, 1, 0, 0, 0, 4, 4, 4, 4, 4]); |
| 339 | try instruction(il::Instr::Ebreak, &[17]); |
| 340 | try instruction(il::Instr::MemoryFence, &[18]); |
| 341 | for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] { |
| 342 | let mut read: [u8; 8] = [19, 0, 1, 0, 0, 0, 4, 4]; |
| 343 | let mut write: [u8; 5] = [20, 0, 4, 4, 4]; |
| 344 | set read[1] = il::typeSize(typ) as u8; |
| 345 | set write[1] = il::typeSize(typ) as u8; |
| 346 | try instruction(il::Instr::DeviceRead { |
| 347 | typ, dst: il::Reg { n: 1 }, handle: il::Val::Undef, offset: il::Val::Undef, |
| 348 | }, &read[..]); |
| 349 | try instruction(il::Instr::DeviceWrite { |
| 350 | typ, handle: il::Val::Undef, offset: il::Val::Undef, value: il::Val::Undef, |
| 351 | }, &write[..]); |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | /// Switch decoding stops at an invalid target or argument in either case. |
| 356 | @test unsafe fn invalidSwitchCases() throws (testing::TestError) { |
| 357 | let namesTable: [*[u8]; 0] = []; |
| 358 | let mut encoded: [u8; 48] = [0; 48]; |
| 359 | set encoded[0] = binary::INSTR_SWITCH; |
| 360 | set encoded[1] = 4; |
| 361 | set encoded[10] = 2; |
| 362 | set encoded[26] = 1; |
| 363 | set encoded[30] = 4; |
| 364 | set encoded[43] = 1; |
| 365 | set encoded[47] = 4; |
| 366 | for offset in [22, 39, 30, 47] { |
| 367 | let original = encoded[offset]; |
| 368 | set encoded[offset] = 255; |
| 369 | let mut arena = alloc::new(&mut MEMORY[..]); |
| 370 | let source: 'input = &encoded[..], names = &namesTable[..] in { |
| 371 | let mut input = reader::new(source, &mut arena, names); |
| 372 | set input.blocks = 1; |
| 373 | let mut failed = false; |
| 374 | try reader::instr(&mut input) catch err { |
| 375 | assert err == binary::Error::Invalid; |
| 376 | set failed = true; |
| 377 | }; |
| 378 | assert failed; |
| 379 | assert input.offset == offset + (4 if offset == 22 or offset == 39 else 1); |
| 380 | } |
| 381 | set encoded[offset] = original; |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | /// Byte decoding retains exact allocation and cursor state on storage failure. |
| 386 | @test unsafe fn byteStorage() throws (testing::TestError) { |
| 387 | let encoded: [u8; 11] = [0, 0, 0, 0, 3, 0, 0, 0, 65, 0, 255]; |
| 388 | let namesTable: [*[u8]; 0] = []; |
| 389 | for capacity in 0..4 { |
| 390 | let mut arena = alloc::new(&mut MEMORY[..capacity]); |
| 391 | let source: 'input = &encoded[..], names = &namesTable[..] in { |
| 392 | let mut input = reader::new(source, &mut arena, names); |
| 393 | let empty = try! reader::bytes(&mut input); |
| 394 | assert empty.len == 0; |
| 395 | assert input.offset == 4; |
| 396 | let mut failed = false; |
| 397 | let result = try reader::bytes(&mut input) catch err { |
| 398 | assert err == binary::Error::Storage; |
| 399 | set failed = true; |
| 400 | &[] as *[u8] |
| 401 | }; |
| 402 | assert failed == (capacity < 3); |
| 403 | assert input.offset == (8 if failed else 11); |
| 404 | if not failed { try testing::expectBytesEq(result, &[65, 0, 255]); } |
| 405 | } |
| 406 | assert arena.offset == (3 if capacity == 3 else 0); |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | /// Exact operand tables use one allocation and publish only complete prefixes. |
| 411 | @test unsafe fn exactValueStorage() throws (testing::TestError) { |
| 412 | let namesTable: [*[u8]; 0] = []; |
| 413 | let alignment = @alignOf(il::Val); |
| 414 | let remainder = ((&MEMORY[0]) as u64) & (alignment as u64 - 1); |
| 415 | let padding = ((alignment as u64 - remainder) & (alignment as u64 - 1)) as u32; |
| 416 | for count in 0..4 { |
| 417 | let required = 0 if count == 0 else padding + count * @sizeOf(il::Val); |
| 418 | let mut encoded: [u8; 7] = [0, 0, 0, 0, 4, 4, 4]; |
| 419 | set encoded[0] = count as u8; |
| 420 | for capacity in 0..required + 1 { |
| 421 | let mut arena = alloc::new(&mut MEMORY[..capacity]); |
| 422 | let source: 'input = &encoded[..4 + count], names = &namesTable[..] in { |
| 423 | let mut input = reader::new(source, &mut arena, names); |
| 424 | let mut failed = false; |
| 425 | let result = try reader::values(&mut input) catch error { |
| 426 | assert error == binary::Error::Storage; |
| 427 | set failed = true; |
| 428 | &mut [] as *unsafe mut [il::Val] |
| 429 | }; |
| 430 | assert failed == (capacity < required); |
| 431 | assert input.offset == (4 if failed else 4 + count); |
| 432 | if not failed { |
| 433 | assert result.len == count; |
| 434 | for value in result { assert value == il::Val::Undef; } |
| 435 | } |
| 436 | } |
| 437 | assert arena.offset == (0 if capacity < required else required); |
| 438 | } |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | /// Invalid values stop counted decoding at the failing tag or register index. |
| 443 | @test unsafe fn invalidValueSequences() throws (testing::TestError) { |
| 444 | let namesTable: [*[u8]; 0] = []; |
| 445 | for position in 0..2 { |
| 446 | for invalidTag in [false, true] { |
| 447 | let mut encoded: [u8; 14] = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; |
| 448 | if invalidTag { |
| 449 | set encoded[4 + position * 5] = 255; |
| 450 | } else { |
| 451 | set encoded[5 + position * 5] = 1; |
| 452 | } |
| 453 | let mut arena = alloc::new(&mut MEMORY[..128]); |
| 454 | let source: 'input = &encoded[..], names = &namesTable[..] in { |
| 455 | let mut input = reader::new(source, &mut arena, names); |
| 456 | set input.registers = 1; |
| 457 | let mut failed = false; |
| 458 | try reader::values(&mut input) catch err { |
| 459 | assert err == binary::Error::Invalid; |
| 460 | set failed = true; |
| 461 | }; |
| 462 | assert failed; |
| 463 | assert input.offset == position * 5 + (5 if invalidTag else 9); |
| 464 | } |
| 465 | } |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | /// Check every value tag and empty and nonempty sequences. |
| 470 | @test unsafe fn values() throws (testing::TestError) { |
| 471 | let mut buffer: [u8; 64] = [0; 64]; |
| 472 | let namesTable: [*[u8]; 2] = ["data", "fn"]; |
| 473 | let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in { |
| 474 | let mut out = writer::new(storage, names); |
| 475 | try writer::values(&mut out, &[]) catch { |
| 476 | throw testing::TestError::Failed; |
| 477 | }; |
| 478 | try writer::values(&mut out, &[ |
| 479 | il::Val::Reg(il::Reg { n: 0x12345678 }), |
| 480 | il::Val::Imm(-2), il::Val::DataSym("data"), |
| 481 | il::Val::FnAddr("fn"), il::Val::Undef, |
| 482 | ]) catch { |
| 483 | throw testing::TestError::Failed; |
| 484 | }; |
| 485 | try testing::expectBytesEq(&out.bytes[..out.offset], &[ |
| 486 | 0, 0, 0, 0, 5, 0, 0, 0, |
| 487 | 0, 0x78, 0x56, 0x34, 0x12, |
| 488 | 1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 489 | 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4, |
| 490 | ]); |
| 491 | let memory = &mut MEMORY[..256]; |
| 492 | let mut arena = alloc::new(&mut memory[..]); |
| 493 | let mut input = reader::new(&out.bytes[..out.offset], &mut arena, &out.symbols[..]); |
| 494 | set input.registers = 0x12345679; |
| 495 | let empty = try reader::values(&mut input) catch { |
| 496 | throw testing::TestError::Failed; |
| 497 | }; |
| 498 | let decoded = try reader::values(&mut input) catch { |
| 499 | throw testing::TestError::Failed; |
| 500 | }; |
| 501 | try testing::expect(empty.len == 0); |
| 502 | try testing::expect(decoded.len == 5); |
| 503 | try testing::expect(input.offset == out.offset); |
| 504 | let mut repeated: [u8; 64] = [0; 64]; |
| 505 | let copyNamesTable: [*[u8]; 2] = ["data", "fn"]; |
| 506 | let copyStorage: 'copy = &mut repeated[..], copyNames = ©NamesTable[..] in { |
| 507 | let mut copy = writer::new(copyStorage, copyNames); |
| 508 | try writer::values(&mut copy, empty) catch { |
| 509 | throw testing::TestError::Failed; |
| 510 | }; |
| 511 | try writer::values(&mut copy, decoded) catch { |
| 512 | throw testing::TestError::Failed; |
| 513 | }; |
| 514 | try testing::expectBytesEq(&out.bytes[..out.offset], ©.bytes[..copy.offset]); |
| 515 | |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /// Check initializer bytes and repetition counts. |
| 521 | @test fn initializers() throws (testing::TestError) { |
| 522 | let mut buffer: [u8; 128] = [0; 128]; |
| 523 | let namesTable: [*[u8]; 2] = ["data", "fn"]; |
| 524 | let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in { |
| 525 | let mut out = writer::new(storage, names); |
| 526 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, |
| 527 | val: -1 }, count: 0 }) catch { |
| 528 | throw testing::TestError::Failed; |
| 529 | }; |
| 530 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, |
| 531 | val: -2 }, count: 1 }) catch { |
| 532 | throw testing::TestError::Failed; |
| 533 | }; |
| 534 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, |
| 535 | val: -3 }, count: 2 }) catch { |
| 536 | throw testing::TestError::Failed; |
| 537 | }; |
| 538 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, |
| 539 | val: -4 }, count: 3 }) catch { |
| 540 | throw testing::TestError::Failed; |
| 541 | }; |
| 542 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Sym("data"), count: 4 }) |
| 543 | catch { |
| 544 | throw testing::TestError::Failed; |
| 545 | }; |
| 546 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Fn("fn"), count: 5 }) catch |
| 547 | { |
| 548 | throw testing::TestError::Failed; |
| 549 | }; |
| 550 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str("ab"), count: 6 }) catch |
| 551 | { |
| 552 | throw testing::TestError::Failed; |
| 553 | }; |
| 554 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str(""), count: 7 }) catch { |
| 555 | throw testing::TestError::Failed; }; |
| 556 | try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Undef, count: 8 }) catch { |
| 557 | throw testing::TestError::Failed; }; |
| 558 | try testing::expectBytesEq(&out.bytes[..out.offset], &[0, 1, 255, 0, 0, 0, 0, 0, 2, 254, 255, 1, 0, |
| 559 | 0, 0, 0, 4, 253, 255, 255, 255, 2, 0, 0, 0, 0, 8, 252, 255, 255, 255, 255, 255, 255, 255, 3, |
| 560 | 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, 0, 0, 0, 5, 0, 0, 0, 3, 2, 0, 0, 0, 97, 98, 6, 0, |
| 561 | 0, 0, 3, 0, 0, 0, 0, 7, 0, 0, 0, 4, 8, 0, 0, 0]); |
| 562 | } |
| 563 | } |
| 564 | |
| 565 | /// Reject invalid widths, insufficient storage, and absent symbols. |
| 566 | @test fn errors() throws (testing::TestError) { |
| 567 | let mut buffer: [u8; 8] = [0; 8]; |
| 568 | let namesTable: [*[u8]; 0] = []; |
| 569 | let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in { |
| 570 | let mut out = writer::new(storage, names); |
| 571 | let mut failures: u32 = 0; |
| 572 | try writer::integer(&mut out, 1, 3) catch err { |
| 573 | try testing::expect(err == binary::Error::Invalid); |
| 574 | set failures += 1; |
| 575 | }; |
| 576 | try writer::symbol(&mut out, "absent") catch err { |
| 577 | try testing::expect(err == binary::Error::Symbol); |
| 578 | set failures += 1; |
| 579 | }; |
| 580 | try testing::expect(out.offset == 0); |
| 581 | try writer::integer(&mut out, 0, 8) catch { |
| 582 | throw testing::TestError::Failed; |
| 583 | }; |
| 584 | try writer::integer(&mut out, 1, 1) catch err { |
| 585 | try testing::expect(err == binary::Error::Capacity); |
| 586 | set failures += 1; |
| 587 | }; |
| 588 | try testing::expect(failures == 3); |
| 589 | try testing::expect(out.offset == 8); |
| 590 | } |
| 591 | } |
| 592 | |
| 593 | /// Check operation tags independently of the native union representation. |
| 594 | @test unsafe fn operations() throws (testing::TestError) { |
| 595 | let binaryOps = &[ |
| 596 | il::BinOp::Add, il::BinOp::Sub, il::BinOp::Mul, |
| 597 | il::BinOp::Sdiv, il::BinOp::Udiv, il::BinOp::Srem, |
| 598 | il::BinOp::Urem, il::BinOp::Eq, il::BinOp::Ne, |
| 599 | il::BinOp::Slt, il::BinOp::Sge, il::BinOp::Ult, |
| 600 | il::BinOp::Uge, il::BinOp::And, il::BinOp::Or, |
| 601 | il::BinOp::Xor, il::BinOp::Shl, il::BinOp::Sshr, |
| 602 | il::BinOp::Ushr, |
| 603 | ]; |
| 604 | for op, tag in binaryOps { |
| 605 | try instruction(il::Instr::BinOp { |
| 606 | op, typ: il::Type::W64, dst: il::Reg { n: 1 }, |
| 607 | a: il::Val::Undef, b: il::Val::Undef, |
| 608 | }, &[6, tag as u8, 8, 1, 0, 0, 0, 4, 4]); |
| 609 | } |
| 610 | for op, tag in &[il::UnOp::Neg, il::UnOp::Not] { |
| 611 | try instruction(il::Instr::UnOp { |
| 612 | op, typ: il::Type::W64, dst: il::Reg { n: 1 }, |
| 613 | a: il::Val::Undef, |
| 614 | }, &[7, tag as u8, 8, 1, 0, 0, 0, 4]); |
| 615 | } |
| 616 | let mut empty: [il::Val; 0] = []; |
| 617 | for op, tag in &[il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] { |
| 618 | try instruction(il::Instr::Br { |
| 619 | op, typ: il::Type::W8, a: il::Val::Undef, b: il::Val::Undef, |
| 620 | thenTarget: 1, thenArgs: &mut empty[..], |
| 621 | elseTarget: 2, elseArgs: &mut empty[..], |
| 622 | }, &[13, tag as u8, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]); |
| 623 | } |
| 624 | } |
| 625 | |
| 626 | /// Check byte-sequence bounds at every output size. |
| 627 | @test fn byteCapacity() throws (testing::TestError) { |
| 628 | let mut buffer: [u8; 7] = [0; 7]; |
| 629 | for capacity in 0..7 { |
| 630 | let namesTable: [*[u8]; 0] = []; |
| 631 | let storage: 'short = &mut buffer[..capacity], names = &namesTable[..] in { |
| 632 | let mut out = writer::new(storage, names); |
| 633 | let mut failed = false; |
| 634 | try writer::bytes(&mut out, "abc") catch err { |
| 635 | try testing::expect(err == binary::Error::Capacity); |
| 636 | set failed = true; |
| 637 | }; |
| 638 | try testing::expect(failed); |
| 639 | try testing::expect(out.offset <= capacity); |
| 640 | } |
| 641 | } |
| 642 | let namesTable: [*[u8]; 0] = []; |
| 643 | let storage: 'buffer = &mut buffer[..], names = &namesTable[..] in { |
| 644 | let mut out = writer::new(storage, names); |
| 645 | try writer::bytes(&mut out, "abc") catch { |
| 646 | throw testing::TestError::Failed; |
| 647 | }; |
| 648 | try testing::expectBytesEq(&out.bytes[..], &[3, 0, 0, 0, 97, 98, 99]); |
| 649 | } |
| 650 | } |