研究报告 · 约 30,029 字 · 预计推送 4 部分

正则引擎原理:NFA/DFA、回溯灾难,RE2 与 Rust regex 的设计取舍 未开始

目录

题记与说明

综合报告说明:本文由课题两份章节综合而成——纵轴《正则引擎原理: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 节一次讲清,其余四处改为交叉引用、删除重复;③ 修正回溯炸弹样例的非法叠加量词写法(Python re 与 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)——"正则"从神经科学里长出来

驱动因素:纯理论(数理逻辑、神经建模),与"文本搜索"的工程实践尚未合流。

阶段一:从论文到工具——Thompson 构造法与 grep(1968–1979)

驱动因素:Unix 文本工具文化 + 编译器词法分析需求;此时的正则语法仍极简(无捕获组、无反向引用),线性时间毫无压力。

阶段二:两条语义路线分叉,回溯引擎上位(1980s–1996)

驱动因素:脚本语言的表达力竞赛(动态文本处理是 Perl 的立身之本)+ Unix 标准化。回溯引擎赢了市场:捕获组、反向引用、环视这些 Perl 特性当年只有回溯实现可用——"要捕获就得回溯"在当时是工程现实而非数学必然(线性时间与捕获组的兼容性要到 2000 年代初才由 TRE 证明,见阶段四;真正的数学边界只有反向引用一条,见交叉洞察(2))。

阶段三:PCRE 时代与回溯灾难显形(1997–2006)

驱动因素:Web 兴起,不可信用户输入第一次大规模流入正则引擎;而 PCRE 式回溯引擎对恶意输入没有任何防护——灾难的舞台已经搭好,只等有人踩上去。

阶段四:Cox 三部曲与 RE2——线性时间的回归(2006–2013)

驱动因素:Google 规模的对抗性查询 + 云服务对"单请求不能拖垮全局"的可用性要求 + Cox 极具说服力的布道。设计取舍从此有了清晰框架:要么支持反向引用/环视并接受指数最坏情形(Perl 系),要么保证线性时间并放弃这些特性(RE2 系)。

阶段五:ReDoS 进入主流安全议程,两次全球宕机立碑(2014–2019)

事后复盘的整改清单第 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)

驱动因素:各大平台把"secure by default"提上日程——Web 引擎、语言标准库纷纷内置或实验非回溯路径;Cox 确立的取舍框架成为行业共识而非学者清谈。

当下格局(截至 2026-10)


横轴:横向对比

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 regexpRE2 移植(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 regex1.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 场景
RE2J1.8(2025-01-09,MVNRepository)低频维护,"稳定但近乎冻结"简单模式慢于 java.util.regex(倍数量级,经验值待核实——常被引用的 mariomka 基准含 Java 但不含 RE2J/Joni),但最坏情况优雅降级中Java/Scala 处理不可信模式
PCRE210.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 / VectorscanHyperscan 5.4.2 为最后开源版,5.5+(2024-05 起)闭源;社区继承者 Vectorscan(VectorCamp)活跃维护,已支持 x86/ARM/PowerVectorscan 活跃,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. 引擎选型决策树(最重要的一个决定)

  1. 模式来自用户/外部系统(WAF 规则、用户过滤器、配置文件)→ 必须线性引擎:Go regexp、RE2、Rust regex、.NET RegexOptions.NonBacktracking、Java 用 RE2J、JS 用 node-re2/re2js。任何回溯引擎 + 用户模式 = 潜在 ReDoS。Cloudflare 事故复盘的原话就是整改项:"切换到 re2 或 Rust regex"(复盘)。
  2. 模式由自己写、输入可信、需要环视/反向引用 → PCRE2(开 JIT)或各语言原生引擎,配合下文第 3 节(工程纪律)的护栏。
  3. 单文本并行匹配上万条模式(IDS/DPI/网关)→ Vectorscan(Hyperscan 5.5+ 已闭源,别再新装,VectorCamp/vectorscan)。
  4. Rust 里就是想要 lookaround/backref → fancy-regex,并接受"花哨部分退回回溯语义、失去最坏情况保证"(lib.rs)。
  5. Java:简单模式用原生 java.util.regex(快),不可信模式换 RE2J(稳)。

2. 写法层面:先消灭灾难,再谈常数(换什么引擎都成立)

3. 工程纪律

4. 换引擎时的语义坑(迁移成本的大头)

5. 高频/大输入调优

6. 一句话收束

把两条轴拧成一股:纵轴解释了横轴为什么长成这样——RE2 家族的每一次"砍功能",都是对某次具体事故(或某个还没发生的事故)的回应;横轴检验了纵轴的结论——Cox 2007 年"简单而快"的判断,在 2026 年的基准、生态与平台演进中全部应验。对工程师的可操作结论只有一条分界线:模式谁写的、输入谁的。不可信输入,上线性引擎(Go regexp / RE2 / Rust regex / .NET NonBacktracking / RE2J),把"不会爆炸"交给数学;自己写的模式碰可信输入,才轮到回溯引擎(PCRE2 JIT / 各语言原生),并配上占有量词、超时限额与 CI 耗时断言。两大阵营的并存不会终结——表达力仍在膨胀(ES2024/2025),安全底线也在抬升(非回溯成为平台默认)——但分界线本身,已经从学者的清谈变成了全行业的共识。


参考来源

核心文献与作者原著

  1. Cox: Regular Expression Matching Can Be Simple And Fast(2007-01)
  2. Cox: Regular Expression Matching: the Virtual Machine Approach(2010-01)
  3. Cox: Regular Expression Matching in the Wild(2012-01)
  4. Hazel《A Brief History of PCRE》 · pcre.org
  5. Davis et al. ESEC/FSE 2018:The Impact of ReDoS in Practice(含 ReDoS 术语出处 2009 OWASP/Checkmarx) · CWE-1333
  6. Gallant(BurntSushi): Regex engine internals as a library

引擎官方与版本

  1. google/re2 · RE2 Syntax Wiki · re2 releases · issue #365(拒绝环视) · issue #156
  2. google/re2j(Java) · MVNRepository: re2j 1.8(2025-01-09) · le0pard/re2js(JS)
  3. Go 1.1 Release Notes(regexp 重写为线性时间) · pkg.go.dev/regexp(线性保证)
  4. 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
  5. .NET 7 发布说明 · .NET 博客:Regular Expression Improvements in .NET 7 · Microsoft Learn:Regular expression options(NonBacktracking 能力边界) · dotnet/runtime issue #135585
  6. PCRE2 releases(10.47,2025-10) · PCRE2 官网
  7. 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)
  8. intel/hyperscan(2015 开源,2023 归档) · VectorCamp/vectorscan · Suricata 文档:Hyperscan/Vectorscan
  9. BigQuery 正则函数文档(采用 RE2) · OpenJDK JDK-8260688(TRegex/线性匹配讨论) · ripgrep 早期记录 issue #132(2016-09) · crates.io/crates/ripgrep

历史与事故

  1. Wikipedia: Regular expression(Kleene/McCulloch-Pitts 历史) · Wikipedia: Grep(V4 Unix, 1973-03) · Thompson 1968 论文相关讨论
  2. 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)
  3. Cloudflare 复盘:Details of the Cloudflare outage on July 2, 2019 · Stack Overflow 复盘(2016-07-20) · Accidentally Quadratic 技术复盘

基准与生态

  1. mariomka/regex-benchmark · Rust Leipzig 引擎对比(2017) · 二次方陷阱独立分析(iev.ee)

本次综合会话新增核实(2026-10-29)

  1. 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"。
  2. 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 转闭源"两说并存实为同一事件的两个节点,本文按时间线合并。
  3. .NET NonBacktracking 捕获组支持核实:Microsoft Learn 正则选项文档(NonBacktracking 不支持反向引用/环视/原子组/平衡组/条件表达式,循环内捕获仅保留最后值)· dotnet/runtime #61903 · #79116 · #135585。原横轴表"不支持捕获组"对应 .NET 7/8,.NET 9 起已支持,本文已修正。

终稿修订新增核实(2026-10-29)

  1. google/re2 re2.h(RE2::Options::max_mem 注释:缓存清空与 NFA 回退原文) · docs.rs: regex RegexBuilder(size_limit=编译期拒绝;dfa_size_limit=运行期缓存重置与引擎切换) · docs.rs: regex-automata meta::Regex(线程共享池争用与"每线程克隆"官方建议)
  2. NVD: CVE-2025-58050(PCRE2 pcre2_match.c 栈溢出,10.46 修复)
  3. Hyperscan dev-reference: Compilation(\b/\B 之外的零宽断言列为不支持构造;prefilter 模式近似替换;"capturing is ignored") · Mozilla Bugzilla bug 1690142("not yet mature enough for us to import",2021-02)
  4. 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

思考题(读完后想一想,参考答案可展开)

  1. 报告的核心选型判断是什么?“表达力与线性时间决裂”的数学根源出在哪里?
    参考答案

    一句话判断是看“模式谁写的、输入谁的”——不可信输入必须上线性引擎(Go regexp、RE2、Rust regex、.NET NonBacktracking、RE2J),可信场景才用回溯引擎加护栏。数学根源在 1994 年 Perl 5 引入反向引用:Aho 1990 年证明含反向引用的模式匹配是 NP 完全问题,任何想支持反向引用的引擎都无法保证最坏情形多项式时间,表达能力与线性时间自此决裂。

  2. 横向对比显示“线性保证 ≠ 一定最快”,报告中的基准数据如何支撑这一点?回溯引擎与线性引擎的“快”有何本质区别?
    参考答案

    mariomka 基准(Email/URI/IPv4 三项总计)中 Rust regex 57.63ms 最快,比 PCRE2(251.37ms)快约 4 倍、比 Go regexp(850.32ms)快约 15 倍;但报告同时指出 PCRE2 JIT 与 .NET 回溯引擎在简单模式上常常最快。本质区别在于:回溯引擎的快是“好情况下的快”,最坏情形指数级;线性引擎的快是“最坏情况也不坏”。病态模式一旦进来,差距从“倍数”变成“能不能跑完”——Cloudflare 一条 `.*(?:.*=.*)` 规则曾让全球边缘宕机约 27 分钟。

  3. RE2/Rust regex 等线性引擎都不支持环视与反向引用,报告如何区分这两种“不支持”的性质?为什么这一区分影响迁移成本的判断?
    参考答案

    反向引用是数学边界:Aho 1990 年证明含反向引用的匹配为 NP 完全,“记住之前捕获并回头重试”在自动机模型里没有位置,排除它是承认数学。环视不是数学边界:定长/有限宽度的环视只依赖输入的有界窗口,自动机理论上可以表达,主流线性引擎不做它是实现复杂度与保证范围的取舍。混淆两者会错判迁移成本:反向引用换引擎必须改写(如查重复单词改为匹配相邻词对再在应用层比对),环视则常可改写为捕获组(如 `(?<=@)\w+` 改成 `@(\w+)` 取第 1 组)。

  4. 对于必须处理用户自定义正则的个人开发者,报告在选型与工程纪律上给出了哪些可操作建议?
    参考答案

    选型上:模式来自用户或外部系统(如 WAF 规则、用户过滤器、配置文件)必须用线性引擎——Go regexp、RE2、Rust regex、.NET RegexOptions.NonBacktracking,Java 用 RE2J、JS 用 node-re2/re2js;单文本并行匹配上万条模式用 Vectorscan(Hyperscan 5.5+ 已闭源,别再新装)。工程纪律上:避免嵌套量词、歧义交替等回溯炸弹模板;预编译一次处处复用;只要判断就别提取(用 is_match 而非 captures);CI 里加 CPU 时间断言并用病态语料做 fuzz——功能测试不涨耗时,不代表生产 CPU 不爆炸。

← 资料库今日播报 →