[libc-commits] [libc] e33d0ab - [libc][regex] Add ERE literal parser and flag guards (#233045)

via libc-commits libc-commits at lists.llvm.org
Mon Oct 19 08:47:17 PDT 2026


Author: Jeff Bailey
Date: 2026-10-19T16:47:12+01:00
New Revision: e33d0abccf654de354aeb4a8dcd8ef1778348515

URL: https://github.com/llvm/llvm-project/commit/e33d0abccf654de354aeb4a8dcd8ef1778348515
DIFF: https://github.com/llvm/llvm-project/commit/e33d0abccf654de354aeb4a8dcd8ef1778348515.diff

LOG: [libc][regex] Add ERE literal parser and flag guards (#233045)

Introduce regex::parse_ere in
libc/src/__support/regex/regex_parser.{h,cpp} to parse Extended Regular
Expression (ERE) literal sequences and backslash escape sequences into
ExprPool, returning REG_EESCAPE on a trailing backslash.

To avoid silently mis-matching patterns that use syntax or flags not yet
wired up, return REG_BADPAT when regcomp or regexec receives
unimplemented flags (BRE mode, REG_ICASE, REG_NEWLINE, REG_NOSPEC, or
non-zero eflags) or when parse_ere encounters unimplemented ERE
metacharacters or special escapes. Each metacharacter and flag will be
enabled as its implementation lands.

Against the external compliance suites in REG_NOSUB mode, this passes
20/456 tests in the Henry Spencer suite (+8 new ERE escape tests; -55
false-positive NOMATCH/BRE/flag passes removed by the REG_BADPAT guards)
and 22/202 ERE tests in Glenn Fowler's AT&T testregex basic.dat suite
(+10 new ERE tests).

Assisted-by: Automated tooling, human reviewed.

Added: 
    libc/src/__support/regex/regex_parser.cpp
    libc/src/__support/regex/regex_parser.h

Modified: 
    libc/src/__support/regex/CMakeLists.txt
    libc/src/regex/CMakeLists.txt
    libc/src/regex/regcomp.cpp
    libc/src/regex/regexec.cpp
    libc/test/src/__support/regex/CMakeLists.txt
    libc/test/src/__support/regex/matcher_test.cpp
    libc/test/src/regex/regex_basic_test.cpp

Removed: 
    


################################################################################
diff  --git a/libc/src/__support/regex/CMakeLists.txt b/libc/src/__support/regex/CMakeLists.txt
index 4b1e772212c93..7b6ed6becca37 100644
--- a/libc/src/__support/regex/CMakeLists.txt
+++ b/libc/src/__support/regex/CMakeLists.txt
@@ -41,3 +41,17 @@ add_object_library(
     libc.src.__support.CPP.string_view
     libc.src.__support.error_or
 )
+
+add_object_library(
+  regex_parser
+  SRCS
+    regex_parser.cpp
+  HDRS
+    regex_parser.h
+  DEPENDS
+    .regex_expr_pool
+    libc.hdr.regex_macros
+    libc.src.__support.CPP.string_view
+    libc.src.__support.error_or
+    libc.src.__support.macros.config
+)

diff  --git a/libc/src/__support/regex/regex_parser.cpp b/libc/src/__support/regex/regex_parser.cpp
new file mode 100644
index 0000000000000..2a3f3d2c73dae
--- /dev/null
+++ b/libc/src/__support/regex/regex_parser.cpp
@@ -0,0 +1,113 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Regular expression parser implementation.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/regex/regex_parser.h"
+
+#include "hdr/regex_macros.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 {
+
+namespace {
+
+// Returns true for backslash escape sequences that represent backreferences,
+// boundary assertions, or character-class shorthands rather than escaped
+// literal characters.
+constexpr bool is_unimplemented_ere_escape(char c) {
+  switch (c) {
+  case '1':
+  case '2':
+  case '3':
+  case '4':
+  case '5':
+  case '6':
+  case '7':
+  case '8':
+  case '9':
+  case 'b':
+  case 'B':
+  case '<':
+  case '>':
+  case 'A':
+  case 'Z':
+  case '`':
+  case '\'':
+  case 'w':
+  case 'W':
+  case 's':
+  case 'S':
+  case 'd':
+  case 'D':
+    return true;
+  default:
+    return false;
+  }
+}
+
+} // namespace
+
+ErrorOr<ExprId> parse_ere(cpp::string_view pattern, [[maybe_unused]] int cflags,
+                          ExprPool &pool) {
+  ErrorOr<ExprId> cur = pool.empty_str();
+  if (!cur.has_value())
+    return cur;
+
+  cpp::string_view rem = pattern;
+  while (!rem.empty()) {
+    char c = rem.front();
+    rem.remove_prefix(1);
+    switch (c) {
+    case '\\': {
+      if (rem.empty())
+        return Error(REG_EESCAPE);
+      c = rem.front();
+      rem.remove_prefix(1);
+      if (is_unimplemented_ere_escape(c))
+        return Error(REG_BADPAT);
+      break;
+    }
+    // TODO: Wire ERE metacharacters as each operator is implemented in
+    // subsequent patches.
+    case '.':
+    case '^':
+    case '$':
+    case '*':
+    case '+':
+    case '?':
+    case '(':
+    case ')':
+    case '[':
+    case '{':
+    case '|':
+      return Error(REG_BADPAT);
+    default:
+      break;
+    }
+
+    ErrorOr<ExprId> lit = pool.make_lit(c);
+    if (!lit.has_value())
+      return lit;
+    cur = pool.make_concat(*cur, *lit);
+    if (!cur.has_value())
+      return cur;
+  }
+  return cur;
+}
+
+} // namespace regex
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/__support/regex/regex_parser.h b/libc/src/__support/regex/regex_parser.h
new file mode 100644
index 0000000000000..2bf42cfdf2d8a
--- /dev/null
+++ b/libc/src/__support/regex/regex_parser.h
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Regular expression parser interface.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_PARSER_H
+#define LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_PARSER_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 {
+
+// Parses an Extended Regular Expression (ERE) into the given ExprPool.
+ErrorOr<ExprId> parse_ere(cpp::string_view pattern, int cflags, ExprPool &pool);
+
+} // namespace regex
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_PARSER_H

diff  --git a/libc/src/regex/CMakeLists.txt b/libc/src/regex/CMakeLists.txt
index 4c984802554e5..f5c9bdfff3ab2 100644
--- a/libc/src/regex/CMakeLists.txt
+++ b/libc/src/regex/CMakeLists.txt
@@ -13,6 +13,7 @@ add_entrypoint_object(
     libc.src.__support.error_or
     libc.src.__support.macros.null_check
     libc.src.__support.regex.regex_internal
+    libc.src.__support.regex.regex_parser
 )
 
 add_entrypoint_object(

diff  --git a/libc/src/regex/regcomp.cpp b/libc/src/regex/regcomp.cpp
index c4854be0535c7..505c31065f179 100644
--- a/libc/src/regex/regcomp.cpp
+++ b/libc/src/regex/regcomp.cpp
@@ -23,6 +23,7 @@
 #include "src/__support/macros/null_check.h"
 #include "src/__support/regex/regex_ast.h"
 #include "src/__support/regex/regex_internal.h"
+#include "src/__support/regex/regex_parser.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -40,6 +41,11 @@ LLVM_LIBC_FUNCTION(int, regcomp,
   preg->__internal = nullptr;
   preg->re_nsub = 0;
 
+  // TODO: Enable BRE (!(cflags & REG_EXTENDED)), REG_ICASE, REG_NEWLINE, and
+  // REG_NOSPEC as each feature is implemented in subsequent patches.
+  if (!(cflags & REG_EXTENDED) || (cflags & ~(REG_EXTENDED | REG_NOSUB)) != 0)
+    return REG_BADPAT;
+
   // 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.
@@ -48,29 +54,14 @@ LLVM_LIBC_FUNCTION(int, regcomp,
   if (!ac)
     return REG_ESPACE;
 
-  ErrorOr<regex::ExprId> cur = internal->pool.empty_str();
-  if (!cur.has_value()) {
+  ErrorOr<regex::ExprId> root =
+      regex::parse_ere(cpp::string_view(pattern), cflags, internal->pool);
+  if (!root.has_value()) {
     delete internal;
-    return cur.error();
-  }
-
-  // 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, *lit);
-    if (!cur.has_value()) {
-      delete internal;
-      return cur.error();
-    }
+    return root.error();
   }
 
-  internal->root = *cur;
+  internal->root = *root;
   internal->cflags = cflags;
   preg->__internal = internal;
   return 0;

diff  --git a/libc/src/regex/regexec.cpp b/libc/src/regex/regexec.cpp
index fdeab64029b09..4da121afd0aab 100644
--- a/libc/src/regex/regexec.cpp
+++ b/libc/src/regex/regexec.cpp
@@ -35,7 +35,8 @@ LLVM_LIBC_FUNCTION(int, regexec,
   // will be wired in subsequent patches.
   (void)nmatch;
   (void)pmatch;
-  (void)eflags;
+  if (eflags != 0)
+    return REG_BADPAT;
 
   // Guard against a null internal pointer (e.g. called after regfree).
   auto *internal = static_cast<regex::RegexInternal *>(preg->__internal);

diff  --git a/libc/test/src/__support/regex/CMakeLists.txt b/libc/test/src/__support/regex/CMakeLists.txt
index 772626c405513..156b99c761901 100644
--- a/libc/test/src/__support/regex/CMakeLists.txt
+++ b/libc/test/src/__support/regex/CMakeLists.txt
@@ -21,4 +21,5 @@ add_libc_test(
     libc.src.__support.error_or
     libc.src.__support.regex.regex_expr_pool
     libc.src.__support.regex.regex_matcher
+    libc.src.__support.regex.regex_parser
 )

diff  --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
index 9cad1e30ba214..53f178a0fc4c3 100644
--- a/libc/test/src/__support/regex/matcher_test.cpp
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -17,6 +17,7 @@
 #include "src/__support/regex/regex_ast.h"
 #include "src/__support/regex/regex_expr_pool.h"
 #include "src/__support/regex/regex_matcher.h"
+#include "src/__support/regex/regex_parser.h"
 #include "test/UnitTest/Test.h"
 
 using LIBC_NAMESPACE::ErrorOr;
@@ -28,25 +29,7 @@ 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, *lit);
-    if (!cur.has_value())
-      return cur;
-  }
-  return cur;
-}
-
-} // namespace
+using LIBC_NAMESPACE::regex::parse_ere;
 
 TEST(LlvmLibcRegexMatcherTest, DerivativeBaseCases) {
   ExprPool pool;
@@ -90,7 +73,7 @@ TEST(LlvmLibcRegexMatcherTest, DerivativeConcatAndAltRules) {
 
 TEST(LlvmLibcRegexMatcherTest, MatchSubstringAndMemoization) {
   ExprPool pool;
-  ErrorOr<ExprId> hello = make_literal_string(pool, "hello");
+  ErrorOr<ExprId> hello = parse_ere("hello", REG_EXTENDED, pool);
   ASSERT_TRUE(hello.has_value());
   EXPECT_EQ(pool.size(), size_t(10));
   EXPECT_LE(pool.allocated_bytes(), size_t(1024));
@@ -111,7 +94,7 @@ TEST(LlvmLibcRegexMatcherTest, MatchSubstringAndMemoization) {
 
 TEST(LlvmLibcRegexMatcherTest, OverlappingPrefixMatch) {
   ExprPool pool;
-  ErrorOr<ExprId> pat = make_literal_string(pool, "ababac");
+  ErrorOr<ExprId> pat = parse_ere("ababac", REG_EXTENDED, pool);
   ASSERT_TRUE(pat.has_value());
 
   // At index 4 ('a'), the partial match "ababa" fails on 'b' at index 5 while
@@ -120,6 +103,37 @@ TEST(LlvmLibcRegexMatcherTest, OverlappingPrefixMatch) {
   EXPECT_EQ(match(pool, *pat, "abababab"), MatchResult::NoMatch);
 }
 
+TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
+  ExprPool pool;
+  ErrorOr<ExprId> escaped_dot = parse_ere(R"(a\.b)", REG_EXTENDED, pool);
+  ErrorOr<ExprId> escaped_star = parse_ere(R"(a\*b)", REG_EXTENDED, pool);
+  ErrorOr<ExprId> escaped_slash = parse_ere(R"(a\\b)", REG_EXTENDED, pool);
+  ASSERT_TRUE(escaped_dot.has_value());
+  ASSERT_TRUE(escaped_star.has_value());
+  ASSERT_TRUE(escaped_slash.has_value());
+
+  EXPECT_EQ(match(pool, *escaped_dot, "a.b"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *escaped_dot, "axb"), MatchResult::NoMatch);
+  EXPECT_EQ(match(pool, *escaped_star, "a*b"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *escaped_slash, R"(a\b)"), MatchResult::Match);
+
+  ErrorOr<ExprId> trailing_escape = parse_ere(R"(abc\)", REG_EXTENDED, pool);
+  ASSERT_FALSE(trailing_escape.has_value());
+  EXPECT_EQ(trailing_escape.error(), REG_EESCAPE);
+
+  const string_view bad_patterns[] = {
+      ".",     "^",     "$",     "*",     "+",     "?",     "(",
+      ")",     "[",     "{",     "|",     R"(\1)", R"(\2)", R"(\3)",
+      R"(\4)", R"(\5)", R"(\6)", R"(\7)", R"(\8)", R"(\9)", R"(\b)",
+      R"(\B)", R"(\<)", R"(\>)", R"(\A)", R"(\Z)", R"(\`)", R"(\')",
+      R"(\w)", R"(\W)", R"(\s)", R"(\S)", R"(\d)", R"(\D)"};
+  for (string_view bad : bad_patterns) {
+    ErrorOr<ExprId> err = parse_ere(bad, REG_EXTENDED, pool);
+    ASSERT_FALSE(err.has_value());
+    EXPECT_EQ(err.error(), REG_BADPAT);
+  }
+}
+
 TEST(LlvmLibcRegexMatcherTest, PoolExhaustionPropagation) {
   ExprPool pool;
   const ExprId empty_str = *pool.empty_str();

diff  --git a/libc/test/src/regex/regex_basic_test.cpp b/libc/test/src/regex/regex_basic_test.cpp
index 0d8e367a23fe2..10aea4bb5d42b 100644
--- a/libc/test/src/regex/regex_basic_test.cpp
+++ b/libc/test/src/regex/regex_basic_test.cpp
@@ -118,6 +118,50 @@ TEST(LlvmLibcRegexTest, PoolExhaustionReturnsEspace) {
   LIBC_NAMESPACE::regfree(&preg);
 }
 
+TEST(LlvmLibcRegexTest, EscapedMetacharacters) {
+  regex_t preg;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::regcomp(&preg, R"(a\.b\*c\\d)", REG_EXTENDED | REG_NOSUB),
+      0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, R"(xx a.b*c\d yy)", 0, nullptr, 0),
+            0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, R"(aXb*c\d)", 0, nullptr, 0),
+            REG_NOMATCH);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, R"(trailing\)", REG_EXTENDED),
+            REG_EESCAPE);
+  LIBC_NAMESPACE::regfree(&preg);
+}
+
+TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
+  regex_t preg;
+  // Missing REG_EXTENDED (BRE mode) and unsupported cflags return REG_BADPAT.
+  EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a", 0), REG_BADPAT);
+  LIBC_NAMESPACE::regfree(&preg);
+  EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a", REG_EXTENDED | REG_ICASE),
+            REG_BADPAT);
+  LIBC_NAMESPACE::regfree(&preg);
+  EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a", REG_EXTENDED | REG_NEWLINE),
+            REG_BADPAT);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  // Unimplemented ERE metacharacters and special escapes return REG_BADPAT.
+  const char *const bad_patterns[] = {
+      "a.b",  "^a",  "a$",    "a*",    "a+",    "a?",    "(a)",  "[a]",
+      "a{1}", "a|b", R"(\1)", R"(\b)", R"(\w)", R"(\s)", R"(\d)"};
+  for (const char *pat : bad_patterns) {
+    EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_BADPAT);
+    LIBC_NAMESPACE::regfree(&preg);
+  }
+
+  // Unsupported eflags in regexec return REG_BADPAT.
+  ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a", REG_EXTENDED), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a", 0, nullptr, REG_NOTBOL),
+            REG_BADPAT);
+  LIBC_NAMESPACE::regfree(&preg);
+}
+
 #if defined(LIBC_ADD_NULL_CHECKS)
 TEST(LlvmLibcRegexTest, NullPointerCrash) {
   regex_t preg{};


        


More information about the libc-commits mailing list