[libc-commits] [libc] [libc][regex] Wire regex entrypoints to ExprPool matcher (PR #232291)

via libc-commits libc-commits at lists.llvm.org
Thu Oct 15 06:16:19 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Jeff Bailey (kaladron)

<details>
<summary>Changes</summary>

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.

---

Patch is 29.34 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/232291.diff


13 Files Affected:

- (modified) libc/src/__support/regex/CMakeLists.txt (+22) 
- (modified) libc/src/__support/regex/regex_expr_pool.h (+3) 
- (added) libc/src/__support/regex/regex_internal.h (+41) 
- (added) libc/src/__support/regex/regex_matcher.cpp (+89) 
- (added) libc/src/__support/regex/regex_matcher.h (+45) 
- (modified) libc/src/regex/CMakeLists.txt (+13-8) 
- (modified) libc/src/regex/regcomp.cpp (+41-13) 
- (modified) libc/src/regex/regexec.cpp (+26-19) 
- (modified) libc/src/regex/regfree.cpp (+7-5) 
- (modified) libc/test/src/__support/regex/CMakeLists.txt (+14) 
- (modified) libc/test/src/__support/regex/expr_test.cpp (+32) 
- (added) libc/test/src/__support/regex/matcher_test.cpp (+154) 
- (modified) libc/test/src/regex/regex_basic_test.cpp (+58) 


``````````diff
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).val...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/232291


More information about the libc-commits mailing list