You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(specializer): two new fuzzer-found regex reject classes — leading-$ row-path bug + RE2 program-size budget
The standing fuzzer's 20k-case deep run on seed 20260728 caught:
- Class 13: DuckDB's row path literal-optimizes a leading '$'+literal
into a PREFIX match ('$hello' matches 'hello world') while its own
constant fold matches normally — self-inconsistent, unservable.
Reject: unescaped '$' outside a class not followed by end, '|', ')',
'$', or an anchor escape.
- Class 14: counted repetitions can blow RE2's POST-SIMPLIFICATION
program budget ('(\p{L}){1,500}' is 'pattern too large' in DuckDB)
where rust-regex still compiles. Guard: one-sided weight estimate
(\p/\P = 800 range-units, classes = member count, literals 4;
threshold 100k, below the measured ~216k error floor) — the engine may
over-refuse but can never serve where DuckDB errors.
Pins: pins-waveB/fuzzer-20260728.json. Doc + twin rows added. Gates:
cargo 163, pytest 609 + 13 xfail, corpus 529 unchanged, 20k re-sweep on
the finding seed divergence-free.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/known-limitations.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -92,7 +92,7 @@ wrong answer or require semantics we can't reproduce exactly:
92
92
|`^` operator | It IS pow in DuckDB, but sqlparser's precedence differs from DuckDB's (`2*x^y` would parse as `(2*x)^y`). Mapping it computes the wrong tree silently. Use `pow()`. |
93
93
| prefix `~`, `#`, `NOT GLOB`| Same class: precedence/parse divergences that would silently mis-associate. `xor()` covers bit-xor; `NOT (x GLOB p)` works. |
94
94
| Regex reject list: `\B`, `\Q…\E`, `(?<name>…)`, duplicate group names, bounds > 1000, stacked quantifiers (`a*+`), `\u` escapes, negated Perl classes inside `[...]`| The RE2↔rust-regex differential battery (98 entries) proved these are the constructs where the engines disagree or DuckDB itself is broken (`\B` crashes DuckDB at runtime on non-ASCII). Everything else is byte-identical. |
95
-
| Fuzzer-found regex rejects (TASK-54): `\1`–`\9` backrefs outside classes, the full stacked-quantifier grammar (`{2}*`, `?*`, `a???` — one lazy `?` is the only legal follower), nested repetition products > 1000, whitespace inside `{m, n}` bounds, class set-op lookalikes (`--`/`&&`/`~~`), non-POSIX `[` inside classes, Perl-class range endpoints (`[a-\d]`), capturing `(x){0}`, anchor-only multi-anchor patterns (DuckDB is SELF-inconsistent on these — its row path disagrees with its own constant fold) | The standing differential fuzzer (`tests/test_duckdb_regexp_fuzz.py`, in the normal gate) found these 12 classes in its first 3k-case deep run — each one a silent-wrong-answer risk in rust-regex — then re-swept to ZERO divergences over 40k cases across 8 seeds. Pins: `pins-waveB/fuzzer-task54.json`. |
95
+
| Fuzzer-found regex rejects (TASK-54): `\1`–`\9` backrefs outside classes, the full stacked-quantifier grammar (`{2}*`, `?*`, `a???` — one lazy `?` is the only legal follower), nested repetition products > 1000, whitespace inside `{m, n}` bounds, class set-op lookalikes (`--`/`&&`/`~~`), non-POSIX `[` inside classes, Perl-class range endpoints (`[a-\d]`), capturing `(x){0}`, anchor-only multi-anchor patterns (DuckDB is SELF-inconsistent on these — its row path disagrees with its own constant fold), `$` anchors in non-final position (`'$hello'` — DuckDB's row path literal-optimizes the leading `$`+literal into a PREFIX match, matching "hello world", while its own constant fold matches normally; found by the standing fuzzer on seed 20260728), and counted repetitions over RE2's PROGRAM-SIZE budget (`(\p{L}){1,500}` is "pattern too large" in DuckDB while rust-regex serves it — rejected via a one-sided weight estimate that always fires before DuckDB's real budget; same seed, pins `pins-waveB/fuzzer-20260728.json`) | The standing differential fuzzer (`tests/test_duckdb_regexp_fuzz.py`, in the normal gate) found these 12 classes in its first 3k-case deep run — each one a silent-wrong-answer risk in rust-regex — then re-swept to ZERO divergences over 40k cases across 8 seeds. Pins: `pins-waveB/fuzzer-task54.json`. |
96
96
|`SIMILAR TO ... ESCAPE`| Not implemented in DuckDB itself. |
97
97
|`* EXCLUDE (t.key)` on a USING join | DuckDB UNMERGES the coalesced column (it reappears at the right table's position) — measured, not modeled. Unqualified EXCLUDE works. |
98
98
|`BETWEEN`/`IN` mixing non-numeric string literals with numbers | DuckDB converts at EXECUTION time (an empty input succeeds!); a bind-time conversion was measured to be over-eager. Numeric literals convert fine. |
"q": "SELECT regexp_matches(s, '$h') FROM t -- s='hello world'",
7
+
"result": "row path TRUE, constant fold FALSE",
8
+
"note": "Class 13: DuckDB SELF-inconsistent on a leading '$' anchor followed by a literal. Its row path literal-optimizes '$'+literal into a PREFIX match: '$hello' matches 'hello world', '$b' matches 'b' (but not 'ab'); the constant path matches normally (never). Consistent (both False/True) for: 'h$', '^h', 'x^h', 'a$|b', '(h$)x', '($)h', 'a$b*', '$h*', '$[hb]', '$(h)', '(?i)$H', '[$]h', '\\\\$h', bare '$'. Reject: unescaped '$' outside a class not followed by end, '|', ')', '$', or an anchor escape (\\A \\z \\b \\B). Found: fuzz case 4275."
"result": "DuckDB: Invalid Input Error: pattern too large - compile failed; rust-regex: compiles and serves",
13
+
"note": "Class 14: RE2 program-size budget. Boundary probes: (\\p{L}){1,300} ok, {1,500} ERR; (\\p{Nd}){1,1000} ok; (\\d){1,1000} ok; ([^\\w]){1,1000} ok; ([é-ü]){1,1000} ok; (.|\\p{L}){1,1000} ok (RE2 SIMPLIFIES the alternation to '.'). The budget is on the POST-SIMPLIFICATION program (range units), so exact modeling is impossible; the engine uses a one-sided over-estimate (\\p/\\P = 800 units >= any property's range count, classes = member count, literals 4) and rejects counted repetitions whose weight product clears 100_000 — below the measured error floor (~216k units), so the engine may over-refuse but can never serve where DuckDB errors. Found: fuzz case 17388."
14
+
}
15
+
],
16
+
"summary": "Two new reject classes from the 20k-case deep run on seed 20260728: (13) leading-'$'-then-atom patterns are DuckDB-self-inconsistent (row-path prefix optimization bug) and unservable in principle; (14) counted repetitions can blow RE2's post-simplification program budget where rust-regex still compiles — guarded by a conservative one-sided weight estimate. After both rejects the full 20k sweep is divergence-free."
0 commit comments