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/stdio.cpp",
"stdio/stdio_ext.cpp",
"stdio/vfscanf.c",
"stdio/vfscanf.cpp",
"stdio/vfwscanf.c",
"stdlib/exit.c",
]

View file

@ -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 @@ 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;

View file

@ -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"));