From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Stanislav Zudin Subject: [PATCH 01/13] sql: Convert big integers from string Date: Fri, 15 Mar 2019 18:45:30 +0300 Message-Id: In-Reply-To: References: In-Reply-To: References: To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: Stanislav Zudin List-ID: Correctly handles the signed and unsigned integers on the way from sql expression to VDBE. VDBE doesn't distinguish yet signed and unsigned integers. --- src/box/sql/expr.c | 23 ++++--- src/box/sql/main.c | 2 +- src/box/sql/sqlInt.h | 63 +++++++++++------ src/box/sql/util.c | 160 +++++++++++++++++++------------------------ src/box/sql/vdbe.c | 2 +- 5 files changed, 125 insertions(+), 125 deletions(-) diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c index 82688dff3..3fbfab7a0 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -3335,23 +3335,24 @@ expr_code_int(struct Parse *parse, struct Expr *expr, bool is_neg, 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)) { + enum atoi_result c = sql_dec_or_hex_to_i64(z, is_neg, &value); + switch(c) { + case ATOI_OVERFLOW: if (sql_strnicmp(z, "0x", 2) == 0) { sqlErrorMsg(parse, - "hex literal too big: %s%s", - is_neg ? "-" : "", z); + "hex literal too big: %s%s", + is_neg ? "-" : "", z); } else { sqlErrorMsg(parse, - "oversized integer: %s%s", - is_neg ? "-" : "", z); + "oversized integer: %s%s", + is_neg ? "-" : "", z); } - } else { - if (is_neg) - value = c == 2 ? SMALLEST_INT64 : -value; + break; + case ATOI_UNSIGNED: + case ATOI_SIGNED: sqlVdbeAddOp4Dup8(v, OP_Int64, 0, mem, 0, - (u8 *)&value, P4_INT64); + (u8 *)&value, P4_INT64); + break; } } } diff --git a/src/box/sql/main.c b/src/box/sql/main.c index 0b3bd201e..9fe2e2c9d 100644 --- a/src/box/sql/main.c +++ b/src/box/sql/main.c @@ -1920,7 +1920,7 @@ sql_uri_int64(const char *zFilename, /* Filename as passed to xOpen */ { const char *z = sql_uri_parameter(zFilename, zParam); int64_t v; - if (z != NULL && sql_dec_or_hex_to_i64(z, &v) == 0) + if (z != NULL && sql_dec_or_hex_to_i64(z, false, &v) == 0) bDflt = v; return bDflt; } diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index eb1488576..0ef373c24 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -4274,21 +4274,37 @@ enum field_type * field_type_sequence_dup(struct Parse *parse, enum field_type *types, uint32_t len); +enum atoi_result { + /** Successful transformation. + * Fits in a 64-bit signed integer. + */ + ATOI_SIGNED = 0, + /** Integer is too large for a 64-bit + * unsigned integer or is malformed + */ + ATOI_OVERFLOW = 1, + /** Successful transformation. + * Fits in a 64-bit unsigned integer. + */ + ATOI_UNSIGNED = 2 +}; + + /** - * Convert z to a 64-bit signed integer. z must be decimal. This - * routine does *not* accept hexadecimal notation. + * Converts z to a 64-bit signed or unsigned 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. + * integer, then write that value into *val and return ATOI_SIGNED. * - * 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 a number in the range + * [9223372036854775808, 18446744073709551615] function returns + * ATOI_UNSIGNED and result must be treated as unsigned. * * 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. + * then return ATOI_OVERFLOW. * * length is the number of bytes in the string (bytes, not * characters). The string is not necessarily zero-terminated. @@ -4298,13 +4314,14 @@ field_type_sequence_dup(struct Parse *parse, enum field_type *types, * @param[out] val Output integer value. * @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 + * ATOI_SIGNED Successful transformation. + * Fits in a 64-bit signed integer + * ATOI_OVERFLOW Integer too large for a 64-bit + * unsigned integer or is malformed + * ATOI_UNSIGNED Successful transformation. + * Fits in a 64-bit signed integer + */ +enum atoi_result sql_atoi64(const char *z, int64_t *val, int length); /** @@ -4313,14 +4330,18 @@ sql_atoi64(const char *z, int64_t *val, int length); * accepts hexadecimal literals, whereas sql_atoi64() does not. * * @param z Literal being parsed. + * @param is_neg Sign of the number being converted * @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 - */ -int -sql_dec_or_hex_to_i64(const char *z, int64_t *val); + * ATOI_SIGNED Successful transformation. + * Fits in a 64-bit signed integer + * ATOI_OVERFLOW Integer too large for a 64-bit + * unsigned integer or is malformed + * ATOI_UNSIGNED Successful transformation. + * Fits in a 64-bit signed integer + */ +enum atoi_result +sql_dec_or_hex_to_i64(const char *z, bool is_neg, int64_t *val); void sqlErrorWithMsg(sql *, int, const char *, ...); void sqlError(sql *, int); diff --git a/src/box/sql/util.c b/src/box/sql/util.c index c89e2e8ab..be77f72f8 100644 --- a/src/box/sql/util.c +++ b/src/box/sql/util.c @@ -591,110 +591,56 @@ sqlAtoF(const char *z, double *pResult, int length) #endif /* SQL_OMIT_FLOATING_POINT */ } -/* - * 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; -} +#ifndef INT64_MIN_MOD +/* Modulo of INT64_MIN */ +#define INT64_MIN_MOD 0x8000000000000000 +#endif -int +enum atoi_result sql_atoi64(const char *z, int64_t *val, 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; + int 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') { + + if (z >= zEnd) + return ATOI_OVERFLOW; /* invalid format */ + if (*z == '-') { + neg = 1; 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; + + char* end = NULL; + u64 u = strtoull(z, &end, 10); + if (end == z) + return ATOI_OVERFLOW; + if (errno == ERANGE) + return ATOI_OVERFLOW; + + enum atoi_result rc; + if (neg) { + rc = ATOI_SIGNED; + if (u <= INT64_MAX) + *val = -u; + else if (u == INT64_MIN_MOD) + *val = (i64) u; + else + rc = ATOI_OVERFLOW; } else { *val = (i64) u; + rc = (u <= INT64_MAX) ? ATOI_SIGNED + : ATOI_UNSIGNED; } - 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; - } - } + + return rc; } -int -sql_dec_or_hex_to_i64(const char *z, int64_t *val) +enum atoi_result +sql_dec_or_hex_to_i64(const char *z, bool is_neg, int64_t *val) { + enum atoi_result rc; if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X')) { uint64_t u = 0; int i, k; @@ -702,9 +648,41 @@ sql_dec_or_hex_to_i64(const char *z, int64_t *val) 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; + + /* Determine result */ + if ((k - i) > 16) + rc = ATOI_OVERFLOW; + else if (u > INT64_MAX) + rc = ATOI_UNSIGNED; + else + rc = ATOI_SIGNED; + } + else + rc = sql_atoi64(z, val, sqlStrlen30(z)); + + /* Apply sign */ + if (is_neg) { + switch (rc) { + case ATOI_SIGNED: + *val = -*val; + break; + case ATOI_OVERFLOW: + /* n/a */ + break; + case ATOI_UNSIGNED: + /* A special processing is required + * for the INT64_MIN value. Any other + * values can't be presented as signed, + * so change the return value to error. */ + if (*val == INT64_MIN) + rc = ATOI_SIGNED; + else + rc = ATOI_OVERFLOW; + break; + } } - return sql_atoi64(z, val, sqlStrlen30(z)); + + return rc; } /* diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index c1da9a4aa..c87b10757 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -410,7 +410,7 @@ 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)==SQL_OK) + if (sql_atoi64(pMem->z, (int64_t *)&pMem->u.i, pMem->n)==ATOI_SIGNED) return MEM_Int; return MEM_Real; } -- 2.17.1