Use string2ll from Redis
This commit pulls string2ll from Redis (with permission from Antirez) as strtoll is 2-3x slower and even worse vs the original version in hiredis that didn't check for overflow at all. By using string2ll there is almost no measurable performance impact of overflow detection even in integer parsing heavy workloads (e.g. INCRBY commands).
This commit is contained in:
parent
1091975857
commit
7bef042c03
86
read.c
86
read.c
@ -142,24 +142,78 @@ static char *seekNewline(char *s, size_t len) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read a long long value starting at *s, under the assumption that it will be
|
/* Convert a string into a long long. Returns REDIS_OK if the string could be
|
||||||
* terminated by \r\n. Returns REDIS_ERR for unexpected inputs or if the
|
* parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
|
||||||
* resulting value would be greater than LLONG_MAX. */
|
* will be set to the parsed value when appropriate.
|
||||||
static int readLongLong(char *s, long long* val) {
|
*
|
||||||
char* end;
|
* Note that this function demands that the string strictly represents
|
||||||
errno = 0;
|
* a long long: no spaces or other characters before or after the string
|
||||||
|
* representing the number are accepted, nor zeroes at the start if not
|
||||||
|
* for the string "0" representing the zero number.
|
||||||
|
*
|
||||||
|
* Because of its strictness, it is safe to use this function to check if
|
||||||
|
* you can convert a string into a long long, and obtain back the string
|
||||||
|
* from the number without any loss in the string representation. */
|
||||||
|
int string2ll(const char *s, size_t slen, long long *value) {
|
||||||
|
const char *p = s;
|
||||||
|
size_t plen = 0;
|
||||||
|
int negative = 0;
|
||||||
|
unsigned long long v;
|
||||||
|
|
||||||
long long v = strtoll(s, &end, 10);
|
if (plen == slen)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
if(s == end || ((v == LLONG_MAX || v == LLONG_MIN) && errno == ERANGE)
|
/* Special case: first and only digit is 0. */
|
||||||
|| (*end != '\r' || *(end+1) != '\n')) {
|
if (slen == 1 && p[0] == '0') {
|
||||||
|
if (value != NULL) *value = 0;
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p[0] == '-') {
|
||||||
|
negative = 1;
|
||||||
|
p++; plen++;
|
||||||
|
|
||||||
|
/* Abort on only a negative sign. */
|
||||||
|
if (plen == slen)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(val) {
|
/* First digit should be 1-9, otherwise the string should just be 0. */
|
||||||
*val = v;
|
if (p[0] >= '1' && p[0] <= '9') {
|
||||||
|
v = p[0]-'0';
|
||||||
|
p++; plen++;
|
||||||
|
} else if (p[0] == '0' && slen == 1) {
|
||||||
|
*value = 0;
|
||||||
|
return REDIS_OK;
|
||||||
|
} else {
|
||||||
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
|
||||||
|
if (v > (ULLONG_MAX / 10)) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
v *= 10;
|
||||||
|
|
||||||
|
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
v += p[0]-'0';
|
||||||
|
|
||||||
|
p++; plen++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return if not all bytes were used. */
|
||||||
|
if (plen < slen)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
if (value != NULL) *value = -v;
|
||||||
|
} else {
|
||||||
|
if (v > LLONG_MAX) /* Overflow. */
|
||||||
|
return REDIS_ERR;
|
||||||
|
if (value != NULL) *value = v;
|
||||||
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +267,7 @@ static int processLineItem(redisReader *r) {
|
|||||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||||
if (r->fn && r->fn->createInteger) {
|
if (r->fn && r->fn->createInteger) {
|
||||||
long long v;
|
long long v;
|
||||||
if(readLongLong(p, &v) == REDIS_ERR) {
|
if (string2ll(p, len, &v) == REDIS_ERR) {
|
||||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
"Bad integer value");
|
"Bad integer value");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -258,7 +312,7 @@ static int processBulkItem(redisReader *r) {
|
|||||||
p = r->buf+r->pos;
|
p = r->buf+r->pos;
|
||||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||||
|
|
||||||
if (readLongLong(p, &len) == REDIS_ERR) {
|
if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
|
||||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
"Bad bulk string length");
|
"Bad bulk string length");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -313,7 +367,7 @@ static int processMultiBulkItem(redisReader *r) {
|
|||||||
void *obj;
|
void *obj;
|
||||||
char *p;
|
char *p;
|
||||||
long long elements;
|
long long elements;
|
||||||
int root = 0;
|
int root = 0, len;
|
||||||
|
|
||||||
/* Set error for nested multi bulks with depth > 7 */
|
/* Set error for nested multi bulks with depth > 7 */
|
||||||
if (r->ridx == 8) {
|
if (r->ridx == 8) {
|
||||||
@ -322,8 +376,8 @@ static int processMultiBulkItem(redisReader *r) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((p = readLine(r,NULL)) != NULL) {
|
if ((p = readLine(r,&len)) != NULL) {
|
||||||
if(readLongLong(p, &elements) == REDIS_ERR) {
|
if (string2ll(p, len, &elements) == REDIS_ERR) {
|
||||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
"Bad multi-bulk length");
|
"Bad multi-bulk length");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
Loading…
Reference in New Issue
Block a user