diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 221b47298c..28891ba337 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -79,6 +79,7 @@ #include "utils/builtins.h" #include "utils/hashutils.h" #include "utils/memutils.h" +#include "common/string.h" PG_MODULE_MAGIC; @@ -1022,7 +1023,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, /* parse command tag to retrieve the number of affected rows. */ if (completionTag && strncmp(completionTag, "COPY ", 5) == 0) - rows = pg_strtouint64(completionTag + 5, NULL, 10); + (void) pg_strtouint64(completionTag + 5, &rows); else rows = 0; diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 2c0ae395ba..8e75d52b06 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -21,6 +21,7 @@ #include "catalog/heap.h" #include "catalog/pg_type.h" #include "commands/trigger.h" +#include "common/string.h" #include "executor/executor.h" #include "executor/spi_priv.h" #include "miscadmin.h" @@ -2338,8 +2339,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, CreateTableAsStmt *ctastmt = (CreateTableAsStmt *) stmt->utilityStmt; if (strncmp(completionTag, "SELECT ", 7) == 0) - _SPI_current->processed = - pg_strtouint64(completionTag + 7, NULL, 10); + (void) pg_strtouint64(completionTag + 7, &_SPI_current->processed); else { /* @@ -2361,8 +2361,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, else if (IsA(stmt->utilityStmt, CopyStmt)) { Assert(strncmp(completionTag, "COPY ", 5) == 0); - _SPI_current->processed = pg_strtouint64(completionTag + 5, - NULL, 10); + (void) pg_strtouint64(completionTag + 5, &_SPI_current->processed); } } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 764e3bb90c..17cde42b4d 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -34,6 +34,7 @@ #include "fmgr.h" #include "miscadmin.h" +#include "common/string.h" #include "nodes/extensible.h" #include "nodes/parsenodes.h" #include "nodes/plannodes.h" @@ -80,7 +81,7 @@ #define READ_UINT64_FIELD(fldname) \ token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* get field value */ \ - local_node->fldname = pg_strtouint64(token, NULL, 10) + (void) pg_strtouint64(token, &local_node->fldname) /* Read a long integer field (anything written as ":fldname %ld") */ #define READ_LONG_FIELD(fldname) \ diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index 1baf7ef31f..4595c35875 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -25,10 +25,10 @@ #include "parser/parse_expr.h" #include "parser/parse_relation.h" #include "utils/builtins.h" -#include "utils/int8.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/varbit.h" +#include "common/string.h" static void pcb_error_callback(void *arg); @@ -496,7 +496,7 @@ make_const(ParseState *pstate, Value *value, int location) case T_Float: /* could be an oversize integer as well as a float ... */ - if (scanint8(strVal(value), true, &val64)) + if (pg_strtoint64(strVal(value), &val64) == strtoint_ok) { /* * It might actually fit in int32. Probably only INT_MIN can diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 9c08757fca..b364a41fb5 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -12,6 +12,7 @@ */ #include "postgres.h" +#include "common/string.h" #include "catalog/pg_publication.h" #include "fmgr.h" @@ -22,7 +23,6 @@ #include "replication/pgoutput.h" #include "utils/inval.h" -#include "utils/int8.h" #include "utils/memutils.h" #include "utils/syscache.h" #include "utils/varlena.h" @@ -113,7 +113,7 @@ parse_output_parameters(List *options, uint32 *protocol_version, errmsg("conflicting or redundant options"))); protocol_version_given = true; - if (!scanint8(strVal(defel->arg), true, &parsed)) + if (unlikely(pg_strtoint64(strVal(defel->arg), &parsed) != strtoint_ok)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid proto_version"))); diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index 6515fc8ec6..15f3babc05 100644 --- a/src/backend/utils/adt/cash.c +++ b/src/backend/utils/adt/cash.c @@ -26,7 +26,6 @@ #include "libpq/pqformat.h" #include "utils/builtins.h" #include "utils/cash.h" -#include "utils/int8.h" #include "utils/numeric.h" #include "utils/pg_locale.h" diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index b3115e4bea..05bbd2a006 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -92,7 +92,6 @@ #include "utils/datetime.h" #include "utils/float.h" #include "utils/formatting.h" -#include "utils/int8.h" #include "utils/memutils.h" #include "utils/numeric.h" #include "utils/pg_locale.h" diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 0ff9394a2f..5782ea7cab 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -18,12 +18,12 @@ #include #include "common/int.h" +#include "common/string.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" #include "optimizer/optimizer.h" -#include "utils/int8.h" #include "utils/builtins.h" @@ -47,87 +47,32 @@ typedef struct * Formatting and conversion routines. *---------------------------------------------------------*/ -/* - * scanint8 --- try to parse a string into an int8. - * - * If errorOK is false, ereport a useful error message if the string is bad. - * If errorOK is true, just return "false" for bad input. - */ -bool -scanint8(const char *str, bool errorOK, int64 *result) +static bool +scanint8(const char * str, int64 * result) { - const char *ptr = str; - int64 tmp = 0; - bool neg = false; + strtoint_status stat = pg_strtoint64(str, result); - /* - * Do our own scan, rather than relying on sscanf which might be broken - * for long long. - * - * As INT64_MIN can't be stored as a positive 64 bit integer, accumulate - * value as a negative number. - */ - - /* skip leading spaces */ - while (*ptr && isspace((unsigned char) *ptr)) - ptr++; - - /* handle sign */ - if (*ptr == '-') - { - ptr++; - neg = true; - } - else if (*ptr == '+') - ptr++; - - /* require at least one digit */ - if (unlikely(!isdigit((unsigned char) *ptr))) - goto invalid_syntax; - - /* process digits */ - while (*ptr && isdigit((unsigned char) *ptr)) - { - int8 digit = (*ptr++ - '0'); - - if (unlikely(pg_mul_s64_overflow(tmp, 10, &tmp)) || - unlikely(pg_sub_s64_overflow(tmp, digit, &tmp))) - goto out_of_range; - } - - /* allow trailing whitespace, but not other trailing chars */ - while (*ptr != '\0' && isspace((unsigned char) *ptr)) - ptr++; - - if (unlikely(*ptr != '\0')) - goto invalid_syntax; - - if (!neg) + if (likely(stat == strtoint_ok)) + return true; + else if (stat == strtoint_range_error) { - /* could fail if input is most negative number */ - if (unlikely(tmp == PG_INT64_MIN)) - goto out_of_range; - tmp = -tmp; - } - - *result = tmp; - return true; - -out_of_range: - if (!errorOK) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value \"%s\" is out of range for type %s", str, "bigint"))); - return false; - -invalid_syntax: - if (!errorOK) + return false; + } + else if (stat == strtoint_syntax_error) + { ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s: \"%s\"", "bigint", str))); - return false; + return false; + } + else + /* cannot get here */ + Assert(0); } /* int8in() @@ -138,7 +83,7 @@ int8in(PG_FUNCTION_ARGS) char *str = PG_GETARG_CSTRING(0); int64 result; - (void) scanint8(str, false, &result); + (void) scanint8(str, &result); PG_RETURN_INT64(result); } diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index a00db3ce7a..bc02958950 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -39,7 +39,6 @@ #include "utils/float.h" #include "utils/guc.h" #include "utils/hashutils.h" -#include "utils/int8.h" #include "utils/numeric.h" #include "utils/sortsupport.h" diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c index 70138feb29..f5d488c569 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -543,25 +543,3 @@ pg_ltostr(char *str, int32 value) return end; } - -/* - * pg_strtouint64 - * Converts 'str' into an unsigned 64-bit integer. - * - * This has the identical API to strtoul(3), except that it will handle - * 64-bit ints even where "long" is narrower than that. - * - * For the moment it seems sufficient to assume that the platform has - * such a function somewhere; let's not roll our own. - */ -uint64 -pg_strtouint64(const char *str, char **endptr, int base) -{ -#ifdef _MSC_VER /* MSVC only */ - return _strtoui64(str, endptr, base); -#elif defined(HAVE_STRTOULL) && SIZEOF_LONG < 8 - return strtoull(str, endptr, base); -#else - return strtoul(str, endptr, base); -#endif -} diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c index e5c7e5c7ee..749fc44623 100644 --- a/src/backend/utils/adt/rangetypes.c +++ b/src/backend/utils/adt/rangetypes.c @@ -37,7 +37,6 @@ #include "utils/builtins.h" #include "utils/date.h" #include "utils/hashutils.h" -#include "utils/int8.h" #include "utils/lsyscache.h" #include "utils/rangetypes.h" #include "utils/timestamp.h" diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l index e9020ad231..11e211f294 100644 --- a/src/bin/pgbench/exprscan.l +++ b/src/bin/pgbench/exprscan.l @@ -23,6 +23,7 @@ *------------------------------------------------------------------------- */ +#include "common/string.h" #include "fe_utils/psqlscan_int.h" /* context information for reporting errors in expressions */ @@ -205,7 +206,7 @@ notnull [Nn][Oo][Tt][Nn][Uu][Ll][Ll] return MAXINT_PLUS_ONE_CONST; } {digit}+ { - if (!strtoint64(yytext, true, &yylval->ival)) + if (unlikely(pg_strtoint64(yytext, &yylval->ival) != strtoint_ok)) expr_yyerror_more(yyscanner, "bigint constant overflow", strdup(yytext)); return INTEGER_CONST; diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 570cf3306a..d1cfa07e87 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -34,6 +34,7 @@ #include "postgres_fe.h" #include "common/int.h" #include "common/logging.h" +#include "common/string.h" #include "fe_utils/conditional.h" #include "getopt_long.h" #include "libpq-fe.h" @@ -670,8 +671,8 @@ is_an_int(const char *str) { const char *ptr = str; - /* skip leading spaces; cast is consistent with strtoint64 */ - while (*ptr && isspace((unsigned char) *ptr)) + /* skip leading spaces; cast is consistent with pg_strtoint64 */ + while (isspace((unsigned char) *ptr)) ptr++; /* skip sign */ @@ -679,101 +680,21 @@ is_an_int(const char *str) ptr++; /* at least one digit */ - if (*ptr && !isdigit((unsigned char) *ptr)) + if (!isdigit((unsigned char) *ptr++)) return false; - /* eat all digits */ - while (*ptr && isdigit((unsigned char) *ptr)) + /* eat all other digits */ + while (isdigit((unsigned char) *ptr)) + ptr++; + + /* allow trailing spaces */ + while (isspace((unsigned char) *ptr)) ptr++; /* must have reached end of string */ return *ptr == '\0'; } - -/* - * strtoint64 -- convert a string to 64-bit integer - * - * This function is a slightly modified version of scanint8() from - * src/backend/utils/adt/int8.c. - * - * The function returns whether the conversion worked, and if so - * "*result" is set to the result. - * - * If not errorOK, an error message is also printed out on errors. - */ -bool -strtoint64(const char *str, bool errorOK, int64 *result) -{ - const char *ptr = str; - int64 tmp = 0; - bool neg = false; - - /* - * Do our own scan, rather than relying on sscanf which might be broken - * for long long. - * - * As INT64_MIN can't be stored as a positive 64 bit integer, accumulate - * value as a negative number. - */ - - /* skip leading spaces */ - while (*ptr && isspace((unsigned char) *ptr)) - ptr++; - - /* handle sign */ - if (*ptr == '-') - { - ptr++; - neg = true; - } - else if (*ptr == '+') - ptr++; - - /* require at least one digit */ - if (unlikely(!isdigit((unsigned char) *ptr))) - goto invalid_syntax; - - /* process digits */ - while (*ptr && isdigit((unsigned char) *ptr)) - { - int8 digit = (*ptr++ - '0'); - - if (unlikely(pg_mul_s64_overflow(tmp, 10, &tmp)) || - unlikely(pg_sub_s64_overflow(tmp, digit, &tmp))) - goto out_of_range; - } - - /* allow trailing whitespace, but not other trailing chars */ - while (*ptr != '\0' && isspace((unsigned char) *ptr)) - ptr++; - - if (unlikely(*ptr != '\0')) - goto invalid_syntax; - - if (!neg) - { - if (unlikely(tmp == PG_INT64_MIN)) - goto out_of_range; - tmp = -tmp; - } - - *result = tmp; - return true; - -out_of_range: - if (!errorOK) - fprintf(stderr, - "value \"%s\" is out of range for type bigint\n", str); - return false; - -invalid_syntax: - if (!errorOK) - fprintf(stderr, - "invalid input syntax for type bigint: \"%s\"\n", str); - return false; -} - /* convert string to double, detecting overflows/underflows */ bool strtodouble(const char *str, bool errorOK, double *dv) @@ -1283,6 +1204,22 @@ getVariable(CState *st, char *name) return var->svalue; } +static bool +str2int64(const char *str, int64 *val) +{ + strtoint_status stat = pg_strtoint64(str, val); + + if (likely(stat == strtoint_ok)) + return true; + + if (stat == strtoint_range_error) + fprintf(stderr, "value \"%s\" is out of range for type %s\n", str, "int64"); + else if (stat == strtoint_syntax_error) + fprintf(stderr, "invalid input syntax for type %s: \"%s\"", "int64", str); + + return false; +} + /* Try to convert variable to a value; return false on failure */ static bool makeVariableValue(Variable *var) @@ -1325,7 +1262,7 @@ makeVariableValue(Variable *var) /* if it looks like an int, it must be an int without overflow */ int64 iv; - if (!strtoint64(var->svalue, false, &iv)) + if (!str2int64(var->svalue, &iv)) return false; setIntValue(&var->value, iv); diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h index c4a1e298e0..da3f8fa055 100644 --- a/src/bin/pgbench/pgbench.h +++ b/src/bin/pgbench/pgbench.h @@ -160,7 +160,6 @@ extern void syntax_error(const char *source, int lineno, const char *line, const char *cmd, const char *msg, const char *more, int col) pg_attribute_noreturn(); -extern bool strtoint64(const char *str, bool errorOK, int64 *pi); extern bool strtodouble(const char *str, bool errorOK, double *pd); #endif /* PGBENCH_H */ diff --git a/src/common/string.c b/src/common/string.c index c9b8482cb0..3385106f60 100644 --- a/src/common/string.c +++ b/src/common/string.c @@ -22,6 +22,7 @@ #endif #include "common/string.h" +#include "common/int.h" /* @@ -57,6 +58,132 @@ strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base) return (int) val; } +/* + * pg_strtoint64 -- convert a string to 64-bit integer + * + * The function returns if the conversion failed, or + * "*result" is set to the result. + */ +strtoint_status +pg_strtoint64(const char *str, int64 *result) +{ + const char *ptr = str; + int64 tmp = 0; + bool neg = false; + + /* + * Do our own scan, rather than relying on sscanf which might be broken + * for long long. + * + * As INT64_MIN can't be stored as a positive 64 bit integer, accumulate + * value as a negative number. + */ + + /* skip leading spaces */ + while (isspace((unsigned char) *ptr)) + ptr++; + + /* handle sign */ + if (*ptr == '-') + { + ptr++; + neg = true; + } + else if (*ptr == '+') + ptr++; + + /* require at least one digit */ + if (unlikely(!isdigit((unsigned char) *ptr))) + return strtoint_syntax_error; + + /* process digits, we know that we have one ahead */ + do + { + int64 digit = (*ptr++ - '0'); + + if (unlikely(pg_mul_s64_overflow(tmp, 10, &tmp)) || + unlikely(pg_sub_s64_overflow(tmp, digit, &tmp))) + return strtoint_range_error; + } + while (isdigit((unsigned char) *ptr)); + + /* allow trailing whitespace */ + while (isspace((unsigned char) *ptr)) + ptr++; + + /* but not other trailing chars */ + if (unlikely(*ptr != '\0')) + return strtoint_syntax_error; + + if (!neg) + { + if (unlikely(tmp == PG_INT64_MIN)) + return strtoint_range_error; + tmp = -tmp; + } + + *result = tmp; + return strtoint_ok; +} + +/* + * pg_strtouint64 -- convert a string to unsigned 64-bit integer + * + * The function returns if the conversion failed, or + * "*result" is set to the result. + */ +strtoint_status +pg_strtouint64(const char *str, uint64 *result) +{ + const char *ptr = str; + uint64 tmp = 0; + + /* skip leading spaces */ + while (isspace((unsigned char) *ptr)) + ptr++; + + /* handle sign */ + if (*ptr == '+') + ptr++; + else if (unlikely(*ptr == '-')) + return strtoint_syntax_error; + + /* require at least one digit */ + if (unlikely(!isdigit((unsigned char) *ptr))) + return strtoint_syntax_error; + + /* process digits, we know that we have one ahead */ + do + { + uint64 next; + + /* should we add pg_{mul,add}_u64_overflow variants? */ + + /* catch mul overflow: MAX_UINT64 / 10 == 1844674407370955161 */ + if (unlikely(tmp > 1844674407370955161)) + return strtoint_range_error; + + next = 10 * tmp + (*ptr++ - '0');; + + /* catch add overflow */ + if (unlikely(next < tmp)) + return strtoint_range_error; + + tmp = next; + } + while (isdigit((unsigned char) *ptr)); + + /* allow trailing whitespace */ + while (isspace((unsigned char) *ptr)) + ptr++; + + /* but not other trailing chars */ + if (unlikely(*ptr != '\0')) + return strtoint_syntax_error; + + *result = tmp; + return strtoint_ok; +} /* * pg_clean_ascii -- Replace any non-ASCII chars with a '?' char diff --git a/src/include/common/string.h b/src/include/common/string.h index 94f653fdd7..9b58305319 100644 --- a/src/include/common/string.h +++ b/src/include/common/string.h @@ -11,8 +11,19 @@ #define COMMON_STRING_H extern bool pg_str_endswith(const char *str, const char *end); + extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base); + +typedef enum { + strtoint_ok, + strtoint_syntax_error, + strtoint_range_error +} strtoint_status; + +extern strtoint_status pg_strtoint64(const char *str, int64 *result); +extern strtoint_status pg_strtouint64(const char *str, uint64 *result); + extern void pg_clean_ascii(char *str); extern int pg_strip_crlf(char *str); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 937ddb7ef0..8a0c2a5c48 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -50,7 +50,6 @@ extern void pg_ltoa(int32 l, char *a); extern void pg_lltoa(int64 ll, char *a); extern char *pg_ltostr_zeropad(char *str, int32 value, int32 minwidth); extern char *pg_ltostr(char *str, int32 value); -extern uint64 pg_strtouint64(const char *str, char **endptr, int base); /* oid.c */ extern oidvector *buildoidvector(const Oid *oids, int n); diff --git a/src/include/utils/int8.h b/src/include/utils/int8.h deleted file mode 100644 index 4836095d93..0000000000 --- a/src/include/utils/int8.h +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------- - * - * int8.h - * Declarations for operations on 64-bit integers. - * - * - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/include/utils/int8.h - * - * NOTES - * These data types are supported on all 64-bit architectures, and may - * be supported through libraries on some 32-bit machines. If your machine - * is not currently supported, then please try to make it so, then post - * patches to the postgresql.org hackers mailing list. - * - *------------------------------------------------------------------------- - */ -#ifndef INT8_H -#define INT8_H - -extern bool scanint8(const char *str, bool errorOK, int64 *result); - -#endif /* INT8_H */