asm: Make parser functions safe
719e243ef2dbbde2acd6b1645bc77e1e9970e55dccf866d22e065b53fe2a402c
Assembler parsing propagated unsafe through every label consumer because quoted labels allocate temporary decode storage. Isolate that allocation in one unsafe block so the parser API and its call graph remain safe. Assisted-by: Codex:gpt-5.6-sol
1 parent
2d2cb3d6
lib/std/arch/rv64/asm/parser.rad
+21 -18
| 20 | 20 | /// Signed byte offset preceding the base register. |
|
| 21 | 21 | offset: i32, |
|
| 22 | 22 | } |
|
| 23 | 23 | ||
| 24 | 24 | /// Parse assembler source into the supplied assembler state. |
|
| 25 | - | export unsafe fn parseProgram 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 25 | + | export fn parseProgram 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 26 | 26 | advance(a); |
|
| 27 | 27 | ||
| 28 | 28 | while a.scan.current.kind <> scanner::TokenKind::Eof { |
|
| 29 | 29 | try parseItem(a); |
|
| 30 | 30 | } |
| 153 | 153 | fn isLabel(tok: scanner::TokenKind) -> bool { |
|
| 154 | 154 | return tok == scanner::TokenKind::Label or tok == scanner::TokenKind::QuotedLabel; |
|
| 155 | 155 | } |
|
| 156 | 156 | ||
| 157 | 157 | /// Parse the contents of a quoted label token, decoding escapes as needed. |
|
| 158 | - | unsafe fn parseQuotedLabelName 'parse (a: &mut super::Assembler 'parse) -> *[u8] throws (super::Error) { |
|
| 158 | + | fn parseQuotedLabelName 'parse (a: &mut super::Assembler 'parse) -> *[u8] throws (super::Error) { |
|
| 159 | 159 | let tok = try expectToken(a, scanner::TokenKind::QuotedLabel, "expected label name"); |
|
| 160 | 160 | let rawStart = super::LABEL_SIGIL_LEN + super::QUOTE_DELIM_LEN; |
|
| 161 | 161 | let raw = &tok.source[rawStart..tok.source.len - super::QUOTE_DELIM_LEN]; |
|
| 162 | - | let storage = try alloc::allocSlice(a.arena, 1, 1, raw.len) catch { |
|
| 163 | - | panic "asm: out of memory allocating quoted label"; |
|
| 164 | - | } as *mut [u8]; |
|
| 162 | + | let mut storage: *mut [u8] = &mut []; |
|
| 163 | + | unsafe { |
|
| 164 | + | set storage = try alloc::allocSlice(a.arena, 1, 1, raw.len) catch { |
|
| 165 | + | panic "asm: out of memory allocating quoted label"; |
|
| 166 | + | } as *mut [u8]; |
|
| 167 | + | } |
|
| 165 | 168 | let len = fmt::unescapeString(raw, storage); |
|
| 166 | 169 | ||
| 167 | 170 | return strings::intern(a.pool, &storage[..len]); |
|
| 168 | 171 | } |
|
| 169 | 172 | ||
| 170 | 173 | /// Parse a label reference or definition name. |
|
| 171 | - | unsafe fn parseLabelName 'parse (a: &mut super::Assembler 'parse) -> *[u8] throws (super::Error) { |
|
| 174 | + | fn parseLabelName 'parse (a: &mut super::Assembler 'parse) -> *[u8] throws (super::Error) { |
|
| 172 | 175 | if a.scan.current.kind == scanner::TokenKind::QuotedLabel { |
|
| 173 | 176 | return try parseQuotedLabelName(a); |
|
| 174 | 177 | } |
|
| 175 | 178 | return try parseScopedName(a, scanner::TokenKind::Label, "expected label name", super::LABEL_SIGIL_LEN); |
|
| 176 | 179 | } |
| 180 | 183 | let name = try expectToken(a, scanner::TokenKind::Directive, "expected directive name"); |
|
| 181 | 184 | return &name.source[super::DIRECTIVE_SIGIL_LEN..]; |
|
| 182 | 185 | } |
|
| 183 | 186 | ||
| 184 | 187 | /// Parse one top-level assembler item. |
|
| 185 | - | unsafe fn parseItem 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 188 | + | fn parseItem 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 186 | 189 | match a.scan.current.kind { |
|
| 187 | 190 | case scanner::TokenKind::Ident => { |
|
| 188 | 191 | let tok = a.scan.current; |
|
| 189 | 192 | let name = try parseSymbolName(a); |
|
| 190 | 193 | try parseInstruction(a, name, tok); |
| 279 | 282 | }; |
|
| 280 | 283 | return super::CSRS[index].csr; |
|
| 281 | 284 | } |
|
| 282 | 285 | ||
| 283 | 286 | /// Parse an instruction after its mnemonic has already been consumed. |
|
| 284 | - | unsafe fn parseInstruction 'parse (a: &mut super::Assembler 'parse, name: *[u8], tok: scanner::Token) throws (super::Error) { |
|
| 287 | + | fn parseInstruction 'parse (a: &mut super::Assembler 'parse, name: *[u8], tok: scanner::Token) throws (super::Error) { |
|
| 285 | 288 | if a.section <> super::Section::Text { |
|
| 286 | 289 | throw failOnToken(tok, "instructions are only valid in the text section"); |
|
| 287 | 290 | } |
|
| 288 | 291 | if let format = atomics::parse(name) { |
|
| 289 | 292 | let rd = try parseRegister(a); |
| 347 | 350 | try emit::emitText(a, encode::lui(rd, split.hi)); |
|
| 348 | 351 | try emit::emitText(a, encode::addi(rd, rd, split.lo)); |
|
| 349 | 352 | } |
|
| 350 | 353 | ||
| 351 | 354 | /// Parse the `la` pseudo-instruction. |
|
| 352 | - | unsafe fn parseLa 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 355 | + | fn parseLa 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 353 | 356 | let rd = try parseRegister(a); |
|
| 354 | 357 | let target = try parseLabelName(a); |
|
| 355 | 358 | let index = a.textLen; |
|
| 356 | 359 | ||
| 357 | 360 | try emit::recordTextFixup(a, target, super::FixupInfo::Addr { rd, index }, 2); |
| 455 | 458 | ||
| 456 | 459 | try emit::emitText(a, enc(rs2, memop.base, memop.offset)); |
|
| 457 | 460 | } |
|
| 458 | 461 | ||
| 459 | 462 | /// Parse a two-register branch instruction. |
|
| 460 | - | unsafe fn parseBranch 'parse (a: &mut super::Assembler 'parse, op: super::BranchOp) throws (super::Error) { |
|
| 463 | + | fn parseBranch 'parse (a: &mut super::Assembler 'parse, op: super::BranchOp) throws (super::Error) { |
|
| 461 | 464 | let rs1 = try parseRegister(a); |
|
| 462 | 465 | let rs2 = try parseRegister(a); |
|
| 463 | 466 | ||
| 464 | 467 | try parseBranchLabel(a, op, rs1, rs2); |
|
| 465 | 468 | } |
|
| 466 | 469 | ||
| 467 | 470 | /// Parse an optional label operand. |
|
| 468 | - | unsafe fn parseOptionalLabel 'parse (a: &mut super::Assembler 'parse) -> ?*[u8] throws (super::Error) { |
|
| 471 | + | fn parseOptionalLabel 'parse (a: &mut super::Assembler 'parse) -> ?*[u8] throws (super::Error) { |
|
| 469 | 472 | if not isLabel(a.scan.current.kind) { |
|
| 470 | 473 | return nil; |
|
| 471 | 474 | } |
|
| 472 | 475 | return try parseLabelName(a); |
|
| 473 | 476 | } |
|
| 474 | 477 | ||
| 475 | 478 | /// Parse a branch target as either a label fixup or immediate offset. |
|
| 476 | - | unsafe fn parseBranchLabel 'parse (a: &mut super::Assembler 'parse, op: super::BranchOp, rs1: gen::Reg, rs2: gen::Reg) throws (super::Error) { |
|
| 479 | + | fn parseBranchLabel 'parse (a: &mut super::Assembler 'parse, op: super::BranchOp, rs1: gen::Reg, rs2: gen::Reg) throws (super::Error) { |
|
| 477 | 480 | let index = a.textLen; |
|
| 478 | 481 | if let target = try parseOptionalLabel(a) { |
|
| 479 | 482 | try emit::recordTextFixup(a, target, super::FixupInfo::Branch { op, rs1, rs2, index }, 1); |
|
| 480 | 483 | return; |
|
| 481 | 484 | } |
|
| 482 | 485 | let imm = try parseBranchImm(a); |
|
| 483 | 486 | try emit::emitText(a, emit::encodeBranch(op, rs1, rs2, imm)); |
|
| 484 | 487 | } |
|
| 485 | 488 | ||
| 486 | 489 | /// Parse a branch-to-zero pseudo-instruction. |
|
| 487 | - | unsafe fn parseBranchZero 'parse (a: &mut super::Assembler 'parse, op: super::BranchOp) throws (super::Error) { |
|
| 490 | + | fn parseBranchZero 'parse (a: &mut super::Assembler 'parse, op: super::BranchOp) throws (super::Error) { |
|
| 488 | 491 | let rs = try parseRegister(a); |
|
| 489 | 492 | try parseBranchLabel(a, op, rs, rv64::ZERO); |
|
| 490 | 493 | } |
|
| 491 | 494 | ||
| 492 | 495 | /// Parse `jal` with an explicit destination register. |
|
| 493 | - | unsafe fn parseJal 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 496 | + | fn parseJal 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 494 | 497 | let rd = try parseRegister(a); |
|
| 495 | 498 | try parseJ(a, rd); |
|
| 496 | 499 | } |
|
| 497 | 500 | ||
| 498 | 501 | /// Parse a jump target for `jal` or a jump pseudo-instruction. |
|
| 499 | - | unsafe fn parseJ 'parse (a: &mut super::Assembler 'parse, rd: gen::Reg) throws (super::Error) { |
|
| 502 | + | fn parseJ 'parse (a: &mut super::Assembler 'parse, rd: gen::Reg) throws (super::Error) { |
|
| 500 | 503 | let index = a.textLen; |
|
| 501 | 504 | if let target = try parseOptionalLabel(a) { |
|
| 502 | 505 | try emit::recordTextFixup(a, target, super::FixupInfo::Jal { rd, index }, 1); |
|
| 503 | 506 | return; |
|
| 504 | 507 | } |
| 515 | 518 | } |
|
| 516 | 519 | try emit::emitText(a, enc(rd, imm64 as i32)); |
|
| 517 | 520 | } |
|
| 518 | 521 | ||
| 519 | 522 | /// Parse a directive after its name has already been consumed. |
|
| 520 | - | unsafe fn parseDirective 'parse (a: &mut super::Assembler 'parse, name: *[u8], tok: scanner::Token) throws (super::Error) { |
|
| 523 | + | fn parseDirective 'parse (a: &mut super::Assembler 'parse, name: *[u8], tok: scanner::Token) throws (super::Error) { |
|
| 521 | 524 | let directive = classifyDirective(name) else { |
|
| 522 | 525 | throw failOnToken(tok, "unknown directive"); |
|
| 523 | 526 | }; |
|
| 524 | 527 | match directive { |
|
| 525 | 528 | case super::DirectiveKind::Text => { |
| 568 | 571 | ||
| 569 | 572 | dict::insert(&mut a.constMap, name, value); |
|
| 570 | 573 | } |
|
| 571 | 574 | ||
| 572 | 575 | /// Parse a `.export` directive. |
|
| 573 | - | unsafe fn parseExportDirective 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 576 | + | fn parseExportDirective 'parse (a: &mut super::Assembler 'parse) throws (super::Error) { |
|
| 574 | 577 | let name = try parseLabelName(a); |
|
| 575 | 578 | dict::insert(&mut a.exportMap, name, 1); |
|
| 576 | 579 | if let idx = dict::get(&a.symbolMap, name) { |
|
| 577 | 580 | set a.symbols[idx as u32].isExported = true; |
|
| 578 | 581 | } |
| 651 | 654 | } |
|
| 652 | 655 | } |
|
| 653 | 656 | } |
|
| 654 | 657 | ||
| 655 | 658 | /// Parse a fixed-width integer data directive. |
|
| 656 | - | unsafe fn parseIntDirective 'parse (a: &mut super::Assembler 'parse, width: super::DataWidth) throws (super::Error) { |
|
| 659 | + | fn parseIntDirective 'parse (a: &mut super::Assembler 'parse, width: super::DataWidth) throws (super::Error) { |
|
| 657 | 660 | loop { |
|
| 658 | 661 | if isLabel(a.scan.current.kind) { |
|
| 659 | 662 | let target = try parseLabelName(a); |
|
| 660 | 663 | try emit::recordDataFixup(a, target, width); |
|
| 661 | 664 | } else if a.scan.current.kind == scanner::TokenKind::Char { |