diff --git a/ChangeLog b/ChangeLog index 0a76ccba5..bdc6c5e93 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,9 @@ library installation directories. pattern is called recursively, which could prevent valid matches requiring backtracking after the recursive call. +3. (#1040) Fixed the start_match field passed to interpreter callouts after \K +resets the start of a match, including restoration on backtracking. + Version 10.49 28-September-2026 ------------------------------- diff --git a/src/pcre2_match.c b/src/pcre2_match.c index e4f8f551c..f0a3735d3 100644 --- a/src/pcre2_match.c +++ b/src/pcre2_match.c @@ -307,15 +307,15 @@ do_callout(heapframe *F, match_block *mb, PCRE2_SIZE *lengthptr) callout_ovector = (PCRE2_SIZE *)(Fovector)-2; - /* The cb->version, cb->subject, cb->subject_length, and cb->start_match fields - are set externally. The first 3 never change; the last is updated for each - bumpalong. */ + /* The cb->version, cb->subject, and cb->subject_length fields are set + externally and never change. */ cb = mb->cb; cb->capture_top = (uint32_t)Foffset_top / 2 + 1; cb->capture_last = Fcapture_last; cb->offset_vector = callout_ovector; cb->mark = mb->nomatch_mark; + cb->start_match = (PCRE2_SIZE)(Fstart_match - mb->start_subject); cb->current_position = (PCRE2_SIZE)(Feptr - mb->start_subject); cb->pattern_position = GET(Fecode, 1); cb->next_item_length = GET(Fecode, 1 + LINK_SIZE); @@ -8719,7 +8719,6 @@ pcre2_match(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, PCRE2 /* OK, we can now run the match. If "hitend" is set afterwards, remember the first starting point for which a partial match was found. */ - cb.start_match = (PCRE2_SIZE)(start_match - subject); cb.callout_flags |= PCRE2_CALLOUT_STARTMATCH; mb->start_used_ptr = start_match; diff --git a/testdata/testinput2 b/testdata/testinput2 index ab50b797e..72f00b7b4 100644 --- a/testdata/testinput2 +++ b/testdata/testinput2 @@ -8453,4 +8453,37 @@ a)"xI abc\=ps,substitute_replacement_only,replace=>$_< abc\=ps,substitute_replacement_only,replace=>$'< +# Callout start offsets after resetting the start of a match. + +/(?C1)A\K(?C2)B(?C3)C/ + _ABC_ + _ABC_ABC_\=offset=4 + +/A\K(?C"after")BC/ + _ABC_ + +/A\K(?C1)B\K(?C2)C/ + _ABC_ + +/A(?:B\K(?C1)D|(?C2)B)(?C3)C/ + _ABC_ + +/A\K(?(?C1)(?=B)B|D)C/ + _ABC_\=no_jit + +/(?(DEFINE)(?A\K(?C1)B))(?&x)(?C2)C/ + _ABC_ + +/A\K(?C1)B/ + _AC_AB_ + +/(?C1)A\K(?C2)B/no_start_optimize + _AC_AB_ + +/(?C1)A(?C2)B(?C3)C/ + _ABC_ + +/A\KBC/auto_callout + _ABC_ + # End of testinput2 diff --git a/testdata/testoutput2 b/testdata/testoutput2 index 9fb4658c0..8feccd978 100644 --- a/testdata/testoutput2 +++ b/testdata/testoutput2 @@ -23955,6 +23955,94 @@ Failed: error -76 at offset 3 in replacement: replacement $' or $_ not supported Failed: error -76 at offset 3 in replacement: replacement $' or $_ not supported with partial match here: >$' |<--| < +# Callout start offsets after resetting the start of a match. + +/(?C1)A\K(?C2)B(?C3)C/ + _ABC_ +--->_ABC_ + 1 ^ A + 2 ^ B + 3 ^^ C + 0: BC + _ABC_ABC_\=offset=4 +--->_ABC_ABC_ + 1 ^ A + 2 ^ B + 3 ^^ C + 0: BC + +/A\K(?C"after")BC/ + _ABC_ +Callout (7): "after" +--->_ABC_ + ^ B + 0: BC + +/A\K(?C1)B\K(?C2)C/ + _ABC_ +--->_ABC_ + 1 ^ B + 2 ^ C + 0: C + +/A(?:B\K(?C1)D|(?C2)B)(?C3)C/ + _ABC_ +--->_ABC_ + 1 ^ D + 2 ^^ B + 3 ^ ^ C + 0: ABC + +/A\K(?(?C1)(?=B)B|D)C/ + _ABC_\=no_jit +--->_ABC_ + 1 ^ (?= + 0: BC + +/(?(DEFINE)(?A\K(?C1)B))(?&x)(?C2)C/ + _ABC_ +--->_ABC_ + 1 ^ B + 2 ^^ C + 0: BC + +/A\K(?C1)B/ + _AC_AB_ +--->_AC_AB_ + 1 ^ B + 1 ^ B + 0: B + +/(?C1)A\K(?C2)B/no_start_optimize + _AC_AB_ +--->_AC_AB_ + 1 ^ A + 1 ^ A + 2 ^ B + 1 ^ A + 1 ^ A + 1 ^ A + 2 ^ B + 0: B + +/(?C1)A(?C2)B(?C3)C/ + _ABC_ +--->_ABC_ + 1 ^ A + 2 ^^ B + 3 ^ ^ C + 0: ABC + +/A\KBC/auto_callout + _ABC_ +--->_ABC_ + +0 ^ A + +1 ^^ \K + +3 ^ B + +4 ^^ C + +5 ^ ^ End of pattern + 0: BC + # End of testinput2 Error -80: PCRE2_ERROR_BADDATA (unknown error number) Error -62: bad serialized data