]> git.saurik.com Git - redis.git/blobdiff - redis.c
check eptr inline
[redis.git] / redis.c
diff --git a/redis.c b/redis.c
index a004bd66f70414d076e014aff3c9126b01da1adb..b926e5a48f30112a3c9bab72b738900c66f9e9c9 100644 (file)
--- a/redis.c
+++ b/redis.c
@@ -237,7 +237,9 @@ static char* strencoding[] = {
 
 /* We can print the stacktrace, so our assert is defined this way: */
 #define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
+#define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1)
 static void _redisAssert(char *estr, char *file, int line);
+static void _redisPanic(char *msg, char *file, int line);
 
 /*================================= Data types ============================== */
 
@@ -2773,6 +2775,23 @@ static robj *createStringObject(char *ptr, size_t len) {
     return createObject(REDIS_STRING,sdsnewlen(ptr,len));
 }
 
+static robj *createStringObjectFromLongLong(long long value) {
+    robj *o;
+    if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
+        incrRefCount(shared.integers[value]);
+        o = shared.integers[value];
+    } else {
+        o = createObject(REDIS_STRING, NULL);
+        if (value >= LONG_MIN && value <= LONG_MAX) {
+            o->encoding = REDIS_ENCODING_INT;
+            o->ptr = (void*)((long)value);
+        } else {
+            o->ptr = sdscatprintf(sdsempty(),"%lld",value);
+        }
+    }
+    return o;
+}
+
 static robj *dupStringObject(robj *o) {
     assert(o->encoding == REDIS_ENCODING_RAW);
     return createStringObject(o->ptr,sdslen(o->ptr));
@@ -2839,7 +2858,7 @@ static void freeHashObject(robj *o) {
         zfree(o->ptr);
         break;
     default:
-        redisAssert(0);
+        redisPanic("Unknown hash encoding type");
         break;
     }
 }
@@ -2851,6 +2870,7 @@ static void incrRefCount(robj *o) {
 static void decrRefCount(void *obj) {
     robj *o = obj;
 
+    if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
     /* Object is a key of a swapped out value, or in the process of being
      * loaded. */
     if (server.vm_enabled &&
@@ -2878,7 +2898,7 @@ static void decrRefCount(void *obj) {
         case REDIS_SET: freeSetObject(o); break;
         case REDIS_ZSET: freeZsetObject(o); break;
         case REDIS_HASH: freeHashObject(o); break;
-        default: redisAssert(0); break;
+        default: redisPanic("Unknown object type"); break;
         }
         if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex);
         if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX ||
@@ -3084,74 +3104,94 @@ static size_t stringObjectLen(robj *o) {
     }
 }
 
-static int getDoubleFromObject(redisClient *c, robj *o, double *value) {
-    double parsedValue;
-    char *eptr = NULL;
+static int getDoubleFromObject(robj *o, double *target) {
+    double value;
+    char *eptr;
 
-    if (o && o->type != REDIS_STRING) {
-        addReplySds(c,sdsnew("-ERR value is not a double\r\n"));
-        return REDIS_ERR;
+    if (o == NULL) {
+        value = 0;
+    } else {
+        redisAssert(o->type == REDIS_STRING);
+        if (o->encoding == REDIS_ENCODING_RAW) {
+            value = strtod(o->ptr, &eptr);
+            if (eptr[0] != '\0') return REDIS_ERR;
+        } else if (o->encoding == REDIS_ENCODING_INT) {
+            value = (long)o->ptr;
+        } else {
+            redisAssert(1 != 1);
+        }
     }
 
-    if (o == NULL)
-        parsedValue = 0;
-    else if (o->encoding == REDIS_ENCODING_RAW)
-        parsedValue = strtod(o->ptr, &eptr);
-    else if (o->encoding == REDIS_ENCODING_INT)
-        parsedValue = (long)o->ptr;
-    else
-        redisAssert(1 != 1);
+    *target = value;
+    return REDIS_OK;
+}
 
-    if (eptr != NULL && *eptr != '\0') {
-        addReplySds(c,sdsnew("-ERR value is not a double\r\n"));
+static int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) {
+    double value;
+    if (getDoubleFromObject(o, &value) != REDIS_OK) {
+        if (msg != NULL) {
+            addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
+        } else {
+            addReplySds(c, sdsnew("-ERR value is not a double\r\n"));
+        }
         return REDIS_ERR;
     }
 
-    *value = parsedValue;
-
+    *target = value;
     return REDIS_OK;
 }
 
-static int getLongLongFromObject(redisClient *c, robj *o, long long *value) {
-    long long parsedValue;
-    char *eptr = NULL;
+static int getLongLongFromObject(robj *o, long long *target) {
+    long long value;
+    char *eptr;
 
-    if (o && o->type != REDIS_STRING) {
-        addReplySds(c,sdsnew("-ERR value is not an integer\r\n"));
-        return REDIS_ERR;
+    if (o == NULL) {
+        value = 0;
+    } else {
+        redisAssert(o->type == REDIS_STRING);
+        if (o->encoding == REDIS_ENCODING_RAW) {
+            value = strtoll(o->ptr, &eptr, 10);
+            if (eptr[0] != '\0') return REDIS_ERR;
+        } else if (o->encoding == REDIS_ENCODING_INT) {
+            value = (long)o->ptr;
+        } else {
+            redisAssert(1 != 1);
+        }
     }
 
-    if (o == NULL)
-        parsedValue = 0;
-    else if (o->encoding == REDIS_ENCODING_RAW)
-        parsedValue = strtoll(o->ptr, &eptr, 10);
-    else if (o->encoding == REDIS_ENCODING_INT)
-        parsedValue = (long)o->ptr;
-    else
-        redisAssert(1 != 1);
+    *target = value;
+    return REDIS_OK;
+}
 
-    if (eptr != NULL && *eptr != '\0') {
-        addReplySds(c,sdsnew("-ERR value is not an integer\r\n"));
+static int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg) {
+    long long value;
+    if (getLongLongFromObject(o, &value) != REDIS_OK) {
+        if (msg != NULL) {
+            addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
+        } else {
+            addReplySds(c, sdsnew("-ERR value is not an integer\r\n"));
+        }
         return REDIS_ERR;
     }
 
-    *value = parsedValue;
-
+    *target = value;
     return REDIS_OK;
 }
 
-static int getLongFromObject(redisClient *c, robj *o, long *value) {
-    long long actualValue;
-
-    if (getLongLongFromObject(c, o, &actualValue) != REDIS_OK) return REDIS_ERR;
+static int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg) {
+    long long value;
 
-    if (actualValue < LONG_MIN || actualValue > LONG_MAX) {
-        addReplySds(c,sdsnew("-ERR value is out of range\r\n"));
+    if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
+    if (value < LONG_MIN || value > LONG_MAX) {
+        if (msg != NULL) {
+            addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
+        } else {
+            addReplySds(c, sdsnew("-ERR value is out of range\r\n"));
+        }
         return REDIS_ERR;
     }
 
-    *value = actualValue;
-
+    *target = value;
     return REDIS_OK;
 }
 
@@ -3408,7 +3448,7 @@ static int rdbSaveObject(FILE *fp, robj *o) {
             dictReleaseIterator(di);
         }
     } else {
-        redisAssert(0);
+        redisPanic("Unknown object type");
     }
     return 0;
 }
@@ -3629,7 +3669,7 @@ static robj *rdbLoadIntegerObject(FILE *fp, int enctype) {
         val = (int32_t)v;
     } else {
         val = 0; /* anti-warning */
-        redisAssert(0);
+        redisPanic("Unknown RDB integer encoding type");
     }
     return createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",val));
 }
@@ -3668,7 +3708,7 @@ static robj *rdbLoadStringObject(FILE*fp) {
         case REDIS_RDB_ENC_LZF:
             return rdbLoadLzfStringObject(fp);
         default:
-            redisAssert(0);
+            redisPanic("Unknown RDB encoding type");
         }
     }
 
@@ -3790,7 +3830,7 @@ static robj *rdbLoadObject(int type, FILE *fp) {
             }
         }
     } else {
-        redisAssert(0);
+        redisPanic("Unknown object type");
     }
     return o;
 }
@@ -4041,7 +4081,7 @@ static void incrDecrCommand(redisClient *c, long long incr) {
 
     o = lookupKeyWrite(c->db,c->argv[1]);
 
-    if (getLongLongFromObject(c, o, &value) != REDIS_OK) return;
+    if (getLongLongFromObjectOrReply(c, o, &value, NULL) != REDIS_OK) return;
 
     value += incr;
     o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
@@ -4070,16 +4110,14 @@ static void decrCommand(redisClient *c) {
 static void incrbyCommand(redisClient *c) {
     long long incr;
 
-    if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return;
-
+    if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
     incrDecrCommand(c,incr);
 }
 
 static void decrbyCommand(redisClient *c) {
     long long incr;
 
-    if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return;
-
+    if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
     incrDecrCommand(c,-incr);
 }
 
@@ -5477,16 +5515,14 @@ static void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scor
 static void zaddCommand(redisClient *c) {
     double scoreval;
 
-    if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return;
-
+    if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return;
     zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
 }
 
 static void zincrbyCommand(redisClient *c) {
     double scoreval;
 
-    if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return;
-
+    if (getDoubleFromObjectOrReply(c, c->argv[2], &scoreval, NULL) != REDIS_OK) return;
     zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
 }
 
@@ -5526,8 +5562,8 @@ static void zremrangebyscoreCommand(redisClient *c) {
     robj *zsetobj;
     zset *zs;
 
-    if ((getDoubleFromObject(c, c->argv[2], &min) != REDIS_OK) ||
-        (getDoubleFromObject(c, c->argv[3], &max) != REDIS_OK)) return;
+    if ((getDoubleFromObjectOrReply(c, c->argv[2], &min, NULL) != REDIS_OK) ||
+        (getDoubleFromObjectOrReply(c, c->argv[3], &max, NULL) != REDIS_OK)) return;
 
     if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
         checkType(c,zsetobj,REDIS_ZSET)) return;
@@ -5548,8 +5584,8 @@ static void zremrangebyrankCommand(redisClient *c) {
     robj *zsetobj;
     zset *zs;
 
-    if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) ||
-        (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return;
+    if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
+        (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
 
     if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
         checkType(c,zsetobj,REDIS_ZSET)) return;
@@ -5604,7 +5640,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat
         *target = val > *target ? val : *target;
     } else {
         /* safety net */
-        redisAssert(0 != 0);
+        redisPanic("Unknown ZUNION/INTER aggregate type");
     }
 }
 
@@ -5657,7 +5693,7 @@ static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
             if (remaining >= (zsetnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) {
                 j++; remaining--;
                 for (i = 0; i < zsetnum; i++, j++, remaining--) {
-                    if (getDoubleFromObject(c, c->argv[j], &src[i].weight) != REDIS_OK)
+                    if (getDoubleFromObjectOrReply(c, c->argv[j], &src[i].weight, NULL) != REDIS_OK)
                         return;
                 }
             } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) {
@@ -5790,8 +5826,8 @@ static void zrangeGenericCommand(redisClient *c, int reverse) {
     zskiplistNode *ln;
     robj *ele;
 
-    if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) ||
-        (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return;
+    if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
+        (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
 
     if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
         withscores = 1;
@@ -6065,6 +6101,14 @@ static void hashTryConversion(robj *subject, robj **argv, int start, int end) {
     }
 }
 
+/* Encode given objects in-place when the hash uses a dict. */
+static void hashTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
+    if (subject->encoding == REDIS_ENCODING_HT) {
+        if (o1) *o1 = tryObjectEncoding(*o1);
+        if (o2) *o2 = tryObjectEncoding(*o2);
+    }
+}
+
 /* Get the value from a hash identified by key. Returns either a string
  * object or NULL if the value cannot be found. The refcount of the object
  * is always increased by 1 when the value was found. */
@@ -6123,7 +6167,6 @@ static int hashSet(robj *o, robj *key, robj *value) {
         if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries)
             convertToRealHash(o);
     } else {
-        value = tryObjectEncoding(value);
         if (dictReplace(o->ptr,key,value)) {
             /* Insert */
             incrRefCount(key);
@@ -6247,6 +6290,7 @@ static void hsetCommand(redisClient *c) {
 
     if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
     hashTryConversion(o,c->argv,2,3);
+    hashTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
     update = hashSet(o,c->argv[2],c->argv[3]);
     addReply(c, update ? shared.czero : shared.cone);
     server.dirty++;
@@ -6260,6 +6304,7 @@ static void hsetnxCommand(redisClient *c) {
     if (hashExists(o, c->argv[2])) {
         addReply(c, shared.czero);
     } else {
+        hashTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
         hashSet(o,c->argv[2],c->argv[3]);
         addReply(c, shared.cone);
         server.dirty++;
@@ -6278,16 +6323,18 @@ static void hmsetCommand(redisClient *c) {
     if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
     hashTryConversion(o,c->argv,2,c->argc-1);
     for (i = 2; i < c->argc; i += 2) {
+        hashTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
         hashSet(o,c->argv[i],c->argv[i+1]);
     }
     addReply(c, shared.ok);
+    server.dirty++;
 }
 
 static void hincrbyCommand(redisClient *c) {
     long long value, incr;
     robj *o, *current, *new;
 
-    if (getLongLongFromObject(c,c->argv[3],&incr) != REDIS_OK) return;
+    if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
     if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
     if ((current = hashGet(o,c->argv[2])) != NULL) {
         if (current->encoding == REDIS_ENCODING_RAW)
@@ -6302,7 +6349,8 @@ static void hincrbyCommand(redisClient *c) {
     }
 
     value += incr;
-    new = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
+    new = createStringObjectFromLongLong(value);
+    hashTryObjectEncoding(o,&c->argv[2],NULL);
     hashSet(o,c->argv[2],new);
     decrRefCount(new);
     addReplyLongLong(c,value);
@@ -6467,7 +6515,9 @@ static redisSortOperation *createSortOperation(int type, robj *pattern) {
 }
 
 /* Return the value associated to the key with a name obtained
- * substituting the first occurence of '*' in 'pattern' with 'subst' */
+ * substituting the first occurence of '*' in 'pattern' with 'subst'.
+ * The returned object will always have its refcount increased by 1
+ * when it is non-NULL. */
 static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
     char *p, *f;
     sds spat, ssub;
@@ -6484,6 +6534,7 @@ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
      * to implement the "SORT ... GET #" feature. */
     spat = pattern->ptr;
     if (spat[0] == '#' && spat[1] == '\0') {
+        incrRefCount(subst);
         return subst;
     }
 
@@ -6523,14 +6574,21 @@ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
     /* Lookup substituted key */
     initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(long)*2));
     o = lookupKeyRead(db,&keyobj);
+    if (o == NULL) return NULL;
 
-    /* Retrieve value from hash by the field name */
-    if (o != NULL && fieldlen > 0) {
-        if (o->type != REDIS_HASH || fieldname.len < 1) {
-            return NULL;
-        }
+    if (fieldlen > 0) {
+        if (o->type != REDIS_HASH || fieldname.len < 1) return NULL;
+
+        /* Retrieve value from hash by the field name. This operation
+         * already increases the refcount of the returned object. */
         initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(long)*2));
         o = hashGet(o, &fieldobj);
+    } else {
+        if (o->type != REDIS_STRING) return NULL;
+
+        /* Every object that this function returns needs to have its refcount
+         * increased. sortCommand decreases it again. */
+        incrRefCount(o);
     }
 
     return o;
@@ -6653,7 +6711,7 @@ static void sortCommand(redisClient *c) {
     case REDIS_LIST: vectorlen = listLength((list*)sortval->ptr); break;
     case REDIS_SET: vectorlen =  dictSize((dict*)sortval->ptr); break;
     case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
-    default: vectorlen = 0; redisAssert(0); /* Avoid GCC warning */
+    default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
     }
     vector = zmalloc(sizeof(redisSortObject)*vectorlen);
     j = 0;
@@ -6701,15 +6759,13 @@ static void sortCommand(redisClient *c) {
             if (sortby) {
                 /* lookup value to sort by */
                 byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
-                if (!byval || byval->type != REDIS_STRING) continue;
+                if (!byval) continue;
             } else {
                 /* use object itself to sort by */
                 byval = vector[j].obj;
             }
 
             if (alpha) {
-                /* getDecodedObject increments refcount, so the corresponding
-                 * decrRefCount will clean up values coming from a zipmap. */
                 vector[j].u.cmpobj = getDecodedObject(byval);
             } else {
                 if (byval->encoding == REDIS_ENCODING_RAW) {
@@ -6722,12 +6778,12 @@ static void sortCommand(redisClient *c) {
                 } else {
                     redisAssert(1 != 1);
                 }
+            }
 
-                /* clean up immediately if this value came from a zipmap */
-                if (byval->refcount == 0) {
-                    byval->refcount = 1;
-                    decrRefCount(byval);
-                }
+            /* when the object was retrieved using lookupKeyByPattern,
+             * its refcount needs to be decreased. */
+            if (sortby) {
+                decrRefCount(byval);
             }
         }
     }
@@ -6770,10 +6826,11 @@ static void sortCommand(redisClient *c) {
                     vector[j].obj);
 
                 if (sop->type == REDIS_SORT_GET) {
-                    if (!val || val->type != REDIS_STRING) {
+                    if (!val) {
                         addReply(c,shared.nullbulk);
                     } else {
                         addReplyBulk(c,val);
+                        decrRefCount(val);
                     }
                 } else {
                     redisAssert(sop->type == REDIS_SORT_GET); /* always fails */
@@ -6800,11 +6857,14 @@ static void sortCommand(redisClient *c) {
                     vector[j].obj);
 
                 if (sop->type == REDIS_SORT_GET) {
-                    if (!val || val->type != REDIS_STRING) {
+                    if (!val) {
                         listAddNodeTail(listPtr,createStringObject("",0));
                     } else {
+                        /* We should do a incrRefCount on val because it is
+                         * added to the list, but also a decrRefCount because
+                         * it is returned by lookupKeyByPattern. This results
+                         * in doing nothing at all. */
                         listAddNodeTail(listPtr,val);
-                        incrRefCount(val);
                     }
                 } else {
                     redisAssert(sop->type == REDIS_SORT_GET); /* always fails */
@@ -7052,7 +7112,7 @@ static void expireGenericCommand(redisClient *c, robj *key, robj *param, long of
     dictEntry *de;
     time_t seconds;
 
-    if (getLongFromObject(c, param, &seconds) != REDIS_OK) return;
+    if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
 
     seconds -= offset;
 
@@ -7061,7 +7121,7 @@ static void expireGenericCommand(redisClient *c, robj *key, robj *param, long of
         addReply(c,shared.czero);
         return;
     }
-    if (seconds < 0) {
+    if (seconds <= 0) {
         if (deleteKey(c->db,key)) server.dirty++;
         addReply(c, shared.cone);
         return;
@@ -8200,7 +8260,7 @@ static int rewriteAppendOnlyFile(char *filename) {
                     dictReleaseIterator(di);
                 }
             } else {
-                redisAssert(0);
+                redisPanic("Unknown object type");
             }
             /* Save the expire time */
             if (expiretime != -1) {
@@ -9847,6 +9907,15 @@ static void _redisAssert(char *estr, char *file, int line) {
 #endif
 }
 
+static void _redisPanic(char *msg, char *file, int line) {
+    redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue");
+    redisLog(REDIS_WARNING,"Guru Mediation: %s #%s:%d",msg,file,line);
+#ifdef HAVE_BACKTRACE
+    redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
+    *((char*)-1) = 'x';
+#endif
+}
+
 /* =================================== Main! ================================ */
 
 #ifdef __linux__