[libc-commits] [libc] [libc][regex] Add bracket expressions and character ranges (PR #234895)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Fri Oct 23 03:10:50 PDT 2026


https://github.com/kaladron created https://github.com/llvm/llvm-project/pull/234895

Implement POSIX bracket expressions in parse_ere, compiling character lists, character ranges, and negated sets into canonical ByteSet nodes. In POSIX regular expressions, bracket expressions support literal right brackets when placed first, literal hyphens when placed at the start or end of a list, and character range endpoints.  Negated bracket expressions must exclude the NUL byte and exclude newlines when REG_NEWLINE is specified.

Add parse_bracket_expr and parse_coll_elem to parse single bytes, ranges, and collating elements into a 256-bit bitset.  This implementation intentionally covers 8-bit byte values and US-ASCII ranges, with multi-byte UTF-8 range compilation scheduled for a subsequent patch.  Enforce POSIX error reporting for unclosed brackets (REG_EBRACK), reversed or chained ranges (REG_ERANGE), and unknown multi-character collating symbols outside the POSIX locale (REG_ECOLLATE).  Guard POSIX character class prefixes with REG_BADPAT until subsequent class support lands.

External compliance suites: Henry Spencer suite passes 116/456 (+45) boolean/error cases and 46/456 (+17) full-position cases; AT&T testregex passes 71/395 (+17) ERE boolean/error cases and 71/465 (+17) overall boolean/error cases with 0 regressions.

Assisted-by: Automated tooling, human reviewed.

>From 24328f3a6bfc6bf1f5d2d1eb137acd592ba16368 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 23 Oct 2026 07:26:32 +0100
Subject: [PATCH] [libc][regex] Add bracket expressions and character ranges

Implement POSIX bracket expressions in parse_ere, compiling character
lists, character ranges, and negated sets into canonical ByteSet nodes.
In POSIX regular expressions, bracket expressions support literal right
brackets when placed first, literal hyphens when placed at the start or
end of a list, and character range endpoints.  Negated bracket
expressions must exclude the NUL byte and exclude newlines when
REG_NEWLINE is specified.

Add parse_bracket_expr and parse_coll_elem to parse single bytes,
ranges, and collating elements into a 256-bit bitset.  This
implementation intentionally covers 8-bit byte values and US-ASCII
ranges, with multi-byte UTF-8 range compilation scheduled for a
subsequent patch.  Enforce POSIX error reporting for unclosed brackets
(REG_EBRACK), reversed or chained ranges (REG_ERANGE), and unknown
multi-character collating symbols outside the POSIX locale
(REG_ECOLLATE).  Guard POSIX character class prefixes with REG_BADPAT
until subsequent class support lands.

External compliance suites: Henry Spencer suite passes 116/456 (+45)
boolean/error cases and 46/456 (+17) full-position cases; AT&T testregex
passes 71/395 (+17) ERE boolean/error cases and 71/465 (+17) overall
boolean/error cases with 0 regressions.

Assisted-by: Automated tooling, human reviewed.
---
 libc/src/__support/CPP/bitset.h               |  4 +
 libc/src/__support/regex/regex_parser.cpp     | 89 ++++++++++++++++-
 .../test/src/__support/regex/matcher_test.cpp | 93 +++++++++++++++++-
 libc/test/src/regex/regex_basic_test.cpp      | 96 ++++++++++++++++++-
 4 files changed, 276 insertions(+), 6 deletions(-)

diff --git a/libc/src/__support/CPP/bitset.h b/libc/src/__support/CPP/bitset.h
index db193f414abd21..e264a363b9dad1 100644
--- a/libc/src/__support/CPP/bitset.h
+++ b/libc/src/__support/CPP/bitset.h
@@ -29,6 +29,10 @@ template <size_t NumberOfBits> struct bitset {
       Data[i] = 0;
   }
 
+  LIBC_INLINE constexpr void reset(size_t Index) {
+    Data[Index / BITS_PER_UNIT] &= ~mask(Index);
+  }
+
   LIBC_INLINE constexpr bool test(size_t Index) const {
     return Data[Index / BITS_PER_UNIT] & mask(Index);
   }
diff --git a/libc/src/__support/regex/regex_parser.cpp b/libc/src/__support/regex/regex_parser.cpp
index 818d3564da99e3..9b22a4b9df3fc5 100644
--- a/libc/src/__support/regex/regex_parser.cpp
+++ b/libc/src/__support/regex/regex_parser.cpp
@@ -64,6 +64,91 @@ constexpr bool is_unimplemented_ere_escape(char c) {
   }
 }
 
+// Parses a single collating element [[.c.]] or equivalence class [[=c=]].
+ErrorOr<uint8_t> parse_coll_elem(cpp::string_view &rem) {
+  cpp::string_view close_delim = rem.starts_with("[.") ? ".]" : "=]";
+  rem.remove_prefix(2);
+  size_t delim_pos = 0;
+  while (delim_pos + 2 <= rem.size() &&
+         !rem.substr(delim_pos).starts_with(close_delim))
+    ++delim_pos;
+  if (delim_pos + 2 > rem.size())
+    return Error(REG_EBRACK);
+  cpp::string_view elem = rem.substr(0, delim_pos);
+  rem.remove_prefix(delim_pos + 2);
+  if (elem.size() == 1)
+    return static_cast<uint8_t>(elem.front());
+  if (elem == "one")
+    return static_cast<uint8_t>('1');
+  return Error(REG_ECOLLATE);
+}
+
+// Parses a single bracket expression item (collating element or single byte).
+ErrorOr<uint8_t> parse_bracket_elem(cpp::string_view &rem) {
+  if (rem.starts_with("[:"))
+    return Error(REG_BADPAT);
+  if (rem.starts_with("[.") || rem.starts_with("[="))
+    return parse_coll_elem(rem);
+  uint8_t c = static_cast<uint8_t>(rem.front());
+  rem.remove_prefix(1);
+  return c;
+}
+
+// Parses a POSIX bracket expression [...], returning a ByteSet node.
+ErrorOr<ExprId> parse_bracket_expr(cpp::string_view &rem, int cflags,
+                                   ExprPool &pool) {
+  if (rem.empty())
+    return Error(REG_EBRACK);
+
+  bool negate = false;
+  if (rem.starts_with('^')) {
+    negate = true;
+    rem.remove_prefix(1);
+    if (rem.empty())
+      return Error(REG_EBRACK);
+  }
+
+  ByteSet bs;
+  bool first = true;
+  while (!rem.empty()) {
+    if (rem.starts_with(']') && !first) {
+      rem.remove_prefix(1);
+      if (negate) {
+        bs.flip();
+        bs.reset(0);
+        if (cflags & REG_NEWLINE)
+          bs.reset('\n');
+      }
+      return pool.make_byte_set(bs);
+    }
+    first = false;
+
+    ErrorOr<uint8_t> c1 = parse_bracket_elem(rem);
+    if (!c1.has_value())
+      return Error(c1.error());
+
+    if (rem.starts_with('-') && !rem.starts_with("-]")) {
+      rem.remove_prefix(1);
+      if (rem.empty())
+        return Error(REG_ERANGE);
+      if (rem.starts_with("[:"))
+        return Error(REG_ERANGE);
+      ErrorOr<uint8_t> c2 = parse_bracket_elem(rem);
+      if (!c2.has_value())
+        return Error(c2.error());
+      if (*c1 > *c2)
+        return Error(REG_ERANGE);
+      bs.set_range(*c1, *c2);
+      if (rem.starts_with('-') && !rem.starts_with("-]"))
+        return Error(REG_ERANGE);
+    } else {
+      bs.set(*c1);
+    }
+  }
+
+  return Error(REG_EBRACK);
+}
+
 // Parses a single ERE atom and an optional trailing '*' quantifier.
 ErrorOr<ExprId> parse_quantified_atom(cpp::string_view &rem, int cflags,
                                       ExprPool &pool) {
@@ -112,11 +197,13 @@ ErrorOr<ExprId> parse_quantified_atom(cpp::string_view &rem, int cflags,
     if (!rem.empty() && rem.front() >= '0' && rem.front() <= '9')
       return Error(REG_BADRPT);
     return Error(REG_BADPAT);
+  case '[':
+    atom = parse_bracket_expr(rem, cflags, pool);
+    break;
   // TODO: Wire ERE metacharacters as each operator is implemented in
   // subsequent patches.
   case '(':
   case ')':
-  case '[':
   case '|':
     return Error(REG_BADPAT);
   default:
diff --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
index cbda7a16e19ef2..ec0c8668c7f582 100644
--- a/libc/test/src/__support/regex/matcher_test.cpp
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -261,11 +261,35 @@ TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
     EXPECT_EQ(err.error(), REG_BADRPT);
   }
 
+  const string_view ebrack_patterns[] = {"[abc", "[]", "a[[.x", "a[[.x."};
+  for (string_view pat : ebrack_patterns) {
+    ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
+    ASSERT_FALSE(err.has_value());
+    EXPECT_EQ(err.error(), REG_EBRACK);
+  }
+
+  const string_view erange_patterns[] = {"a[3-1]c", "a[1-3-5]c", "a[1-",
+                                         "a[a-[:digit:]]c"};
+  for (string_view pat : erange_patterns) {
+    ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
+    ASSERT_FALSE(err.has_value());
+    EXPECT_EQ(err.error(), REG_ERANGE);
+  }
+
+  const string_view ecollate_patterns[] = {"a[[.notdef.]]b", "a[[.x,.]]",
+                                           "a[[=b,=]]"};
+  for (string_view pat : ecollate_patterns) {
+    ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
+    ASSERT_FALSE(err.has_value());
+    EXPECT_EQ(err.error(), REG_ECOLLATE);
+  }
+
   const string_view bad_patterns[] = {
-      "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"(\')", R"(\w)", R"(\W)", R"(\s)", R"(\S)", R"(\d)", R"(\D)"};
+      "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)"};
   for (string_view bad : bad_patterns) {
     ErrorOr<ExprId> err = parse_ere(bad, REG_EXTENDED, pool);
     ASSERT_FALSE(err.has_value());
@@ -273,6 +297,67 @@ TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
   }
 }
 
+TEST(LlvmLibcRegexMatcherTest, BracketMatchingAndRanges) {
+  ExprPool pool;
+  ErrorOr<ExprId> range = parse_ere("x[c-f]y", REG_EXTENDED, pool);
+  ASSERT_TRUE(range.has_value());
+  EXPECT_EQ(match(pool, *range, "xcy"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *range, "xdy"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *range, "xey"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *range, "xfy"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *range, "xby"), MatchResult::NoMatch);
+  EXPECT_EQ(match(pool, *range, "xgy"), MatchResult::NoMatch);
+  EXPECT_EQ(match(pool, *range, "xy"), MatchResult::NoMatch);
+  EXPECT_EQ(match(pool, *range, "xccy"), MatchResult::NoMatch);
+  EXPECT_EQ(match(pool, *range, "x-y"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> negated = parse_ere("x[^c-f]y", REG_EXTENDED, pool);
+  ASSERT_TRUE(negated.has_value());
+  EXPECT_EQ(match(pool, *negated, "xby"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *negated, "xgy"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *negated, "xcy"), MatchResult::NoMatch);
+  EXPECT_EQ(match(pool, *negated, "xfy"), MatchResult::NoMatch);
+  EXPECT_EQ(match(pool, *negated, "xy"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> nl_neg =
+      parse_ere("x[^c-f]y", REG_EXTENDED | REG_NEWLINE, pool);
+  ASSERT_TRUE(nl_neg.has_value());
+  EXPECT_EQ(match(pool, *nl_neg, "x\ny", REG_NEWLINE, 0), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> first_rb = parse_ere("a[]b]c", REG_EXTENDED, pool);
+  ASSERT_TRUE(first_rb.has_value());
+  EXPECT_EQ(match(pool, *first_rb, "a]c"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *first_rb, "abc"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *first_rb, "ac"), MatchResult::NoMatch);
+
+  ErrorOr<ExprId> lead_dash = parse_ere("a[-b]c", REG_EXTENDED, pool);
+  ASSERT_TRUE(lead_dash.has_value());
+  EXPECT_EQ(match(pool, *lead_dash, "a-c"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *lead_dash, "abc"), MatchResult::Match);
+
+  ErrorOr<ExprId> trail_dash = parse_ere("a[b-]c", REG_EXTENDED, pool);
+  ASSERT_TRUE(trail_dash.has_value());
+  EXPECT_EQ(match(pool, *trail_dash, "a-c"), MatchResult::Match);
+  EXPECT_EQ(match(pool, *trail_dash, "abc"), MatchResult::Match);
+
+  ErrorOr<ExprId> coll_sym = parse_ere("a[[.x.]]b", REG_EXTENDED, pool);
+  ASSERT_TRUE(coll_sym.has_value());
+  EXPECT_EQ(match(pool, *coll_sym, "axb"), MatchResult::Match);
+
+  ErrorOr<ExprId> coll_rb = parse_ere("a[[.].]]b", REG_EXTENDED, pool);
+  ASSERT_TRUE(coll_rb.has_value());
+  EXPECT_EQ(match(pool, *coll_rb, "a]b"), MatchResult::Match);
+
+  ErrorOr<ExprId> coll_one = parse_ere("a[[.one.]]b", REG_EXTENDED, pool);
+  ASSERT_TRUE(coll_one.has_value());
+  EXPECT_EQ(match(pool, *coll_one, "a1b"), MatchResult::Match);
+
+  ErrorOr<ExprId> star_range = parse_ere("[a-c]*", REG_EXTENDED, pool);
+  ASSERT_TRUE(star_range.has_value());
+  EXPECT_EQ(match(pool, *star_range, ""), MatchResult::Match);
+  EXPECT_EQ(match(pool, *star_range, "abcba"), MatchResult::Match);
+}
+
 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 a15cc6aca1499a..28958aa543a145 100644
--- a/libc/test/src/regex/regex_basic_test.cpp
+++ b/libc/test/src/regex/regex_basic_test.cpp
@@ -200,6 +200,100 @@ TEST(LlvmLibcRegexTest, EscapedMetacharacters) {
   LIBC_NAMESPACE::regfree(&preg);
 }
 
+TEST(LlvmLibcRegexTest, BracketExpressionsAndRanges) {
+  regex_t preg;
+  // Matching and range checks with near-miss boundary tests.
+  ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "x[c-f]y", REG_EXTENDED | REG_NOSUB),
+            0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xcy", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xdy", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xey", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xfy", 0, nullptr, 0), 0);
+  // Near-misses: adjacent ASCII values 'b' (c - 1) and 'g' (f + 1), empty,
+  // multi-char, and literal '-'.
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xby", 0, nullptr, 0), REG_NOMATCH);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xgy", 0, nullptr, 0), REG_NOMATCH);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xy", 0, nullptr, 0), REG_NOMATCH);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xccy", 0, nullptr, 0), REG_NOMATCH);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "x-y", 0, nullptr, 0), REG_NOMATCH);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  // Negated brackets and REG_NEWLINE interaction.
+  ASSERT_EQ(
+      LIBC_NAMESPACE::regcomp(&preg, "x[^c-f]y", REG_EXTENDED | REG_NOSUB), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xby", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xgy", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xcy", 0, nullptr, 0), REG_NOMATCH);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xfy", 0, nullptr, 0), REG_NOMATCH);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xy", 0, nullptr, 0), REG_NOMATCH);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xbbby", 0, nullptr, 0),
+            REG_NOMATCH);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "x[^c-f]y",
+                                    REG_EXTENDED | REG_NEWLINE | REG_NOSUB),
+            0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "x\ny", 0, nullptr, 0), REG_NOMATCH);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  // First-position ']', leading '-', trailing '-', and collating elements.
+  ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[]b]c", REG_EXTENDED | REG_NOSUB),
+            0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a]c", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abc", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "ac", 0, nullptr, 0), REG_NOMATCH);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[-b]c", REG_EXTENDED | REG_NOSUB),
+            0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a-c", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abc", 0, nullptr, 0), 0);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[b-]c", REG_EXTENDED | REG_NOSUB),
+            0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a-c", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abc", 0, nullptr, 0), 0);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::regcomp(&preg, "a[[.x.]]b", REG_EXTENDED | REG_NOSUB), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "axb", 0, nullptr, 0), 0);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  ASSERT_EQ(
+      LIBC_NAMESPACE::regcomp(&preg, "a[[.].]]b", REG_EXTENDED | REG_NOSUB), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a]b", 0, nullptr, 0), 0);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "[a-c]*", REG_EXTENDED | REG_NOSUB),
+            0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "", 0, nullptr, 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abcba", 0, nullptr, 0), 0);
+  LIBC_NAMESPACE::regfree(&preg);
+
+  // Bracket error conditions.
+  const char *const ebrack_patterns[] = {"[abc", "[]", "a[[.x", "a[[.x."};
+  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"};
+  for (const char *pat : erange_patterns) {
+    EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_ERANGE);
+    LIBC_NAMESPACE::regfree(&preg);
+  }
+
+  const char *const ecollate_patterns[] = {"a[[.notdef.]]b", "a[[.x,.]]",
+                                           "a[[=b,=]]"};
+  for (const char *pat : ecollate_patterns) {
+    EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_ECOLLATE);
+    LIBC_NAMESPACE::regfree(&preg);
+  }
+}
+
 TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
   regex_t preg;
   // Missing REG_EXTENDED (BRE mode) and unsupported cflags return REG_BADPAT.
@@ -213,7 +307,7 @@ TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
   LIBC_NAMESPACE::regfree(&preg);
 
   // Unimplemented ERE metacharacters and special escapes return REG_BADPAT.
-  const char *const bad_patterns[] = {"a+",    "a?",    "(a)",   "[a]",
+  const char *const bad_patterns[] = {"a+",    "a?",    "(a)",   "[[:alpha:]]",
                                       "a{1}",  "a|b",   R"(\1)", R"(\b)",
                                       R"(\w)", R"(\s)", R"(\d)"};
   for (const char *pat : bad_patterns) {



More information about the libc-commits mailing list