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

This commit is contained in:
Treehugger Robot 2018-01-20 01:17:25 +00:00 committed by Gerrit Code Review
commit 2b66844a0b
3 changed files with 307 additions and 113 deletions

View file

@ -18,7 +18,7 @@ libc_common_src_files = [
"stdio/refill.c", "stdio/refill.c",
"stdio/stdio.cpp", "stdio/stdio.cpp",
"stdio/stdio_ext.cpp", "stdio/stdio_ext.cpp",
"stdio/vfscanf.c", "stdio/vfscanf.cpp",
"stdio/vfwscanf.c", "stdio/vfwscanf.c",
"stdlib/exit.c", "stdlib/exit.c",
] ]

View file

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

View file

@ -1016,6 +1016,138 @@ TEST(STDIO_TEST, swscanf_ccl) {
CheckScanf(swscanf, L"+,-/.", L"%[+--/]", 1, "+,-/"); 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 // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=202240
TEST(STDIO_TEST, scanf_wscanf_EOF) { TEST(STDIO_TEST, scanf_wscanf_EOF) {
EXPECT_EQ(0, sscanf("b", "ab")); EXPECT_EQ(0, sscanf("b", "ab"));