[libc-commits] [libc] [libc][regex] Wire regex entrypoints to ExprPool matcher (PR #232291)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Fri Oct 16 07:55:50 PDT 2026
https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/232291
>From 4a68acf585fff7627aebd493623fe263c6039127 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Wed, 14 Oct 2026 21:16:55 +0100
Subject: [PATCH 1/3] [libc][regex] Wire regex entrypoints to ExprPool matcher
The initial regcomp, regexec, and regfree stubs copied the raw pattern
string into a heap buffer and delegated matching to inline_strstr,
leaving the hash-consed ExprPool arena unused by the public entrypoints.
In addition, POSIX requires regexec to be thread-safe across concurrent
callers sharing a compiled const regex_t handle, whereas lazy derivative
evaluation interns new AST states into the pool at match time.
Replace the string-copy stub with RegexInternal and a Brzozowski
derivative matcher (derivative and match) backed by ExprPool. regcomp
initialises preg->__internal to nullptr before allocating RegexInternal
so regfree is a safe no-op after a failed compilation, and folds the
pattern into the pool. regexec serialises lazy derivative interning on
the shared pool using SpinLock and cpp::lock_guard.
Assisted-by: Automated tooling, human reviewed.
---
libc/src/__support/regex/CMakeLists.txt | 22 +++
libc/src/__support/regex/regex_expr_pool.h | 3 +
libc/src/__support/regex/regex_internal.h | 41 +++++
libc/src/__support/regex/regex_matcher.cpp | 89 ++++++++++
libc/src/__support/regex/regex_matcher.h | 45 +++++
libc/src/regex/CMakeLists.txt | 21 ++-
libc/src/regex/regcomp.cpp | 54 ++++--
libc/src/regex/regexec.cpp | 45 ++---
libc/src/regex/regfree.cpp | 12 +-
libc/test/src/__support/regex/CMakeLists.txt | 14 ++
libc/test/src/__support/regex/expr_test.cpp | 32 ++++
.../test/src/__support/regex/matcher_test.cpp | 154 ++++++++++++++++++
libc/test/src/regex/regex_basic_test.cpp | 58 +++++++
13 files changed, 545 insertions(+), 45 deletions(-)
create mode 100644 libc/src/__support/regex/regex_internal.h
create mode 100644 libc/src/__support/regex/regex_matcher.cpp
create mode 100644 libc/src/__support/regex/regex_matcher.h
create mode 100644 libc/test/src/__support/regex/matcher_test.cpp
diff --git a/libc/src/__support/regex/CMakeLists.txt b/libc/src/__support/regex/CMakeLists.txt
index e72e74cd634518..4b1e772212c933 100644
--- a/libc/src/__support/regex/CMakeLists.txt
+++ b/libc/src/__support/regex/CMakeLists.txt
@@ -19,3 +19,25 @@ add_object_library(
libc.src.__support.macros.config
libc.src.string.memory_utils.inline_memset
)
+
+add_header_library(
+ regex_internal
+ HDRS
+ regex_internal.h
+ DEPENDS
+ .regex_expr_pool
+ libc.src.__support.macros.config
+ libc.src.__support.threads.spin_lock
+)
+
+add_object_library(
+ regex_matcher
+ SRCS
+ regex_matcher.cpp
+ HDRS
+ regex_matcher.h
+ DEPENDS
+ .regex_expr_pool
+ libc.src.__support.CPP.string_view
+ libc.src.__support.error_or
+)
diff --git a/libc/src/__support/regex/regex_expr_pool.h b/libc/src/__support/regex/regex_expr_pool.h
index fe3e9724c2a036..ec377863d6a8c2 100644
--- a/libc/src/__support/regex/regex_expr_pool.h
+++ b/libc/src/__support/regex/regex_expr_pool.h
@@ -118,6 +118,9 @@ class ExprPool {
/// O(1) nullability query for a valid ExprId.
bool is_nullable(ExprId id) const { return get(id).nullable; }
+ /// Returns the number of unique AST nodes currently interned in this pool.
+ size_t size() const { return node_count; }
+
/// Returns the current heap bytes allocated by this pool.
size_t allocated_bytes() const {
return (block_count * sizeof(Block)) + blocks.size_bytes() +
diff --git a/libc/src/__support/regex/regex_internal.h b/libc/src/__support/regex/regex_internal.h
new file mode 100644
index 00000000000000..d3fcac732b900b
--- /dev/null
+++ b/libc/src/__support/regex/regex_internal.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Internal compiled regular expression representation owned by regex_t.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_INTERNAL_H
+#define LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_INTERNAL_H
+
+#include "src/__support/macros/config.h"
+#include "src/__support/regex/regex_ast.h"
+#include "src/__support/regex/regex_expr_pool.h"
+#include "src/__support/threads/spin_lock.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace regex {
+
+/// Internal state stored in regex_t::__internal after a successful regcomp.
+struct RegexInternal {
+ /// Arena and hash-consing table for the pattern AST and derivative states.
+ ExprPool pool;
+ /// Root ExprId of the compiled regular expression.
+ ExprId root = INVALID_EXPR_ID;
+ /// Compilation flags passed to regcomp.
+ int cflags = 0;
+ /// Spinlock serialising lazy derivative interning across concurrent regexec
+ /// calls sharing the same const regex_t*.
+ SpinLock lock{};
+};
+
+} // namespace regex
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_INTERNAL_H
diff --git a/libc/src/__support/regex/regex_matcher.cpp b/libc/src/__support/regex/regex_matcher.cpp
new file mode 100644
index 00000000000000..d60097f0e228cc
--- /dev/null
+++ b/libc/src/__support/regex/regex_matcher.cpp
@@ -0,0 +1,89 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Brzozowski derivative evaluation and regular expression matching
+/// implementation.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/regex/regex_matcher.h"
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/regex/regex_ast.h"
+#include "src/__support/regex/regex_expr_pool.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace regex {
+
+ErrorOr<ExprId> derivative(ExprId id, char c, ExprPool &pool) {
+ const Expr e = pool.get(id);
+ switch (e.kind) {
+ case ExprKind::EmptySet:
+ case ExprKind::EmptyStr:
+ return EMPTY_SET_ID;
+ case ExprKind::Literal:
+ return (c == e.ch) ? EMPTY_STR_ID : EMPTY_SET_ID;
+ case ExprKind::Concat: {
+ // Product Rule:
+ // D_c(L . R) = (D_c(L) . R) | (nullable(L) ? D_c(R) : EmptySet)
+ const bool left_nullable = pool.is_nullable(e.bin.left);
+ ErrorOr<ExprId> d_l = derivative(e.bin.left, c, pool);
+ if (!d_l.has_value())
+ return d_l;
+ ErrorOr<ExprId> lhs = pool.make_concat(d_l.value(), e.bin.right);
+ if (!lhs.has_value() || !left_nullable)
+ return lhs;
+ ErrorOr<ExprId> d_r = derivative(e.bin.right, c, pool);
+ if (!d_r.has_value())
+ return d_r;
+ return pool.make_alt(lhs.value(), d_r.value());
+ }
+ case ExprKind::Alt: {
+ // Sum Rule: D_c(L | R) = D_c(L) | D_c(R)
+ ErrorOr<ExprId> d_l = derivative(e.bin.left, c, pool);
+ if (!d_l.has_value())
+ return d_l;
+ ErrorOr<ExprId> d_r = derivative(e.bin.right, c, pool);
+ if (!d_r.has_value())
+ return d_r;
+ return pool.make_alt(d_l.value(), d_r.value());
+ }
+ }
+ __builtin_unreachable();
+}
+
+MatchResult match(ExprPool &pool, const ExprId pattern,
+ cpp::string_view input) {
+ if (pool.is_nullable(pattern))
+ return MatchResult::Match;
+
+ // Unanchored substring search: at each byte, step active partial matches via
+ // derivative(state, c) and union with pattern so a new match can begin after
+ // the current byte (equivalent to matching .* . pattern without constructing
+ // a prefix wildcard node).
+ ExprId state = pattern;
+ for (char c : input) {
+ ErrorOr<ExprId> d = derivative(state, c, pool);
+ if (!d.has_value())
+ return MatchResult::OutOfMemory;
+ if (pool.is_nullable(d.value()))
+ return MatchResult::Match;
+ ErrorOr<ExprId> next = pool.make_alt(pattern, d.value());
+ if (!next.has_value())
+ return MatchResult::OutOfMemory;
+ state = next.value();
+ }
+
+ return MatchResult::NoMatch;
+}
+
+} // namespace regex
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/regex/regex_matcher.h b/libc/src/__support/regex/regex_matcher.h
new file mode 100644
index 00000000000000..759c7ebdafb258
--- /dev/null
+++ b/libc/src/__support/regex/regex_matcher.h
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Brzozowski derivative evaluation and regular expression matching interface.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_MATCHER_H
+#define LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_MATCHER_H
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/regex/regex_ast.h"
+#include "src/__support/regex/regex_expr_pool.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace regex {
+
+/// Outcome of evaluating a regular expression against an input string.
+enum class MatchResult {
+ Match,
+ NoMatch,
+ OutOfMemory,
+};
+
+/// Computes the Brzozowski derivative D_c(id) with respect to character c.
+///
+/// Formally, D_c(L) = { w | c . w in L }.
+ErrorOr<ExprId> derivative(ExprId id, char c, ExprPool &pool);
+
+/// Evaluates whether pattern matches any substring of input using lazy
+/// Brzozowski derivatives.
+MatchResult match(ExprPool &pool, const ExprId pattern, cpp::string_view input);
+
+} // namespace regex
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_MATCHER_H
diff --git a/libc/src/regex/CMakeLists.txt b/libc/src/regex/CMakeLists.txt
index 3db701645b26ad..4c984802554e59 100644
--- a/libc/src/regex/CMakeLists.txt
+++ b/libc/src/regex/CMakeLists.txt
@@ -5,13 +5,14 @@ add_entrypoint_object(
HDRS
regcomp.h
DEPENDS
- libc.hdr.types.regex_t
libc.hdr.regex_macros
- libc.src.__support.common
+ libc.hdr.types.regex_t
+ libc.src.__support.alloc_checker
libc.src.__support.CPP.new
libc.src.__support.CPP.string_view
- libc.src.__support.alloc_checker
- libc.src.string.memory_utils.inline_memcpy
+ libc.src.__support.error_or
+ libc.src.__support.macros.null_check
+ libc.src.__support.regex.regex_internal
)
add_entrypoint_object(
@@ -21,12 +22,15 @@ add_entrypoint_object(
HDRS
regexec.h
DEPENDS
+ libc.hdr.regex_macros
libc.hdr.types.regex_t
libc.hdr.types.regmatch_t
libc.hdr.types.size_t
- libc.hdr.regex_macros
- libc.src.__support.common
- libc.src.string.memory_utils.inline_strstr
+ libc.src.__support.CPP.mutex
+ libc.src.__support.CPP.string_view
+ libc.src.__support.macros.null_check
+ libc.src.__support.regex.regex_internal
+ libc.src.__support.regex.regex_matcher
)
add_entrypoint_object(
@@ -52,6 +56,7 @@ add_entrypoint_object(
regfree.h
DEPENDS
libc.hdr.types.regex_t
- libc.src.__support.common
libc.src.__support.CPP.new
+ libc.src.__support.macros.null_check
+ libc.src.__support.regex.regex_internal
)
diff --git a/libc/src/regex/regcomp.cpp b/libc/src/regex/regcomp.cpp
index f4c339f70f9d47..b1fd5e218333f8 100644
--- a/libc/src/regex/regcomp.cpp
+++ b/libc/src/regex/regcomp.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
///
/// \file
-/// Implementation of regcomp (stub).
+/// Implementation of regcomp.
///
//===----------------------------------------------------------------------===//
@@ -17,34 +17,62 @@
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/alloc-checker.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
#include "src/__support/macros/config.h"
-#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/regex/regex_ast.h"
+#include "src/__support/regex/regex_internal.h"
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, regcomp,
(regex_t *__restrict preg, const char *__restrict pattern,
int cflags)) {
- // Silencing unused parameter warning for the stub.
- (void)cflags;
+ LIBC_CRASH_ON_NULLPTR(preg);
+ LIBC_CRASH_ON_NULLPTR(pattern);
// Note: POSIX requires callers to call regfree() before reusing a preg
// object. We therefore do not attempt to free any previous __internal here
- // — preg is uninitialized on first use and the pointer would be garbage.
+ // as preg is uninitialised on first use and the pointer would be garbage.
+ // However, we clear __internal and re_nsub immediately so calling regfree()
+ // after a failed regcomp() is guaranteed to be a safe no-op.
+ preg->__internal = nullptr;
+ preg->re_nsub = 0;
- cpp::string_view pattern_view(pattern);
- size_t len = pattern_view.size();
+ // TODO: Once cpp::unique_ptr and AllocChecker-backed cpp::make_unique are
+ // available in src/__support/CPP/, manage RegexInternal via unique_ptr so
+ // error paths do not require manual delete.
AllocChecker ac;
- char *copy = new (ac) char[len + 1];
+ regex::RegexInternal *internal = new (ac) regex::RegexInternal();
if (!ac)
return REG_ESPACE;
- inline_memcpy(copy, pattern, len + 1);
+ ErrorOr<regex::ExprId> cur = internal->pool.empty_str();
+ if (!cur.has_value()) {
+ delete internal;
+ return cur.error();
+ }
- // TODO: This is a stub. re_nsub is always 0 because parenthesised
- // subexpressions are not yet parsed. REG_NOSUB is effectively always active.
- preg->re_nsub = 0;
- preg->__internal = copy;
+ // TODO: Replace direct literal concatenation with the ERE/BRE parser as
+ // regular expression syntax support is wired up.
+ cpp::string_view pattern_view(pattern);
+ for (char c : pattern_view) {
+ ErrorOr<regex::ExprId> lit = internal->pool.make_lit(c);
+ if (!lit.has_value()) {
+ delete internal;
+ return lit.error();
+ }
+ cur = internal->pool.make_concat(cur.value(), lit.value());
+ if (!cur.has_value()) {
+ delete internal;
+ return cur.error();
+ }
+ }
+
+ internal->root = cur.value();
+ internal->cflags = cflags;
+ preg->__internal = internal;
return 0;
}
diff --git a/libc/src/regex/regexec.cpp b/libc/src/regex/regexec.cpp
index 7d3927a2db8eeb..fdeab64029b092 100644
--- a/libc/src/regex/regexec.cpp
+++ b/libc/src/regex/regexec.cpp
@@ -7,16 +7,20 @@
//===----------------------------------------------------------------------===//
///
/// \file
-/// Implementation of regexec (stub).
+/// Implementation of regexec.
///
//===----------------------------------------------------------------------===//
#include "src/regex/regexec.h"
#include "hdr/regex_macros.h"
+#include "src/__support/CPP/mutex.h"
+#include "src/__support/CPP/string_view.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
-#include "src/string/memory_utils/inline_strstr.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/regex/regex_internal.h"
+#include "src/__support/regex/regex_matcher.h"
namespace LIBC_NAMESPACE_DECL {
@@ -24,31 +28,34 @@ LLVM_LIBC_FUNCTION(int, regexec,
(const regex_t *__restrict preg,
const char *__restrict string, size_t nmatch,
regmatch_t *__restrict pmatch, int eflags)) {
- // TODO: This is a stub. The following are not yet implemented:
- // - Regex metacharacters (., *, +, ?, [], {}, (), |, ^, $).
- // - REG_EXTENDED / REG_ICASE / REG_NEWLINE compile flags.
- // - REG_NOTBOL / REG_NOTEOL eflags.
- // - pmatch[] filling (subexpression offsets).
- // - Only literal substring search (strstr) is performed.
+ LIBC_CRASH_ON_NULLPTR(preg);
+ LIBC_CRASH_ON_NULLPTR(string);
+
+ // TODO: Submatch extraction (nmatch/pmatch) and execution flags (eflags)
+ // will be wired in subsequent patches.
(void)nmatch;
(void)pmatch;
(void)eflags;
// Guard against a null internal pointer (e.g. called after regfree).
- const char *pattern = static_cast<const char *>(preg->__internal);
- if (!pattern)
+ auto *internal = static_cast<regex::RegexInternal *>(preg->__internal);
+ if (!internal)
return REG_NOMATCH;
- // An empty pattern always matches.
- if (*pattern == '\0')
- return 0;
-
- // Use inline_strstr for literal substring matching.
- auto comp = [](char l, char r) -> int { return l - r; };
- if (inline_strstr(string, pattern, comp))
+ // POSIX requires regexec to be thread-safe across concurrent calls sharing a
+ // const regex_t*. Serialise access to internal->pool during lazy derivative
+ // evaluation.
+ cpp::lock_guard guard(internal->lock);
+ switch (
+ regex::match(internal->pool, internal->root, cpp::string_view(string))) {
+ case regex::MatchResult::Match:
return 0;
-
- return REG_NOMATCH;
+ case regex::MatchResult::NoMatch:
+ return REG_NOMATCH;
+ case regex::MatchResult::OutOfMemory:
+ return REG_ESPACE;
+ }
+ __builtin_unreachable();
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/regex/regfree.cpp b/libc/src/regex/regfree.cpp
index 06fd791f628ff0..f7550d53eeada2 100644
--- a/libc/src/regex/regfree.cpp
+++ b/libc/src/regex/regfree.cpp
@@ -16,15 +16,17 @@
#include "src/__support/CPP/new.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/regex/regex_internal.h"
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(void, regfree, (regex_t * preg)) {
- if (preg->__internal) {
- char *ptr = static_cast<char *>(preg->__internal);
- delete[] ptr;
- preg->__internal = nullptr;
- }
+ LIBC_CRASH_ON_NULLPTR(preg);
+ // TODO: Once RegexInternal is managed via cpp::unique_ptr, reset the smart
+ // pointer here instead of calling delete manually.
+ delete static_cast<regex::RegexInternal *>(preg->__internal);
+ preg->__internal = nullptr;
preg->re_nsub = 0;
}
diff --git a/libc/test/src/__support/regex/CMakeLists.txt b/libc/test/src/__support/regex/CMakeLists.txt
index 0576eaea3684c1..772626c4055130 100644
--- a/libc/test/src/__support/regex/CMakeLists.txt
+++ b/libc/test/src/__support/regex/CMakeLists.txt
@@ -8,3 +8,17 @@ add_libc_test(
libc.hdr.regex_macros
libc.src.__support.regex.regex_expr_pool
)
+
+add_libc_test(
+ matcher_test
+ SUITE
+ libc-support-tests
+ SRCS
+ matcher_test.cpp
+ DEPENDS
+ libc.hdr.regex_macros
+ libc.src.__support.CPP.string_view
+ libc.src.__support.error_or
+ libc.src.__support.regex.regex_expr_pool
+ libc.src.__support.regex.regex_matcher
+)
diff --git a/libc/test/src/__support/regex/expr_test.cpp b/libc/test/src/__support/regex/expr_test.cpp
index baba79cb582b15..1e66fcd02a5a03 100644
--- a/libc/test/src/__support/regex/expr_test.cpp
+++ b/libc/test/src/__support/regex/expr_test.cpp
@@ -69,9 +69,14 @@ TEST(LlvmLibcRegexExprTest, RecursiveInterning) {
TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesConcat) {
ExprPool pool;
auto lit_a = pool.make_lit('a').value();
+ auto lit_b = pool.make_lit('b').value();
auto empty_set = pool.empty_set().value();
auto empty_str = pool.empty_str().value();
+ // Invalid IDs return REG_BADPAT.
+ EXPECT_EQ(pool.make_concat(INVALID_EXPR_ID, lit_a).error(), REG_BADPAT);
+ EXPECT_EQ(pool.make_concat(lit_a, INVALID_EXPR_ID).error(), REG_BADPAT);
+
// (Ø · R) or (R · Ø) => Ø
EXPECT_EQ(pool.make_concat(empty_set, lit_a).value(), empty_set);
EXPECT_EQ(pool.make_concat(lit_a, empty_set).value(), empty_set);
@@ -79,6 +84,22 @@ TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesConcat) {
// (ε · R) or (R · ε) => R
EXPECT_EQ(pool.make_concat(empty_str, lit_a).value(), lit_a);
EXPECT_EQ(pool.make_concat(lit_a, empty_str).value(), lit_a);
+
+ // Nullability of (L · R) requires both L and R to be nullable.
+ auto opt_a = pool.make_alt(lit_a, empty_str).value();
+ auto opt_b = pool.make_alt(lit_b, empty_str).value();
+ EXPECT_FALSE(pool.is_nullable(pool.make_concat(lit_a, opt_b).value()));
+ EXPECT_FALSE(pool.is_nullable(pool.make_concat(opt_a, lit_b).value()));
+ EXPECT_TRUE(pool.is_nullable(pool.make_concat(opt_a, opt_b).value()));
+
+ // Expr::operator== structural comparisons across kinds, literals, and binary
+ // children.
+ EXPECT_FALSE(Expr::make_empty_set() == Expr::make_literal('a'));
+ EXPECT_FALSE(Expr::make_literal('a') == Expr::make_literal('b'));
+ EXPECT_FALSE(Expr::make_concat(lit_a, lit_b) ==
+ Expr::make_concat(lit_a, opt_b));
+ EXPECT_FALSE(Expr::make_concat(lit_a, lit_b) ==
+ Expr::make_concat(lit_b, lit_b));
}
TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesAlt) {
@@ -86,6 +107,11 @@ TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesAlt) {
auto lit_a = pool.make_lit('a').value();
auto lit_b = pool.make_lit('b').value();
auto empty_set = pool.empty_set().value();
+ auto empty_str = pool.empty_str().value();
+
+ // Invalid IDs return REG_BADPAT.
+ EXPECT_EQ(pool.make_alt(INVALID_EXPR_ID, lit_a).error(), REG_BADPAT);
+ EXPECT_EQ(pool.make_alt(lit_a, INVALID_EXPR_ID).error(), REG_BADPAT);
// (Ø | R) or (R | Ø) => R
EXPECT_EQ(pool.make_alt(empty_set, lit_a).value(), lit_a);
@@ -97,6 +123,12 @@ TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesAlt) {
// (A | B) == (B | A) (Commutativity)
EXPECT_EQ(pool.make_alt(lit_a, lit_b).value(),
pool.make_alt(lit_b, lit_a).value());
+
+ // Nullability of (L | R) when L is non-nullable and R (with higher ExprId) is
+ // nullable.
+ auto opt_b = pool.make_alt(lit_b, empty_str).value();
+ auto opt_bb = pool.make_concat(opt_b, opt_b).value();
+ EXPECT_TRUE(pool.is_nullable(pool.make_alt(lit_a, opt_bb).value()));
}
TEST(LlvmLibcRegexExprTest, HermeticHundredPools) {
diff --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
new file mode 100644
index 00000000000000..a29904191af2b3
--- /dev/null
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -0,0 +1,154 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unit tests for Brzozowski derivative evaluation and substring matching.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/regex_macros.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/error_or.h"
+#include "src/__support/regex/regex_ast.h"
+#include "src/__support/regex/regex_expr_pool.h"
+#include "src/__support/regex/regex_matcher.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::ErrorOr;
+using LIBC_NAMESPACE::cpp::string_view;
+using LIBC_NAMESPACE::regex::derivative;
+using LIBC_NAMESPACE::regex::EMPTY_SET_ID;
+using LIBC_NAMESPACE::regex::EMPTY_STR_ID;
+using LIBC_NAMESPACE::regex::ExprId;
+using LIBC_NAMESPACE::regex::ExprPool;
+using LIBC_NAMESPACE::regex::match;
+using LIBC_NAMESPACE::regex::MatchResult;
+
+namespace {
+
+ErrorOr<ExprId> make_literal_string(ExprPool &pool, string_view str) {
+ ErrorOr<ExprId> cur = pool.empty_str();
+ if (!cur.has_value())
+ return cur;
+ for (char c : str) {
+ ErrorOr<ExprId> lit = pool.make_lit(c);
+ if (!lit.has_value())
+ return lit;
+ cur = pool.make_concat(cur.value(), lit.value());
+ if (!cur.has_value())
+ return cur;
+ }
+ return cur;
+}
+
+} // namespace
+
+TEST(LlvmLibcRegexMatcherTest, DerivativeBaseCases) {
+ ExprPool pool;
+ ErrorOr<ExprId> empty_set = pool.empty_set();
+ ErrorOr<ExprId> empty_str = pool.empty_str();
+ ErrorOr<ExprId> lit_a = pool.make_lit('a');
+ ASSERT_TRUE(empty_set.has_value());
+ ASSERT_TRUE(empty_str.has_value());
+ ASSERT_TRUE(lit_a.has_value());
+
+ EXPECT_EQ(derivative(empty_set.value(), 'a', pool).value(), EMPTY_SET_ID);
+ EXPECT_EQ(derivative(empty_str.value(), 'a', pool).value(), EMPTY_SET_ID);
+ EXPECT_EQ(derivative(lit_a.value(), 'a', pool).value(), EMPTY_STR_ID);
+ EXPECT_EQ(derivative(lit_a.value(), 'b', pool).value(), EMPTY_SET_ID);
+}
+
+TEST(LlvmLibcRegexMatcherTest, DerivativeConcatAndAltRules) {
+ ExprPool pool;
+ ExprId lit_a = pool.make_lit('a').value();
+ ExprId lit_b = pool.make_lit('b').value();
+ ExprId empty_str = pool.empty_str().value();
+
+ // Non-nullable left: D_a(a . b) = b, D_b(a . b) = EmptySet.
+ ExprId ab = pool.make_concat(lit_a, lit_b).value();
+ EXPECT_EQ(derivative(ab, 'a', pool).value(), lit_b);
+ EXPECT_EQ(derivative(ab, 'b', pool).value(), EMPTY_SET_ID);
+
+ // Nullable left: D_b((a | EmptyStr) . b) = EmptyStr.
+ ExprId opt_a = pool.make_alt(lit_a, empty_str).value();
+ ExprId opt_a_b = pool.make_concat(opt_a, lit_b).value();
+ EXPECT_EQ(derivative(opt_a_b, 'a', pool).value(), lit_b);
+ EXPECT_EQ(derivative(opt_a_b, 'b', pool).value(), EMPTY_STR_ID);
+
+ // Sum rule: D_a(a | b) = EmptyStr, D_b(a | b) = EmptyStr, D_c(a | b) =
+ // EmptySet.
+ ExprId a_or_b = pool.make_alt(lit_a, lit_b).value();
+ EXPECT_EQ(derivative(a_or_b, 'a', pool).value(), EMPTY_STR_ID);
+ EXPECT_EQ(derivative(a_or_b, 'b', pool).value(), EMPTY_STR_ID);
+ EXPECT_EQ(derivative(a_or_b, 'c', pool).value(), EMPTY_SET_ID);
+}
+
+TEST(LlvmLibcRegexMatcherTest, MatchSubstringAndMemoization) {
+ ExprPool pool;
+ ErrorOr<ExprId> hello = make_literal_string(pool, "hello");
+ ASSERT_TRUE(hello.has_value());
+ EXPECT_EQ(pool.size(), size_t(10));
+ EXPECT_LE(pool.allocated_bytes(), size_t(1024));
+
+ EXPECT_EQ(match(pool, hello.value(), "say hello world"), MatchResult::Match);
+ EXPECT_LE(pool.allocated_bytes(), size_t(1024));
+
+ // Once the derivative states for "say hello" are interned in the pool, a
+ // subsequent match over the same input must allocate zero new nodes or bytes.
+ const size_t warm_nodes = pool.size();
+ const size_t warm_bytes = pool.allocated_bytes();
+ EXPECT_EQ(match(pool, hello.value(), "say hello world"), MatchResult::Match);
+ EXPECT_EQ(pool.size(), warm_nodes);
+ EXPECT_EQ(pool.allocated_bytes(), warm_bytes);
+
+ EXPECT_EQ(match(pool, hello.value(), "hell"), MatchResult::NoMatch);
+}
+
+TEST(LlvmLibcRegexMatcherTest, OverlappingPrefixMatch) {
+ ExprPool pool;
+ ErrorOr<ExprId> pat = make_literal_string(pool, "ababac");
+ ASSERT_TRUE(pat.has_value());
+
+ // At index 4 ('a'), the partial match "ababa" fails on 'b' at index 5 while
+ // a second partial match started at index 2 ("aba") advances to "ababac".
+ EXPECT_EQ(match(pool, pat.value(), "abababac"), MatchResult::Match);
+ EXPECT_EQ(match(pool, pat.value(), "abababab"), MatchResult::NoMatch);
+}
+
+TEST(LlvmLibcRegexMatcherTest, PoolExhaustionPropagation) {
+ ExprPool pool;
+ ExprId empty_str = pool.empty_str().value();
+ ExprId lit_a = pool.make_lit('a').value();
+ ExprId lit_b = pool.make_lit('b').value();
+ ExprId lit_c = pool.make_lit('c').value();
+
+ ExprId ab = pool.make_concat(lit_a, lit_b).value();
+ ExprId opt_a = pool.make_alt(lit_a, empty_str).value();
+ ExprId opt_ab = pool.make_concat(opt_a, lit_b).value();
+ // D_a(((a | "") . b) . c) = b . c, which requires interning Concat(b, c).
+ ExprId need_bc = pool.make_concat(opt_ab, lit_c).value();
+ ExprId concat_left_fail = pool.make_concat(need_bc, lit_a).value();
+ ExprId concat_right_fail = pool.make_concat(opt_a, need_bc).value();
+ ExprId alt_right_fail = pool.make_alt(lit_a, need_bc).value();
+ ExprId lit_d = pool.make_lit('d').value();
+ ExprId alt_left_fail = pool.make_alt(need_bc, lit_d).value();
+
+ // Fill the remainder of the pool to MAX_NODE_LIMIT (10000 nodes).
+ ExprId chain = lit_a;
+ while (pool.size() < 10000)
+ chain = pool.make_concat(lit_a, chain).value();
+
+ EXPECT_EQ(derivative(need_bc, 'a', pool).error(), REG_ESPACE);
+ EXPECT_EQ(derivative(concat_left_fail, 'a', pool).error(), REG_ESPACE);
+ EXPECT_EQ(derivative(concat_right_fail, 'a', pool).error(), REG_ESPACE);
+ EXPECT_EQ(derivative(alt_left_fail, 'a', pool).error(), REG_ESPACE);
+ EXPECT_EQ(derivative(alt_right_fail, 'a', pool).error(), REG_ESPACE);
+
+ EXPECT_EQ(match(pool, need_bc, "a"), MatchResult::OutOfMemory);
+ EXPECT_EQ(match(pool, ab, "a"), MatchResult::OutOfMemory);
+}
diff --git a/libc/test/src/regex/regex_basic_test.cpp b/libc/test/src/regex/regex_basic_test.cpp
index fc598227665cce..0d8e367a23fe25 100644
--- a/libc/test/src/regex/regex_basic_test.cpp
+++ b/libc/test/src/regex/regex_basic_test.cpp
@@ -75,3 +75,61 @@ TEST(LlvmLibcRegexTest, NullByteStopsParsing) {
LIBC_NAMESPACE::regexec(&preg, "doesn't \0 match", 0, nullptr, 0));
LIBC_NAMESPACE::regfree(&preg);
}
+
+TEST(LlvmLibcRegexTest, OverlappingPrefixAndPostFreeSafety) {
+ regex_t preg;
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "ababac", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abababac", 0, nullptr, 0), 0);
+ LIBC_NAMESPACE::regfree(&preg);
+ // Repeated regfree and post-free regexec must be safe.
+ LIBC_NAMESPACE::regfree(&preg);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abababac", 0, nullptr, 0),
+ REG_NOMATCH);
+}
+
+TEST(LlvmLibcRegexTest, PoolExhaustionReturnsEspace) {
+ // 9998 'a' bytes plus EmptySet (1) and EmptyStr (2) fill all 10000 ExprPool
+ // slots during regcomp; regexec then hits MAX_NODE_LIMIT when interning the
+ // first derivative Alt state.
+ char pattern[10000];
+ for (size_t i = 0; i < 9998; ++i)
+ pattern[i] = 'a';
+ pattern[9998] = '\0';
+
+ regex_t preg;
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, pattern, REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a", 0, nullptr, 0), REG_ESPACE);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ // One additional repeated byte (9999 'a's) hits MAX_NODE_LIMIT in
+ // make_concat; a novel byte ('b' at index 9998) hits MAX_NODE_LIMIT in
+ // make_lit. Calling regfree afterwards must be a safe no-op in both cases.
+ pattern[9998] = 'a';
+ pattern[9999] = '\0';
+ EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pattern, REG_EXTENDED | REG_NOSUB),
+ REG_ESPACE);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ pattern[9998] = 'b';
+ EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pattern, REG_EXTENDED | REG_NOSUB),
+ REG_ESPACE);
+ LIBC_NAMESPACE::regfree(&preg);
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST(LlvmLibcRegexTest, NullPointerCrash) {
+ regex_t preg{};
+ ASSERT_DEATH([] { LIBC_NAMESPACE::regcomp(nullptr, "a", 0); },
+ WITH_SIGNAL(-1));
+ ASSERT_DEATH([&preg] { LIBC_NAMESPACE::regcomp(&preg, nullptr, 0); },
+ WITH_SIGNAL(-1));
+ ASSERT_DEATH([] { LIBC_NAMESPACE::regexec(nullptr, "a", 0, nullptr, 0); },
+ WITH_SIGNAL(-1));
+ ASSERT_DEATH(
+ [&preg] { LIBC_NAMESPACE::regexec(&preg, nullptr, 0, nullptr, 0); },
+ WITH_SIGNAL(-1));
+ ASSERT_DEATH([] { LIBC_NAMESPACE::regfree(nullptr); }, WITH_SIGNAL(-1));
+}
+#endif // defined(LIBC_ADD_NULL_CHECKS)
>From 328da7ad6959cad9a8c39b3c87e4a30b369ec552 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 16 Oct 2026 15:46:38 +0100
Subject: [PATCH 2/3] [libc][regex] Address PR review feedback on matcher
Document why derivative() copies the 16-byte Expr node by value before mutating ExprPool in recursive calls. Switch ErrorOr value extraction after has_value() checks from .value() to operator*() across regex_matcher.cpp, regcomp.cpp, and unit tests, and assert !has_value() before inspecting .error() in test cases.
Assisted-by: Automated tooling, human reviewed.
---
libc/src/__support/regex/regex_matcher.cpp | 13 +--
libc/src/regex/regcomp.cpp | 4 +-
libc/test/src/__support/regex/expr_test.cpp | 65 +++++++------
.../test/src/__support/regex/matcher_test.cpp | 93 ++++++++++---------
4 files changed, 93 insertions(+), 82 deletions(-)
diff --git a/libc/src/__support/regex/regex_matcher.cpp b/libc/src/__support/regex/regex_matcher.cpp
index d60097f0e228cc..b39386f1ae6641 100644
--- a/libc/src/__support/regex/regex_matcher.cpp
+++ b/libc/src/__support/regex/regex_matcher.cpp
@@ -24,6 +24,7 @@ namespace LIBC_NAMESPACE_DECL {
namespace regex {
ErrorOr<ExprId> derivative(ExprId id, char c, ExprPool &pool) {
+ // Copy the 16-byte node by value as recursive calls below mutate pool.
const Expr e = pool.get(id);
switch (e.kind) {
case ExprKind::EmptySet:
@@ -38,13 +39,13 @@ ErrorOr<ExprId> derivative(ExprId id, char c, ExprPool &pool) {
ErrorOr<ExprId> d_l = derivative(e.bin.left, c, pool);
if (!d_l.has_value())
return d_l;
- ErrorOr<ExprId> lhs = pool.make_concat(d_l.value(), e.bin.right);
+ ErrorOr<ExprId> lhs = pool.make_concat(*d_l, e.bin.right);
if (!lhs.has_value() || !left_nullable)
return lhs;
ErrorOr<ExprId> d_r = derivative(e.bin.right, c, pool);
if (!d_r.has_value())
return d_r;
- return pool.make_alt(lhs.value(), d_r.value());
+ return pool.make_alt(*lhs, *d_r);
}
case ExprKind::Alt: {
// Sum Rule: D_c(L | R) = D_c(L) | D_c(R)
@@ -54,7 +55,7 @@ ErrorOr<ExprId> derivative(ExprId id, char c, ExprPool &pool) {
ErrorOr<ExprId> d_r = derivative(e.bin.right, c, pool);
if (!d_r.has_value())
return d_r;
- return pool.make_alt(d_l.value(), d_r.value());
+ return pool.make_alt(*d_l, *d_r);
}
}
__builtin_unreachable();
@@ -74,12 +75,12 @@ MatchResult match(ExprPool &pool, const ExprId pattern,
ErrorOr<ExprId> d = derivative(state, c, pool);
if (!d.has_value())
return MatchResult::OutOfMemory;
- if (pool.is_nullable(d.value()))
+ if (pool.is_nullable(*d))
return MatchResult::Match;
- ErrorOr<ExprId> next = pool.make_alt(pattern, d.value());
+ ErrorOr<ExprId> next = pool.make_alt(pattern, *d);
if (!next.has_value())
return MatchResult::OutOfMemory;
- state = next.value();
+ state = *next;
}
return MatchResult::NoMatch;
diff --git a/libc/src/regex/regcomp.cpp b/libc/src/regex/regcomp.cpp
index b1fd5e218333f8..c4854be0535c75 100644
--- a/libc/src/regex/regcomp.cpp
+++ b/libc/src/regex/regcomp.cpp
@@ -63,14 +63,14 @@ LLVM_LIBC_FUNCTION(int, regcomp,
delete internal;
return lit.error();
}
- cur = internal->pool.make_concat(cur.value(), lit.value());
+ cur = internal->pool.make_concat(*cur, *lit);
if (!cur.has_value()) {
delete internal;
return cur.error();
}
}
- internal->root = cur.value();
+ internal->root = *cur;
internal->cflags = cflags;
preg->__internal = internal;
return 0;
diff --git a/libc/test/src/__support/regex/expr_test.cpp b/libc/test/src/__support/regex/expr_test.cpp
index 1e66fcd02a5a03..0490c08dbd4610 100644
--- a/libc/test/src/__support/regex/expr_test.cpp
+++ b/libc/test/src/__support/regex/expr_test.cpp
@@ -68,29 +68,33 @@ TEST(LlvmLibcRegexExprTest, RecursiveInterning) {
TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesConcat) {
ExprPool pool;
- auto lit_a = pool.make_lit('a').value();
- auto lit_b = pool.make_lit('b').value();
- auto empty_set = pool.empty_set().value();
- auto empty_str = pool.empty_str().value();
+ auto lit_a = *pool.make_lit('a');
+ auto lit_b = *pool.make_lit('b');
+ auto empty_set = *pool.empty_set();
+ auto empty_str = *pool.empty_str();
// Invalid IDs return REG_BADPAT.
- EXPECT_EQ(pool.make_concat(INVALID_EXPR_ID, lit_a).error(), REG_BADPAT);
- EXPECT_EQ(pool.make_concat(lit_a, INVALID_EXPR_ID).error(), REG_BADPAT);
+ auto bad_left = pool.make_concat(INVALID_EXPR_ID, lit_a);
+ ASSERT_FALSE(bad_left.has_value());
+ EXPECT_EQ(bad_left.error(), REG_BADPAT);
+ auto bad_right = pool.make_concat(lit_a, INVALID_EXPR_ID);
+ ASSERT_FALSE(bad_right.has_value());
+ EXPECT_EQ(bad_right.error(), REG_BADPAT);
// (Ø · R) or (R · Ø) => Ø
- EXPECT_EQ(pool.make_concat(empty_set, lit_a).value(), empty_set);
- EXPECT_EQ(pool.make_concat(lit_a, empty_set).value(), empty_set);
+ EXPECT_EQ(*pool.make_concat(empty_set, lit_a), empty_set);
+ EXPECT_EQ(*pool.make_concat(lit_a, empty_set), empty_set);
// (ε · R) or (R · ε) => R
- EXPECT_EQ(pool.make_concat(empty_str, lit_a).value(), lit_a);
- EXPECT_EQ(pool.make_concat(lit_a, empty_str).value(), lit_a);
+ EXPECT_EQ(*pool.make_concat(empty_str, lit_a), lit_a);
+ EXPECT_EQ(*pool.make_concat(lit_a, empty_str), lit_a);
// Nullability of (L · R) requires both L and R to be nullable.
- auto opt_a = pool.make_alt(lit_a, empty_str).value();
- auto opt_b = pool.make_alt(lit_b, empty_str).value();
- EXPECT_FALSE(pool.is_nullable(pool.make_concat(lit_a, opt_b).value()));
- EXPECT_FALSE(pool.is_nullable(pool.make_concat(opt_a, lit_b).value()));
- EXPECT_TRUE(pool.is_nullable(pool.make_concat(opt_a, opt_b).value()));
+ auto opt_a = *pool.make_alt(lit_a, empty_str);
+ auto opt_b = *pool.make_alt(lit_b, empty_str);
+ EXPECT_FALSE(pool.is_nullable(*pool.make_concat(lit_a, opt_b)));
+ EXPECT_FALSE(pool.is_nullable(*pool.make_concat(opt_a, lit_b)));
+ EXPECT_TRUE(pool.is_nullable(*pool.make_concat(opt_a, opt_b)));
// Expr::operator== structural comparisons across kinds, literals, and binary
// children.
@@ -104,31 +108,34 @@ TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesConcat) {
TEST(LlvmLibcRegexExprTest, AlgebraicIdentitiesAlt) {
ExprPool pool;
- auto lit_a = pool.make_lit('a').value();
- auto lit_b = pool.make_lit('b').value();
- auto empty_set = pool.empty_set().value();
- auto empty_str = pool.empty_str().value();
+ auto lit_a = *pool.make_lit('a');
+ auto lit_b = *pool.make_lit('b');
+ auto empty_set = *pool.empty_set();
+ auto empty_str = *pool.empty_str();
// Invalid IDs return REG_BADPAT.
- EXPECT_EQ(pool.make_alt(INVALID_EXPR_ID, lit_a).error(), REG_BADPAT);
- EXPECT_EQ(pool.make_alt(lit_a, INVALID_EXPR_ID).error(), REG_BADPAT);
+ auto bad_left = pool.make_alt(INVALID_EXPR_ID, lit_a);
+ ASSERT_FALSE(bad_left.has_value());
+ EXPECT_EQ(bad_left.error(), REG_BADPAT);
+ auto bad_right = pool.make_alt(lit_a, INVALID_EXPR_ID);
+ ASSERT_FALSE(bad_right.has_value());
+ EXPECT_EQ(bad_right.error(), REG_BADPAT);
// (Ø | R) or (R | Ø) => R
- EXPECT_EQ(pool.make_alt(empty_set, lit_a).value(), lit_a);
- EXPECT_EQ(pool.make_alt(lit_a, empty_set).value(), lit_a);
+ EXPECT_EQ(*pool.make_alt(empty_set, lit_a), lit_a);
+ EXPECT_EQ(*pool.make_alt(lit_a, empty_set), lit_a);
// (R | R) => R (Idempotency)
- EXPECT_EQ(pool.make_alt(lit_a, lit_a).value(), lit_a);
+ EXPECT_EQ(*pool.make_alt(lit_a, lit_a), lit_a);
// (A | B) == (B | A) (Commutativity)
- EXPECT_EQ(pool.make_alt(lit_a, lit_b).value(),
- pool.make_alt(lit_b, lit_a).value());
+ EXPECT_EQ(*pool.make_alt(lit_a, lit_b), *pool.make_alt(lit_b, lit_a));
// Nullability of (L | R) when L is non-nullable and R (with higher ExprId) is
// nullable.
- auto opt_b = pool.make_alt(lit_b, empty_str).value();
- auto opt_bb = pool.make_concat(opt_b, opt_b).value();
- EXPECT_TRUE(pool.is_nullable(pool.make_alt(lit_a, opt_bb).value()));
+ auto opt_b = *pool.make_alt(lit_b, empty_str);
+ auto opt_bb = *pool.make_concat(opt_b, opt_b);
+ EXPECT_TRUE(pool.is_nullable(*pool.make_alt(lit_a, opt_bb)));
}
TEST(LlvmLibcRegexExprTest, HermeticHundredPools) {
diff --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
index a29904191af2b3..520081461a6729 100644
--- a/libc/test/src/__support/regex/matcher_test.cpp
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -39,7 +39,7 @@ ErrorOr<ExprId> make_literal_string(ExprPool &pool, string_view str) {
ErrorOr<ExprId> lit = pool.make_lit(c);
if (!lit.has_value())
return lit;
- cur = pool.make_concat(cur.value(), lit.value());
+ cur = pool.make_concat(*cur, *lit);
if (!cur.has_value())
return cur;
}
@@ -57,35 +57,35 @@ TEST(LlvmLibcRegexMatcherTest, DerivativeBaseCases) {
ASSERT_TRUE(empty_str.has_value());
ASSERT_TRUE(lit_a.has_value());
- EXPECT_EQ(derivative(empty_set.value(), 'a', pool).value(), EMPTY_SET_ID);
- EXPECT_EQ(derivative(empty_str.value(), 'a', pool).value(), EMPTY_SET_ID);
- EXPECT_EQ(derivative(lit_a.value(), 'a', pool).value(), EMPTY_STR_ID);
- EXPECT_EQ(derivative(lit_a.value(), 'b', pool).value(), EMPTY_SET_ID);
+ EXPECT_EQ(*derivative(*empty_set, 'a', pool), EMPTY_SET_ID);
+ EXPECT_EQ(*derivative(*empty_str, 'a', pool), EMPTY_SET_ID);
+ EXPECT_EQ(*derivative(*lit_a, 'a', pool), EMPTY_STR_ID);
+ EXPECT_EQ(*derivative(*lit_a, 'b', pool), EMPTY_SET_ID);
}
TEST(LlvmLibcRegexMatcherTest, DerivativeConcatAndAltRules) {
ExprPool pool;
- ExprId lit_a = pool.make_lit('a').value();
- ExprId lit_b = pool.make_lit('b').value();
- ExprId empty_str = pool.empty_str().value();
+ ExprId lit_a = *pool.make_lit('a');
+ ExprId lit_b = *pool.make_lit('b');
+ ExprId empty_str = *pool.empty_str();
// Non-nullable left: D_a(a . b) = b, D_b(a . b) = EmptySet.
- ExprId ab = pool.make_concat(lit_a, lit_b).value();
- EXPECT_EQ(derivative(ab, 'a', pool).value(), lit_b);
- EXPECT_EQ(derivative(ab, 'b', pool).value(), EMPTY_SET_ID);
+ ExprId ab = *pool.make_concat(lit_a, lit_b);
+ EXPECT_EQ(*derivative(ab, 'a', pool), lit_b);
+ EXPECT_EQ(*derivative(ab, 'b', pool), EMPTY_SET_ID);
// Nullable left: D_b((a | EmptyStr) . b) = EmptyStr.
- ExprId opt_a = pool.make_alt(lit_a, empty_str).value();
- ExprId opt_a_b = pool.make_concat(opt_a, lit_b).value();
- EXPECT_EQ(derivative(opt_a_b, 'a', pool).value(), lit_b);
- EXPECT_EQ(derivative(opt_a_b, 'b', pool).value(), EMPTY_STR_ID);
+ ExprId opt_a = *pool.make_alt(lit_a, empty_str);
+ ExprId opt_a_b = *pool.make_concat(opt_a, lit_b);
+ EXPECT_EQ(*derivative(opt_a_b, 'a', pool), lit_b);
+ EXPECT_EQ(*derivative(opt_a_b, 'b', pool), EMPTY_STR_ID);
// Sum rule: D_a(a | b) = EmptyStr, D_b(a | b) = EmptyStr, D_c(a | b) =
// EmptySet.
- ExprId a_or_b = pool.make_alt(lit_a, lit_b).value();
- EXPECT_EQ(derivative(a_or_b, 'a', pool).value(), EMPTY_STR_ID);
- EXPECT_EQ(derivative(a_or_b, 'b', pool).value(), EMPTY_STR_ID);
- EXPECT_EQ(derivative(a_or_b, 'c', pool).value(), EMPTY_SET_ID);
+ ExprId a_or_b = *pool.make_alt(lit_a, lit_b);
+ EXPECT_EQ(*derivative(a_or_b, 'a', pool), EMPTY_STR_ID);
+ EXPECT_EQ(*derivative(a_or_b, 'b', pool), EMPTY_STR_ID);
+ EXPECT_EQ(*derivative(a_or_b, 'c', pool), EMPTY_SET_ID);
}
TEST(LlvmLibcRegexMatcherTest, MatchSubstringAndMemoization) {
@@ -95,18 +95,18 @@ TEST(LlvmLibcRegexMatcherTest, MatchSubstringAndMemoization) {
EXPECT_EQ(pool.size(), size_t(10));
EXPECT_LE(pool.allocated_bytes(), size_t(1024));
- EXPECT_EQ(match(pool, hello.value(), "say hello world"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *hello, "say hello world"), MatchResult::Match);
EXPECT_LE(pool.allocated_bytes(), size_t(1024));
// Once the derivative states for "say hello" are interned in the pool, a
// subsequent match over the same input must allocate zero new nodes or bytes.
const size_t warm_nodes = pool.size();
const size_t warm_bytes = pool.allocated_bytes();
- EXPECT_EQ(match(pool, hello.value(), "say hello world"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *hello, "say hello world"), MatchResult::Match);
EXPECT_EQ(pool.size(), warm_nodes);
EXPECT_EQ(pool.allocated_bytes(), warm_bytes);
- EXPECT_EQ(match(pool, hello.value(), "hell"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *hello, "hell"), MatchResult::NoMatch);
}
TEST(LlvmLibcRegexMatcherTest, OverlappingPrefixMatch) {
@@ -116,38 +116,41 @@ TEST(LlvmLibcRegexMatcherTest, OverlappingPrefixMatch) {
// At index 4 ('a'), the partial match "ababa" fails on 'b' at index 5 while
// a second partial match started at index 2 ("aba") advances to "ababac".
- EXPECT_EQ(match(pool, pat.value(), "abababac"), MatchResult::Match);
- EXPECT_EQ(match(pool, pat.value(), "abababab"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *pat, "abababac"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *pat, "abababab"), MatchResult::NoMatch);
}
TEST(LlvmLibcRegexMatcherTest, PoolExhaustionPropagation) {
ExprPool pool;
- ExprId empty_str = pool.empty_str().value();
- ExprId lit_a = pool.make_lit('a').value();
- ExprId lit_b = pool.make_lit('b').value();
- ExprId lit_c = pool.make_lit('c').value();
-
- ExprId ab = pool.make_concat(lit_a, lit_b).value();
- ExprId opt_a = pool.make_alt(lit_a, empty_str).value();
- ExprId opt_ab = pool.make_concat(opt_a, lit_b).value();
+ ExprId empty_str = *pool.empty_str();
+ ExprId lit_a = *pool.make_lit('a');
+ ExprId lit_b = *pool.make_lit('b');
+ ExprId lit_c = *pool.make_lit('c');
+
+ ExprId ab = *pool.make_concat(lit_a, lit_b);
+ ExprId opt_a = *pool.make_alt(lit_a, empty_str);
+ ExprId opt_ab = *pool.make_concat(opt_a, lit_b);
// D_a(((a | "") . b) . c) = b . c, which requires interning Concat(b, c).
- ExprId need_bc = pool.make_concat(opt_ab, lit_c).value();
- ExprId concat_left_fail = pool.make_concat(need_bc, lit_a).value();
- ExprId concat_right_fail = pool.make_concat(opt_a, need_bc).value();
- ExprId alt_right_fail = pool.make_alt(lit_a, need_bc).value();
- ExprId lit_d = pool.make_lit('d').value();
- ExprId alt_left_fail = pool.make_alt(need_bc, lit_d).value();
+ ExprId need_bc = *pool.make_concat(opt_ab, lit_c);
+ ExprId concat_left_fail = *pool.make_concat(need_bc, lit_a);
+ ExprId concat_right_fail = *pool.make_concat(opt_a, need_bc);
+ ExprId alt_right_fail = *pool.make_alt(lit_a, need_bc);
+ ExprId lit_d = *pool.make_lit('d');
+ ExprId alt_left_fail = *pool.make_alt(need_bc, lit_d);
// Fill the remainder of the pool to MAX_NODE_LIMIT (10000 nodes).
ExprId chain = lit_a;
while (pool.size() < 10000)
- chain = pool.make_concat(lit_a, chain).value();
-
- EXPECT_EQ(derivative(need_bc, 'a', pool).error(), REG_ESPACE);
- EXPECT_EQ(derivative(concat_left_fail, 'a', pool).error(), REG_ESPACE);
- EXPECT_EQ(derivative(concat_right_fail, 'a', pool).error(), REG_ESPACE);
- EXPECT_EQ(derivative(alt_left_fail, 'a', pool).error(), REG_ESPACE);
- EXPECT_EQ(derivative(alt_right_fail, 'a', pool).error(), REG_ESPACE);
+ chain = *pool.make_concat(lit_a, chain);
+
+ const ExprId exhausted_exprs[] = {need_bc, concat_left_fail,
+ concat_right_fail, alt_left_fail,
+ alt_right_fail};
+ for (ExprId expr : exhausted_exprs) {
+ ErrorOr<ExprId> err = derivative(expr, 'a', pool);
+ ASSERT_FALSE(err.has_value());
+ EXPECT_EQ(err.error(), REG_ESPACE);
+ }
EXPECT_EQ(match(pool, need_bc, "a"), MatchResult::OutOfMemory);
EXPECT_EQ(match(pool, ab, "a"), MatchResult::OutOfMemory);
>From 0591f094538a5397b14150feffeff196bf95e066 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 16 Oct 2026 15:55:32 +0100
Subject: [PATCH 3/3] [libc][regex] Mark ExprId locals const in matcher_test
Mark non-mutated ExprId local variables in matcher_test.cpp as const.
Assisted-by: Automated tooling, human reviewed.
---
.../test/src/__support/regex/matcher_test.cpp | 42 +++++++++----------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
index 520081461a6729..9cad1e30ba2148 100644
--- a/libc/test/src/__support/regex/matcher_test.cpp
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -65,24 +65,24 @@ TEST(LlvmLibcRegexMatcherTest, DerivativeBaseCases) {
TEST(LlvmLibcRegexMatcherTest, DerivativeConcatAndAltRules) {
ExprPool pool;
- ExprId lit_a = *pool.make_lit('a');
- ExprId lit_b = *pool.make_lit('b');
- ExprId empty_str = *pool.empty_str();
+ const ExprId lit_a = *pool.make_lit('a');
+ const ExprId lit_b = *pool.make_lit('b');
+ const ExprId empty_str = *pool.empty_str();
// Non-nullable left: D_a(a . b) = b, D_b(a . b) = EmptySet.
- ExprId ab = *pool.make_concat(lit_a, lit_b);
+ const ExprId ab = *pool.make_concat(lit_a, lit_b);
EXPECT_EQ(*derivative(ab, 'a', pool), lit_b);
EXPECT_EQ(*derivative(ab, 'b', pool), EMPTY_SET_ID);
// Nullable left: D_b((a | EmptyStr) . b) = EmptyStr.
- ExprId opt_a = *pool.make_alt(lit_a, empty_str);
- ExprId opt_a_b = *pool.make_concat(opt_a, lit_b);
+ const ExprId opt_a = *pool.make_alt(lit_a, empty_str);
+ const ExprId opt_a_b = *pool.make_concat(opt_a, lit_b);
EXPECT_EQ(*derivative(opt_a_b, 'a', pool), lit_b);
EXPECT_EQ(*derivative(opt_a_b, 'b', pool), EMPTY_STR_ID);
// Sum rule: D_a(a | b) = EmptyStr, D_b(a | b) = EmptyStr, D_c(a | b) =
// EmptySet.
- ExprId a_or_b = *pool.make_alt(lit_a, lit_b);
+ const ExprId a_or_b = *pool.make_alt(lit_a, lit_b);
EXPECT_EQ(*derivative(a_or_b, 'a', pool), EMPTY_STR_ID);
EXPECT_EQ(*derivative(a_or_b, 'b', pool), EMPTY_STR_ID);
EXPECT_EQ(*derivative(a_or_b, 'c', pool), EMPTY_SET_ID);
@@ -122,21 +122,21 @@ TEST(LlvmLibcRegexMatcherTest, OverlappingPrefixMatch) {
TEST(LlvmLibcRegexMatcherTest, PoolExhaustionPropagation) {
ExprPool pool;
- ExprId empty_str = *pool.empty_str();
- ExprId lit_a = *pool.make_lit('a');
- ExprId lit_b = *pool.make_lit('b');
- ExprId lit_c = *pool.make_lit('c');
-
- ExprId ab = *pool.make_concat(lit_a, lit_b);
- ExprId opt_a = *pool.make_alt(lit_a, empty_str);
- ExprId opt_ab = *pool.make_concat(opt_a, lit_b);
+ const ExprId empty_str = *pool.empty_str();
+ const ExprId lit_a = *pool.make_lit('a');
+ const ExprId lit_b = *pool.make_lit('b');
+ const ExprId lit_c = *pool.make_lit('c');
+
+ const ExprId ab = *pool.make_concat(lit_a, lit_b);
+ const ExprId opt_a = *pool.make_alt(lit_a, empty_str);
+ const ExprId opt_ab = *pool.make_concat(opt_a, lit_b);
// D_a(((a | "") . b) . c) = b . c, which requires interning Concat(b, c).
- ExprId need_bc = *pool.make_concat(opt_ab, lit_c);
- ExprId concat_left_fail = *pool.make_concat(need_bc, lit_a);
- ExprId concat_right_fail = *pool.make_concat(opt_a, need_bc);
- ExprId alt_right_fail = *pool.make_alt(lit_a, need_bc);
- ExprId lit_d = *pool.make_lit('d');
- ExprId alt_left_fail = *pool.make_alt(need_bc, lit_d);
+ const ExprId need_bc = *pool.make_concat(opt_ab, lit_c);
+ const ExprId concat_left_fail = *pool.make_concat(need_bc, lit_a);
+ const ExprId concat_right_fail = *pool.make_concat(opt_a, need_bc);
+ const ExprId alt_right_fail = *pool.make_alt(lit_a, need_bc);
+ const ExprId lit_d = *pool.make_lit('d');
+ const ExprId alt_left_fail = *pool.make_alt(need_bc, lit_d);
// Fill the remainder of the pool to MAX_NODE_LIMIT (10000 nodes).
ExprId chain = lit_a;
More information about the libc-commits
mailing list