From b0d431c02770196d4ee0347339f74b50c74f2288 Mon Sep 17 00:00:00 2001 From: "maksim.nabokikh" Date: Mon, 14 Sep 2026 11:00:19 +0200 Subject: [PATCH] Fix out-of-bounds panic when lexing an identifier followed by U+0080 consumeUnquotedIdentifier guards the identifierTrailingBits lookup with `r > 128`, but the bit mask is a [2]uint64 covering runes 0-127. For r == 128 (U+0080) the guard passes and the index expression evaluates to identifierTrailingBits[2], which panics with "index out of range [2] with length 2". Any expression where an unquoted identifier is followed by U+0080 hits this, for example "a\u0080". It is reachable from the public API, so Search and Compile panic on an untrusted expression instead of returning a syntax error. Change the guard to `r >= 128`. After the fix the rune falls through to tokenize, which reports "Unknown char: '\u0080'", matching the behaviour of every other non-ASCII rune (U+0081 and above already errored). Signed-off-by: maksim.nabokikh --- lexer.go | 2 +- lexer_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lexer.go b/lexer.go index 817900c..9f214d0 100644 --- a/lexer.go +++ b/lexer.go @@ -386,7 +386,7 @@ func (lexer *Lexer) consumeUnquotedIdentifier() token { start := lexer.currentPos - lexer.lastWidth for { r := lexer.next() - if r < 0 || r > 128 || identifierTrailingBits[uint64(r)/64]&(1<<(uint64(r)%64)) == 0 { + if r < 0 || r >= 128 || identifierTrailingBits[uint64(r)/64]&(1<<(uint64(r)%64)) == 0 { lexer.back() break } diff --git a/lexer_test.go b/lexer_test.go index 307f2af..2baa30e 100644 --- a/lexer_test.go +++ b/lexer_test.go @@ -96,6 +96,10 @@ var lexingErrorTests = []struct { }{ {"'foo", "Missing closing single quote"}, {"[?foo==bar?]", "Unknown char '?'"}, + // U+0080 is the first rune outside the ASCII bit mask used for + // unquoted identifiers. It must be rejected rather than indexed. + {"a\u0080", "Unknown char '\\u0080'"}, + {"a\u0080b", "Unknown char '\\u0080'"}, } func TestLexingErrors(t *testing.T) {