This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 34c349cfea3171abe80e60601205afba8cc74f99
Author: Marco Casaroli <[email protected]>
AuthorDate: Sat Oct 17 19:58:06 2026 +0200

    libs/libc/regex: Add REG_STARTEND to regexec().
    
    regexec() searches a NUL-terminated string only.  BSD and glibc
    have the REG_STARTEND flag: pmatch[0].rm_so and pmatch[0].rm_eo then
    give the range of the string to search.  The range can contain NUL
    bytes, and regexec() does not need to call strlen() on a large buffer.
    Programs such as git use this flag.
    
    This commit adds REG_STARTEND.  The matchers get the length of the
    input.  With a length, the input ends after that number of bytes and a
    NUL byte is an ordinary character.  The start of the range is the start
    of a line unless REG_NOTBOL is set, as in BSD.  The match offsets stay
    relative to the string.  A back reference does not compare bytes after
    the end of the range.  Without the flag, regexec() does not change.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 include/regex.h           |  1 +
 libs/libc/regex/regexec.c | 91 +++++++++++++++++++++++++++++++++++++----------
 2 files changed, 74 insertions(+), 18 deletions(-)

diff --git a/include/regex.h b/include/regex.h
index f222d96032d..966626b0ca0 100644
--- a/include/regex.h
+++ b/include/regex.h
@@ -66,6 +66,7 @@ typedef struct
 
 #define REG_NOTBOL      1
 #define REG_NOTEOL      2
+#define REG_STARTEND    4
 
 #define REG_OK          0
 #define REG_NOMATCH     1
diff --git a/libs/libc/regex/regexec.c b/libs/libc/regex/regexec.c
index 06ba59ed169..f2be062331d 100644
--- a/libs/libc/regex/regexec.c
+++ b/libs/libc/regex/regexec.c
@@ -50,14 +50,29 @@
  ****************************************************************************/
 
 static void tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
-                            const tre_tnfa_t *tnfa, int *tags, int match_eo);
+                            const tre_tnfa_t *tnfa, int *tags, int match_eo,
+                            int offset);
 
 /* from tre-match-utils.h
  */
 
+/* The input ends at the first NUL byte when len is negative, or after
+ * len bytes otherwise (REG_STARTEND).  A NUL byte before the end of the
+ * input is an ordinary character.
+ */
+
+#define AT_EOS()            (len < 0 ? next_c == L'\0' : pos >= len)
+
 #define GET_NEXT_WCHAR()    do {                                        \
       prev_c = next_c;pos += pos_add_next;                              \
-      if ((pos_add_next = mbtowc(&next_c, str_byte, MB_LEN_MAX)) <= 0){ \
+      if (len >= 0 && pos >= len)                                       \
+        {                                                               \
+          next_c = L'\0';                                               \
+          pos_add_next = 1;                                             \
+        }                                                               \
+      else if ((pos_add_next = mbtowc(&next_c, str_byte,                \
+                len < 0 ? MB_LEN_MAX : len - pos)) <= 0)                \
+        {                                                               \
           if (pos_add_next < 0){ ret = REG_NOMATCH;goto error_exit; }   \
           else pos_add_next++;                                          \
         }                                                               \
@@ -71,17 +86,17 @@ static void tre_fill_pmatch(size_t nmatch, regmatch_t 
pmatch[], int cflags,
     && (pos > 0 || reg_notbol)                               \
     && (prev_c != L'\n' || !reg_newline))                    \
    || ((assertions & ASSERT_AT_EOL)                          \
-       && (next_c != L'\0' || reg_noteol)                    \
+       && (!AT_EOS() || reg_noteol)                         \
        && (next_c != L'\n' || !reg_newline))                 \
    || ((assertions & ASSERT_AT_BOW)                          \
        && (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c)))   \
    || ((assertions & ASSERT_AT_EOW)                          \
        && (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c)))   \
    || ((assertions & ASSERT_AT_WB)                           \
-       && (pos != 0 && next_c != L'\0'                       \
+       && (pos != 0 && !AT_EOS()                            \
            && IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c))) \
    || ((assertions & ASSERT_AT_WB_NEG)                       \
-       && (pos == 0 || next_c == L'\0'                       \
+       && (pos == 0 || AT_EOS()                             \
            || IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c))))
 
 #define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)                              
\
@@ -191,7 +206,7 @@ typedef struct
 } tre_reach_pos_t;
 
 static reg_errcode_t tre_tnfa_run_parallel(const tre_tnfa_t *tnfa,
-                                           const void *string,
+                                           const void *string, int len,
                                            int *match_tags, int eflags,
                                            int *match_end_ofs)
 {
@@ -377,7 +392,7 @@ static reg_errcode_t tre_tnfa_run_parallel(const tre_tnfa_t 
*tnfa,
 
       /* Check for end of string. */
 
-      if (!next_c)
+      if (AT_EOS())
         {
           break;
         }
@@ -703,7 +718,7 @@ typedef struct tre_backtrack_struct *tre_backtrack_t;
 #define MIN(a, b)  ((a) <= (b) ? (a) : (b))
 
 static reg_errcode_t tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa,
-                                            const void *string,
+                                            const void *string, int len,
                                             int *match_tags, int eflags,
                                             int *match_end_ofs)
 {
@@ -943,13 +958,25 @@ retry:
            */
 
           tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB, tnfa,
-                          tags, pos);
+                          tags, pos, 0);
           so        = pmatch[bt].rm_so;
           eo        = pmatch[bt].rm_eo;
           bt_len    = eo - so;
 
-          result = strncmp((const char *) string + so, str_byte - 1,
-                           (size_t) bt_len);
+          if (len < 0)
+            {
+              result = strncmp((const char *) string + so, str_byte - 1,
+                               (size_t) bt_len);
+            }
+          else if (len - pos < bt_len)
+            {
+              result = 1;
+            }
+          else
+            {
+              result = memcmp((const char *) string + so, str_byte - 1,
+                              (size_t) bt_len);
+            }
 
           if (result == 0)
             {
@@ -984,7 +1011,7 @@ retry:
         {
           /* Check for end of string. */
 
-          if (next_c == L'\0')
+          if (AT_EOS())
             {
               goto backtrack;
             }
@@ -1083,7 +1110,7 @@ backtrack:
                * Check for end of string.
                */
 
-              if (next_c == L'\0')
+              if (AT_EOS())
                 {
                   break;
                 }
@@ -1131,11 +1158,12 @@ error_exit:
  */
 
 /* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match
- * endpoint values.
+ * endpoint values.  The offset is added to each offset that is set.
  */
 
 static void tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
-                            const tre_tnfa_t *tnfa, int *tags, int match_eo)
+                            const tre_tnfa_t *tnfa, int *tags, int match_eo,
+                            int offset)
 {
   tre_submatch_data_t   *submatch_data;
   unsigned int          i;
@@ -1176,6 +1204,11 @@ static void tre_fill_pmatch(size_t nmatch, regmatch_t 
pmatch[], int cflags,
             {
               pmatch[i].rm_so = pmatch[i].rm_eo = -1;
             }
+          else
+            {
+              pmatch[i].rm_so += offset;
+              pmatch[i].rm_eo += offset;
+            }
 
           i++;
         }
@@ -1229,6 +1262,26 @@ int regexec(const regex_t *restrict preg, const char 
*restrict string,
   reg_errcode_t status;
   int           *tags = NULL;
   int           eo;
+  int           so = 0;
+  int           len = -1;
+
+  /* With REG_STARTEND, pmatch[0] gives the range of the string to search.
+   * The range can contain NUL bytes.  The start of the range is the start
+   * of a line unless REG_NOTBOL is set.  The match offsets stay relative
+   * to the string.
+   */
+
+  if (eflags & REG_STARTEND)
+    {
+      if (pmatch[0].rm_so < 0 || pmatch[0].rm_eo < pmatch[0].rm_so)
+        {
+          return REG_NOMATCH;
+        }
+
+      so      = pmatch[0].rm_so;
+      len     = pmatch[0].rm_eo - so;
+      string += so;
+    }
 
   if (tnfa->cflags & REG_NOSUB)
     {
@@ -1250,20 +1303,22 @@ int regexec(const regex_t *restrict preg, const char 
*restrict string,
     {
       /* The regex has back references, use the backtracking matcher. */
 
-      status = tre_tnfa_run_backtrack(tnfa, string, tags, eflags, &eo);
+      status = tre_tnfa_run_backtrack(tnfa, string, len, tags, eflags,
+                                      &eo);
     }
   else
     {
       /* Exact matching, no back references, use the parallel matcher. */
 
-      status = tre_tnfa_run_parallel(tnfa, string, tags, eflags, &eo);
+      status = tre_tnfa_run_parallel(tnfa, string, len, tags, eflags,
+                                     &eo);
     }
 
   if (status == REG_OK)
     {
       /* A match was found, so fill the submatch registers. */
 
-      tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo);
+      tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo, so);
     }
 
   if (tags)

Reply via email to