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
Alexis Sellier committed ago 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 {