Conversation
bitfield's lengthFor example, if we use diff --git a/src/bitops.c b/src/bitops.c
index 51f9cc0..44e9d54 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -964,6 +964,7 @@ void bitfieldCommand(client *c) {
* we need fetch & store as well. */
if ((o = lookupStringForBitCommand(c,bitoffset)) == NULL) return;
+ printf("%d\n", sdslen(o->ptr));
/* We need two different but very similar code paths for signed
* and unsigned operations, since the set of functions to get/setincr overflowFor example, if we use it should actually overflow, but the overflow is not detected, we can see it by diff --git a/src/bitops.c b/src/bitops.c
index 51f9cc0..b2131a9 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -1010,6 +1010,7 @@ void bitfieldCommand(client *c) {
overflow = checkUnsignedBitfieldOverflow(oldval,
thisop->i64,thisop->bits,thisop->owtype,&wrapped);
if (overflow) newval = wrapped;
+ if (overflow) printf("INCR OVERFLOW!\n");
retval = newval;
} else {
newval = thisop->i64;:-) |
|
Hey, thanks @sunheehnus! I think you spotted a bug indeed! Thanks :-) About the fix, I'm not sure if it's correct. So I think the condition of calculating the max byte touched as |
|
In the example you made: |
|
Hi @antirez , make sense :-) What about the other |
|
Yep there is also the overflow bug :-( But the conditions are useful in order to avoid the undefined signed overflow (which is a C undefined behavior) to happen with large numbers. News ASAP. Thanks! |
|
So to recap, I think the following should fix both issues, but avoiding the overflow that happens if we remove certain checks with 64 bit numbers: diff --git a/src/bitops.c b/src/bitops.c
index 51f9cc0..505b9cb 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -318,7 +318,8 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
int64_t maxincr = max-value;
int64_t minincr = min-value;
- if (value > max || (value >= 0 && incr > 0 && incr > maxincr)) {
+ if (value > max || incr > max || (value >= 0 && incr > 0 && incr > maxincr))
+ {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
@@ -327,7 +328,7 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
}
}
return 1;
- } else if (value < min || (value < 0 && incr < 0 && incr < minincr)) {
+ } else if (value < min || incr < min || (value < 0 && incr < 0 && incr < minincr)) {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
@@ -963,7 +964,8 @@ void bitfieldCommand(client *c) {
* for simplicity. SET return value is the previous value so
* we need fetch & store as well. */
- if ((o = lookupStringForBitCommand(c,bitoffset)) == NULL) return;
+ if ((o = lookupStringForBitCommand(c,thisop->offset + thisop->bits))
+ == NULL) return;
/* We need two different but very similar code paths for signed
* and unsigned operations, since the set of functions to get/setMakes sense to you as well? Thanks. |
|
p.s. updated the comment above with a patch fixing both the issues hopefully. |
|
LGTM :) |
|
Finally we fix it :) |
|
Thanks @sunheehnus! Please could you commit it yourself? 99% of work was your! |
|
Hi @antirez , sorry, a little sleepy just now, but I have a question... |
|
@sunheehnus the problem is that the |
|
This happens with i64 types only so if you reason with smaller types everything looks ok... |
|
Make sense, I overlook this edge case >.< |
|
Maybe the right way is we add the edge case condition for i64 type? |
|
Hi @antirez , what about this patch, it takes i64 type as an edge case condition :-) diff --git a/src/bitops.c b/src/bitops.c
index 51f9cc0..28405a3 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -318,7 +318,8 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
int64_t maxincr = max-value;
int64_t minincr = min-value;
- if (value > max || (value >= 0 && incr > 0 && incr > maxincr)) {
+ if (value > max || (bits == 64 && value >= 0 && incr > 0 && incr > maxincr)
+ || (bits < 64 && incr > 0 && incr > maxincr)) {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
@@ -327,7 +328,8 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
}
}
return 1;
- } else if (value < min || (value < 0 && incr < 0 && incr < minincr)) {
+ } else if (value < min || (bits == 64 && value < 0 && incr < 0 && incr < minincr)
+ || (bits < 64 && incr < 0 && incr < minincr)) {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
@@ -445,8 +447,8 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
if ((string2ll(p+1,strlen(p+1),&llbits)) == 0 ||
llbits < 1 ||
- (*sign == 1 && llbits > 63) ||
- (*sign == 0 && llbits > 64))
+ (*sign == 1 && llbits > 64) ||
+ (*sign == 0 && llbits > 63))
{
addReplyError(c,err);
return C_ERR;
@@ -963,7 +965,8 @@ void bitfieldCommand(client *c) {
* for simplicity. SET return value is the previous value so
* we need fetch & store as well. */
- if ((o = lookupStringForBitCommand(c,bitoffset)) == NULL) return;
+ if ((o = lookupStringForBitCommand(c,thisop->offset + thisop->bits))
+ == NULL) return;
/* We need two different but very similar code paths for signed
* and unsigned operations, since the set of functions to get/set |
|
@sunheehnus what's the advantage of explicitly checking for the 64 bit operation? If it's for performances, the difference will be impossible to measure practically or may be even slower, very hard to tell. On the other hand, the code with the explicit 64 bit conditional looks more complex in some way. Or does this change fixes something? Thank you. |
|
Hi @antirez ,
so I take i64 case as a special condition,
What this fix is the smaller types(such as the i4) behavior. :-) Example:without the condition for i64with the condition for i64 |
The new bitfield command is an extension to the Redis bit operations,
where not just single bit operations are performed, but the array of
bits composing a string, can be addressed at random, not aligned
offsets, with any width unsigned and signed integers like u8, s5, u10
(up to 64 bit signed integers and 63 bit unsigned integers).
The BITFIELD command supports subcommands that can SET, GET, or INCRBY
those arbitrary bit counters, with multiple overflow semantics.
Trivial and credits:
A similar command was imagined a few times in the past, but for
some reason looked a bit far fetched or not well specified.
Finally the command was proposed again in a clear form by
Yoav Steinberg from Redis Labs, that proposed a set of commands on
arbitrary sized integers stored at bit offsets.
Starting from this proposal I wrote an initial specification of a single
command with sub-commands similar to what Yoav envisioned, using short
names for types definitions, and adding control on the overflow.
This commit is the resulting implementation.
Examples:
BITFIELD mykey OVERFLOW wrap INCRBY i2 10 -1 GET i2 10
|
Yes you are right, and also thanks for the |
|
Hi, @antirez, already squash the changes into one commit and |
|
Ok, I just wrote a fuzzer and I'm experimenting with your patch and a modification that may be simpler. News ASAP. Thanks! |
|
Ok so... this is hopefully the end of the story:
So the final patch to commit should be the following, hopefully! diff --git a/src/bitops.c b/src/bitops.c
index 51f9cc0..753db53 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -203,7 +203,7 @@ void setUnsignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits, uint6
uint64_t byte, bit, byteval, bitval, j;
for (j = 0; j < bits; j++) {
- bitval = (value & (1<<(bits-1-j))) != 0;
+ bitval = (value & ((uint64_t)1<<(bits-1-j))) != 0;
byte = offset >> 3;
bit = 7 - (offset & 0x7);
byteval = p[byte];
@@ -243,7 +243,7 @@ int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
/* If the top significant bit is 1, propagate it to all the
* higher bits for two complement representation of signed
* integers. */
- if (value & (1 << (bits-1)))
+ if (value & ((uint64_t)1 << (bits-1)))
value |= ((uint64_t)-1) << bits;
return value;
}
@@ -272,7 +272,7 @@ int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
#define BFOVERFLOW_FAIL 2 /* Used by the BITFIELD command implementation. */
int checkUnsignedBitfieldOverflow(uint64_t value, int64_t incr, uint64_t bits, int owtype, uint64_t *limit) {
- uint64_t max = (bits == 64) ? UINT64_MAX : ((1<<bits)-1);
+ uint64_t max = (bits == 64) ? UINT64_MAX : (((uint64_t)1<<bits)-1);
int64_t maxincr = max-value;
int64_t minincr = -value;
@@ -309,7 +309,7 @@ handle_wrap:
}
int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int owtype, int64_t *limit) {
- int64_t max = (bits == 64) ? INT64_MAX : ((1<<(bits-1))-1);
+ int64_t max = (bits == 64) ? INT64_MAX : (((int64_t)1<<(bits-1))-1);
int64_t min = (-max)-1;
/* Note that maxincr and minincr could overflow, but we use the values
@@ -318,7 +318,8 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
int64_t maxincr = max-value;
int64_t minincr = min-value;
- if (value > max || (value >= 0 && incr > 0 && incr > maxincr)) {
+ if (value > max || (bits != 64 && incr > maxincr) || (value >= 0 && incr > 0 && incr > maxincr))
+ {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
@@ -327,7 +328,7 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
}
}
return 1;
- } else if (value < min || (value < 0 && incr < 0 && incr < minincr)) {
+ } else if (value < min || (bits != 64 && incr < minincr) || (value < 0 && incr < 0 && incr < minincr)) {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
@@ -445,8 +446,8 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
if ((string2ll(p+1,strlen(p+1),&llbits)) == 0 ||
llbits < 1 ||
- (*sign == 1 && llbits > 63) ||
- (*sign == 0 && llbits > 64))
+ (*sign == 1 && llbits > 64) ||
+ (*sign == 0 && llbits > 63))
{
addReplyError(c,err);
return C_ERR;
@@ -963,7 +964,8 @@ void bitfieldCommand(client *c) {
* for simplicity. SET return value is the previous value so
* we need fetch & store as well. */
- if ((o = lookupStringForBitCommand(c,bitoffset)) == NULL) return;
+ if ((o = lookupStringForBitCommand(c,thisop->offset + thisop->bits))
+ == NULL) return;
/* We need two different but very similar code paths for signed
* and unsigned operations, since the set of functions to get/set |
|
Looks more correct and clean :-) |
No description provided.