[libc-commits] [libc] [libc][regex] Add POSIX character classes and shorthands (PR #234991)

via libc-commits libc-commits at lists.llvm.org
Fri Oct 23 08:50:07 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Jeff Bailey (kaladron)

<details>
<summary>Changes</summary>

POSIX ERE character class expressions like [:alpha:] and [:digit:] allow portable character classifications inside bracket expressions, while common escape shorthands (\d, \D, \s, \S, \w, \W) provide concise alternatives for character classes.  In POSIX regular expressions, character classes inside brackets must correctly reject unknown class names with REG_ECTYPE, reject range endpoints involving classes with REG_ERANGE, and integrate with negated bracket sets and REG_NEWLINE.

Add regex_charclass.h containing lookup_posix_class to map all 12 POSIX classes (alnum, alpha, blank, cntrl, digit, graph, lower, print, punct, space, upper, xdigit) to constexpr 256-bit bitsets in the POSIX/C locale.  Support operator|= in bitset.h for bitset composition.  In parse_bracket_expr, recognize [:name:] sequences, enforce REG_ECTYPE on invalid names, reject class endpoints with REG_ERANGE, and reserve word boundary anchors [[:<:]] and [[:>:]] with REG_BADPAT until word anchor support lands.  Support shorthands \d, \D, \s, \S, \w, and \W in parse_quantified_atom, clearing newlines on negated shorthands when REG_NEWLINE is set.

External compliance suites: Henry Spencer suite passes 126/456 (+10) boolean/error cases and 55/456 (+9) full-position cases; AT&T testregex passes 74/400 (+3) ERE boolean/error cases and 76/473 (+5) overall boolean/error cases with 0 regressions.

Assisted-by: Automated tooling, human reviewed.

---
Full diff: https://github.com/llvm/llvm-project/pull/234991.diff


7 Files Affected:

- (modified) libc/src/__support/CPP/bitset.h (+7) 
- (modified) libc/src/__support/regex/CMakeLists.txt (+4) 
- (added) libc/src/__support/regex/regex_charclass.h (+88) 
- (modified) libc/src/__support/regex/regex_parser.cpp (+53-9) 
- (modified) libc/test/src/__support/CPP/bitset_test.cpp (+15) 
- (modified) libc/test/src/__support/regex/matcher_test.cpp (+44-8) 
- (modified) libc/test/src/regex/regex_basic_test.cpp (+69-6) 


``````````diff
diff --git a/libc/src/__support/CPP/bitset.h b/libc/src/__support/CPP/bitset.h
index e264a363b9dad..ab492ab1f549d 100644
--- a/libc/src/__support/CPP/bitset.h
+++ b/libc/src/__support/CPP/bitset.h
@@ -80,6 +80,13 @@ template <size_t NumberOfBits> struct bitset {
     return true;
   }
 
+  LIBC_INLINE constexpr bitset<NumberOfBits> &
+  operator|=(const bitset<NumberOfBits> &other) {
+    for (size_t i = 0; i < NUMBER_OF_UNITS; ++i)
+      Data[i] |= other.Data[i];
+    return *this;
+  }
+
 private:
   static constexpr size_t BITS_PER_BYTE = 8;
   static constexpr size_t BITS_PER_UNIT = BITS_PER_BYTE * sizeof(size_t);
diff --git a/libc/src/__support/regex/CMakeLists.txt b/libc/src/__support/regex/CMakeLists.txt
index 870ec56993eca..1191e79b1773b 100644
--- a/libc/src/__support/regex/CMakeLists.txt
+++ b/libc/src/__support/regex/CMakeLists.txt
@@ -54,16 +54,20 @@ add_object_library(
   SRCS
     regex_parser.cpp
   HDRS
+    regex_charclass.h
     regex_parser.h
   DEPENDS
     .regex_expr_pool
     libc.hdr.regex_macros
     libc.src.__support.alloc_checker
     libc.src.__support.CPP.array
+    libc.src.__support.CPP.bitset
     libc.src.__support.CPP.new
+    libc.src.__support.CPP.optional
     libc.src.__support.CPP.scope
     libc.src.__support.CPP.span
     libc.src.__support.CPP.string_view
+    libc.src.__support.ctype_utils
     libc.src.__support.error_or
     libc.src.__support.macros.config
 )
diff --git a/libc/src/__support/regex/regex_charclass.h b/libc/src/__support/regex/regex_charclass.h
new file mode 100644
index 0000000000000..cb01bc33fe273
--- /dev/null
+++ b/libc/src/__support/regex/regex_charclass.h
@@ -0,0 +1,88 @@
+//===-- POSIX character class tables for regex ------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_CHARCLASS_H
+#define LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_CHARCLASS_H
+
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/bitset.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/ctype_utils.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/regex/regex_ast.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace regex {
+
+struct PosixClassEntry {
+  cpp::string_view name;
+  ByteSet set;
+};
+
+template <typename Pred>
+LIBC_INLINE constexpr ByteSet make_class_set(Pred pred) {
+  ByteSet bs;
+  for (size_t c = 0; c < 256; ++c)
+    if (pred(static_cast<char>(c)))
+      bs.set(c);
+  return bs;
+}
+
+LIBC_INLINE constexpr ByteSet digit_class_set() {
+  return make_class_set([](char c) { return internal::isdigit(c); });
+}
+
+LIBC_INLINE constexpr ByteSet space_class_set() {
+  return make_class_set([](char c) { return internal::isspace(c); });
+}
+
+LIBC_INLINE constexpr ByteSet word_class_set() {
+  return make_class_set(
+      [](char c) { return internal::isalnum(c) || c == '_'; });
+}
+
+LIBC_INLINE constexpr cpp::array<PosixClassEntry, 12> POSIX_CLASSES = {{
+    {"alnum", make_class_set([](char c) { return internal::isalnum(c); })},
+    {"alpha", make_class_set([](char c) { return internal::isalpha(c); })},
+    {"blank", make_class_set([](char c) { return c == ' ' || c == '\t'; })},
+    {"cntrl", make_class_set([](char c) {
+       const unsigned ch = static_cast<unsigned char>(c);
+       return ch < 0x20 || ch == 0x7F;
+     })},
+    {"digit", digit_class_set()},
+    {"graph", make_class_set([](char c) { return internal::isgraph(c); })},
+    {"lower", make_class_set([](char c) { return internal::islower(c); })},
+    {"print", make_class_set([](char c) {
+       const unsigned ch = static_cast<unsigned char>(c);
+       return (ch - ' ') < 95;
+     })},
+    {"punct", make_class_set([](char c) {
+       return !internal::isalnum(c) && internal::isgraph(c);
+     })},
+    {"space", space_class_set()},
+    {"upper", make_class_set([](char c) { return internal::isupper(c); })},
+    {"xdigit", make_class_set([](char c) {
+       return internal::isdigit(c) || (c >= 'a' && c <= 'f') ||
+              (c >= 'A' && c <= 'F');
+     })},
+}};
+
+LIBC_INLINE constexpr cpp::optional<ByteSet>
+lookup_posix_class(cpp::string_view name) {
+  for (const auto &entry : POSIX_CLASSES)
+    if (entry.name == name)
+      return entry.set;
+  return cpp::nullopt;
+}
+
+} // namespace regex
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_REGEX_REGEX_CHARCLASS_H
diff --git a/libc/src/__support/regex/regex_parser.cpp b/libc/src/__support/regex/regex_parser.cpp
index 7637183fb55aa..14adb0f0e7bb8 100644
--- a/libc/src/__support/regex/regex_parser.cpp
+++ b/libc/src/__support/regex/regex_parser.cpp
@@ -23,6 +23,7 @@
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/regex/regex_ast.h"
+#include "src/__support/regex/regex_charclass.h"
 #include "src/__support/regex/regex_expr_pool.h"
 
 namespace LIBC_NAMESPACE_DECL {
@@ -52,12 +53,6 @@ constexpr bool is_unimplemented_ere_escape(char c) {
   case 'Z':
   case '`':
   case '\'':
-  case 'w':
-  case 'W':
-  case 's':
-  case 'S':
-  case 'd':
-  case 'D':
     return true;
   default:
     return false;
@@ -120,6 +115,29 @@ ErrorOr<ExprId> parse_bracket_expr(cpp::string_view &rem, int cflags,
     }
     first = false;
 
+    if (rem.starts_with("[:")) {
+      rem.remove_prefix(2);
+      // TODO: Adopt cpp::string_view::find once implemented in
+      // src/__support/CPP/string_view.h.
+      size_t delim_pos = 0;
+      while (delim_pos + 2 <= rem.size() &&
+             !rem.substr(delim_pos).starts_with(":]"))
+        ++delim_pos;
+      if (delim_pos + 2 > rem.size())
+        return Error(REG_EBRACK);
+      cpp::string_view name = rem.substr(0, delim_pos);
+      rem.remove_prefix(delim_pos + 2);
+      if (name == "<" || name == ">")
+        return Error(REG_BADPAT);
+      cpp::optional<ByteSet> opt = lookup_posix_class(name);
+      if (!opt.has_value())
+        return Error(REG_ECTYPE);
+      bs |= *opt;
+      if (rem.starts_with('-') && !rem.starts_with("-]"))
+        return Error(REG_ERANGE);
+      continue;
+    }
+
     ErrorOr<uint8_t> c1 = parse_bracket_elem(rem);
     if (!c1.has_value())
       return Error(c1.error());
@@ -159,9 +177,35 @@ ErrorOr<ExprId> parse_quantified_atom(cpp::string_view &rem, int cflags,
       return Error(REG_EESCAPE);
     c = rem.front();
     rem.remove_prefix(1);
-    if (is_unimplemented_ere_escape(c))
-      return Error(REG_BADPAT);
-    atom = pool.make_lit(c);
+    switch (c) {
+    case 'd':
+    case 's':
+    case 'w': {
+      const ByteSet bs = (c == 'd')   ? digit_class_set()
+                         : (c == 's') ? space_class_set()
+                                      : word_class_set();
+      atom = pool.make_byte_set(bs);
+      break;
+    }
+    case 'D':
+    case 'S':
+    case 'W': {
+      ByteSet bs = (c == 'D')   ? digit_class_set()
+                   : (c == 'S') ? space_class_set()
+                                : word_class_set();
+      bs.flip();
+      bs.reset(0);
+      if (cflags & REG_NEWLINE)
+        bs.reset('\n');
+      atom = pool.make_byte_set(bs);
+      break;
+    }
+    default:
+      if (is_unimplemented_ere_escape(c))
+        return Error(REG_BADPAT);
+      atom = pool.make_lit(c);
+      break;
+    }
     break;
   }
   case '.': {
diff --git a/libc/test/src/__support/CPP/bitset_test.cpp b/libc/test/src/__support/CPP/bitset_test.cpp
index 3632e54907ceb..777d6b8ab7be7 100644
--- a/libc/test/src/__support/CPP/bitset_test.cpp
+++ b/libc/test/src/__support/CPP/bitset_test.cpp
@@ -221,3 +221,18 @@ TEST(LlvmLibcBitsetTest, SetRangeTest) {
     EXPECT_TRUE(bitset.test(j));
   bitset.reset();
 }
+
+TEST(LlvmLibcBitsetTest, BitwiseOrAssign) {
+  LIBC_NAMESPACE::cpp::bitset<64> b1;
+  LIBC_NAMESPACE::cpp::bitset<64> b2;
+  b1.set(1);
+  b1.set(5);
+  b2.set(5);
+  b2.set(10);
+  b1 |= b2;
+  EXPECT_TRUE(b1.test(1));
+  EXPECT_TRUE(b1.test(5));
+  EXPECT_TRUE(b1.test(10));
+  EXPECT_FALSE(b1.test(0));
+  EXPECT_FALSE(b1.test(2));
+}
diff --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
index 891789434e2ab..9c9a5e7e3eca3 100644
--- a/libc/test/src/__support/regex/matcher_test.cpp
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -261,8 +261,8 @@ TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
     EXPECT_EQ(err.error(), REG_BADRPT);
   }
 
-  const string_view ebrack_patterns[] = {"[",  "[^",    "[abc",
-                                         "[]", "a[[.x", "a[[.x."};
+  const string_view ebrack_patterns[] = {
+      "[", "[^", "[abc", "[]", "a[[.x", "a[[.x.", "a[[:digit:", "a[[:digit"};
   for (string_view pat : ebrack_patterns) {
     ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
     ASSERT_FALSE(err.has_value());
@@ -270,7 +270,7 @@ TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
   }
 
   const string_view erange_patterns[] = {"a[3-1]c", "a[1-3-5]c", "a[1-",
-                                         "a[a-[:digit:]]c"};
+                                         "a[a-[:digit:]]c", "a[[:alpha:]-z]c"};
   for (string_view pat : erange_patterns) {
     ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
     ASSERT_FALSE(err.has_value());
@@ -285,12 +285,19 @@ TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
     EXPECT_EQ(err.error(), REG_ECOLLATE);
   }
 
+  const string_view ectype_patterns[] = {"a[[:notdef:]]b", "a[[:alph:]]b",
+                                         "a[[:]:]]b", "a[[:-:]]b"};
+  for (string_view pat : ectype_patterns) {
+    ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
+    ASSERT_FALSE(err.has_value());
+    EXPECT_EQ(err.error(), REG_ECTYPE);
+  }
+
   const string_view bad_patterns[] = {
-      "a+",    "a?",    "a{1}",  "{",     "(",     ")",     "[[:alpha:]]",
-      "|",     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)"};
+      "a+",      "a?",    "a{1}",  "{",     "(",     ")",     "[[:<:]]",
+      "[[:>:]]", "|",     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"(\')"};
   for (string_view bad : bad_patterns) {
     ErrorOr<ExprId> err = parse_ere(bad, REG_EXTENDED, pool);
     ASSERT_FALSE(err.has_value());
@@ -373,6 +380,35 @@ TEST(LlvmLibcRegexMatcherTest, BracketMatchingAndRanges) {
   EXPECT_EQ(match(pool, *star_range, ""), MatchResult::Match);
   EXPECT_EQ(match(pool, *star_range, "abcba"), MatchResult::Match);
   EXPECT_EQ(match(pool, *star_range, "abzcba"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> alpha = parse_ere("^[[:alpha:]]*$", REG_EXTENDED, pool);
+  ASSERT_TRUE(alpha.has_value());
+  EXPECT_EQ(match(pool, *alpha, ""), MatchResult::Match);
+  EXPECT_EQ(match(pool, *alpha, "HelloWorld"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *alpha, "Hello1World"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> sh_w = parse_ere(R"(^\w*$)", REG_EXTENDED, pool);
+  ASSERT_TRUE(sh_w.has_value());
+  EXPECT_EQ(match(pool, *sh_w, ""), MatchResult::Match);
+  EXPECT_EQ(match(pool, *sh_w, "Hello_123"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *sh_w, "Hello-123"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> sh_d = parse_ere(R"(^\d*$)", REG_EXTENDED, pool);
+  ASSERT_TRUE(sh_d.has_value());
+  EXPECT_EQ(match(pool, *sh_d, ""), MatchResult::Match);
+  EXPECT_EQ(match(pool, *sh_d, "12345"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *sh_d, "123a45"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> sh_neg = parse_ere(R"(x\Wy)", REG_EXTENDED, pool);
+  ASSERT_TRUE(sh_neg.has_value());
+  EXPECT_EQ(match(pool, *sh_neg, "x y"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *sh_neg, "x_y"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> sh_neg_nl =
+      parse_ere(R"(x\Wy)", REG_EXTENDED | REG_NEWLINE, pool);
+  ASSERT_TRUE(sh_neg_nl.has_value());
+  EXPECT_EQ(match(pool, *sh_neg_nl, "x\ny", REG_NEWLINE, 0),
+            MatchResult::NoMatch);
 }
 
 TEST(LlvmLibcRegexMatcherTest, PoolExhaustionPropagation) {
diff --git a/libc/test/src/regex/regex_basic_test.cpp b/libc/test/src/regex/regex_basic_test.cpp
index dc575549f3563..557b51a39f827 100644
--- a/libc/test/src/regex/regex_basic_test.cpp
+++ b/libc/test/src/regex/regex_basic_test.cpp
@@ -292,15 +292,15 @@ TEST(LlvmLibcRegexTest, BracketExpressionsAndRanges) {
   LIBC_NAMESPACE::regfree(&preg);
 
   // Bracket error conditions.
-  const char *const ebrack_patterns[] = {"[",  "[^",    "[abc",
-                                         "[]", "a[[.x", "a[[.x."};
+  const char *const ebrack_patterns[] = {
+      "[", "[^", "[abc", "[]", "a[[.x", "a[[.x.", "a[[:digit:", "a[[:digit"};
   for (const char *pat : ebrack_patterns) {
     EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_EBRACK);
     LIBC_NAMESPACE::regfree(&preg);
   }
 
   const char *const erange_patterns[] = {"a[3-1]c", "a[1-3-5]c", "a[1-",
-                                         "a[a-[:digit:]]c"};
+                                         "a[a-[:digit:]]c", "a[[:alpha:]-z]c"};
   for (const char *pat : erange_patterns) {
     EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_ERANGE);
     LIBC_NAMESPACE::regfree(&preg);
@@ -312,6 +312,13 @@ TEST(LlvmLibcRegexTest, BracketExpressionsAndRanges) {
     EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_ECOLLATE);
     LIBC_NAMESPACE::regfree(&preg);
   }
+
+  const char *const ectype_patterns[] = {"a[[:notdef:]]b", "a[[:alph:]]b",
+                                         "a[[:]:]]b", "a[[:-:]]b"};
+  for (const char *pat : ectype_patterns) {
+    EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_ECTYPE);
+    LIBC_NAMESPACE::regfree(&preg);
+  }
 }
 
 TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
@@ -327,9 +334,8 @@ TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
   LIBC_NAMESPACE::regfree(&preg);
 
   // Unimplemented ERE metacharacters and special escapes return REG_BADPAT.
-  const char *const bad_patterns[] = {"a+",    "a?",    "(a)",   "[[:alpha:]]",
-                                      "a{1}",  "a|b",   R"(\1)", R"(\b)",
-                                      R"(\w)", R"(\s)", R"(\d)"};
+  const char *const bad_patterns[] = {
+      "a+", "a?", "(a)", "a{1}", "a|b", R"(\1)", R"(\b)", "[[:<:]]", "[[:>:]]"};
   for (const char *pat : bad_patterns) {
     EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_BADPAT);
     LIBC_NAMESPACE::regfree(&preg);
@@ -343,6 +349,63 @@ TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
             REG_BADPAT);
 }
 
+TEST(LlvmLibcRegexTest, PosixCharacterClassesAndShorthands) {
+  auto check = [](const char *pat, const char *str, int expected = 0) {
+    regex_t preg;
+    ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED | REG_NOSUB), 0);
+    EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, str, 0, nullptr, 0), expected);
+    LIBC_NAMESPACE::regfree(&preg);
+  };
+
+  // Near-miss ASCII neighbours for [:digit:] and \d.
+  check("^x[[:digit:]]y$", "x0y");
+  check("^x[[:digit:]]y$", "x9y");
+  check("^x[[:digit:]]y$", "x/y", REG_NOMATCH);
+  check("^x[[:digit:]]y$", "x:y", REG_NOMATCH);
+  check("^x[[:digit:]]y$", "x12y", REG_NOMATCH);
+
+  check(R"(^x\dy$)", "x5y");
+  check(R"(^x\dy$)", "x/y", REG_NOMATCH);
+  check(R"(^x\dy$)", "x:y", REG_NOMATCH);
+
+  // Near-miss ASCII neighbours for [:xdigit:].
+  check("^x[[:xdigit:]]y$", "xfy");
+  check("^x[[:xdigit:]]y$", "xFy");
+  check("^x[[:xdigit:]]y$", "xgy", REG_NOMATCH);
+  check("^x[[:xdigit:]]y$", "xGy", REG_NOMATCH);
+
+  // [:blank:] matches only space and tab, not newline.
+  check("^x[[:blank:]]y$", "x y");
+  check("^x[[:blank:]]y$", "x\ty");
+  check("^x[[:blank:]]y$", "x\ny", REG_NOMATCH);
+
+  // \w and \W: underscore matches \w; hyphen does not.
+  check(R"(^x\wy$)", "x_y");
+  check(R"(^x\wy$)", "x-y", REG_NOMATCH);
+  check(R"(^x\Wy$)", "x-y");
+  check(R"(^x\Wy$)", "x_y", REG_NOMATCH);
+
+  // Hyphen endpoint combinations with character classes.
+  check("^[--0[:alpha:]]$", "-");
+  check("^[--0[:alpha:]]$", ".");
+  check("^[--0[:alpha:]]$", "a");
+  check("^[--0[:alpha:]]$", "1", REG_NOMATCH);
+
+  check("^[[:alpha:]!--]$", "z");
+  check("^[[:alpha:]!--]$", "!");
+  check("^[[:alpha:]!--]$", "-");
+  check("^[[:alpha:]!--]$", ".", REG_NOMATCH);
+
+  // Anchored repetitions with negative rejection checks.
+  check(R"(^\w*$)", "");
+  check(R"(^\w*$)", "valid_word1");
+  check(R"(^\w*$)", "ab!ba", REG_NOMATCH);
+
+  check(R"(^\d*$)", "");
+  check(R"(^\d*$)", "12345");
+  check(R"(^\d*$)", "12a45", REG_NOMATCH);
+}
+
 #if defined(LIBC_ADD_NULL_CHECKS)
 TEST(LlvmLibcRegexTest, NullPointerCrash) {
   regex_t preg{};

``````````

</details>


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


More information about the libc-commits mailing list