Skip to content

Commit a561077

Browse files
committed
fix(regex): widen repeat counters to u16 so counts up to RE_DUP_MAX compile
neomacs stored regex repeat counters (succeed_n/jump_n/set_number_at) as signed i16, so a valid count in 32768..=65535 (GNU allows up to RE_DUP_MAX=0xffff) was rejected with "Regular expression too big". Counts are always non-negative; only jump offsets are signed. Switch the counter path to u16 -- the counter table, get/set_counter, checked_u16_counter, the store (store_number_u16) and the set_number_at value read (extract_number_u16) -- while jump offsets keep extract_number/store_number (i16). Decrements use saturating_sub. Fixes div_r6_regex_dupmax_boundary (a\{32768\}, a\{65535\} now compile; a\{65536\}+ still signal "Invalid content of \{\}").
1 parent 32973fa commit a561077

1 file changed

Lines changed: 27 additions & 13 deletions

File tree

neovm-core/src/emacs_core/regex_emacs.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ struct FailurePoint {
485485

486486
/// Saved interval-counter overrides at this point.
487487
/// Keyed by bytecode position of the 2-byte counter field.
488-
saved_counters: HashMap<usize, i16>,
488+
saved_counters: HashMap<usize, u16>,
489489
}
490490

491491
// SyntaxClass is imported from crate::emacs_core::syntax.
@@ -506,20 +506,34 @@ fn extract_number(buf: &[u8], pos: usize) -> i16 {
506506
i16::from_le_bytes([buf[pos], buf[pos + 1]])
507507
}
508508

509+
/// Store a 2-byte UNSIGNED repeat counter at position in bytecode buffer. GNU
510+
/// counters are `unsigned short` (0..=RE_DUP_MAX), unlike the signed jump
511+
/// offsets stored by `store_number`.
512+
fn store_number_u16(buf: &mut [u8], pos: usize, number: u16) {
513+
let bytes = number.to_le_bytes();
514+
buf[pos] = bytes[0];
515+
buf[pos + 1] = bytes[1];
516+
}
517+
518+
/// Read a 2-byte UNSIGNED repeat counter from bytecode buffer.
519+
fn extract_number_u16(buf: &[u8], pos: usize) -> u16 {
520+
u16::from_le_bytes([buf[pos], buf[pos + 1]])
521+
}
522+
509523
/// Read a counter value from the counter table, falling back to the bytecode
510524
/// if no override has been stored yet. Used by `succeed_n`, `jump_n`, and
511525
/// `set_number_at` to emulate GNU's in-place bytecode mutation on immutable
512526
/// bytecode.
513-
fn get_counter(counters: &HashMap<usize, i16>, bytecode: &[u8], pos: usize) -> i16 {
527+
fn get_counter(counters: &HashMap<usize, u16>, bytecode: &[u8], pos: usize) -> u16 {
514528
counters
515529
.get(&pos)
516530
.copied()
517-
.unwrap_or_else(|| extract_number(bytecode, pos))
531+
.unwrap_or_else(|| extract_number_u16(bytecode, pos))
518532
}
519533

520534
/// Store a counter value in the mutable counter table (keyed by bytecode
521535
/// position).
522-
fn set_counter(counters: &mut HashMap<usize, i16>, pos: usize, val: i16) {
536+
fn set_counter(counters: &mut HashMap<usize, u16>, pos: usize, val: u16) {
523537
counters.insert(pos, val);
524538
}
525539

@@ -2336,8 +2350,8 @@ fn checked_i16_offset(offset: isize) -> Result<i16, RegexCompileError> {
23362350
})
23372351
}
23382352

2339-
fn checked_i16_counter(value: usize) -> Result<i16, RegexCompileError> {
2340-
i16::try_from(value).map_err(|_| RegexCompileError {
2353+
fn checked_u16_counter(value: usize) -> Result<u16, RegexCompileError> {
2354+
u16::try_from(value).map_err(|_| RegexCompileError {
23412355
message: "Regular expression too big".to_string(),
23422356
})
23432357
}
@@ -2362,7 +2376,7 @@ fn store_jump2_at(
23622376
count: usize,
23632377
) -> Result<(), RegexCompileError> {
23642378
store_jump_at(buffer, op_pos, op, target)?;
2365-
store_number(buffer, op_pos + 3, checked_i16_counter(count)?);
2379+
store_number_u16(buffer, op_pos + 3, checked_u16_counter(count)?);
23662380
Ok(())
23672381
}
23682382

@@ -2396,7 +2410,7 @@ fn insert_set_number_at(
23962410
buf.splice_bytecode(at, &[RegexOp::SetNumberAt as u8, 0, 0, 0, 0]);
23972411
let offset = checked_i16_offset(target_counter_offset as isize)?;
23982412
store_number(&mut buf.buffer, at + 1, offset);
2399-
store_number(&mut buf.buffer, at + 3, checked_i16_counter(value)?);
2413+
store_number_u16(&mut buf.buffer, at + 3, checked_u16_counter(value)?);
24002414
Ok(())
24012415
}
24022416

@@ -2747,7 +2761,7 @@ pub(crate) fn re_match(
27472761

27482762
// Mutable counter table for interval repetition (succeed_n / jump_n / set_number_at).
27492763
// GNU modifies bytecode in-place; we use a side table keyed by bytecode position.
2750-
let mut counters: HashMap<usize, i16> = HashMap::new();
2764+
let mut counters: HashMap<usize, u16> = HashMap::new();
27512765

27522766
// GNU regex-emacs.c:4188-4204 skips all internal register arrays
27532767
// when the pattern has no subexpressions. Register 0 is handled
@@ -3582,7 +3596,7 @@ pub(crate) fn re_match(
35823596
let count = get_counter(&counters, bytecode, counter_pos);
35833597
if count != 0 {
35843598
// Still must succeed more times — decrement & continue
3585-
set_counter(&mut counters, counter_pos, count - 1);
3599+
set_counter(&mut counters, counter_pos, count.saturating_sub(1));
35863600
pc += 4;
35873601
} else {
35883602
// Counter exhausted — behave like on_failure_jump_loop.
@@ -3617,7 +3631,7 @@ pub(crate) fn re_match(
36173631
let count = get_counter(&counters, bytecode, counter_pos);
36183632
if count != 0 {
36193633
// Decrement counter and perform unconditional jump
3620-
set_counter(&mut counters, counter_pos, count - 1);
3634+
set_counter(&mut counters, counter_pos, count.saturating_sub(1));
36213635
let offset = extract_number(bytecode, pc);
36223636
pc = ((pc as i64) + 2 + (offset as i64)) as usize;
36233637
} else {
@@ -3631,7 +3645,7 @@ pub(crate) fn re_match(
36313645
// Used to reset interval counters at the start of a loop.
36323646
let rel_offset = extract_number(bytecode, pc);
36333647
pc += 2; // advance past the offset field
3634-
let value = extract_number(bytecode, pc);
3648+
let value = extract_number_u16(bytecode, pc);
36353649
pc += 2; // advance past the value field
36363650
// Target counter position: relative to position after
36373651
// the offset field (same convention as GNU).
@@ -3726,7 +3740,7 @@ fn goto_fail(
37263740
fail_stack: &mut Vec<FailurePoint>,
37273741
regstart: &mut [Option<usize>],
37283742
regend: &mut [Option<usize>],
3729-
counters: &mut HashMap<usize, i16>,
3743+
counters: &mut HashMap<usize, u16>,
37303744
) -> Option<()> {
37313745
// Mirrors GNU `regex-emacs.c:5236`: poll quit at the failure /
37323746
// backtrack site. Backtracking loops are the worst offenders for

0 commit comments

Comments
 (0)