@@ -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