diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c index 471fbb7ee6..bb48e5157e 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -301,6 +301,70 @@ pg_strtoint32_safe(const char *s, Node *escontext) uint32 tmp = 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(tmp > -(PG_INT32_MIN / 10))) + goto out_of_range; + + tmp = tmp * 10 + digit; + } + + /* 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) + { + /* check the negative equivalent will fit without overflowing */ + if (unlikely(tmp > (uint32) (-(PG_INT32_MIN + 1)) + 1)) + goto out_of_range; + return -((int32) tmp); + } + + if (unlikely(tmp > PG_INT32_MAX)) + goto out_of_range; + + return (int32) tmp; + +slow: + tmp = 0; + ptr = s; + /* no need to reset neg */ + /* skip leading spaces */ while (likely(*ptr) && isspace((unsigned char) *ptr)) ptr++;