]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
stdio-common: Optimize scanf %ms series array expansion
authorRocket Ma <marocketbd@gmail.com>
Thu, 21 May 2026 03:22:38 +0000 (20:22 -0700)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Thu, 21 May 2026 12:08:06 +0000 (09:08 -0300)
* stdio-common/vfscanf-internal.c: If user explicitly set the maximum
size of the string, respect it when reading characters. Instead of
always expanding exponentially, try to expand array to the exact size
user requested when `user_size < current_size * 2`.

Signed-off-by: Rocket Ma <marocketbd@gmail.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
stdio-common/vfscanf-internal.c

index 3d11ac261eddf1e1997a60133fe9036139137181..9871cff6a48aee19bb7bdace9c9841500e2f878d 100644 (file)
@@ -265,6 +265,19 @@ char_buffer_add (struct char_buffer *buffer, CHAR_T ch)
     *buffer->current++ = ch;
 }
 
+/* Calculate the result size of expanded char array in %ms, %mS, %m[
+  or %lm[.  OLDSIZE is current allocation size and NEED is the
+   remaining field-width budget (chars still to read) or negative if
+   unbounded.  */
+static __always_inline size_t
+grow_to_fit (size_t oldsize, int need)
+{
+  if (need < 0 || oldsize < need)
+    return oldsize * 2;
+  /* oldsize >= need: grow requested capacity and 1 byte for `\0' */
+  return oldsize + need + 1;
+}
+
 /* Read formatted input from S according to the format string
    FORMAT, using the argument list in ARG.
    Return the number of assignments made, or -1 for an input error.  */
@@ -804,7 +817,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                      && *strptr + strsize - str <= MB_LEN_MAX)
                    {
                      /* We have to enlarge the buffer if the `m' flag
-                        was given.  */
+                        was given.  And we may not expand str by width
+                        as the wcrtomb may return various bytes.  */
                      size_t strleng = str - *strptr;
                      char *newstr;
 
@@ -1098,7 +1112,8 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                        && *strptr + strsize - str <= MB_LEN_MAX)
                      {
                        /* We have to enlarge the buffer if the `a' or `m'
-                          flag was given.  */
+                          flag was given.  And we may not expand str by
+                          width as the wcrtomb may return various bytes.  */
                        size_t strleng = str - *strptr;
                        char *newstr;
 
@@ -1156,7 +1171,9 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                          && (char *) str == *strptr + strsize)
                        {
                          /* Enlarge the buffer.  */
-                         str = (char *) realloc (*strptr, 2 * strsize);
+                         size_t newsize = grow_to_fit (strsize, width);
+
+                         str = (char *) realloc (*strptr, newsize);
                          if (str == NULL)
                            {
                              /* Can't allocate that much.  Last-ditch
@@ -1188,7 +1205,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                            {
                              *strptr = (char *) str;
                              str += strsize;
-                             strsize *= 2;
+                             strsize = newsize;
                            }
                        }
                    }
@@ -1286,9 +1303,10 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                        && wstr == (wchar_t *) *strptr + strsize)
                      {
                        /* Enlarge the buffer.  */
-                       wstr = (wchar_t *) realloc (*strptr,
-                                                   (2 * strsize)
-                                                   * sizeof (wchar_t));
+                       size_t newsize = grow_to_fit (strsize, width);
+
+                       wstr = (wchar_t *) realloc (
+                           *strptr, newsize * sizeof (wchar_t));
                        if (wstr == NULL)
                          {
                            /* Can't allocate that much.  Last-ditch
@@ -1322,7 +1340,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                          {
                            *strptr = (char *) wstr;
                            wstr += strsize;
-                           strsize *= 2;
+                           strsize = newsize;
                          }
                      }
                  }
@@ -1362,9 +1380,10 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                      && wstr == (wchar_t *) *strptr + strsize)
                    {
                      /* Enlarge the buffer.  */
+                     size_t newsize = grow_to_fit (strsize, width);
+
                      wstr = (wchar_t *) realloc (*strptr,
-                                                 (2 * strsize
-                                                  * sizeof (wchar_t)));
+                                                 newsize * sizeof (wchar_t));
                      if (wstr == NULL)
                        {
                          /* Can't allocate that much.  Last-ditch effort.  */
@@ -1397,7 +1416,7 @@ __vfscanf_internal (FILE *s, const char *format, va_list argptr,
                        {
                          *strptr = (char *) wstr;
                          wstr += strsize;
-                         strsize *= 2;
+                         strsize = newsize;
                        }
                    }
                }
@@ -2754,9 +2773,10 @@ digits_extended_fail:
                          && wstr == (wchar_t *) *strptr + strsize)
                        {
                          /* Enlarge the buffer.  */
-                         wstr = (wchar_t *) realloc (*strptr,
-                                                     (2 * strsize)
-                                                     * sizeof (wchar_t));
+                         size_t newsize = grow_to_fit (strsize, width);
+
+                         wstr = (wchar_t *) realloc (
+                             *strptr, newsize * sizeof (wchar_t));
                          if (wstr == NULL)
                            {
                              /* Can't allocate that much.  Last-ditch
@@ -2790,7 +2810,7 @@ digits_extended_fail:
                            {
                              *strptr = (char *) wstr;
                              wstr += strsize;
-                             strsize *= 2;
+                             strsize = newsize;
                            }
                        }
                    }
@@ -2839,9 +2859,10 @@ digits_extended_fail:
                          && wstr == (wchar_t *) *strptr + strsize)
                        {
                          /* Enlarge the buffer.  */
-                         wstr = (wchar_t *) realloc (*strptr,
-                                                     (2 * strsize
-                                                      * sizeof (wchar_t)));
+                         size_t newsize = grow_to_fit (strsize, width);
+
+                         wstr = (wchar_t *) realloc (
+                             *strptr, newsize * sizeof (wchar_t));
                          if (wstr == NULL)
                            {
                              /* Can't allocate that much.  Last-ditch
@@ -2875,7 +2896,7 @@ digits_extended_fail:
                            {
                              *strptr = (char *) wstr;
                              wstr += strsize;
-                             strsize *= 2;
+                             strsize = newsize;
                            }
                        }
                    }
@@ -2983,7 +3004,9 @@ digits_extended_fail:
                      if ((flags & MALLOC)
                          && *strptr + strsize - str <= MB_LEN_MAX)
                        {
-                         /* Enlarge the buffer.  */
+                         /* Enlarge the buffer.  And we may not
+                          expand str by width as the wcrtomb may
+                          return various bytes.  */
                          size_t strleng = str - *strptr;
                          char *newstr;
 
@@ -3051,7 +3074,7 @@ digits_extended_fail:
                          && (char *) str == *strptr + strsize)
                        {
                          /* Enlarge the buffer.  */
-                         size_t newsize = 2 * strsize;
+                         size_t newsize = grow_to_fit (strsize, width);
 
                        allocagain:
                          str = (char *) realloc (*strptr, newsize);