aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2018-01-20 01:17:25 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2018-01-20 01:17:25 +0000
commit2b66844a0beffff0d2fcf20938e64cdd387131ac (patch)
treec3882636c464bd981f06ac7f99ff136e68c636b4
parentdf4c92888e6283c8f6f1731391a6bccbc30d64db (diff)
parent38e4aefe6c7804d809cfe9e05619ac1a6935e565 (diff)
downloadbionic-2b66844a0beffff0d2fcf20938e64cdd387131ac.tar.gz
Merge "Support %mc/%ms/%m[ in sscanf."
-rw-r--r--libc/Android.bp2
-rw-r--r--libc/stdio/vfscanf.cpp (renamed from libc/stdio/vfscanf.c)290
-rw-r--r--tests/stdio_test.cpp132
3 files changed, 309 insertions, 115 deletions
diff --git a/libc/Android.bp b/libc/Android.bp
index 5f04e2878..78fcb126b 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -18,7 +18,7 @@ libc_common_src_files = [
"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
index f0ed4ae71..f2e136c68 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 */
-
-/*
- * 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 */
-
-#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*);
+// 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
+
+// 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
+
+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 @@ int __svfscanf(FILE* fp, const char* fmt0, __va_list ap) {
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 @@ literal:
flags |= LONG;
}
goto again;
+ case 'm':
+ flags |= ALLOCATE;
+ goto again;
case 'q':
flags |= LLONG; /* deprecated */
goto again;
@@ -262,29 +258,30 @@ literal:
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 @@ literal:
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 @@ literal:
/* 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 @@ literal:
}
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 @@ literal:
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 @@ literal:
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 @@ literal:
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 @@ literal:
break;
}
}
+allocation_failure:
input_failure:
+ free(allocation);
if (nassigned == 0) nassigned = -1;
match_failure:
- return (nassigned);
+ return nassigned;
}
/*
@@ -671,7 +733,7 @@ match_failure:
* 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 @@ static u_char* __sccl(char* tab, u_char* fmt) {
break;
case ']': /* end of scanset */
- return (fmt);
+ return fmt;
default: /* just another character */
c = n;
diff --git a/tests/stdio_test.cpp b/tests/stdio_test.cpp
index d0d91309e..c1a51a808 100644
--- a/tests/stdio_test.cpp
+++ b/tests/stdio_test.cpp
@@ -1016,6 +1016,138 @@ TEST(STDIO_TEST, swscanf_ccl) {
CheckScanf(swscanf, L"+,-/.", L"%[+--/]", 1, "+,-/");
}
+template <typename T1, typename T2>
+static void CheckScanfM(int sscanf_fn(const T1*, const T1*, ...),
+ const T1* input, const T1* fmt,
+ int expected_count, const T2* expected_string) {
+ T2* result = nullptr;
+ ASSERT_EQ(expected_count, sscanf_fn(input, fmt, &result)) << fmt;
+ if (expected_string == nullptr) {
+ ASSERT_EQ(nullptr, result);
+ } else {
+ ASSERT_STREQ(expected_string, result) << fmt;
+ }
+ free(result);
+}
+
+TEST(STDIO_TEST, sscanf_mc) {
+ char* p1 = nullptr;
+ char* p2 = nullptr;
+ ASSERT_EQ(2, sscanf("hello", "%mc%mc", &p1, &p2));
+ ASSERT_EQ('h', *p1);
+ ASSERT_EQ('e', *p2);
+ free(p1);
+ free(p2);
+
+ p1 = nullptr;
+ ASSERT_EQ(1, sscanf("hello", "%4mc", &p1));
+ ASSERT_EQ('h', p1[0]);
+ ASSERT_EQ('e', p1[1]);
+ ASSERT_EQ('l', p1[2]);
+ ASSERT_EQ('l', p1[3]);
+ free(p1);
+
+ p1 = nullptr;
+ ASSERT_EQ(1, sscanf("hello world", "%30mc", &p1));
+ ASSERT_EQ('h', p1[0]);
+ ASSERT_EQ('e', p1[1]);
+ ASSERT_EQ('l', p1[2]);
+ ASSERT_EQ('l', p1[3]);
+ ASSERT_EQ('o', p1[4]);
+ free(p1);
+}
+
+
+TEST(STDIO_TEST, sscanf_mlc) {
+ // This is so useless that clang doesn't even believe it exists...
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wformat-invalid-specifier"
+#pragma clang diagnostic ignored "-Wformat-extra-args"
+
+ wchar_t* p1 = nullptr;
+ wchar_t* p2 = nullptr;
+ ASSERT_EQ(2, sscanf("hello", "%mlc%mlc", &p1, &p2));
+ ASSERT_EQ(L'h', *p1);
+ ASSERT_EQ(L'e', *p2);
+ free(p1);
+ free(p2);
+
+ p1 = nullptr;
+ ASSERT_EQ(1, sscanf("hello", "%4mlc", &p1));
+ ASSERT_EQ(L'h', p1[0]);
+ ASSERT_EQ(L'e', p1[1]);
+ ASSERT_EQ(L'l', p1[2]);
+ ASSERT_EQ(L'l', p1[3]);
+ free(p1);
+
+ p1 = nullptr;
+ ASSERT_EQ(1, sscanf("hello world", "%30mlc", &p1));
+ ASSERT_EQ(L'h', p1[0]);
+ ASSERT_EQ(L'e', p1[1]);
+ ASSERT_EQ(L'l', p1[2]);
+ ASSERT_EQ(L'l', p1[3]);
+ ASSERT_EQ(L'o', p1[4]);
+ free(p1);
+#pragma clang diagnostic pop
+}
+
+
+TEST(STDIO_TEST, sscanf_ms) {
+ CheckScanfM(sscanf, "hello", "%ms", 1, "hello");
+ CheckScanfM(sscanf, "hello", "%4ms", 1, "hell");
+ CheckScanfM(sscanf, "hello world", "%30ms", 1, "hello");
+}
+
+TEST(STDIO_TEST, sscanf_mls) {
+ CheckScanfM(sscanf, "hello", "%mls", 1, L"hello");
+ CheckScanfM(sscanf, "hello", "%4mls", 1, L"hell");
+ CheckScanfM(sscanf, "hello world", "%30mls", 1, L"hello");
+}
+
+TEST(STDIO_TEST, sscanf_m_ccl) {
+ CheckScanfM(sscanf, "hello", "%m[a-z]", 1, "hello");
+ CheckScanfM(sscanf, "hello", "%4m[a-z]", 1, "hell");
+ CheckScanfM(sscanf, "hello world", "%30m[a-z]", 1, "hello");
+}
+
+TEST(STDIO_TEST, sscanf_ml_ccl) {
+ CheckScanfM(sscanf, "hello", "%ml[a-z]", 1, L"hello");
+ CheckScanfM(sscanf, "hello", "%4ml[a-z]", 1, L"hell");
+ CheckScanfM(sscanf, "hello world", "%30ml[a-z]", 1, L"hello");
+}
+
+TEST(STDIO_TEST, sscanf_ls) {
+ wchar_t w[32] = {};
+ ASSERT_EQ(1, sscanf("hello world", "%ls", w));
+ ASSERT_EQ(L"hello", std::wstring(w));
+}
+
+TEST(STDIO_TEST, sscanf_ls_suppress) {
+ ASSERT_EQ(0, sscanf("hello world", "%*ls %*ls"));
+}
+
+TEST(STDIO_TEST, sscanf_ls_n) {
+ setlocale(LC_ALL, "C.UTF-8");
+ wchar_t w[32] = {};
+ int pos = 0;
+ ASSERT_EQ(1, sscanf("\xc4\x80", "%ls%n", w, &pos));
+ ASSERT_EQ(static_cast<wchar_t>(256), w[0]);
+ ASSERT_EQ(2, pos);
+}
+
+TEST(STDIO_TEST, sscanf_ls_realloc) {
+ // This is so useless that clang doesn't even believe it exists...
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wformat-invalid-specifier"
+#pragma clang diagnostic ignored "-Wformat-extra-args"
+ wchar_t* p1 = nullptr;
+ wchar_t* p2 = nullptr;
+ ASSERT_EQ(2, sscanf("1234567890123456789012345678901234567890 world", "%mls %mls", &p1, &p2));
+ ASSERT_EQ(L"1234567890123456789012345678901234567890", std::wstring(p1));
+ ASSERT_EQ(L"world", std::wstring(p2));
+#pragma clang diagnostic pop
+}
+
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=202240
TEST(STDIO_TEST, scanf_wscanf_EOF) {
EXPECT_EQ(0, sscanf("b", "ab"));