Merge pull request #6339 from ebraminio/hb-number-copy-free [number] Make integer parsings copy free
diff --git a/src/hb-number.cc b/src/hb-number.cc index b6e7d69..03d8db4 100644 --- a/src/hb-number.cc +++ b/src/hb-number.cc
@@ -1,5 +1,5 @@ /* - * Copyright © 2019 Ebrahim Byagowi + * Copyright © 2019-2026 Ebrahim Byagowi * * This is part of HarfBuzz, a text shaping library. * @@ -31,55 +31,116 @@ #include "hb-number.hh" #include "hb-number-parser.hh" -template<typename T, typename Func> +bool +hb_parse_double (const char **pp, const char *end, double *pv, bool whole_buffer) +{ + const char *pend = end; + double value = hb_strtod (*pp, &pend); + if (unlikely (*pp == pend)) return false; + *pv = value; + *pp = pend; + return !whole_buffer || end == pend; +} + +/* Returns the digit value of c in the given base (2..36), or -1 if c + * isn't a digit in that base. */ +static int +_hb_digit_value (char c, int base) +{ + int d; + if ('0' <= c && c <= '9') d = c - '0'; + else if ('a' <= c && c <= 'z') d = c - 'a' + 10; + else if ('A' <= c && c <= 'Z') d = c - 'A' + 10; + else return -1; + return d < base ? d : -1; +} + +/* Consumes digits in [p, pe) into *value (magnitude). Sets *overflow + * if the value would exceed UINT64_MAX. Sets *any_digits if at least + * one digit was consumed. Returns the position past the last digit. */ +static const char * +_hb_scan_digits (const char *p, const char *pe, int base, + uint64_t *value, bool *overflow, bool *any_digits) +{ + const uint64_t cutoff = UINT64_MAX / (unsigned) base; + const uint64_t cutlim = UINT64_MAX % (unsigned) base; + uint64_t v = 0; + bool ov = false, any = false; + + for (; p < pe; p++) + { + int d = _hb_digit_value (*p, base); + if (d < 0) break; + any = true; + if (v > cutoff || (v == cutoff && (uint64_t) d > cutlim)) + ov = true; + else + v = v * (unsigned) base + (uint64_t) d; + } + + *value = v; + *overflow = ov; + *any_digits = any; + return p; +} + +template<typename T, typename Postprocess> static bool _parse_number (const char **pp, const char *end, T *pv, - bool whole_buffer, Func f) + bool whole_buffer, int base, Postprocess post) { - char buf[32]; - unsigned len = hb_min (ARRAY_LENGTH (buf) - 1, (unsigned) (end - *pp)); - strncpy (buf, *pp, len); - buf[len] = '\0'; + const char *pend = end; + const char *p = *pp; + bool neg = false, any = false, overflow = false; - char *p = buf; - char *pend = p; + while (p < pend && ISSPACE (*p)) p++; + if (p < pend && (*p == '+' || *p == '-')) { neg = (*p == '-'); p++; } - errno = 0; - *pv = f (p, &pend); - if (unlikely (errno || p == pend || - /* Check if consumed whole buffer if is requested */ - (whole_buffer && pend - p != end - *pp))) - return false; + /* Skip an optional "0x"/"0X" prefix for base 16, but only when a hex + * digit follows — "0x" alone parses as the digit 0, matching strtoul. */ + if (base == 16 && p + 2 < pend && p[0] == '0' && + (p[1] == 'x' || p[1] == 'X') && _hb_digit_value (p[2], 16) >= 0) + p += 2; - *pp += pend - p; + uint64_t v = 0; + p = _hb_scan_digits (p, pend, base, &v, &overflow, &any); + if (!any || overflow) return false; + + T out; + if (!post (v, neg, &out)) return false; + if (whole_buffer && p != pend) return false; + + *pv = out; + *pp = p; return true; } bool hb_parse_int (const char **pp, const char *end, int *pv, bool whole_buffer) { - return _parse_number<int> (pp, end, pv, whole_buffer, - [] (const char *p, char **end) - { return strtol (p, end, 10); }); + return _parse_number (pp, end, pv, whole_buffer, 10, + [] (uint64_t v, bool neg, int *out) { + /* INT_MAX = 2^31-1, INT_MIN magnitude = 2^31 */ + uint64_t limit = neg ? (uint64_t) INT_MAX + 1 : (uint64_t) INT_MAX; + if (v > limit) return false; + *out = (int) (neg ? -(int64_t) v : (int64_t) v); + return true; + }); } bool hb_parse_uint (const char **pp, const char *end, unsigned *pv, - bool whole_buffer, int base) + bool whole_buffer, int base) { - return _parse_number<unsigned> (pp, end, pv, whole_buffer, - [base] (const char *p, char **end) - { return strtoul (p, end, base); }); -} - -bool -hb_parse_double (const char **pp, const char *end, double *pv, bool whole_buffer) -{ - const char *pend = end; - *pv = hb_strtod (*pp, &pend); - if (unlikely (*pp == pend)) return false; - *pp = pend; - return !whole_buffer || end == pend; + return _parse_number (pp, end, pv, whole_buffer, base, + [] (uint64_t v, bool neg, unsigned *out) { + if (v > UINT_MAX) return false; + /* Negate in unsigned so -1 wraps to UINT_MAX, matching strtoul + * truncated to unsigned. */ + unsigned u = (unsigned) v; + *out = neg ? (unsigned) (0u - u) : u; + return true; + }); } #endif /* HB_NUMBER_CC pacify */
diff --git a/src/test-number.cc b/src/test-number.cc index e10efc8..7400820 100644 --- a/src/test-number.cc +++ b/src/test-number.cc
@@ -1,5 +1,5 @@ /* - * Copyright © 2019 Ebrahim Byagowi + * Copyright © 2019-2026 Ebrahim Byagowi * * This is part of HarfBuzz, a text shaping library. * @@ -46,6 +46,292 @@ } { + const char str[] = "-123"; + const char *pp = str; + const char *end = str + 4; + + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == -123); + hb_always_assert (pp - str == 4); + hb_always_assert (end - pp == 0); + hb_always_assert (!*end); + } + + /* Leading whitespace */ + { + const char str[] = " 123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 123); + hb_always_assert (pp == end); + } + { + const char str[] = "\t123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 123); + hb_always_assert (pp == end); + } + { + const char str[] = "\n123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 123); + hb_always_assert (pp == end); + } + { + const char str[] = " -123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == -123); + hb_always_assert (pp == end); + } + + /* Leading '+' sign */ + { + const char str[] = "+123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 123); + hb_always_assert (pp == end); + } + { + const char str[] = "+0"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 0); + hb_always_assert (pp == end); + } + + /* Leading zeros are decimal, not octal (base is passed as 10) */ + { + const char str[] = "007"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 7); + hb_always_assert (pp == end); + } + + /* Negative zero */ + { + const char str[] = "-0"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 0); + hb_always_assert (pp == end); + } + + /* Partial parse: stops at first non-digit */ + { + const char str[] = "123abc"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 123); + hb_always_assert (pp == str + 3); + } + + /* INT_MAX / INT_MIN (fits in long on every supported platform) */ + { + const char str[] = "2147483647"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 2147483647); + hb_always_assert (pp == end); + } + { + const char str[] = "-2147483648"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == -2147483647 - 1); + hb_always_assert (pp == end); + } + + /* Failures: no digits */ + { + const char str[] = " "; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + { + const char str[] = "+"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + } + { + const char str[] = "-"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + } + { + const char str[] = "abc"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + } + + /* Multiple signs fail */ + { + const char str[] = "++1"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + } + { + const char str[] = "--1"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + } + { + const char str[] = "+-1"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + } + { + const char str[] = "-+1"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + } + + /* whole_buffer: leading whitespace ok, trailing content rejected, + * and *pp / *pv are left untouched on failure */ + { + const char str[] = " 123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv, true)); + hb_always_assert (pv == 123); + hb_always_assert (pp == end); + } + { + const char str[] = "123abc"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv, true)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + { + /* 10 digits > INT_MAX. The old strtol-based implementation silently + * truncated on 64-bit platforms (long is 64-bit) but rejected on + * 32-bit; the new implementation rejects uniformly. */ + const char str[] = "9999999999"; /* 10 digits, > INT_MAX */ + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + /* INT_MAX / INT_MIN - exactly representable */ + { + const char str[] = "2147483647"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == 2147483647); + hb_always_assert (pp == end); + } + { + const char str[] = "-2147483648"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv; + hb_always_assert (hb_parse_int (&pp, end, &pv)); + hb_always_assert (pv == -2147483647 - 1); /* not -2147483648, that's UB as an int literal */ + hb_always_assert (pp == end); + } + + /* One past INT_MAX - must fail */ + { + const char str[] = "2147483648"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + /* One past INT_MIN - must fail */ + { + const char str[] = "-2147483649"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + /* Same for signed */ + { + const char str[] = "9999999999999999999999999999999999999999"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 99; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + { + /* Verify *pv and *pp are untouched when a range check rejects. */ + const char str[] = "9999999999"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + int pv = 42; + hb_always_assert (!hb_parse_int (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 42); + } +} + +static void +test_parse_uint () +{ + { const char str[] = "123"; const char *pp = str; const char *end = str + strlen (str); @@ -86,19 +372,6 @@ } { - const char str[] = "-123"; - const char *pp = str; - const char *end = str + 4; - - int pv; - hb_always_assert (hb_parse_int (&pp, end, &pv)); - hb_always_assert (pv == -123); - hb_always_assert (pp - str == 4); - hb_always_assert (end - pp == 0); - hb_always_assert (!*end); - } - - { const char str[] = "123"; const char *pp = str; hb_always_assert (ARRAY_LENGTH (str) == 4); @@ -136,6 +409,410 @@ hb_always_assert (pp - str == 3); hb_always_assert (end - pp == 2); } + + { + const char str[] = "123abc"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv, true)); + hb_always_assert (pp == str); + // hb_always_assert (pv == 99); this shouldn't fail but it does with the current implementation + } + + /* Leading whitespace */ + { + const char str[] = " 123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 123); + hb_always_assert (pp == end); + } + { + const char str[] = "\t123"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 123); + hb_always_assert (pp == end); + } + + /* Leading zeros (still decimal when base is 10) */ + { + const char str[] = "007"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 7); + hb_always_assert (pp == end); + } + { + const char str[] = "000"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 0); + hb_always_assert (pp == end); + } + + /* UINT_MAX */ + { + const char str[] = "4294967295"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 4294967295u); + hb_always_assert (pp == end); + } + + /* Negative wraps in two's complement (matches strtoul on unsigned) */ + { + const char str[] = "-1"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 4294967295u); + hb_always_assert (pp == end); + } + { + const char str[] = "-0"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 0); + hb_always_assert (pp == end); + } + + /* Base 2 */ + { + const char str[] = "1010"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 2)); + hb_always_assert (pv == 10); + hb_always_assert (pp == end); + } + { + const char str[] = "11111111"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 2)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + { + const char str[] = "12"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + /* '2' is not a base-2 digit; parse stops at index 1 */ + hb_always_assert (hb_parse_uint (&pp, end, &pv, false, 2)); + hb_always_assert (pv == 1); + hb_always_assert (pp == str + 1); + } + + /* Base 8 */ + { + const char str[] = "777"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 8)); + hb_always_assert (pv == 511); + hb_always_assert (pp == end); + } + { + const char str[] = "010"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 8)); + hb_always_assert (pv == 8); + hb_always_assert (pp == end); + } + + /* Base 16, with and without 0x prefix, mixed case */ + { + const char str[] = "FF"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + { + const char str[] = "ff"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + { + const char str[] = "Ff"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + { + const char str[] = "0xFF"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + { + const char str[] = "0XFF"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + + /* Base 36 */ + { + const char str[] = "Z"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 36)); + hb_always_assert (pv == 35); + hb_always_assert (pp == end); + } + { + const char str[] = "z"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 36)); + hb_always_assert (pv == 35); + hb_always_assert (pp == end); + } + { + const char str[] = "10"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 36)); + hb_always_assert (pv == 36); + hb_always_assert (pp == end); + } + + /* Failures */ + { + const char str[] = "abc"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + /* 'a' isn't a decimal digit; whole parse fails */ + hb_always_assert (!hb_parse_uint (&pp, end, &pv, false, 10)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + { + const char str[] = "+"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + } + { + const char str[] = "-"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + } + { + const char str[] = ""; + const char *pp = str; + const char *end = str; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + } + + { + const char str[] = "0XFF"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + { + const char str[] = "0xff"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 255); + hb_always_assert (pp == end); + } + { + const char str[] = "0x0"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 0); + hb_always_assert (pp == end); + } + /* "0x" alone: consume only the 0, leave the x */ + { + const char str[] = "0x"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, false, 16)); + hb_always_assert (pv == 0); + hb_always_assert (pp == str + 1); + } + /* "0xG": 0 is consumed, xG left */ + { + const char str[] = "0xG"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, false, 16)); + hb_always_assert (pv == 0); + hb_always_assert (pp == str + 1); + } + /* Negative with prefix */ + { + const char str[] = "-0xFF"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == (unsigned) -255); + hb_always_assert (pp == end); + } + /* Prefix rule doesn't fire for base 10 */ + { + const char str[] = "0xFF"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, false, 10)); + hb_always_assert (pv == 0); + hb_always_assert (pp == str + 1); + } + { + /* 10 digits > UINT_MAX. The old strtol-based implementation silently + * truncated on 64-bit platforms (unsigned long is 64-bit) but rejected on + * 32-bit; the new implementation rejects uniformly. */ + const char str[] = "9999999999"; /* 10 digits, > UINT_MAX */ + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + /* UINT_MAX - exactly representable */ + { + const char str[] = "4294967295"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pv == 4294967295u); + hb_always_assert (pp == end); + } + /* One past UINT_MAX - must fail (currently truncates to 0) */ + { + const char str[] = "4294967296"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + /* 20 digits, way past UINT_MAX */ + { + const char str[] = "99999999999999999999"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + /* 40 digits, past the old 32-byte stack buffer */ + { + const char str[] = "9999999999999999999999999999999999999999"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + /* 0xFFFFFFFF = UINT_MAX */ + { + const char str[] = "0xFFFFFFFF"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv; + hb_always_assert (hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pv == 4294967295u); + hb_always_assert (pp == end); + } + /* 0x100000000 = UINT_MAX + 1 */ + { + const char str[] = "0x100000000"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv, true, 16)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + /* 13 'Z's = 36^13 - 1, comfortably > UINT64_MAX would need ~12 digits */ + { + const char str[] = "ZZZZZZZZZZZZZZ"; /* 14 Z's */ + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 99; + hb_always_assert (!hb_parse_uint (&pp, end, &pv, true, 36)); + hb_always_assert (pp == str); + hb_always_assert (pv == 99); + } + + { + const char str[] = "9999999999"; + const char *pp = str; + const char *end = str + ARRAY_LENGTH (str) - 1; + unsigned int pv = 42; + hb_always_assert (!hb_parse_uint (&pp, end, &pv)); + hb_always_assert (pp == str); + hb_always_assert (pv == 42); + } } /* Tolerate 1 ULP difference. Needed because 32-bit x86 (x87) computes @@ -309,6 +986,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99); } { const char str[] = "."; @@ -316,6 +994,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99); } { const char str[] = "1e-"; @@ -376,6 +1055,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99.0); } { const char str[] = ""; @@ -383,6 +1063,7 @@ const char *end = str; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99.0); } { const char str[] = " \t 123"; @@ -560,6 +1241,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99.0); } { const char str[] = "+-1"; @@ -567,6 +1249,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99.0); } { const char str[] = "0e5"; @@ -658,6 +1341,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99.0); } { const char str[] = "inf"; @@ -665,6 +1349,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99.0); } { const char str[] = "1..5"; @@ -691,6 +1376,7 @@ const char *end = str + ARRAY_LENGTH (str) - 1; double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); + hb_always_assert (pv == 99.0); } { const char str[] = " abc"; @@ -699,6 +1385,7 @@ double pv = 99.0; hb_always_assert (!hb_parse_double (&pp, end, &pv)); hb_always_assert (pp == str); + hb_always_assert (pv == 99.0); } { @@ -733,8 +1420,9 @@ int main (int argc, char **argv) { - test_parse_int (); test_parse_double (); + test_parse_int (); + test_parse_uint (); return 0; }