compiler: Decode inline instruction operands in checked code
1f3e014e2e782eb35876b9a3d30c129ef1167f23ce2d54f77253e3e4e5746edd
1 parent
2f90db37
lib/std/lang/il/binary/reader.rad
+47 -40
| 193 | 193 | case super::CMP_ULT => return il::CmpOp::Ult, |
|
| 194 | 194 | else => throw binary::Error::Invalid, |
|
| 195 | 195 | } |
|
| 196 | 196 | } |
|
| 197 | 197 | ||
| 198 | - | /// Read one instruction and reconstruct its operand sequences. |
|
| 199 | - | export unsafe fn instr 'input (input: &mut Reader 'input) -> il::Instr throws (binary::Error) { |
|
| 200 | - | let tag = try integer(input, 1) as u8; |
|
| 198 | + | /// Read an instruction with inline operands. |
|
| 199 | + | fn fixedInstr 'input (input: &mut Reader 'input, tag: u8) -> il::Instr throws (binary::Error) { |
|
| 201 | 200 | match tag { |
|
| 202 | 201 | case super::INSTR_RESERVE => { |
|
| 203 | 202 | let vdst = try reg(input); |
|
| 204 | 203 | let vsize = try val(input); |
|
| 205 | 204 | let valignment = try integer(input, 4) as u32; |
| 255 | 254 | if tag == super::INSTR_SEXT { |
|
| 256 | 255 | return il::Instr::Sext { typ: vtyp, dst: vdst, val: vval }; |
|
| 257 | 256 | } |
|
| 258 | 257 | return il::Instr::Zext { typ: vtyp, dst: vdst, val: vval }; |
|
| 259 | 258 | }, |
|
| 259 | + | case super::INSTR_RET => { |
|
| 260 | + | let mut vval: ?il::Val = nil; |
|
| 261 | + | if try flag(input) { |
|
| 262 | + | set vval = try val(input); |
|
| 263 | + | } |
|
| 264 | + | return il::Instr::Ret { val: vval }; |
|
| 265 | + | }, |
|
| 266 | + | case super::INSTR_UNREACHABLE => { |
|
| 267 | + | return il::Instr::Unreachable; |
|
| 268 | + | }, |
|
| 269 | + | case super::INSTR_ECALL => { |
|
| 270 | + | let vdst = try reg(input); |
|
| 271 | + | let vnum = try val(input); |
|
| 272 | + | let va0 = try val(input); |
|
| 273 | + | let va1 = try val(input); |
|
| 274 | + | let va2 = try val(input); |
|
| 275 | + | let va3 = try val(input); |
|
| 276 | + | return il::Instr::Ecall { dst: vdst, num: vnum, a0: va0, a1: va1, a2: va2, a3: va3 }; |
|
| 277 | + | }, |
|
| 278 | + | case super::INSTR_DEVICE_READ => { |
|
| 279 | + | let t = try typ(input); |
|
| 280 | + | let dst = try reg(input); |
|
| 281 | + | let handle = try val(input); let offset = try val(input); |
|
| 282 | + | return il::Instr::DeviceRead { typ: t, dst, handle, offset }; |
|
| 283 | + | }, |
|
| 284 | + | case super::INSTR_DEVICE_WRITE => { |
|
| 285 | + | let t = try typ(input); |
|
| 286 | + | let handle = try val(input); let offset = try val(input); let value = try val(input); |
|
| 287 | + | return il::Instr::DeviceWrite { typ: t, handle, offset, value }; |
|
| 288 | + | }, |
|
| 289 | + | case super::INSTR_EBREAK => { |
|
| 290 | + | return il::Instr::Ebreak; |
|
| 291 | + | }, |
|
| 292 | + | case super::INSTR_MEMORYFENCE => { |
|
| 293 | + | return il::Instr::MemoryFence; |
|
| 294 | + | }, |
|
| 295 | + | else => throw binary::Error::Invalid, |
|
| 296 | + | } |
|
| 297 | + | } |
|
| 298 | + | ||
| 299 | + | /// Read one instruction and reconstruct its operand sequences. |
|
| 300 | + | export unsafe fn instr 'input (input: &mut Reader 'input) -> il::Instr throws (binary::Error) { |
|
| 301 | + | let tag = try integer(input, 1) as u8; |
|
| 302 | + | match tag { |
|
| 260 | 303 | case super::INSTR_CALL => { |
|
| 261 | 304 | let vretTy = try typ(input); |
|
| 262 | 305 | let mut vdst: ?il::Reg = nil; |
|
| 263 | 306 | if try flag(input) { |
|
| 264 | 307 | set vdst = try reg(input); |
|
| 265 | 308 | } |
|
| 266 | 309 | let vfunc = try val(input); |
|
| 267 | 310 | let vargs = try values(input); |
|
| 268 | 311 | return il::Instr::Call { retTy: vretTy, dst: vdst, func: vfunc, args: vargs }; |
|
| 269 | 312 | }, |
|
| 270 | - | case super::INSTR_RET => { |
|
| 271 | - | let mut vval: ?il::Val = nil; |
|
| 272 | - | if try flag(input) { |
|
| 273 | - | set vval = try val(input); |
|
| 274 | - | } |
|
| 275 | - | return il::Instr::Ret { val: vval }; |
|
| 276 | - | }, |
|
| 277 | 313 | case super::INSTR_JMP => { |
|
| 278 | 314 | let vtarget = try target(input); |
|
| 279 | 315 | let vargs = try values(input); |
|
| 280 | 316 | return il::Instr::Jmp { target: vtarget, args: vargs }; |
|
| 281 | 317 | }, |
| 303 | 339 | let args = try values(input); |
|
| 304 | 340 | set vcases[i] = il::SwitchCase { value, target: block, args }; |
|
| 305 | 341 | } |
|
| 306 | 342 | return il::Instr::Switch { val: vval, defaultTarget: vdefaultTarget, defaultArgs: vdefaultArgs, cases: (&mut vcases[..]) as *unsafe mut [il::SwitchCase] }; |
|
| 307 | 343 | }, |
|
| 308 | - | case super::INSTR_UNREACHABLE => { |
|
| 309 | - | return il::Instr::Unreachable; |
|
| 310 | - | }, |
|
| 311 | - | case super::INSTR_ECALL => { |
|
| 312 | - | let vdst = try reg(input); |
|
| 313 | - | let vnum = try val(input); |
|
| 314 | - | let va0 = try val(input); |
|
| 315 | - | let va1 = try val(input); |
|
| 316 | - | let va2 = try val(input); |
|
| 317 | - | let va3 = try val(input); |
|
| 318 | - | return il::Instr::Ecall { dst: vdst, num: vnum, a0: va0, a1: va1, a2: va2, a3: va3 }; |
|
| 319 | - | }, |
|
| 320 | - | case super::INSTR_DEVICE_READ => { |
|
| 321 | - | let t = try typ(input); |
|
| 322 | - | let dst = try reg(input); |
|
| 323 | - | let handle = try val(input); let offset = try val(input); |
|
| 324 | - | return il::Instr::DeviceRead { typ: t, dst, handle, offset }; |
|
| 325 | - | }, |
|
| 326 | - | case super::INSTR_DEVICE_WRITE => { |
|
| 327 | - | let t = try typ(input); |
|
| 328 | - | let handle = try val(input); let offset = try val(input); let value = try val(input); |
|
| 329 | - | return il::Instr::DeviceWrite { typ: t, handle, offset, value }; |
|
| 330 | - | }, |
|
| 331 | - | case super::INSTR_EBREAK => { |
|
| 332 | - | return il::Instr::Ebreak; |
|
| 333 | - | }, |
|
| 334 | - | case super::INSTR_MEMORYFENCE => { |
|
| 335 | - | return il::Instr::MemoryFence; |
|
| 336 | - | }, |
|
| 337 | - | else => throw binary::Error::Invalid, |
|
| 344 | + | else => return try fixedInstr(input, tag), |
|
| 338 | 345 | } |
|
| 339 | 346 | } |
|
| 340 | 347 | ||
| 341 | 348 | /// Read an initializer with a repetition count. |
|
| 342 | 349 | export unsafe fn dataValue 'input (input: &mut Reader 'input) -> il::DataValue throws (binary::Error) { |
lib/std/lang/il/binary/tests.rad
+37 -1
| 129 | 129 | }; |
|
| 130 | 130 | try testing::expect(failed); |
|
| 131 | 131 | try testing::expect(short.offset <= capacity); |
|
| 132 | 132 | } |
|
| 133 | 133 | } |
|
| 134 | - | let memory = &mut MEMORY[..512]; |
|
| 134 | + | // Inline operands use only the input bytes and symbol table. |
|
| 135 | + | let mut arenaSize: u32 = 0; |
|
| 136 | + | match item { |
|
| 137 | + | case il::Instr::Call { .. }, il::Instr::Jmp { .. }, |
|
| 138 | + | il::Instr::Br { .. }, il::Instr::Switch { .. } => set arenaSize = 512, |
|
| 139 | + | else => {} |
|
| 140 | + | } |
|
| 141 | + | let memory = &mut MEMORY[..arenaSize]; |
|
| 135 | 142 | let mut arena = alloc::new(&mut memory[..]); |
|
| 136 | 143 | let source: 'input = &expected[..], inputNames = &namesTable[..] in { |
|
| 137 | 144 | let mut input = reader::new(source, &mut arena, inputNames); |
|
| 138 | 145 | set input.registers = 16; |
|
| 139 | 146 | set input.blocks = 4; |
| 161 | 168 | } |
|
| 162 | 169 | ||
| 163 | 170 | } |
|
| 164 | 171 | } |
|
| 165 | 172 | ||
| 173 | + | /// Malformed inline operands retain bounded cursors and empty arena storage. |
|
| 174 | + | @test unsafe fn invalidFixedInstructions() throws (testing::TestError) { |
|
| 175 | + | let packets: [*[u8]; 6] = [ |
|
| 176 | + | &[255], |
|
| 177 | + | &[6, 255], |
|
| 178 | + | &[1, 3], |
|
| 179 | + | &[5, 16, 0, 0, 0, 4], |
|
| 180 | + | &[11, 2], |
|
| 181 | + | &[5, 0, 0, 0, 0, 2, 0, 0, 0, 0], |
|
| 182 | + | ]; |
|
| 183 | + | let namesTable: [*[u8]; 0] = []; |
|
| 184 | + | for packet, index in packets { |
|
| 185 | + | let mut arena = alloc::new(&mut MEMORY[..0]); |
|
| 186 | + | let source: 'input = &packet[..], names = &namesTable[..] in { |
|
| 187 | + | let mut input = reader::new(source, &mut arena, names); |
|
| 188 | + | set input.registers = 16; |
|
| 189 | + | let mut failed = false; |
|
| 190 | + | try reader::instr(&mut input) catch err { |
|
| 191 | + | let expected = binary::Error::Symbol if index == 5 else binary::Error::Invalid; |
|
| 192 | + | assert err == expected; |
|
| 193 | + | set failed = true; |
|
| 194 | + | }; |
|
| 195 | + | assert failed; |
|
| 196 | + | assert input.offset <= source.len; |
|
| 197 | + | assert arena.offset == 0; |
|
| 198 | + | } |
|
| 199 | + | } |
|
| 200 | + | } |
|
| 201 | + | ||
| 166 | 202 | /// Check all instruction tags and their field order. |
|
| 167 | 203 | @test unsafe fn instructions() throws (testing::TestError) { |
|
| 168 | 204 | let mut args: [il::Val; 1] = [il::Val::Undef]; |
|
| 169 | 205 | let mut cases: [il::SwitchCase; 1] = [il::SwitchCase { |
|
| 170 | 206 | value: -1, target: 2, args: &mut args[..], |