diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c index 471fbb7ee6..a50c5ccd4a 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -298,9 +298,70 @@ pg_strtoint32_safe(const char *s, Node *escontext) { const char *ptr = s; const char *firstdigit; - uint32 tmp = 0; + uint32 tmp; + int32 tmp_s = 0; bool neg = false; + /* + * The majority of cases are likely to be base-10 digits without any + * underscore separator characters. We'll first try to parse the string with + * the assumption that's the case and only fallback on a slower + * implementation which handles hex, octal and binary strings and + * underscores. + */ + + /* leave it up to the slow path to look for leading spaces */ + + if (*ptr == '-') + { + ptr++; + neg = true; + } + + /* a leading '+' is uncommon so leave that for the slow path */ + + /* process digits */ + for (;;) + { + unsigned char digit = (*ptr - '0'); + + /* + * Exploit unsigned arithmetic to save having to check both the upper + * and lower bounds of the digit + */ + if (digit >= 10) + break; + + ptr++; + + if (unlikely(pg_mul_s32_overflow(tmp_s, 10, &tmp_s)) || + unlikely(pg_sub_s32_overflow(tmp_s, digit, &tmp_s))) + goto out_of_range; + } + + /* we need at least one digit */ + if (unlikely(ptr == s)) + goto slow; + + /* when the string does not end in a digit, let the slow path handle it */ + if (unlikely(*ptr != '\0')) + goto slow; + + if (!neg) + { + /* could fail if input is most negative number */ + if (unlikely(tmp_s == PG_INT32_MIN)) + goto out_of_range; + tmp_s = -tmp_s; + } + + return tmp_s; + +slow: + tmp = 0; + ptr = s; + /* no need to reset neg */ + /* skip leading spaces */ while (likely(*ptr) && isspace((unsigned char) *ptr)) ptr++;