题记与说明
综合报告说明:本文由课题两份章节综合而成——纵轴《正则引擎原理:NFA/DFA、回溯灾难,RE2 与 Rust regex 的设计取舍——纵轴发展史(1943–2026)》与横轴《正则引擎原理:NFA/DFA、回溯灾难,RE2 与 Rust regex 的设计取舍——横向对比(横轴 · 2026-10-29)》(均存于output/reports/parts/)。综合时消除了两章的三处矛盾(Hyperscan 结局的时间线、Cloudflare 事故细节、.NET NonBacktracking 的捕获组支持),合并了两章重复的"性能避坑指南",并对关键矛盾点于 2026-10-29 当日联网复核(详见文末参考来源第 20–22 条)。两章中标注"既有知识/知识库"的细节在本文保留原标注。
终稿修订说明(2026-10-29,按独立审读意见逐条修订):① 统一"砍功能"口径——反向引用是数学边界(NP 完全),环视不被主流线性引擎支持是实现复杂度与保证范围的取舍,不是数学边界;②size_limit(编译期闸门)与max_mem/dfa_size_limit(运行期缓存与降级)的职责在"结论与选型建议"第 3 节一次讲清,其余四处改为交叉引用、删除重复;③ 修正回溯炸弹样例的非法叠加量词写法(Pythonre与 Node/V8 实测,原写法两引擎均报语法错误);④ Cloudflare 时间线改为编年;⑤ 无来源论断逐条补来源或降格(RE2J 倍数、PCRE2 CVE-2025-58050、线程 clone 官方出处、ES2025 提案链接等);⑥ "对用户的判断与建议"提为一级章节"结论与选型建议",a|ab、"字面量优先"、限额类重复内容合并去重;⑦ TRE 独立成条、.NET 4.5 超时条目前移为补叙;⑧ 更正初稿中 Vectorscan"受限环视/捕获组"的表述(官方开发文档:lookaround 属"任意零宽断言"不支持项、capturing is ignored),并撤回以 Vectorscan 为"线性引擎支持环视"例证的说法;⑨ 补 lazy DFA/one-pass/符号化 NFA 白话解释、环视/反向引用迁移改写示例与基准具体数据点。初稿保留于drafts/,本稿不改动它。
TL;DR
正则表达式源自 1940 年代神经元形式化模型与 Kleene 的正则事件理论;1968 年 Thompson 构造法证明 NFA 模拟可线性时间匹配。1986–1994 年,Perl 式回溯引擎凭捕获组、反向引用、环视赢得市场,但反向引用使匹配成为 NP 完全问题,表达力与线性时间自此决裂。回溯引擎统治主流平台后,ReDoS 成为现实威胁:2016 年 Stack Overflow 与 2019 年 Cloudflare 两次全球级宕机是标志。2010 年 RE2、2018 年 Rust regex 1.0 相继以砍掉反向引用/环视换取最坏 O(n) 保证;2026 年 Go 标准库、.NET NonBacktracking 与 V8 的非回溯路径已内置或实验接入(SpiderMonkey 至今未跟进),Rust regex 以元引擎架构成为性能与维护双标杆,RE2 设计思想完成行业渗透。一句话判断:看"模式谁写的、输入谁的"——不可信输入必须上线性引擎,可信场景才用回溯引擎加护栏。
纵轴:发展脉络
阶段〇:理论奠基期(1943–1968)——"正则"从神经科学里长出来
- 1943 年,神经生理学家 Warren McCulloch 与逻辑学家 Walter Pitts 发表神经元的形式化模型,第一次用逻辑演算描述"神经系统中的事件",成为有限自动机的思想源头(Wikipedia: Regular expression — History)。
- 1951/1956 年,数学家 Stephen Kleene 在 RAND 报告及论文《Representation of Events in Nerve Nets and Finite Automata》中提出"正则事件"(regular events),并给出 Kleene 星号——"正则表达式"由此得名。
- 1959 年,Michael Rabin 与 Dana Scott 发表《Finite Automata and Their Decision Problem》,确立 NFA 与 DFA 的等价性及判定理论(两人因此获 1978 年图灵奖)。理论上的关键事实:NFA 与 DFA 表达能力等价,但 DFA 状态数最坏情况下是 NFA 的指数倍——这为后来所有"确定性 vs 灵活性"的工程取舍埋下伏笔。
驱动因素:纯理论(数理逻辑、神经建模),与"文本搜索"的工程实践尚未合流。
阶段一:从论文到工具——Thompson 构造法与 grep(1968–1979)
- 1968 年 6 月,贝尔实验室的 Ken Thompson 在 CACM 发表《Regular Expression Search Algorithm》(Vol. 11, No. 6, pp. 419–422):把正则表达式编译成 IBM 7094 机器码,其本质是 NFA 模拟(后世所称 Thompson 构造法),匹配时间与输入长度成线性关系。这是"正则引擎可以既强大又快"的第一份工程证明(swtch.com/~rsc/regexp/regexp1.html、相关讨论)。该实现先用于 QED 编辑器,随后移植进 Unix 的 ed。
- 1973 年 3 月,Thompson 应同事 Lee McMahon 分析《联邦党人文集》作者归属的需求,一夜之间把 ed 的正则搜索抽成独立工具
grep(g/re/p),随 Version 4 Unix 发布(Wikipedia: Grep)。 - 同期,为编译器词法分析服务的 lex(Mike Lesk,1975)与 Aho 的 egrep(DFA 路线)相继出现——"正则 = 快"的 DFA 传统与"正则 = 好用"的编辑器传统并行发展。
驱动因素:Unix 文本工具文化 + 编译器词法分析需求;此时的正则语法仍极简(无捕获组、无反向引用),线性时间毫无压力。
阶段二:两条语义路线分叉,回溯引擎上位(1980s–1996)
- 1986 年,Henry Spencer 发布 C 语言正则库 regexp.c(经 Usenet 分发),采用回溯(backtracking)实现,成为 Unix 历史上被复制最多的正则代码之一(Spencer 三代正则库存档、regexp.old)。
- 1987-12-18,Larry Wall 发布 Perl 1.000,其正则引擎直接借用 Spencer 的代码(perlhist、Mark Jason Dominus《Dirty Secrets of the Perl Regex Engine》)。
- 1992 年,POSIX.2 标准化 BRE/ERE 两套语法,并规定 leftmost-longest(最左最长)匹配语义——这是 DFA 传统(egrep/awk)的语义(POSIX 规范第 9 章)。而 Perl 回溯引擎的天然语义是 leftmost-first(最左优先,按分支书写顺序取第一个成功)。两种匹配语义从此分叉,至今仍是从 POSIX 正则迁移到 Perl 系引擎时最常见的"结果不一致"来源(Cox: Regular Expression Matching in the Wild)。
- 1994-10-17,Perl 5.000 发布,正则新增非贪婪量词、环视(lookaround)、反向引用(backreferences)——反向引用使模式匹配问题变为 NP 完全(Aho, 1990,Cox 亦复述此点)。从此"表达能力"与"线性时间"彻底决裂:任何想支持反向引用的引擎都无法保证最坏情形多项式时间。
- 早期 Python 的
regex模块基于 Spencer 代码移植;其继任者re模块(sre 引擎)语法向 Perl/PCRE 风格看齐,仍是回溯实现(既有知识,正则引擎谱系可参考)。 - 1997 年,Jeffrey Friedl《Mastering Regular Expressions》(O'Reilly)出版,把"NFA 引擎 vs DFA 引擎"的工程叙事普及给一代程序员(既有知识)。
驱动因素:脚本语言的表达力竞赛(动态文本处理是 Perl 的立身之本)+ Unix 标准化。回溯引擎赢了市场:捕获组、反向引用、环视这些 Perl 特性当年只有回溯实现可用——"要捕获就得回溯"在当时是工程现实而非数学必然(线性时间与捕获组的兼容性要到 2000 年代初才由 TRE 证明,见阶段四;真正的数学边界只有反向引用一条,见交叉洞察(2))。
阶段三:PCRE 时代与回溯灾难显形(1997–2006)
- 1997 年夏,剑桥大学的 Philip Hazel 为其邮件服务器 Exim 寻找 Perl 风格正则库未果,动手编写 PCRE;1997 年 9–12 月间密集发布,此后 20 年 PCRE 成为 C/C++ 生态事实标准(Apache、PHP、Nginx 等均内置)(pcre.org、Hazel 亲笔《A Brief History of PCRE》)。
- 2002 年前后,Java 1.4(java.util.regex)、.NET(2002)先后内置回溯型正则引擎;JavaScript 引擎同样是回溯实现。主流平台至此全线回溯化。
- 2003 年,Crosby & Wallach 发表算法复杂度攻击(Algorithmic Complexity Attacks)研究,第一次把"最坏输入让程序卡死"系统化为安全议题——这正是后来 ReDoS 的理论基础(Davis et al. 2018 引用脉络)。
- 补叙(2012 年):.NET 4.5 曾引入匹配超时(
RegexMatchTimeoutException)作为缓解,这是主流平台第一次给回溯引擎内建保险丝;Java 生态则长期只能靠 re2j 或手写护栏(.NET 7 发布说明回溯了这段历史)。
驱动因素:Web 兴起,不可信用户输入第一次大规模流入正则引擎;而 PCRE 式回溯引擎对恶意输入没有任何防护——灾难的舞台已经搭好,只等有人踩上去。
阶段四:Cox 三部曲与 RE2——线性时间的回归(2006–2013)
- 2006 年,Google Code Search 上线:用户提交任意正则作为查询,直接在公开代码上执行——正则引擎第一次需要在对抗性输入下保持确定性能。这正是 RE2 的孵化场景(Cox: Regular Expression Matching in the Wild)。
- 2007 年 1 月,Russ Cox 发表《Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby, ...)》,复盘 Thompson 构造法,指出主流语言把 O(n·m) 的问题做成了指数时间,并演示简单 NFA 模拟的压倒性优势(regexp1.html)。
- 2009 年,Checkmarx 研究员 Alex Roichman 与 Adar Weidman 在 OWASP 发表文章,正式命名 ReDoS(Regular expression Denial of Service);此后该缺陷被收录为 CWE-1333(Davis et al. 对术语出处的引用、CWE-1333)。
- 2000 年代初(早于 RE2),Ville Laurikari 的 TRE(tagged NFA,github.com/laurikari/tre)已经证明线性时间与捕获组可以兼得:TRE 始终返回正确的子模式匹配(submatch),且最坏时间可预测(其仓库 README 自述 "predictable O(M²·N) worst-case time",对输入长度 N 为线性、系数依赖模式长度)。这条证据要单独立在这里:它说明"要捕获组就得回溯"从来不是数学事实,也划清了后文所有"砍功能"论述的边界——真正的数学边界只有反向引用一条(既有知识,出处为 TRE 仓库自述,参考来源 26)。
- 2010 年,Google 开源 RE2(C++,现 github.com/google/re2),自我定位为"PCRE/Perl/Python 等回溯引擎的快速、安全、线程友好的替代品":保证线性时间与有界内存,代价是砍掉反向引用与环视。同年 1 月 Cox 发表《Regular Expression Matching: the Virtual Machine Approach》,系统阐述 PikeVM(NFA 模拟拿子匹配)、one-pass DFA、有界回溯等混合策略(regexp2.html)。RE2 内部采用 lazy DFA + one-pass + NFA 模拟的混合架构,同时提供 POSIX(leftmost-longest)与 Perl(leftmost-first)两种语义。三个内部机制的通俗版:lazy DFA——不预先把 NFA 完整确定化(完整 DFA 最坏会指数膨胀),而是边匹配边把"遇到过的状态组合"缓存成转移表,命中缓存就是纯查表;缓存有上限,超限则清空重建,反复超限会退回更慢的 NFA 模拟(re2.h 原文 "Once a DFA fills its budget, it flushes its cache and starts over. If this happens too often, RE2 falls back on the NFA implementation.",参考来源 23)。one-pass DFA——模式"每一步只有唯一可能转移"(无歧义)时可以确定化、同时保留捕获槽,取子匹配很快。PikeVM/NFA 模拟——逐字节同时跟踪全部活跃状态集合,慢但全能,也是 lazy DFA 降级时的落点。
- 2012 年 1 月,Cox 发表三部曲终篇《Regular Expression Matching in the Wild》,讲解 Code Search 的 trigram 索引如何与 RE2 配合(regexp3.html)。RE2 此后进入 Google 各产品线,例如 BigQuery 的正则函数明确采用 RE2(BigQuery 文档、RE2 语法 wiki)。
- 2013 年 5 月,Go 1.1 发布,标准库
regexp包"完全重写",基于 RE2 设计保证线性时间,较旧实现快约 40%(Go 1.1 Release Notes)。 - 2015 年前后,Google 发布 Java 版 re2j(github.com/google/re2j),后来又出现 JavaScript 移植 re2js(le0pard/re2js),RE2 思路开始跨语言扩散。
驱动因素:Google 规模的对抗性查询 + 云服务对"单请求不能拖垮全局"的可用性要求 + Cox 极具说服力的布道。设计取舍从此有了清晰框架:要么支持反向引用/环视并接受指数最坏情形(Perl 系),要么保证线性时间并放弃这些特性(RE2 系)。
阶段五:ReDoS 进入主流安全议程,两次全球宕机立碑(2014–2019)
- 2015 年,Intel 开源高性能多模式正则库 Hyperscan:混合自动机(literal/NFA/DFA 多引擎组合)+ SIMD,面向 Snort/Suricata 等网络安全场景一次匹配数万条规则(intel/hyperscan)。同年 1 月 PCRE2 10.0 发布(与 PCRE1 不兼容的新 API,加入 JIT;PCRE1 的 8.x 系列如今已 EOL)(pcre.org)。
- 2016 年,Andrew Gallant(BurntSushi)发布 ripgrep:基于 Rust 的 regex crate,用 SIMD 与字面量优化把"线性时间引擎也可以是最快的引擎"演示给了大众(ripgrep 早期记录 issue #132(2016-09)、crates.io)。
- 2016-07-20,Stack Overflow 全网宕机约 34 分钟:一个畸形帖子含约 2 万个连续空白字符,打爆了行首行尾 Unicode 空白裁剪的正则,负载均衡器把整个数据中心摘除(stackstatus postmortem,技术复盘见 Accidentally Quadratic)。
- 2018-05-01,Rust regex 1.0.0 发布(CHANGELOG);此前 RFC 1620 已把"所有搜索最坏情形 O(n)"写进 crate 的 API 契约。核心作者 Gallant 同时是 ripgrep 作者,"工具 + 引擎"互相成就。
- npm 生态的 ReDoS 公告潮出现:Davis 等人 ESEC/FSE 2018 论文《The Impact of Regular Expression Denial of Service (ReDoS) in Practice》实测发现 npm 生态存在大量真实可触发的 ReDoS 漏洞(论文 PDF)。GitHub Advisory Database 此后将 ReDoS(CWE-1333)作为独立漏洞类别跟踪(既有知识)。
- 2019-07-02,Cloudflare 全球宕机约 27 分钟。导火索:一条用于拦截 XSS 的 WAF Managed Rule 中含可化归为
.*.*=.*(原文为.*(?:.*=.*))的堆叠贪婪量词正则,跑在 Lua WAF 的 PCRE 上发生灾难性回溯。按 Cloudflare 官方复盘的时间线编年(均为 UTC):
- 13:42,Cloudflare 上线这条问题规则;随后全球 HTTP/HTTPS 边缘节点 CPU 飙至近 100%,代理、CDN 与 WAF 全线失效,大量站点 502;
- 14:07,执行"全局熔断",关闭整个 WAF;
- 约 2 分钟后,流量恢复——自 13:42 起算,宕机约 27 分钟;
- 14:52,重新启用 WAF。
事后复盘的整改清单第 4 条原文即"Switching to either the re2 or Rust regex engine which both have run-time guarantees(切换到 re2 或 Rust regex 引擎,二者都有运行时保证)"(Cloudflare 官方复盘)。这是 RE2/Rust regex 路线最有分量的一次"实战背书"。
驱动因素:边缘计算与云把"一条慢正则"放大成"全球事故";npm 等供应链把 ReDoS 从理论攻击变成日常 CVE。
阶段六:非回溯引擎成为平台演进方向(2020–2026)
- 2020 年 6 月,Mozilla(与 Igalia 合作)宣布 SpiderMonkey 换用移植自 V8 的上游 Irregexp(字节码解释器 + JIT 的回溯引擎)(Mozilla Hacks)。至于 V8 的非回溯引擎:Mozilla 评估后的结论是"尚不成熟、暂不引入"——2021 年 2 月更新 Irregexp 时,Iain Ireland 原话"Most of the work in irregexp since our last update has been to implement the experimental non-backtracking engine, which is not yet mature enough for us to import"(Bugzilla bug 1690142,2021-02);截至 2026-10,Firefox/SpiderMonkey 仍未上线非回溯路径(后续状态建议跟踪 spidermonkey.dev 通讯与 Bugzilla,参考来源 25)。
- 2021 年初,V8 发布《An additional non-backtracking RegExp engine》(v8.8 起):在 Irregexp 之外增加基于 Thompson 式 NFA 模拟的实验性引擎,对"已知会回溯爆炸"的模式兜底,保证线性时间,但不能处理反向引用等构造(v8.dev)。
- 2022 年 11 月,.NET 7 发布
RegexOptions.NonBacktracking:基于符号化 NFA 的非回溯模式(符号化 NFA:转移边不按具体字节逐条枚举,而是直接挂"字符类"谓词——如"任意汉字""任意 ASCII 字母"——状态数不随字符集大小膨胀),保证线性时间、免疫 ReDoS,不支持反向引用、环视、原子组、平衡组与条件表达式(.NET 博客《Regular Expression Improvements in .NET 7》、Microsoft Learn:正则选项文档)。.NET 7/8 该模式不支持捕获组;.NET 9 起补上了捕获组支持(局限:循环内的捕获只保留最后一次,dotnet/runtime 相关讨论、Learn 文档)。Java 阵营的对应物是 GraalVM 的 TRegex(以 DFA 为主、ReDoS 友好,OpenJDK 讨论)。 - 2023-07-05,Rust regex 1.9 发布:多年重构落地,
regexcrate 变成regex-automata之上的薄封装——元引擎(meta engine)按模式特征自动择优:PikeVM(对全部活跃状态做并行模拟、给每个副本带捕获槽,慢但全能,兜底一切模式)、有界回溯器(小模式专用、回溯步数封顶)、one-pass DFA("每步唯一转移"的无歧义模式,确定化后仍带捕获槽,取捕获快)、lazy DFA(不处理捕获的主力引擎,机制见阶段四 RE2 条)(Rust 官方博客、Gallant《Regex engine internals as a library》、规划 issue #656(2020-03 起))。同年推出二进制体积更小的regex-lite(既有知识,docs.rs)。此后 1.10–1.13 持续迭代:1.12.2(2025-10)→ 1.12.3(2026-02)→ 1.12.4(2026-06)→ 1.13.1(2026-07-15),配套regex-syntax、regex-automata、regex-lite形成完整家族(lib.rs)。 - 2023–2024 年,Hyperscan 生态剧变:Intel 于 2023 年归档 GitHub 仓库(intel/hyperscan),2024 年 5 月起 Hyperscan 5.5+ 转为闭源专有许可(OPNsense 论坛"Intel killed Hyperscan"(2024-05)、Fedora 弃包公告);最后一个开源版本为 5.4.2。社区继承者 Vectorscan(VectorCamp 维护)承诺与开源版 Hyperscan 5.4 API/ABI 兼容,明确不追随闭源的 5.5/5.6,并补齐 AMD/ARM/Power 路径;Fedora、OPNsense、Suricata 生态已陆续换装(Suricata 文档)。选型时今天的等价物是 Vectorscan,不是 Hyperscan。
- Rust 生态同时出现混合方案 fancy-regex(0.20.0,2026-10-24 发布):把普通部分委托给底层线性 NFA 引擎,遇到环视/反向引用/原子组才切换到回溯 VM——明确告诉用户"多出来的表达力要交回性能保证"(lib.rs/crates/fancy-regex)。Go 侧的对应物是社区库
dlclark/regexp2(.NET 引擎移植,知识库)。
驱动因素:各大平台把"secure by default"提上日程——Web 引擎、语言标准库纷纷内置或实验非回溯路径;Cox 确立的取舍框架成为行业共识而非学者清谈。
当下格局(截至 2026-10)
- 两大阵营:表达力优先(Perl/PCRE2、Java、JavaScript/V8 Irregexp、Python re、Oniguruma——全是回溯引擎)vs 时间保证优先(RE2 及其家族 Go regexp/re2j/re2js、Rust regex、.NET NonBacktracking、V8 的非回溯实验引擎,外加 Vectorscan 这类特种多模式引擎)。
- Rust
regex与 Goregexp已成为各自语言的事实默认;ripgrep 用线性引擎拿到了"最快"的名声,Cloudflare 事故复盘则把"切换到 re2 或 Rust regex"白纸黑字写进整改清单(见阶段五)——"多款 WAF/IDS 随之转向线性引擎"这一更宽的说法属经验值(待核实:公开可查证的整改记录只有 Cloudflare 自己的复盘清单);Cox 2007 年"简单而快"的判断在近 20 年后基本应验。但 Perl 系表达力仍在膨胀(ES2024 的v标志、ES2025 的RegExp.escape等,见横轴),两大阵营并存的局面不会终结。
横轴:横向对比
1. 赛道鸟瞰:正则引擎的三大流派
理论基础来自 Russ Cox 的经典系列文章(Implementing Regular Expressions)。在 2026-10-29 的时间截面上,整个赛道可切成三个流派——这是理解所有差异的钥匙:
| 流派 | 核心算法 | 最坏复杂度 | 代表 |
|---|---|---|---|
| 回溯型 NFA | 把正则编译成指令序列,逐字符尝试、失败则回退重试 | 指数级 O(2ⁿ)(灾难性回溯) | PCRE2、java.util.regex、V8/Irregexp、Python re、.NET 默认引擎 |
| 自动机模拟(Thompson NFA / DFA) | 同时跟踪所有可能的状态集合,绝不回退 | 线性 O(m·n) | RE2、Rust regex、Go regexp、RE2J、.NET NonBacktracking |
| 混合/SIMD 加速 | 在自动机之上叠加字面量预筛(memchr/Teddy)、懒 DFA、多模式位并行 | 线性 + 低常数因子 | Rust regex 元引擎、RE2 懒 DFA、Vectorscan(原 Hyperscan) |
回溯引擎赢在功能(反向引用 \1 是真数学边界——Aho 1990 年证明含反向引用的匹配为 NP 完全,自动机模型装不下它;环视则不然,其中定长/有限宽度的部分在自动机模型里本可表达),线性引擎赢在可预测性(ReDoS 免疫)。两条路线的核心取舍正是课题标题里那句"设计取舍"。
2. 能力与理论保证对比
| 引擎 | 内核算法 | 最坏时间 | Lookaround 环视 | 反向引用 \1 | 捕获组 | Unicode 支持 |
|---|---|---|---|---|---|---|
| RE2 (C++) | 懒 DFA + NFA 模拟(OnePass/BitState/PikeVM) | O(m·n) | ❌(issue #365) | ❌ | ✅ | 部分(UTF-8,\p{...} 类) |
Rust regex | 元引擎:懒 DFA / one-pass DFA / 有界回溯 / PikeVM(docs.rs) | O(m·n) | ❌(可用 fancy-regex 补) | ❌(同上) | ✅ | 最强:UTS#18 Level 1,\w\d\s\b 默认 Unicode 感知 |
Go regexp | RE2 移植(NFA/DFA) | O(m·n)(官方保证) | ❌ | ❌ | ✅ | UTF-8 感知 |
| .NET NonBacktracking | 符号化 NFA 模拟(RE2 式) | O(m·n) | ❌(不支持环视) | ❌ | ✅(.NET 7/8 ❌,.NET 9 起支持,循环内仅保留最后捕获) | ✅ |
| RE2J (Java) | RE2 纯 Java 移植 | O(m·n) | ❌ | ❌ | ✅ | 部分 |
| PCRE2 (C) | 回溯 + JIT | 指数级 | ✅ | ✅ | ✅ | ✅(UCP 模式) |
| V8/Irregexp (JS) | 回溯(字节码解释器 + JIT) | 指数级 | ✅ | ✅ | ✅ | ✅(ES2024 v 标志引入集合运算) |
| java.util.regex | 回溯 | 指数级 | ✅ | ✅ | ✅(含占有量词 *+、原子组) | 部分 |
| .NET 默认引擎 | 回溯 | 指数级 | ✅ | ✅ | ✅(balancing groups 等超集功能) | ✅ |
Python re | 回溯 | 指数级 | 部分(lookahead + 定长 lookbehind) | ✅ | ✅ | 部分 |
| Vectorscan(原 Hyperscan) | SIMD + NFA/DFA 混合,多模式 | 线性(受限语法) | ❌(官方开发文档将 lookaround 归入"任意零宽断言"不支持项;仅 prefilter 模式下被近似替换为更宽的构造) | ❌ | 仅语法接受("capturing is ignored",不报告子匹配) | 侧重字节流/字面量 |
(注 1:横轴章节原表将 .NET NonBacktracking 的捕获组标为 ❌,对应 .NET 7/8 的状态;经本次联网核实,.NET 9 已补上捕获组支持,但循环内捕获仅保留最后值,且反向引用、环视、原子组、平衡组、条件表达式至今不支持——Microsoft Learn。)
(注 2(本稿更正):初稿此表曾把 Vectorscan 的环视标为"部分(受限环视)"、捕获组标为"✅(代价高)"。经查 Hyperscan/Vectorscan 官方开发文档,\b/\B 之外的零宽断言(即 lookaround)与反向引用同列"不支持构造",仅在 HS_FLAG_PREFILTER 模式下被内部替换为可能多匹配的更宽构造、再由精确匹配器确认;捕获组为"capturing is ignored"(语法接受、运行时不报告子匹配)——本稿已更正(dev-reference: Compilation,参考来源 25)。)
RE2 与 Rust regex 都刻意不支持 lookaround 与反向引用,但两类"不支持"的性质不同,值得拆开说清:反向引用是数学边界——Aho 1990 年证明含反向引用的模式匹配是 NP 完全问题(纵轴阶段二),"记住之前捕获了什么并回头重试"在自动机模型里根本没有位置,线性引擎排除它是承认数学、不是偷懒;环视不是数学边界——定长(有限宽度)的环视只依赖输入中一个有界窗口内的字符,自动机理论上完全可以表达,RE2/Rust regex/Go/.NET NonBacktracking 不做它,是实现复杂度与保证范围的取舍:多支持一类构造,就多一类要证明、要测试、要为它兜底的路径,而它们选择把保证收紧到最常用的子集。RE2 官方 Syntax Wiki 明确将环视列为不支持项(Syntax),社区 issue(#365、#156)长期开放、明确拒绝。Rust regex 文档同样声明语法"缺少若干无法高效实现的功能"(docs.rs/regex)。
Unicode 是 Rust regex 的独门优势:默认实现 UTS#18 Level 1,\w、\d、\b 默认 Unicode 感知,支持简单大小写折叠与 \p{...} 属性类(docs.rs)。RE2/Go 只覆盖 UTF-8 解码层面的正确性,属性类支持远弱于 Rust。JS 侧 ES2024 的 v 标志带来字符类集合运算与多码点 \q{...}(V8 官方博客),ES2025 加入 RegExp.escape(tc39/proposal-regex-escaping)、重复命名捕获组(tc39/proposal-duplicate-named-capturing-groups,Stage 4 于 2024-04)与内联模式修饰符(tc39/proposal-regexp-modifiers,Stage 4)——Perl 系的表达力竞赛仍在继续。
3. 工程与生态对比(截至 2026-10-29)
| 引擎 | 当前版本 | 维护状态 | 性能定位 | 内存特征 | 典型场景 |
|---|---|---|---|---|---|
| RE2 | 最新 release 2025-11-05(Russ Cox 签发,releases) | 活跃但节奏放缓(2024-07 至 2025-07 停更一年,2025 下半年恢复) | 快且稳定,多线程友好(DFA 缓存按互斥锁共享) | 懒 DFA 缓存可被 max_mem 上限约束(机制与降级行为见下文"结论与选型建议"第 3 节) | C++ 服务端、Google 内外基础设施、不可信输入 |
Rust regex | 1.13.1(2026-07-15,lib.rs) | 全赛道线性引擎中维护最活跃 | 顶级:SIMD 字面量预筛(memchr/Teddy/aho-corasick)+ 懒 DFA;简单模式略逊于 PCRE2 JIT/V8 | 缓存有界,超限自动降级 NFA 模拟(同上见第 3 节);regex-lite 可换小体积 | Rust 生态默认选择、ripgrep、WASM |
Go regexp | 随 Go 标准库发布 | 官方维护,极稳 | 正确性优先,常数因子大(基准中常慢于 PCRE2 JIT 数倍,mariomka/regex-benchmark) | 低 | 标准库自带、不可信输入、运维工具 |
| .NET NonBacktracking | .NET 7+ 内建 | 微软官方 | 慢于默认引擎数倍,但线性安全 | 高(自动机状态) | 用户自定义规则、防 ReDoS 的 SaaS 场景 |
| RE2J | 1.8(2025-01-09,MVNRepository) | 低频维护,"稳定但近乎冻结" | 简单模式慢于 java.util.regex(倍数量级,经验值待核实——常被引用的 mariomka 基准含 Java 但不含 RE2J/Joni),但最坏情况优雅降级 | 中 | Java/Scala 处理不可信模式 |
| PCRE2 | 10.47(2025-10,releases) | 活跃(10.46 修复过 pcre2_match.c 栈溢出 CVE-2025-58050) | JIT 开启时常为全场最快 | 可用 match_limit/heap_limit 限额 | PHP、Apache、Nginx 模块等参考级引擎 |
| V8/Irregexp | 随 Chromium/Node 发布 | 活跃 | 简单模式极快(JIT),字面量优化强 | 中 | 前端/Node.js 一切场景 |
| Hyperscan / Vectorscan | Hyperscan 5.4.2 为最后开源版,5.5+(2024-05 起)闭源;社区继承者 Vectorscan(VectorCamp)活跃维护,已支持 x86/ARM/Power | Vectorscan 活跃,Fedora 等已换装 | 多模式海量并行匹配最强(SIMD) | 编译期数据库很大,按需分区 | IDS/IPS(Suricata)、DPI、日志审计 |
Python re | 随 CPython 发布 | 官方维护 | 基准中普遍垫底 | 低 | 胶水脚本、一次性任务 |
4. 性能维度:线性保证 ≠ 一定最快
跨引擎基准 mariomka/regex-benchmark(对比 Rust、Go、JS、PCRE2、.NET、PHP、Python 等)的总体结论:PCRE2 JIT 与 .NET 回溯引擎在简单模式上常常最快,V8 出人意料地有竞争力,Rust regex 稳定且快,Go regexp 明显偏慢。更早的 Rust Leipzig 引擎对比(PCRE/PCRE2/RE2/Oniguruma/Rust regex)也呈现类似格局:RE2/Rust 慢于开启 JIT 的 PCRE2,但被病态模式攻击时毫发无损。
补一张带具体数字与条件的表(同一来源 mariomka/regex-benchmark,Email/URI/IPv4 三项基准的总计耗时;条件:MacBook Pro 16-inch 2019 / 2.4 GHz Intel Core i9 / 32 GB / macOS Big Sur 11.2.3 / Docker,各引擎版本为 PCRE2 10.36、Rust regex 1.4.5、Go 1.16.2、OpenJDK 11.0.10、node v15.13.0、.NET Core 5.0.201——版本较旧,名次随模式与版本会波动,只代表这一组固定模式):
| 引擎(版本见上) | 三项基准总计(ms) |
|---|---|
| Rust(regex 1.4.5) | 57.63 |
| JavaScript(node v15.13.0) | 107.73 |
| C(PCRE2 10.36) | 251.37 |
| C#(.NET Core 5.0.201) | 263.81 |
| Java(OpenJDK 11.0.10,回溯) | 708.01 |
| Go(1.16.2) | 850.32 |
由上表数字相除:这套基准里 Rust regex 比 PCRE2 快约 4 倍、比 Go regexp 快约 15 倍——"线性保证"与"好情况最快"并不互斥。但反过来的教训也一样:这些是固定三项模式的成绩,病态模式一旦进来,差距会从"倍数"变成"能不能跑完"(Cloudflare 一条规则打挂全球边缘,见纵轴阶段五)。
换句话说:回溯引擎的快是"好情况下的快",线性引擎的快是"最坏情况也不坏"。对内部可控模式(编译器、编辑器语法高亮),前者合理;对面向互联网的输入(WAF 规则、日志管道、用户自定义过滤),后者才是唯一安全选择。
5. 成本维度:线性引擎的钱花在别处
线性引擎单次匹配复杂度有保证,成本在编译期、内存与降级路径:RE2/Go/Rust regex 编译期通常更慢、编译产物更大(经验值,量级随模式而定;Rust 侧缓解手段明确——Unicode 特性可用 crate feature 逐项裁剪(docs.rs),或直接换二进制体积更小的 regex-lite);.NET NonBacktracking 曾长期不支持捕获组(.NET 9 才补上),很多依赖捕获的业务逻辑要重写;运行期还有 lazy DFA 缓存超限的隐式降级——RE2 与 Rust regex 的缓存超限都会清空重建、反复超限退回更慢的引擎路径(慢,但结果不变、复杂度仍受控;机制与两道闸门的分工集中见下文"结论与选型建议"第 3 节);Vectorscan/Hyperscan 模式数据库的内存占用可观("动辄数十 MB"为经验值,待核实,量级随规则集而定),需按场景分区。回溯引擎的成本则是运维事故风险——2016 年 Stack Overflow(约 34 分钟)与 2019 年 Cloudflare(约 27 分钟)两次全球级宕机(细节见纵轴阶段五)的共同教训是:QA 测试只测"功能对不对",不测"CPU 时间",病态模式轻松通过测试进入生产。线性引擎最大的卖点,是把"正则不会爆炸"从运维纪律变成数学保证。
交叉洞察
1. 两轴结合说明了什么
(1)历史是钟摆,推力是需求变迁。 纵轴呈现的不是线性进步,而是一次完整摆动:1968 年 Thompson 证明线性可行(编辑器场景,语法极简)→ 1986–1994 年表达力革命,回溯上位(脚本语言场景,用户要捕获组和反向引用)→ 2010 年 RE2 让线性时间回归(云服务场景,输入不可信)→ 2020 年代平台把非回溯内化为默认演进方向(边缘/多租户场景,一条慢正则就是全球事故)。每次摆动的推力都不是算法突破,而是"正则跑在什么环境里"变了。Thompson 构造法 1968 年就有了,它等待了 40 年,等的不是更好的算法,而是 2006 年那个会把任意用户正则直接跑起来的 Code Search。
(2)"砍功能"里一半是数学边界,一半是工程取舍。 横轴对比表里那一列 ❌ 要拆开看:反向引用 ❌ 是 Aho 1990 年 NP 完全结论的直接推论(纵轴阶段二)——"记住之前捕获了什么并回头重试"在自动机模型里根本不存在,RE2、Rust regex、.NET NonBacktracking 三家独立排除它,是行业在承认数学边界;环视 ❌ 则不是数学边界——定长(有限宽度)的环视只依赖输入的一个有界窗口,自动机理论上表达得了,主流线性引擎不支持它,是实现复杂度与保证范围的取舍,不是数学不可能。把两者混为一谈("不支持 = 数学不可能")会让"数学边界"这个词失去分量,也会错判迁移成本:反向引用换引擎必须改写,环视只是"当前引擎没提供"。RE2 砍功能、Rust regex 砍功能、.NET NonBacktracking 砍功能,三家独立做出了同样的取舍——对反向引用,这是承认边界;对环视,这是收紧保证。反过来,fancy-regex 这类混合方案给出了体面的折中:默认线性,显式付费(退回回溯语义)解锁表达力。(更正说明:初稿曾以 Vectorscan 作为"线性引擎支持受限环视"的例证;经查 Hyperscan/Vectorscan 官方开发文档,lookaround 属"任意零宽断言"不支持项,仅在 prefilter 模式下被近似替换,本稿已撤回该例证——见横轴对比表注 2。)
(3)事故把性能问题变成了安全问题,把运维纪律变成了数学保证。 Stack Overflow(2016)与 Cloudflare(2019)之前,"小心嵌套量词"只是 Friedl 书里的最佳实践;之后,"迁移到 re2 或 Rust regex"写进了 Cloudflare 的官方整改清单。横轴的基准数据(PCRE2 JIT 好情况下最快)与纵轴的事故史结合起来看,结论很清晰:均值没有意义,尾部才是命门。回溯引擎的指数尾部决定它不能直接碰不可信输入,这与算法快慢无关,与"谁的输入"有关——这正是横轴结论"模式谁写的、输入谁的"这条分界线的由来。
(4)语义分叉是被低估的迁移成本。 纵轴阶段二的 leftmost-first vs leftmost-longest 之争(Perl 系 vs POSIX 系)延续至今(具体例子与迁移改写收在下文"结论与选型建议"第 4 节,此处不重复)。换成性能更高或更安全的引擎时,最先翻车的往往不是不支持的功能(那些会直接报错),而是这种静默的语义差异——匹配结果变了,测试还不一定覆盖。迁移成本的大头在语义兼容,不在性能对齐。
(5)原创者放缓时,思想通过移植完成统治。 RE2 本尊 2024-07 至 2025-07 停更一年、节奏肉眼可见放缓(横轴 §3),但它的设计(懒 DFA + NFA 模拟、砍掉不可线性化的语法)早已通过 Go 标准库、.NET NonBacktracking、Rust regex 三条路径渗透整个行业——Google 自己的产品线(BigQuery)也在用。与之对照,Hyperscan 2024 年转闭源则是反面教材:特种引擎一旦供应链断供,生态只能靠社区分叉(Vectorscan)续命。开源引擎的"活着的分叉"本身就是基础设施韧性的一部分。
(6)赢家通吃的不是引擎,是 API 契约。 Rust regex 1.0 前的 RFC 1620 把"所有搜索最坏 O(n)"写进了类型签名——用户无须读源码、无须跑基准,编译器替他守住了这条底线。Cloudflare 整改清单要的也是"runtime guarantee"而非"更快"。2026 年的格局里,回溯引擎仍占据好情况最快的生态位,但"性能下限写进 API 契约"这个 RE2/Rust regex 开创的模式,已被 .NET(NonBacktracking 选项)和 V8(非回溯兜底引擎)采纳为平台默认演进方向。
结论与选型建议
以下指南由纵轴与横轴两章的避坑指南合并去重而成(原为"交叉洞察"下的二级章节,现提为一级章节,便于按目录直达)。
1. 引擎选型决策树(最重要的一个决定)
- 模式来自用户/外部系统(WAF 规则、用户过滤器、配置文件)→ 必须线性引擎:Go
regexp、RE2、Rustregex、.NETRegexOptions.NonBacktracking、Java 用 RE2J、JS 用 node-re2/re2js。任何回溯引擎 + 用户模式 = 潜在 ReDoS。Cloudflare 事故复盘的原话就是整改项:"切换到 re2 或 Rust regex"(复盘)。 - 模式由自己写、输入可信、需要环视/反向引用 → PCRE2(开 JIT)或各语言原生引擎,配合下文第 3 节(工程纪律)的护栏。
- 单文本并行匹配上万条模式(IDS/DPI/网关)→ Vectorscan(Hyperscan 5.5+ 已闭源,别再新装,VectorCamp/vectorscan)。
- Rust 里就是想要 lookaround/backref →
fancy-regex,并接受"花哨部分退回回溯语义、失去最坏情况保证"(lib.rs)。 - Java:简单模式用原生
java.util.regex(快),不可信模式换 RE2J(稳)。
2. 写法层面:先消灭灾难,再谈常数(换什么引擎都成立)
- 三大回溯炸弹模板(在任何回溯引擎上都是指数级):嵌套量词
(a+)+、(.*)*;歧义交替(a|aa)+;嵌套有界重复((a{1,10}){1,100}){1,100}(本稿实测更正:叠加量词的写法(a{1,10}){1,100}{1,100}是非法语法——Pythonre报multiple repeat at position 16、Node/V8 报Nothing to repeat;上表的合法嵌套写法语义相同,两个引擎均可编译)。最后一种在线性引擎里运行期也不会"爆炸匹配",但会让编译期状态数指数膨胀——Rust regex 会用RegexBuilder::size_limit直接拒绝编译(这道编译期闸门的机制,与运行期缓存降级的分工,统一见下文第 3 节工程纪律;docs.rs)。 - 给回溯引擎上保险丝:PCRE2 用占有量词
a*+、原子组(?>...)(PCRE2 会自动"占有化"很多模式),C 层再兜底match_limit/depth_limit/heap_limit;Java 用 possessive quantifier;.NET 设matchTimeout;网关层超时兜底。 - 锚定与字面量优先:模式里保留明确的锚点
^/$/\b和首字面量,让引擎的预筛优化(Rust 的 memchr/Teddy、V8/PCRE2 的字面量提升)能快速跳过不可能位置。开头的裸.*是预筛杀手。 - 用窄字符类替代
.:[^,]*比.*又快又准,在任何引擎上都成立。 - 交替分支提取公共前缀、按"最长/最具体优先"排序:回溯引擎按书写顺序取第一个成功,顺序即语义、也即性能。
- 不需要捕获就用
(?:...):回溯引擎省栈;线性引擎里捕获组会被分到更慢的引擎路径(PikeVM/one-pass 而非 lazy DFA),省捕获组是实打实的性能。 - 大集合字符串匹配别写 500 分支的巨型交替:用 Aho-Corasick(Rust
aho-corasick、rg -F),或依赖 Rust regex/RE2 自动做的字面量提取优化。
3. 工程纪律
- 预编译、编译一次处处复用:Rust 用
LazyLock<Regex>(官方文档明确警告不要在循环里重新编译);.NET 7+ 首选[GeneratedRegex]源生成器;Go 用regexp.MustCompile包级变量;JavaPattern.compile放循环外;PCRE2 复用pcre2_code并调用 JIT 编译。 - 只要判断就别提取:需要布尔结果时用
is_match而不是captures——捕获槽填充是真金白银的开销(docs.rs)。 - 警惕"全量枚举匹配"的二次方陷阱:单次搜索线性,但
find_iter把整个文本扫 n 次,最坏 O(m·n²)。长文本上先分块,或用find手动推进起始位置(.NET 侧独立分析)。 - 超时与资源限额是标配而非可选项:.NET
Regex构造函数带matchTimeout;PCRE2 有三层限额;Java 原生引擎没有内建超时,只能包一层可中断执行器。线性引擎没有超时旋钮,靠两道内存闸门,职责不同、别混为一谈(本文其余提到限额处均以本条为准):- 编译期闸门——
size_limit防编译期产物超限:RustRegexBuilder::size_limit限定编译产物的近似字节体积,超限直接编译失败(docs.rs:"If the regex would otherwise approximately exceed this limit, then compiling that regex will fail")。上文第 2 节炸弹模板"线性引擎运行期不爆炸、编译期拒绝"走的就是这道闸门。 - 运行期闸门——
max_mem/dfa_size_limit管运行期 DFA 缓存上限,超限降级是运行期成本:RE2 的max_mem(默认 8 MB)约束整个 RE2 对象的内存足迹,其中 lazy DFA 的状态缓存撞到预算会清空重建,re2.h 原文 "Once a DFA fills its budget, it flushes its cache and starts over. If this happens too often, RE2 falls back on the NFA implementation."(re2.h);Rust 的对应旋钮是dfa_size_limit——缓存重置、反复触发则放弃 lazy DFA、切换到别的引擎路径(docs.rs)。降级后慢但结果不变,复杂度仍受控。 - 一句话:"线性"是数学保证,"不降级"是工程配置。监控编译时间与内存,必要时拆分模式或调大限额。
- 编译期闸门——
- CI 里加 CPU 时间断言:Cloudflare 事故的直接教训——功能测试不涨耗时,不代表生产 CPU 不爆炸。用病态语料(长串
aaaa...、嵌套括号)做 fuzz + 耗时断言,或接入 recheck/safe-regex 类静态检查;npm 依赖的 ReDoS 公告(CWE-1333)要当真(生态实测论文)。 - JS 特有:拼接正则务必用 ES2025 的
RegExp.escape(tc39)转义用户输入;面向不可信模式的 Node 服务可换node-re2绑定。 - 线程与共享:Rust 多线程共享同一个
Regex,在高并发、超短输入的场景下,内部缓存池会成为争用点——官方文档明列该情形并建议"每线程用独立副本(通常是 clone 一份)":clone 不深拷贝只读部分、各得一份缓存池,与共享相比不额外增加内存(regex-automata meta::Regex 文档,参考来源 23);PCRE2 的match_data不应跨线程复用。 - 量词给上限、输入先校验:
{0,64}优于*,匹配前先校验输入长度。
4. 换引擎时的语义坑(迁移成本的大头)
- leftmost-first(Perl/PCRE/Go/Rust regex 默认)vs leftmost-longest(POSIX、RE2 POSIX 模式):
a|ab对"ab",前者返回a,后者返回ab——迁移时最先翻车的地方(Cox)。这就是交叉洞察(4)说的"静默语义差异":它不报错,只悄悄换答案。 - RE2/Rust regex 不支持反向引用与环视:移植老正则要么改写、要么接受性能换表达力(fancy-regex 路线)。改写思路见下面的迁移改写示例。
- 迁移改写示例(环视/反向引用 → 线性引擎,前三条最常用):
- 定宽环视 → 捕获组。
(?<=@)\w+(取 @ 后的用户名)改写为@(\w+),取第 1 组;\d+(?=px)改写为(\d+)px。环视只是"不消耗字符",捕获组版本会多消耗一个锚字符——提取场景直接取组即可;替换场景把锚字符原样拼回(如替换文本写@$1)。 - 反向引用 → 两步法。查重复单词
\b(\w+) \1\b:线性引擎改为\b(\w+) (\w+)\b,把相邻词对匹配出来,再在应用层比较两组内容是否相同。反向引用"记住捕获并回头比对"的本征能力自动机没有(NP 完全,见交叉洞察(2)),但多数业务里"匹配"与"比对"可以拆开做。 - 实在改不动 → 显式付费并隔离。Rust 用 fancy-regex(普通部分走底层线性 NFA,环视/反向引用退回回溯 VM);JS 用 node-re2 时,把带环视的少数模式留在原生回溯引擎并加超时。原则:花哨构造只碰可信输入或小字符串,别让它们扫不可信的大输入。
- 定宽环视 → 捕获组。
- Unicode 语义与 flag 差异:
\d/\w的 Unicode 语义、.是否含换行(Rust 默认不含)、大小写/多行 flag 各家不同。Rust 里纯 ASCII 热点路径用(?-u:...)显式关闭 Unicode 提速且避免意外匹配;这些 flag 会显著增大自动机——模式过于庞大可能触到默认编译体积上限(调大size_limit或拆分模式;size_limit的编译期语义见上文第 3 节)。 - Go 的性能位次要有预期:标准库
regexp正确性优先、常数偏大(官方保证线性,但基准中常慢于 PCRE2 JIT 数倍);热点路径先用strings.Contains/bytes预筛,再决定是否引入 PCRE2 绑定。
5. 高频/大输入调优
- 搜固定串别用正则:
rg -F或 memmem 就够了——这是上文第 2 节"锚定与字面量优先"的极端形式,连预筛都省了。 - 线性引擎的"隐式降级"要知道:lazy DFA 缓存超限 → 清空重建/退回更慢的 NFA 路径,属运行期成本而非正确性问题;机制与两道闸门的分工见上文第 3 节,此处只留运维动作:监控编译时间与内存,必要时拆分模式(Rust 侧可调
size_limit/dfa_size_limit,或换regex-lite换小体积)。 - 流式/海量模式场景:考虑 Vectorscan(多模式一次扫描)而非循环单模式匹配。
6. 一句话收束
把两条轴拧成一股:纵轴解释了横轴为什么长成这样——RE2 家族的每一次"砍功能",都是对某次具体事故(或某个还没发生的事故)的回应;横轴检验了纵轴的结论——Cox 2007 年"简单而快"的判断,在 2026 年的基准、生态与平台演进中全部应验。对工程师的可操作结论只有一条分界线:模式谁写的、输入谁的。不可信输入,上线性引擎(Go regexp / RE2 / Rust regex / .NET NonBacktracking / RE2J),把"不会爆炸"交给数学;自己写的模式碰可信输入,才轮到回溯引擎(PCRE2 JIT / 各语言原生),并配上占有量词、超时限额与 CI 耗时断言。两大阵营的并存不会终结——表达力仍在膨胀(ES2024/2025),安全底线也在抬升(非回溯成为平台默认)——但分界线本身,已经从学者的清谈变成了全行业的共识。
参考来源
核心文献与作者原著
- Cox: Regular Expression Matching Can Be Simple And Fast(2007-01)
- Cox: Regular Expression Matching: the Virtual Machine Approach(2010-01)
- Cox: Regular Expression Matching in the Wild(2012-01)
- Hazel《A Brief History of PCRE》 · pcre.org
- Davis et al. ESEC/FSE 2018:The Impact of ReDoS in Practice(含 ReDoS 术语出处 2009 OWASP/Checkmarx) · CWE-1333
- Gallant(BurntSushi): Regex engine internals as a library
引擎官方与版本
- google/re2 · RE2 Syntax Wiki · re2 releases · issue #365(拒绝环视) · issue #156
- google/re2j(Java) · MVNRepository: re2j 1.8(2025-01-09) · le0pard/re2js(JS)
- Go 1.1 Release Notes(regexp 重写为线性时间) · pkg.go.dev/regexp(线性保证)
- RFC 1620: regex 1.0 · regex CHANGELOG(1.0.0=2018-05-01,1.9.0=2023-07-05) · Announcing regex 1.9(Rust 博客) · rust-lang/regex issue #656 · docs.rs/regex · lib.rs/crates/regex(1.13.1,2026-07-15) · fancy-regex
- .NET 7 发布说明 · .NET 博客:Regular Expression Improvements in .NET 7 · Microsoft Learn:Regular expression options(NonBacktracking 能力边界) · dotnet/runtime issue #135585
- PCRE2 releases(10.47,2025-10) · PCRE2 官网
- V8: An additional non-backtracking RegExp engine(2021) · V8: RegExp v flag · tc39/proposal-regex-escaping(RegExp.escape) · Mozilla Hacks: A New RegExp Engine in SpiderMonkey(2020-06)
- intel/hyperscan(2015 开源,2023 归档) · VectorCamp/vectorscan · Suricata 文档:Hyperscan/Vectorscan
- BigQuery 正则函数文档(采用 RE2) · OpenJDK JDK-8260688(TRegex/线性匹配讨论) · ripgrep 早期记录 issue #132(2016-09) · crates.io/crates/ripgrep
历史与事故
- Wikipedia: Regular expression(Kleene/McCulloch-Pitts 历史) · Wikipedia: Grep(V4 Unix, 1973-03) · Thompson 1968 论文相关讨论
- Henry Spencer 正则库谱系 · regexp.old · perl.plover.com: Dirty Secrets of the Perl Regex Engine · perlhist(Perl 1.000=1987-12-18,5.000=1994-10-17) · POSIX 规范第 9 章(leftmost-longest) · 正则引擎谱系(Levithan)
- Cloudflare 复盘:Details of the Cloudflare outage on July 2, 2019 · Stack Overflow 复盘(2016-07-20) · Accidentally Quadratic 技术复盘
基准与生态
本次综合会话新增核实(2026-10-29)
- Cloudflare 官方复盘全文抓取核实:中断约 27 分钟;正则为
.*(?:.*=.*);13:42 UTC 上线问题规则、14:07 全局熔断关闭 WAF、约 2 分钟后恢复(自 13:42 起算约 27 分钟)、14:52 重新启用;整改清单第 4 条原文"Switching to either the re2 or Rust regex engine which both have run-time guarantees"。 - Hyperscan 转闭源与 Vectorscan 接棒核实:OPNsense 论坛"Intel killed Hyperscan"(2024-05-10)(Intel 使 Hyperscan 5.5+ 转专有许可)· IPFire 社区(2024-05-13)(5.4.2 为最后开源版)· Fedora 弃包公告 · Netgate 论坛(VectorCamp 声明 5.4 之后闭源)。原纵轴章"2023 归档仓库"与原横轴章"2024-05 转闭源"两说并存实为同一事件的两个节点,本文按时间线合并。
- .NET NonBacktracking 捕获组支持核实:Microsoft Learn 正则选项文档(NonBacktracking 不支持反向引用/环视/原子组/平衡组/条件表达式,循环内捕获仅保留最后值)· dotnet/runtime #61903 · #79116 · #135585。原横轴表"不支持捕获组"对应 .NET 7/8,.NET 9 起已支持,本文已修正。
终稿修订新增核实(2026-10-29)
- google/re2 re2.h(RE2::Options::max_mem 注释:缓存清空与 NFA 回退原文) · docs.rs: regex RegexBuilder(size_limit=编译期拒绝;dfa_size_limit=运行期缓存重置与引擎切换) · docs.rs: regex-automata meta::Regex(线程共享池争用与"每线程克隆"官方建议)
- NVD: CVE-2025-58050(PCRE2
pcre2_match.c栈溢出,10.46 修复) - Hyperscan dev-reference: Compilation(
\b/\B之外的零宽断言列为不支持构造;prefilter 模式近似替换;"capturing is ignored") · Mozilla Bugzilla bug 1690142("not yet mature enough for us to import",2021-02) - tc39/proposal-duplicate-named-capturing-groups(Stage 4,2024-04,ES2025) · tc39/proposal-regexp-modifiers(Stage 4,ES2025) · TRE(laurikari/tre:始终返回正确 submatch、可预测 O(M²N) 最坏时间) · docs.rs: regex-lite