From 24869177948930c4f77ab1e0b54847d72efdd5ba Mon Sep 17 00:00:00 2001 From: Kevin Tang <73975146+vt128@users.noreply.github.com> Date: Sat, 13 Jun 2026 14:57:08 +0800 Subject: [PATCH] [fix] regex: error on a group index that overflows int64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match.group/start/end/span resolve the group selector through groupIndex, which called starlark.Int.Int64() and discarded the ok flag. For an index too large for int64, Int64 returns 0, so the bounds check passed and the call silently selected group 0 (the whole match) instead of erroring — e.g. m.group(1 << 70) returned the full match. Capture ok and reject the overflowing index with "no such group" (compared as int64 to avoid truncation, formatted from the original value). groupIndex is the single chokepoint, so this fixes group, start, end and span together, matching CPython (IndexError: no such group). Test-first: a new section in regex_test asserts group/start/end/span all fail on 1 << 70. Requirement: LET-25. Co-authored-by: Claude Opus 4.8 --- lib/regex/regex.go | 11 ++++++++--- lib/regex/regex_test.go | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/regex/regex.go b/lib/regex/regex.go index 98e0e5e..ebb5e61 100644 --- a/lib/regex/regex.go +++ b/lib/regex/regex.go @@ -605,9 +605,14 @@ func (m *Match) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable type: func (m *Match) groupIndex(v starlark.Value) (int, error) { switch g := v.(type) { case starlark.Int: - i, _ := g.Int64() - if i < 0 || int(i) > m.p.search.NumSubexp() { - return 0, fmt.Errorf("no such group: %d", i) + // Int64 reports ok=false when the index overflows int64; the + // discarded ok used to leave i==0, silently selecting group 0 (the + // whole match) for a huge index instead of erroring. Compare as + // int64 to avoid truncating on the way to int, and format the + // original value (an overflowing index prints as 0 via %d). + i, ok := g.Int64() + if !ok || i < 0 || i > int64(m.p.search.NumSubexp()) { + return 0, fmt.Errorf("no such group: %s", g.String()) } return int(i), nil case starlark.String: diff --git a/lib/regex/regex_test.go b/lib/regex/regex_test.go index 770c800..c0edf98 100644 --- a/lib/regex/regex_test.go +++ b/lib/regex/regex_test.go @@ -233,6 +233,21 @@ func TestLoadModule_Regex(t *testing.T) { `), wantErr: `no such group: 5`, }, + { + // a group index too large for int64 used to be silently truncated + // to 0 (the whole match) instead of erroring; group/start/end/span + // all route through groupIndex and must reject it loudly. + name: `error: group index overflows int64`, + script: itn.HereDoc(` + load('regex', 'search') + m = search('(a)', 'a') + assert.eq(m.group(1), 'a') + assert.fails(lambda: m.group(1 << 70), 'no such group') + assert.fails(lambda: m.start(1 << 70), 'no such group') + assert.fails(lambda: m.end(1 << 70), 'no such group') + assert.fails(lambda: m.span(1 << 70), 'no such group') + `), + }, { name: `error: bad repl type`, script: itn.HereDoc(`