Support %mc/%ms/%m[ in sscanf.

Also support the "long" (wchar_t) variants.

Bug: http://b/68672236
Test: ran tests
Change-Id: I060e98b4d2feb61e12986d9d51e4710b2b1d9724
diff --git a/libc/Android.bp b/libc/Android.bp
index 3ff5c03..bd6b574 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -18,7 +18,7 @@
     "stdio/refill.c",
     "stdio/stdio.cpp",
     "stdio/stdio_ext.cpp",
-    "stdio/vfscanf.c",
+    "stdio/vfscanf.cpp",
     "stdio/vfwscanf.c",
     "stdlib/exit.c",
 ]
diff --git a/libc/stdio/vfscanf.c b/libc/stdio/vfscanf.cpp
similarity index 72%
rename from libc/stdio/vfscanf.c
rename to libc/stdio/vfscanf.cpp
index f0ed4ae..f2e136c 100644
--- a/libc/stdio/vfscanf.c
+++ b/libc/stdio/vfscanf.cpp
@@ -37,74 +37,68 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/param.h>
 #include <wctype.h>
 #include "local.h"
 
 #include <private/bionic_ctype.h>
+#include <private/bionic_fortify.h>
+#include <private/bionic_mbstate.h>
 
 #define BUF 513 /* Maximum length of numeric string. */
 
-/*
- * Flags used during conversion.
- */
-#define LONG 0x00001       /* l: long or double */
-#define LONGDBL 0x00002    /* L: long double */
-#define SHORT 0x00004      /* h: short */
-#define SHORTSHORT 0x00008 /* hh: 8 bit integer */
-#define LLONG 0x00010      /* ll: long long (+ deprecated q: quad) */
-#define POINTER 0x00020    /* p: void * (as hex) */
-#define SIZEINT 0x00040    /* z: (signed) size_t */
-#define MAXINT 0x00080     /* j: intmax_t */
-#define PTRINT 0x00100     /* t: ptrdiff_t */
-#define NOSKIP 0x00200     /* [ or c: do not skip blanks */
-#define SUPPRESS 0x00400   /* *: suppress assignment */
-#define UNSIGNED 0x00800   /* %[oupxX] conversions */
+// Flags used during conversion.
+// Size/type:
+#define LONG       0x00001 // l: long or double
+#define LONGDBL    0x00002 // L: long double
+#define SHORT      0x00004 // h: short
+#define SHORTSHORT 0x00008 // hh: 8 bit integer
+#define LLONG      0x00010 // ll: long long (+ deprecated q: quad)
+#define POINTER    0x00020 // p: void* (as hex)
+#define SIZEINT    0x00040 // z: (signed) size_t
+#define MAXINT     0x00080 // j: intmax_t
+#define PTRINT     0x00100 // t: ptrdiff_t
+#define NOSKIP     0x00200 // [ or c: do not skip blanks
+// Modifiers:
+#define SUPPRESS   0x00400 // *: suppress assignment
+#define UNSIGNED   0x00800 // %[oupxX] conversions
+#define ALLOCATE   0x01000 // m: allocate a char*
+// Internal use during integer parsing:
+#define SIGNOK     0x02000 // +/- is (still) legal
+#define HAVESIGN   0x04000 // Sign detected
+#define NDIGITS    0x08000 // No digits detected
+#define PFXOK      0x10000 // "0x" prefix is (still) legal
+#define NZDIGITS   0x20000 // No zero digits detected
 
-/*
- * The following are used in numeric conversions only:
- * SIGNOK, HAVESIGN, NDIGITS, DPTOK, and EXPOK are for floating point;
- * SIGNOK, HAVESIGN, NDIGITS, PFXOK, and NZDIGITS are for integral.
- */
-#define SIGNOK 0x01000   /* +/- is (still) legal */
-#define HAVESIGN 0x02000 /* sign detected */
-#define NDIGITS 0x04000  /* no digits detected */
+// Conversion types.
+#define CT_CHAR 0   // %c conversion
+#define CT_CCL 1    // %[...] conversion
+#define CT_STRING 2 // %s conversion
+#define CT_INT 3    // Integer: strtoimax/strtoumax
+#define CT_FLOAT 4  // Float: strtod
 
-#define DPTOK 0x08000 /* (float) decimal point is still legal */
-#define EXPOK 0x10000 /* (float) exponent (e+3, etc) still legal */
-
-#define PFXOK 0x08000    /* 0x prefix is (still) legal */
-#define NZDIGITS 0x10000 /* no zero digits detected */
-
-/*
- * Conversion types.
- */
-#define CT_CHAR 0   /* %c conversion */
-#define CT_CCL 1    /* %[...] conversion */
-#define CT_STRING 2 /* %s conversion */
-#define CT_INT 3    /* integer, i.e., strtoimax or strtoumax */
-#define CT_FLOAT 4  /* floating, i.e., strtod */
-
-static u_char* __sccl(char*, u_char*);
+static const unsigned char* __sccl(char*, const unsigned char*);
 
 /*
  * Internal, unlocked version of vfscanf
  */
-int __svfscanf(FILE* fp, const char* fmt0, __va_list ap) {
-  u_char* fmt = (u_char*)fmt0;
+int __svfscanf(FILE* fp, const char* fmt0, va_list ap) {
+  const unsigned char* fmt = reinterpret_cast<const unsigned char*>(fmt0);
   int c;            /* character from format, or conversion */
   size_t width;     /* field width, or 0 */
-  char* p;          /* points into all kinds of strings */
-  int n;            /* handy integer */
+  char* p;
+  wchar_t* wcp;
+  size_t n;
   int flags;        /* flags as defined above */
-  char* p0;         /* saves original value of p when necessary */
   int nassigned;    /* number of fields assigned */
   int nread;        /* number of characters consumed from fp */
   int base;         /* base argument to strtoimax/strtouimax */
   char ccltab[256]; /* character class table for %[...] */
   char buf[BUF];    /* buffer for numeric conversions */
-  wchar_t* wcp;     /* handy wide character pointer */
   size_t nconv;     /* length of multibyte sequence converted */
   mbstate_t mbs;
+  void* allocation = NULL; // Allocated but unassigned result for %mc/%ms/%m[.
+  size_t capacity = 0; // Number of char/wchar_t units allocated in `allocation`.
 
   /* `basefix' is used to avoid `if' tests in the integer scanner */
   static short basefix[17] = { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
@@ -113,10 +107,9 @@
 
   nassigned = 0;
   nread = 0;
-  base = 0; /* XXX just to keep gcc happy */
   for (;;) {
     c = *fmt++;
-    if (c == 0) return (nassigned);
+    if (c == 0) return nassigned;
     if (IsSpace(c)) {
       while ((fp->_r > 0 || __srefill(fp) == 0) && IsSpace(*fp->_p)) nread++, fp->_r--, fp->_p++;
       continue;
@@ -164,6 +157,9 @@
           flags |= LONG;
         }
         goto again;
+      case 'm':
+        flags |= ALLOCATE;
+        goto again;
       case 'q':
         flags |= LLONG; /* deprecated */
         goto again;
@@ -262,29 +258,30 @@
 
       case 'n':
         if (flags & SUPPRESS) continue;
-        if (flags & SHORTSHORT)
+        if (flags & SHORTSHORT) {
           *va_arg(ap, signed char*) = nread;
-        else if (flags & SHORT)
+        } else if (flags & SHORT) {
           *va_arg(ap, short*) = nread;
-        else if (flags & LONG)
+        } else if (flags & LONG) {
           *va_arg(ap, long*) = nread;
-        else if (flags & SIZEINT)
+        } else if (flags & SIZEINT) {
           *va_arg(ap, ssize_t*) = nread;
-        else if (flags & PTRINT)
+        } else if (flags & PTRINT) {
           *va_arg(ap, ptrdiff_t*) = nread;
-        else if (flags & LLONG)
+        } else if (flags & LLONG) {
           *va_arg(ap, long long*) = nread;
-        else if (flags & MAXINT)
+        } else if (flags & MAXINT) {
           *va_arg(ap, intmax_t*) = nread;
-        else
+        } else {
           *va_arg(ap, int*) = nread;
+        }
         continue;
 
       /*
        * Disgusting backwards compatibility hacks.	XXX
        */
       case '\0': /* compat */
-        return (EOF);
+        return EOF;
 
       default: /* compat */
         if (IsUpper(c)) flags |= LONG;
@@ -293,6 +290,13 @@
         break;
     }
 
+    if ((flags & ALLOCATE) != 0 && c > CT_STRING) {
+      __fortify_fatal("scanf 'm' only works with %%c/%%s/%%[");
+    }
+    if ((flags & (ALLOCATE|SUPPRESS)) == (ALLOCATE|SUPPRESS)) {
+      __fortify_fatal("scanf 'm' makes no sense with '*'");
+    }
+
     /*
      * We have a conversion that requires input.
      */
@@ -326,42 +330,53 @@
         /* scan arbitrary characters (sets NOSKIP) */
         if (width == 0) width = 1;
         if (flags & LONG) {
-          wcp = ((flags & SUPPRESS) == 0) ? va_arg(ap, wchar_t*) : NULL;
-          n = 0;
+          if (flags & ALLOCATE) {
+            allocation = wcp = reinterpret_cast<wchar_t*>(malloc(width * sizeof(wchar_t)));
+            if (allocation == NULL) goto allocation_failure;
+          } else if (flags & SUPPRESS) {
+            wcp = NULL;
+          } else {
+            wcp = va_arg(ap, wchar_t*);
+          }
+          size_t bytes = 0;
           while (width != 0) {
-            if (n == (int)MB_CUR_MAX) {
+            if (bytes == MB_CUR_MAX) {
               fp->_flags |= __SERR;
               goto input_failure;
             }
-            buf[n++] = *fp->_p;
+            buf[bytes++] = *fp->_p;
             fp->_p++;
             fp->_r--;
             memset(&mbs, 0, sizeof(mbs));
-            nconv = mbrtowc(wcp, buf, n, &mbs);
-            if (nconv == (size_t)-1) {
+            nconv = mbrtowc(wcp, buf, bytes, &mbs);
+            if (nconv == __MB_ERR_ILLEGAL_SEQUENCE) {
               fp->_flags |= __SERR;
               goto input_failure;
             }
             if (nconv == 0 && !(flags & SUPPRESS)) *wcp = L'\0';
-            if (nconv != (size_t)-2) {
-              nread += n;
+            if (nconv != __MB_ERR_INCOMPLETE_SEQUENCE) {
+              nread += bytes;
               width--;
               if (!(flags & SUPPRESS)) wcp++;
-              n = 0;
+              bytes = 0;
             }
             if (fp->_r <= 0 && __srefill(fp)) {
-              if (n != 0) {
+              if (bytes != 0) {
                 fp->_flags |= __SERR;
                 goto input_failure;
               }
               break;
             }
           }
+          if (allocation != NULL) {
+            *va_arg(ap, wchar_t**) = reinterpret_cast<wchar_t*>(allocation);
+            allocation = NULL;
+          }
           if (!(flags & SUPPRESS)) nassigned++;
         } else if (flags & SUPPRESS) {
           size_t sum = 0;
           for (;;) {
-            if ((n = fp->_r) < (int)width) {
+            if ((n = fp->_r) < width) {
               sum += n;
               width -= n;
               fp->_p += n;
@@ -378,9 +393,18 @@
           }
           nread += sum;
         } else {
-          size_t r = fread((void*)va_arg(ap, char*), 1, width, fp);
-
+          if (flags & ALLOCATE) {
+            allocation = p = reinterpret_cast<char*>(malloc(width));
+            if (allocation == NULL) goto allocation_failure;
+          } else {
+            p = va_arg(ap, char*);
+          }
+          size_t r = fread(p, 1, width, fp);
           if (r == 0) goto input_failure;
+          if (allocation != NULL) {
+            *va_arg(ap, char**) = reinterpret_cast<char*>(allocation);
+            allocation = NULL;
+          }
           nread += r;
           nassigned++;
         }
@@ -390,55 +414,72 @@
       case CT_STRING:
         // CT_CCL: scan a (nonempty) character class (sets NOSKIP).
         // CT_STRING: like CCL, but zero-length string OK, & no NOSKIP.
-        if (width == 0) width = (size_t)~0; // 'infinity'.
+        if (width == 0) width = SIZE_MAX;
         if (flags & LONG) {
-          wchar_t twc;
-          int nchars = 0;
-
-          wcp = (flags & SUPPRESS) == 0 ? va_arg(ap, wchar_t*) : &twc;
+          // TODO: since no-one cares, replace this with a simple fgetwc loop?
           n = 0;
+          if (flags & ALLOCATE) {
+            capacity = MIN(width, 32);
+            allocation = wcp = reinterpret_cast<wchar_t*>(malloc(sizeof(wchar_t) * capacity));
+            if (allocation == NULL) goto allocation_failure;
+          } else if (flags & SUPPRESS) {
+            wcp = NULL;
+          } else {
+            wcp = va_arg(ap, wchar_t*);
+          }
+          size_t bytes = 0;
           while ((c == CT_CCL || !IsSpace(*fp->_p)) && width != 0) {
-            if (n == (int)MB_CUR_MAX) {
+            if (bytes == MB_CUR_MAX) {
               fp->_flags |= __SERR;
               goto input_failure;
             }
-            buf[n++] = *fp->_p;
+            buf[bytes++] = *fp->_p;
             fp->_p++;
             fp->_r--;
+            wchar_t wc = L'\0';
             memset(&mbs, 0, sizeof(mbs));
-            nconv = mbrtowc(wcp, buf, n, &mbs);
-            if (nconv == (size_t)-1) {
+            nconv = mbrtowc(&wc, buf, bytes, &mbs);
+            if (nconv == __MB_ERR_ILLEGAL_SEQUENCE) {
               fp->_flags |= __SERR;
               goto input_failure;
             }
-            if (nconv == 0) *wcp = L'\0';
-            if (nconv != (size_t)-2) {
-              if ((c == CT_CCL && wctob(*wcp) != EOF && !ccltab[wctob(*wcp)]) || (c == CT_STRING && iswspace(*wcp))) {
-                while (n != 0) {
-                  n--;
-                  ungetc(buf[n], fp);
+            if (nconv != __MB_ERR_INCOMPLETE_SEQUENCE) {
+              if ((c == CT_CCL && wctob(wc) != EOF && !ccltab[wctob(wc)]) || (c == CT_STRING && iswspace(wc))) {
+                while (bytes != 0) {
+                  bytes--;
+                  ungetc(buf[bytes], fp);
                 }
                 break;
               }
-              nread += n;
+              if (wcp) wcp[n] = wc;
+              n++;
+              if (allocation != NULL && n == capacity) {
+                capacity *= 2;
+                wchar_t* new_allocation =
+                    reinterpret_cast<wchar_t*>(realloc(allocation, sizeof(wchar_t) * capacity));
+                if (new_allocation == NULL) goto allocation_failure;
+                allocation = wcp = new_allocation;
+              }
+              nread += bytes;
               width--;
-              if (!(flags & SUPPRESS)) wcp++;
-              nchars++;
-              n = 0;
+              bytes = 0;
             }
             if (fp->_r <= 0 && __srefill(fp)) {
-              if (n != 0) {
+              if (bytes != 0) {
                 fp->_flags |= __SERR;
                 goto input_failure;
               }
               break;
             }
           }
-          if (c == CT_CCL && n != 0) {
+          if (c == CT_CCL && bytes != 0) {
             fp->_flags |= __SERR;
             goto input_failure;
           }
-          n = nchars;
+          if (allocation != NULL) {
+            *va_arg(ap, wchar_t**) = reinterpret_cast<wchar_t*>(allocation);
+            allocation = NULL;
+          }
         } else if (flags & SUPPRESS) {
           n = 0;
           while ((c == CT_CCL && ccltab[*fp->_p]) || (c == CT_STRING && !IsSpace(*fp->_p))) {
@@ -449,29 +490,46 @@
               break;
             }
           }
+          nread += n;
         } else {
-          p0 = p = va_arg(ap, char*);
+          if (flags & ALLOCATE) {
+            capacity = MIN(width, 32);
+            allocation = p = reinterpret_cast<char*>(malloc(capacity));
+            if (allocation == NULL) goto allocation_failure;
+          } else {
+            p = va_arg(ap, char*);
+          }
+          n = 0;
           while ((c == CT_CCL && ccltab[*fp->_p]) || (c == CT_STRING && !IsSpace(*fp->_p))) {
             fp->_r--;
-            *p++ = *fp->_p++;
+            p[n++] = *fp->_p++;
+            if (allocation != NULL && n == capacity) {
+              capacity *= 2;
+              char* new_allocation = reinterpret_cast<char*>(realloc(allocation, capacity));
+              if (new_allocation == NULL) goto allocation_failure;
+              allocation = p = new_allocation;
+            }
             if (--width == 0) break;
             if (fp->_r <= 0 && __srefill(fp)) {
-              if (c == CT_CCL && p == p0) goto input_failure;
+              if (c == CT_CCL && n == 0) goto input_failure;
               break;
             }
           }
-          n = p - p0;
+          nread += n;
+          if (allocation != NULL) {
+            *va_arg(ap, char**) = reinterpret_cast<char*>(allocation);
+            allocation = NULL;
+          }
         }
         if (c == CT_CCL && n == 0) goto match_failure;
         if (!(flags & SUPPRESS)) {
           if (flags & LONG) {
-            *wcp = L'\0';
+            wcp[n] = L'\0';
           } else {
-            *p = '\0';
+            p[n] = '\0';
           }
           ++nassigned;
         }
-        nread += n;
         break;
 
       case CT_INT:
@@ -610,28 +668,30 @@
           uintmax_t res;
 
           *p = '\0';
-          if (flags & UNSIGNED)
+          if (flags & UNSIGNED) {
             res = strtoumax(buf, NULL, base);
-          else
+          } else {
             res = strtoimax(buf, NULL, base);
-          if (flags & POINTER)
+          }
+          if (flags & POINTER) {
             *va_arg(ap, void**) = (void*)(uintptr_t)res;
-          else if (flags & MAXINT)
+          } else if (flags & MAXINT) {
             *va_arg(ap, intmax_t*) = res;
-          else if (flags & LLONG)
+          } else if (flags & LLONG) {
             *va_arg(ap, long long*) = res;
-          else if (flags & SIZEINT)
+          } else if (flags & SIZEINT) {
             *va_arg(ap, ssize_t*) = res;
-          else if (flags & PTRINT)
+          } else if (flags & PTRINT) {
             *va_arg(ap, ptrdiff_t*) = res;
-          else if (flags & LONG)
+          } else if (flags & LONG) {
             *va_arg(ap, long*) = res;
-          else if (flags & SHORT)
+          } else if (flags & SHORT) {
             *va_arg(ap, short*) = res;
-          else if (flags & SHORTSHORT)
+          } else if (flags & SHORTSHORT) {
             *va_arg(ap, signed char*) = res;
-          else
+          } else {
             *va_arg(ap, int*) = res;
+          }
           nassigned++;
         }
         nread += p - buf;
@@ -659,10 +719,12 @@
         break;
     }
   }
+allocation_failure:
 input_failure:
+  free(allocation);
   if (nassigned == 0) nassigned = -1;
 match_failure:
-  return (nassigned);
+  return nassigned;
 }
 
 /*
@@ -671,7 +733,7 @@
  * closing `]'.  The table has a 1 wherever characters should be
  * considered part of the scanset.
  */
-static u_char* __sccl(char* tab, u_char* fmt) {
+static const unsigned char* __sccl(char* tab, const unsigned char* fmt) {
   int c, n, v;
 
   /* first `clear' the whole table */
@@ -744,7 +806,7 @@
         break;
 
       case ']': /* end of scanset */
-        return (fmt);
+        return fmt;
 
       default: /* just another character */
         c = n;