diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c index bf952c53b659eaf70872e5df75e1b558f9ef5e38..d31597e5db62f7e164b64d1892871c64c0db8fdd 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -1224,7 +1224,8 @@ sqlExprAssignVarNumber(Parse * pParse, Expr * pExpr, u32 n) * variable number */ int64_t i; - bool is_ok = 0 == sql_atoi64(&z[1], &i, n - 1); + bool is_neg; + bool is_ok = 0 == sql_atoi64(&z[1], &i, &is_neg, n - 1); x = (ynVar) i; testcase(i == 0); testcase(i == 1); @@ -3305,29 +3306,43 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg, if (is_neg) i = -i; sqlVdbeAddOp2(v, OP_Integer, i, mem); + return; + } + int64_t value; + const char *z = expr->u.zToken; + assert(z != NULL); + const char *sign = is_neg ? "-" : ""; + if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X')) { + errno = 0; + if (is_neg) { + value = strtoll(z, NULL, 16); + } else { + value = strtoull(z, NULL, 16); + if (value > INT64_MAX) + goto int_overflow; + } + if (errno != 0) { + diag_set(ClientError, ER_HEX_LITERAL_MAX, sign, z, + strlen(z) - 2, 16); + parse->is_aborted = true; + return; + } } else { - int64_t value; - const char *z = expr->u.zToken; - assert(z != NULL); - int c = sql_dec_or_hex_to_i64(z, &value); - if (c == 1 || (c == 2 && !is_neg) || - (is_neg && value == SMALLEST_INT64)) { - const char *sign = is_neg ? "-" : ""; - if (sql_strnicmp(z, "0x", 2) == 0) { - diag_set(ClientError, ER_HEX_LITERAL_MAX, sign, - z, strlen(z) - 2, 16); - } else { - diag_set(ClientError, ER_INT_LITERAL_MAX, sign, - z, INT64_MIN, INT64_MAX); - } + size_t len = strlen(z); + bool unused; + if (sql_atoi64(z, &value, &unused, len) != 0 || + (is_neg && (uint64_t) value > (uint64_t) INT64_MAX + 1) || + (!is_neg && (uint64_t) value > INT64_MAX)) { +int_overflow: + diag_set(ClientError, ER_INT_LITERAL_MAX, sign, z, + INT64_MIN, INT64_MAX); parse->is_aborted = true; - } else { - if (is_neg) - value = c == 2 ? SMALLEST_INT64 : -value; - sqlVdbeAddOp4Dup8(v, OP_Int64, 0, mem, 0, - (u8 *)&value, P4_INT64); + return; } } + if (is_neg) + value = -value; + sqlVdbeAddOp4Dup8(v, OP_Int64, 0, mem, 0, (u8 *)&value, P4_INT64); } /* diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index 4f5bad2879f2229d06c5b95c104028a427fd48e2..34f5c74affc5d04aa964eda6c561038787ed3fbc 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -3996,52 +3996,25 @@ field_type_sequence_dup(struct Parse *parse, enum field_type *types, uint32_t len); /** - * Convert z to a 64-bit signed integer. z must be decimal. This - * routine does *not* accept hexadecimal notation. - * - * If the z value is representable as a 64-bit twos-complement - * integer, then write that value into *val and return 0. - * - * If z is exactly 9223372036854775808, return 2. This special - * case is broken out because while 9223372036854775808 cannot be - * a signed 64-bit integer, its negative -9223372036854775808 can - * be. - * - * If z is too big for a 64-bit integer and is not - * 9223372036854775808 or if z contains any non-numeric text, - * then return 1. + * Convert z to a 64-bit signed or unsigned integer. + * z must be decimal. This routine does *not* accept + * hexadecimal notation. Under the hood it calls + * strtoll or strtoull depending on presence of '-' char. * * length is the number of bytes in the string (bytes, not * characters). The string is not necessarily zero-terminated. - * The encoding is given by enc. * * @param z String being parsed. * @param[out] val Output integer value. + * @param[out] is_neg Sign of the result. * @param length String length in bytes. - * @retval - * 0 Successful transformation. Fits in a 64-bit signed - * integer. - * 1 Integer too large for a 64-bit signed integer or is - * malformed - * 2 Special case of 9223372036854775808 - */ -int -sql_atoi64(const char *z, int64_t *val, int length); - -/** - * Transform a UTF-8 integer literal, in either decimal or - * hexadecimal, into a 64-bit signed integer. This routine - * accepts hexadecimal literals, whereas sql_atoi64() does not. - * - * @param z Literal being parsed. - * @param[out] val Parsed value. - * @retval - * 0 Successful transformation. Fits in a 64-bit signed integer. - * 1 Integer too large for a 64-bit signed integer or is malformed - * 2 Special case of 9223372036854775808 + * @retval 0 Successful transformation. Fits in a 64-bit signed + * or unsigned integer. + * @retval -1 Error during parsing: it contains non-digit + * characters or it doesn't fit into 64-bit int. */ int -sql_dec_or_hex_to_i64(const char *z, int64_t *val); +sql_atoi64(const char *z, int64_t *val, bool *is_neg, int length); void *sqlHexToBlob(sql *, const char *z, int n); u8 sqlHexToInt(int h); diff --git a/src/box/sql/util.c b/src/box/sql/util.c index 92f6f0fa920cfcba1763b3c0ff4244b80accae46..ce4e3a6c6357540d3bccc8620af13ef3deda343a 100644 --- a/src/box/sql/util.c +++ b/src/box/sql/util.c @@ -38,6 +38,7 @@ */ #include "sqlInt.h" #include <stdarg.h> +#include <ctype.h> #include <math.h> #include "coll/coll.h" #include <unicode/ucasemap.h> @@ -455,120 +456,38 @@ sqlAtoF(const char *z, double *pResult, int length) return z == zEnd && nDigits > 0 && eValid && nonNum == 0; } -/* - * Compare the 19-character string zNum against the text representation - * value 2^63: 9223372036854775808. Return negative, zero, or positive - * if zNum is less than, equal to, or greater than the string. - * Note that zNum must contain exactly 19 characters. - * - * Unlike memcmp() this routine is guaranteed to return the difference - * in the values of the last digit if the only difference is in the - * last digit. So, for example, - * - * compare2pow63("9223372036854775800", 1) - * - * will return -8. - */ -static int -compare2pow63(const char *zNum, int incr) -{ - int c = 0; - int i; - /* 012345678901234567 */ - const char *pow63 = "922337203685477580"; - for (i = 0; c == 0 && i < 18; i++) { - c = (zNum[i * incr] - pow63[i]) * 10; - } - if (c == 0) { - c = zNum[18 * incr] - '8'; - testcase(c == (-1)); - testcase(c == 0); - testcase(c == (+1)); - } - return c; -} - int -sql_atoi64(const char *z, int64_t *val, int length) +sql_atoi64(const char *z, int64_t *val, bool *is_neg, int length) { - int incr = 1; - u64 u = 0; - int neg = 0; /* assume positive */ - int i; - int c = 0; - int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */ - const char *zStart; - const char *zEnd = z + length; - incr = 1; - while (z < zEnd && sqlIsspace(*z)) - z += incr; - if (z < zEnd) { - if (*z == '-') { - neg = 1; - z += incr; - } else if (*z == '+') { - z += incr; - } - } - zStart = z; - /* Skip leading zeros. */ - while (z < zEnd && z[0] == '0') { - z += incr; - } - for (i = 0; &z[i] < zEnd && (c = z[i]) >= '0' && c <= '9'; - i += incr) { - u = u * 10 + c - '0'; - } - if (u > LARGEST_INT64) { - *val = neg ? SMALLEST_INT64 : LARGEST_INT64; - } else if (neg) { - *val = -(i64) u; + *is_neg = false; + const char *str_end = z + length; + for (; z < str_end && isspace(*z); z++); + if (z >= str_end) + return -1; + if (*z == '-') + *is_neg = true; + + char *end = NULL; + errno = 0; + if (*z == '-') { + *is_neg = true; + *val = strtoll(z, &end, 10); } else { - *val = (i64) u; + *is_neg = false; + uint64_t u_val = strtoull(z, &end, 10); + if (u_val > (uint64_t) INT64_MAX + 1) + return -1; + *val = u_val; } - if (&z[i] < zEnd || (i == 0 && zStart == z) || i > 19 * incr || - nonNum) { - /* zNum is empty or contains non-numeric text or is longer - * than 19 digits (thus guaranteeing that it is too large) - */ - return 1; - } else if (i < 19 * incr) { - /* Less than 19 digits, so we know that it fits in 64 bits */ - assert(u <= LARGEST_INT64); - return 0; - } else { - /* zNum is a 19-digit numbers. Compare it against 9223372036854775808. */ - c = compare2pow63(z, incr); - if (c < 0) { - /* zNum is less than 9223372036854775808 so it fits */ - assert(u <= LARGEST_INT64); - return 0; - } else if (c > 0) { - /* zNum is greater than 9223372036854775808 so it overflows */ - return 1; - } else { - /* zNum is exactly 9223372036854775808. Fits if negative. The - * special case 2 overflow if positive - */ - assert(u - 1 == LARGEST_INT64); - return neg ? 0 : 2; - } + /* Overflow and underflow errors. */ + if (errno != 0) + return -1; + for (; *end != 0; ++end) { + if (!isspace(*end)) + return -1; } -} -int -sql_dec_or_hex_to_i64(const char *z, int64_t *val) -{ - if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X')) { - uint64_t u = 0; - int i, k; - for (i = 2; z[i] == '0'; i++); - for (k = i; sqlIsxdigit(z[k]); k++) - u = u * 16 + sqlHexToInt(z[k]); - memcpy(val, &u, 8); - return (z[k] == 0 && k - i <= 16) ? 0 : 1; - } - return sql_atoi64(z, val, sqlStrlen30(z)); + return 0; } /* diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 6a4a303b9c2b9dd86a773c8132321e883ad5f42b..7a880d42fdcaef46e2745fe913e61ab908f14e6c 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -263,7 +263,8 @@ mem_apply_numeric_type(struct Mem *record) if ((record->flags & (MEM_Str | MEM_Int | MEM_Real)) != MEM_Str) return -1; int64_t integer_value; - if (sql_atoi64(record->z, &integer_value, record->n) == 0) { + bool is_neg; + if (sql_atoi64(record->z, &integer_value, &is_neg, record->n) == 0) { record->u.i = integer_value; MemSetTypeFlag(record, MEM_Int); return 0; @@ -368,7 +369,8 @@ static u16 SQL_NOINLINE computeNumericType(Mem *pMem) assert((pMem->flags & (MEM_Str|MEM_Blob))!=0); if (sqlAtoF(pMem->z, &pMem->u.r, pMem->n)==0) return 0; - if (sql_atoi64(pMem->z, (int64_t *)&pMem->u.i, pMem->n) == 0) + bool is_neg; + if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, &is_neg, pMem->n) == 0) return MEM_Int; return MEM_Real; } diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c index f52035b0ee0c714736ac4c98785f5551ad8b6145..147b3b4ffec31a7bebce87245cbff78230be8f03 100644 --- a/src/box/sql/vdbemem.c +++ b/src/box/sql/vdbemem.c @@ -460,7 +460,8 @@ sqlVdbeIntValue(Mem * pMem, int64_t *i) return doubleToInt64(pMem->u.r, i); } else if (flags & (MEM_Str)) { assert(pMem->z || pMem->n == 0); - if (sql_atoi64(pMem->z, (int64_t *)i, pMem->n) == 0) + bool is_neg; + if (sql_atoi64(pMem->z, i, &is_neg, pMem->n) == 0) return 0; } return -1; @@ -580,7 +581,9 @@ sqlVdbeMemNumerify(Mem * pMem) { if ((pMem->flags & (MEM_Int | MEM_Real | MEM_Null)) == 0) { assert((pMem->flags & (MEM_Blob | MEM_Str)) != 0); - if (0 == sql_atoi64(pMem->z, (int64_t *)&pMem->u.i, pMem->n)) { + bool is_neg; + if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, &is_neg, + pMem->n) == 0) { MemSetTypeFlag(pMem, MEM_Int); } else { double v; @@ -645,7 +648,9 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type) if (pMem->flags & MEM_Null) return 0; if ((pMem->flags & MEM_Blob) != 0 && type == FIELD_TYPE_NUMBER) { - if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, pMem->n) == 0) { + bool is_neg; + if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, &is_neg, + pMem->n) == 0) { MemSetTypeFlag(pMem, MEM_Real); pMem->u.r = pMem->u.i; return 0; @@ -676,8 +681,9 @@ sqlVdbeMemCast(Mem * pMem, enum field_type type) return -1; case FIELD_TYPE_INTEGER: if ((pMem->flags & MEM_Blob) != 0) { - if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, - pMem->n) != 0) + bool is_neg; + if (sql_atoi64(pMem->z, (int64_t *) &pMem->u.i, + &is_neg, pMem->n) != 0) return -1; MemSetTypeFlag(pMem, MEM_Int); return 0; diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua index 2770ec29559c7f349ae715eb08ed1e32f426b352..0f14759cf533a0ef1d500e4e14506ecd5f12ce5b 100755 --- a/test/sql-tap/func.test.lua +++ b/test/sql-tap/func.test.lua @@ -912,25 +912,25 @@ test:do_execsql_test( -- </func-8.6> }) -test:do_execsql_test( +test:do_catchsql_test( "func-8.7", [[ SELECT typeof(sum(x)) FROM (SELECT '9223372036' || '854775808' AS x UNION ALL SELECT -9223372036854775807) ]], { -- <func-8.7> - "number" + 1, "Failed to execute SQL statement: integer overflow" -- </func-8.7> }) -test:do_execsql_test( +test:do_catchsql_test( "func-8.8", [[ SELECT sum(x)>0.0 FROM (SELECT '9223372036' || '854775808' AS x UNION ALL SELECT -9223372036850000000) ]], { -- <func-8.8> - true + 1, "Failed to execute SQL statement: integer overflow" -- </func-8.8> }) diff --git a/test/sql/integer-overflow.result b/test/sql/integer-overflow.result index d6ca66ec989454cd09162b8f05ce57c3372abc51..a4944d1a042a10b67da2782b038eb43d2a8a74b5 100644 --- a/test/sql/integer-overflow.result +++ b/test/sql/integer-overflow.result @@ -48,7 +48,11 @@ box.execute('SELECT 9223372036854775808 - 1;') -- box.execute('SELECT CAST(\'9223372036854775808\' AS INTEGER);') --- -- error: 'Type mismatch: can not convert 9223372036854775808 to integer' +- metadata: + - name: CAST('9223372036854775808' AS INTEGER) + type: integer + rows: + - [-9223372036854775808] ... -- Due to inexact represantation of large integers in terms of -- floating point numbers, numerics with value < INT64_MAX