summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2011-07-14 00:51:45 -0400
committerRich Felker <dalias@aerifal.cx>2011-07-14 00:51:45 -0400
commitecc9c5fcfa4831b290cc1a63c0346cbb0c1fcf42 (patch)
tree2fb20d623af9622cb8ac9f461e542ca23fc6d791
parent0e2331c9b6e0c0b4f24019d4062f4c655d28cbaf (diff)
downloadmusl-ecc9c5fcfa4831b290cc1a63c0346cbb0c1fcf42.tar.gz
musl-ecc9c5fcfa4831b290cc1a63c0346cbb0c1fcf42.tar.bz2
musl-ecc9c5fcfa4831b290cc1a63c0346cbb0c1fcf42.tar.xz
musl-ecc9c5fcfa4831b290cc1a63c0346cbb0c1fcf42.zip
new restartable integer parsing framework.
this fixes a number of bugs in integer parsing due to lazy haphazard wrapping, as well as some misinterpretations of the standard. the new parser is able to work character-at-a-time or on whole strings, making it easy to support the wide functions without unbounded space for conversion. it will also be possible to update scanf to use the new parser.
-rw-r--r--src/internal/intparse.c105
-rw-r--r--src/internal/intparse.h11
-rw-r--r--src/stdlib/strtoimax.c37
-rw-r--r--src/stdlib/strtoumax.c123
-rw-r--r--src/stdlib/wcstoimax.c36
-rw-r--r--src/stdlib/wcstoumax.c41
6 files changed, 197 insertions, 156 deletions
diff --git a/src/internal/intparse.c b/src/internal/intparse.c
new file mode 100644
index 00000000..21b07b74
--- /dev/null
+++ b/src/internal/intparse.c
@@ -0,0 +1,105 @@
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "intparse.h"
+
+/* Lookup table for digit values. -1==255>=36 -> invalid */
+static const unsigned char digits[] = {
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
+-1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
+25,26,27,28,29,30,31,32,33,34,35,-1,-1,-1,-1,-1,
+-1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
+25,26,27,28,29,30,31,32,33,34,35,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+};
+
+#define SLIM (UINT_MAX/36-1)
+#define LLIM (UINTMAX_MAX/36-1)
+
+int __intparse(struct intparse *v, const void *buf, size_t n)
+{
+ const unsigned char *s = buf;
+ int d, b = v->base;
+
+ v->cnt += n;
+ for (; n; n--, s++) switch (v->state) {
+ case 0:
+ v->state++;
+ if (*s=='+' || *s=='-') {
+ v->neg = *s=='-';
+ continue;
+ }
+ case 1:
+ v->state++;
+ if (*s=='0' && (!b || b==16)) continue;
+ if (!b) v->base = b = 10;
+ v->state++;
+ goto firstdigit;
+ case 2:
+ v->state++;
+ if ((!b || b==16) && (*s|32) == 'x') {
+ v->base = b = 16;
+ continue;
+ }
+ if (!b) v->base = b = 8;
+ goto seconddigit;
+ case 3:
+ firstdigit:
+ if (digits[*s] >= b) {
+ v->err = EINVAL;
+ return 0;
+ }
+ seconddigit:
+ v->state++;
+ case 4:
+ if (b==10) {
+ for (; n && *s-'0'<10U && v->small<=SLIM; n--, s++)
+ v->small = v->small * 10 + (*s-'0');
+ } else if ((b&-b) == b) {
+ int bs = "\0\1\2\4\7\3\6\5"[(0x17*b)>>5&7];
+ for (; n && (d=digits[*s])<b && v->small<=SLIM; n--, s++)
+ v->small = (v->small<<bs) + d;
+ } else {
+ for (; n && (d=digits[*s])<b && v->small<=SLIM; n--, s++)
+ v->small = v->small * b + d;
+ }
+ if (!n) return 1;
+ v->state++;
+ v->val = v->small;
+ case 5:
+ for (; n && (d=digits[*s])<b && v->val<=LLIM; n--, s++)
+ v->val = v->val * b + d;
+ if (!n) return 1;
+ if (d >= b) goto finished;
+ if (v->val < (UINTMAX_MAX-d)/b)
+ v->val = v->val * b + d;
+ else
+ v->err = ERANGE;
+ v->state++;
+ n--; s++;
+ case 6:
+ if (n && digits[*s]<b) {
+ v->err = ERANGE;
+ v->val = UINTMAX_MAX;
+
+ n--; s++;
+ }
+ for (; n && digits[*s]<b; n--, s++);
+ if (!n) return 1;
+ }
+ return 1;
+finished:
+ v->cnt -= n;
+ return 0;
+}
diff --git a/src/internal/intparse.h b/src/internal/intparse.h
new file mode 100644
index 00000000..78e800d1
--- /dev/null
+++ b/src/internal/intparse.h
@@ -0,0 +1,11 @@
+#include <stdint.h>
+#include <stddef.h>
+
+struct intparse {
+ uintmax_t val;
+ unsigned small;
+ size_t cnt;
+ char neg, base, state, err;
+};
+
+int __intparse(struct intparse *, const void *, size_t);
diff --git a/src/stdlib/strtoimax.c b/src/stdlib/strtoimax.c
index aeb0397f..247f91d4 100644
--- a/src/stdlib/strtoimax.c
+++ b/src/stdlib/strtoimax.c
@@ -1,25 +1,38 @@
#include <inttypes.h>
#include <errno.h>
#include <ctype.h>
+#include "intparse.h"
intmax_t strtoimax(const char *s1, char **p, int base)
{
- const unsigned char *s = (const void *)s1;
- int sign = 0;
- uintmax_t x;
+ const unsigned char *s = (void *)s1;
+ struct intparse ip = {0};
+
+ if (p) *p = (char *)s1;
+
+ if (base && base-2U > 34) {
+ errno = EINVAL;
+ return 0;
+ }
- /* Initial whitespace */
for (; isspace(*s); s++);
- /* Optional sign */
- if (*s == '-') sign = *s++;
- else if (*s == '+') s++;
+ ip.base = base;
+ __intparse(&ip, s, SIZE_MAX);
+
+ if (p && ip.err != EINVAL)
+ *p = (char *)s + ip.cnt;
+
+ if (ip.err) {
+ errno = ip.err;
+ if (ip.err = EINVAL) return 0;
+ return ip.neg ? INTMAX_MIN : INTMAX_MAX;
+ }
- x = strtoumax((const void *)s, p, base);
- if (x > INTMAX_MAX) {
- if (!sign || -x != INTMAX_MIN)
+ if (ip.val > INTMAX_MAX) {
+ if (!ip.neg || -ip.val != INTMAX_MIN)
errno = ERANGE;
- return sign ? INTMAX_MIN : INTMAX_MAX;
+ return ip.neg ? INTMAX_MIN : INTMAX_MAX;
}
- return sign ? -x : x;
+ return ip.neg ? -ip.val : ip.val;
}
diff --git a/src/stdlib/strtoumax.c b/src/stdlib/strtoumax.c
index f1902476..a2bb4d7d 100644
--- a/src/stdlib/strtoumax.c
+++ b/src/stdlib/strtoumax.c
@@ -2,122 +2,33 @@
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
-#include <stdio.h>
-
-/* Lookup table for digit values. -1==255>=36 -> invalid */
-static const unsigned char digits[] = {
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
--1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
-25,26,27,28,29,30,31,32,33,34,35,-1,-1,-1,-1,-1,
--1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
-25,26,27,28,29,30,31,32,33,34,35,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
--1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-};
+#include "intparse.h"
uintmax_t strtoumax(const char *s1, char **p, int base)
{
const unsigned char *s = (void *)s1;
- size_t x1, z1;
- uintmax_t x, z=0;
- int sign = 0;
- int shift;
-
- if (!p) p = (char **)&s1;
-
- /* Initial whitespace */
- for (; isspace(*s); s++);
-
- /* Optional sign */
- if (*s == '-') sign = *s++;
- else if (*s == '+') s++;
+ struct intparse ip = {0};
- /* Default base 8, 10, or 16 depending on prefix */
- if (base == 0) {
- if (s[0] == '0') {
- if ((s[1]|32) == 'x') base = 16;
- else base = 8;
- } else {
- base = 10;
- }
- }
+ if (p) *p = (char *)s1;
- if ((unsigned)base-2 > 36-2 || digits[*s]>=base) {
- *p = (char *)s1;
+ if (base && base-2U > 34) {
errno = EINVAL;
return 0;
}
- /* Main loops. Only use big types if we have to. */
- if (base == 10) {
- for (x1=0; isdigit(*s) && x1<=SIZE_MAX/10-10; s++)
- x1 = 10*x1 + *s-'0';
- for (x=x1; isdigit(*s) && x<=UINTMAX_MAX/10-10; s++)
- x = 10*x + *s-'0';
- if (isdigit(*s)) {
- if (isdigit(s[1]) || 10*x>UINTMAX_MAX-(*s-'0'))
- goto overflow;
- x = 10*x + *s-'0';
- }
- } else if (!(base & base/2)) {
- if (base == 16) {
- if (s[0]=='0' && (s[1]|32)=='x' && digits[s[2]]<16)
- s+=2;
- shift=4;
- z1 = SIZE_MAX/16;
- z = UINTMAX_MAX/16;
- } else if (base == 8) {
- shift=3;
- z1 = SIZE_MAX/8;
- z = UINTMAX_MAX/8;
- } else if (base == 2) {
- shift=1;
- z1 = SIZE_MAX/2;
- z = UINTMAX_MAX/2;
- } else if (base == 4) {
- shift=2;
- z1 = SIZE_MAX/4;
- z = UINTMAX_MAX/4;
- } else /* if (base == 32) */ {
- shift=5;
- z1 = SIZE_MAX/32;
- z = UINTMAX_MAX/32;
- }
- for (x1=0; digits[*s]<base && x1<=z1; s++)
- x1 = (x1<<shift) + digits[*s];
- for (x=x1; digits[*s]<base && x<=z; s++)
- x = (x<<shift) + digits[*s];
- if (digits[*s] < base) goto overflow;
- } else {
- z1 = SIZE_MAX/base-base;
- for (x1=0; digits[*s]<base && x1<=z1; s++)
- x1 = x1*base + digits[*s];
- if (digits[*s]<base)
- z = UINTMAX_MAX/base-base;
- for (x=x1; digits[*s]<base && x<=z; s++)
- x = x*base + digits[*s];
- if (digits[*s] < base) {
- if (digits[s[1]]<base || x*base>UINTMAX_MAX-digits[*s])
- goto overflow;
- x = x*base + digits[*s];
- }
- }
+ for (; isspace(*s); s++);
+
+ ip.base = base;
+ __intparse(&ip, s, SIZE_MAX);
- *p = (char *)s;
- return sign ? -x : x;
+ if (p && ip.err != EINVAL)
+ *p = (char *)s + ip.cnt;
+
+ if (ip.err) {
+ errno = ip.err;
+ if (ip.err = EINVAL) return 0;
+ return UINTMAX_MAX;
+ }
-overflow:
- for (; digits[*s] < base; s++);
- *p = (char *)s;
- errno = ERANGE;
- return UINTMAX_MAX;
+ return ip.neg ? -ip.val : ip.val;
}
diff --git a/src/stdlib/wcstoimax.c b/src/stdlib/wcstoimax.c
index 59894f60..b83206b7 100644
--- a/src/stdlib/wcstoimax.c
+++ b/src/stdlib/wcstoimax.c
@@ -2,24 +2,38 @@
#include <wctype.h>
#include <inttypes.h>
#include <errno.h>
+#include "intparse.h"
intmax_t wcstoimax(const wchar_t *s, wchar_t **p, int base)
{
- int sign = 0;
- uintmax_t x;
+ struct intparse ip = {0};
+ unsigned char tmp;
+
+ if (p) *p = (wchar_t *)s;
+
+ if (base && base-2U > 34) {
+ errno = EINVAL;
+ return 0;
+ }
- /* Initial whitespace */
for (; iswspace(*s); s++);
- /* Optional sign */
- if (*s == '-') sign = *s++;
- else if (*s == '+') s++;
+ ip.base = base;
+ for (; *s<256 && (tmp=*s, __intparse(&ip, &tmp, 1)); s++);
+
+ if (p && ip.err != EINVAL)
+ *p = (wchar_t *)s;
+
+ if (ip.err) {
+ errno = ip.err;
+ if (ip.err = EINVAL) return 0;
+ return ip.neg ? INTMAX_MIN : INTMAX_MAX;
+ }
- x = wcstoumax(s, p, base);
- if (x > INTMAX_MAX) {
- if (!sign || -x != INTMAX_MIN)
+ if (ip.val > INTMAX_MAX) {
+ if (!ip.neg || -ip.val != INTMAX_MIN)
errno = ERANGE;
- return sign ? INTMAX_MIN : INTMAX_MAX;
+ return ip.neg ? INTMAX_MIN : INTMAX_MAX;
}
- return sign ? -x : x;
+ return ip.neg ? -ip.val : ip.val;
}
diff --git a/src/stdlib/wcstoumax.c b/src/stdlib/wcstoumax.c
index 86528ef1..e30b0638 100644
--- a/src/stdlib/wcstoumax.c
+++ b/src/stdlib/wcstoumax.c
@@ -3,46 +3,33 @@
#include <stdlib.h>
#include <inttypes.h>
#include <errno.h>
+#include "intparse.h"
uintmax_t wcstoumax(const wchar_t *s, wchar_t **p, int base)
{
- /* Large enough for largest value in binary */
- char buf[sizeof(uintmax_t)*8+2];
- int sign = 0, skipped=0;
+ struct intparse ip = {0};
+ unsigned char tmp;
- if (!p) p = (wchar_t **)&s;
+ if (p) *p = (wchar_t *)s;
- if (base && (unsigned)base-2 > 36-2) {
- *p = (wchar_t *)s;
+ if (base && base-2U > 34) {
errno = EINVAL;
return 0;
}
- /* Initial whitespace */
for (; iswspace(*s); s++);
- /* Optional sign */
- if (*s == '-') sign = *s++;
- else if (*s == '+') s++;
-
- /* Skip leading zeros but don't allow leading zeros before "0x". */
- for (; s[0]=='0' && s[1]=='0'; s++) skipped=1;
- if (skipped && (base==0 || base==16) && (s[1]|32)=='x') {
- *p = (wchar_t *)(s+1);
- return 0;
- }
-
- /* Convert to normal char string so we can use strtoumax */
- buf[0] = sign;
- if (wcstombs(buf+!!sign, s, sizeof buf-1) == -1) return 0;
- buf[sizeof buf-1]=0;
+ ip.base = base;
+ for (; *s<256 && (tmp=*s, __intparse(&ip, &tmp, 1)); s++);
- /* Compute final position */
- if (p) {
- if ((base==0 || base==16) && s[0]=='0' && (s[1]|32)=='x' && iswxdigit(s[2])) s+=2;
- for(;*s&&((unsigned)*s-'0'<base||((unsigned)*s|32)-'a'<base-10);s++);
+ if (p && ip.err != EINVAL)
*p = (wchar_t *)s;
+
+ if (ip.err) {
+ errno = ip.err;
+ if (ip.err = EINVAL) return 0;
+ return UINTMAX_MAX;
}
- return strtoumax(buf, 0, base);
+ return ip.neg ? -ip.val : ip.val;
}