This is an automated email from the ASF dual-hosted git repository.

maplefu pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new e917552b Fix boundary check in Bitmap::BitOp (#1727)
e917552b is described below

commit e917552b9ce802ffb79aadf2a30aa792d266e52e
Author: mwish <[email protected]>
AuthorDate: Sun Sep 3 01:20:36 2023 +0800

    Fix boundary check in Bitmap::BitOp (#1727)
---
 src/commands/cmd_bit.cc                      |  1 +
 src/types/redis_bitmap.cc                    | 16 ++++++++---
 tests/cppunit/disk_test.cc                   | 40 ++++++++++++++++++++++++++++
 tests/gocase/unit/type/bitmap/bitmap_test.go |  8 ++++++
 tests/gocase/util/random.go                  |  8 ++++--
 5 files changed, 67 insertions(+), 6 deletions(-)

diff --git a/src/commands/cmd_bit.cc b/src/commands/cmd_bit.cc
index a56d5ac4..e8b8b978 100644
--- a/src/commands/cmd_bit.cc
+++ b/src/commands/cmd_bit.cc
@@ -203,6 +203,7 @@ class CommandBitOp : public Commander {
 
   Status Execute(Server *svr, Connection *conn, std::string *output) override {
     std::vector<Slice> op_keys;
+    op_keys.reserve(args_.size() - 2);
     for (uint64_t i = 3; i < args_.size(); i++) {
       op_keys.emplace_back(args_[i]);
     }
diff --git a/src/types/redis_bitmap.cc b/src/types/redis_bitmap.cc
index 501ca7a1..71318d02 100644
--- a/src/types/redis_bitmap.cc
+++ b/src/types/redis_bitmap.cc
@@ -359,7 +359,7 @@ rocksdb::Status Bitmap::BitOp(BitOpFlags op_flag, const 
std::string &op_name, co
       return rocksdb::Status::InvalidArgument(kErrMsgWrongType);
     }
     if (metadata.size > max_size) max_size = metadata.size;
-    meta_pairs.emplace_back(ns_op_key, metadata);
+    meta_pairs.emplace_back(std::move(ns_op_key), metadata);
   }
 
   auto batch = storage_->GetWriteBatchBase();
@@ -376,7 +376,8 @@ rocksdb::Status Bitmap::BitOp(BitOpFlags op_flag, const 
std::string &op_name, co
 
   BitmapMetadata res_metadata;
   if (num_keys == op_keys.size() || op_flag != kBitOpAnd) {
-    uint64_t frag_numkeys = num_keys, stop_index = (max_size - 1) / 
kBitmapSegmentBytes;
+    uint64_t frag_numkeys = num_keys;
+    uint64_t stop_index = (max_size - 1) / kBitmapSegmentBytes;
     std::unique_ptr<unsigned char[]> frag_res(new unsigned 
char[kBitmapSegmentBytes]);
     uint16_t frag_maxlen = 0, frag_minlen = 0;
     std::string fragment;
@@ -404,7 +405,7 @@ rocksdb::Status Bitmap::BitOp(BitOpFlags op_flag, const 
std::string &op_name, co
         } else {
           if (frag_maxlen < fragment.size()) frag_maxlen = fragment.size();
           if (fragment.size() < frag_minlen || frag_minlen == 0) frag_minlen = 
fragment.size();
-          fragments.emplace_back(fragment);
+          fragments.emplace_back(std::move(fragment));
         }
       }
 
@@ -502,7 +503,14 @@ rocksdb::Status Bitmap::BitOp(BitOpFlags op_flag, const 
std::string &op_name, co
 
         if (op_flag == kBitOpNot) {
           if (frag_index == stop_index) {
-            frag_maxlen = max_size % kBitmapSegmentBytes;
+            if (max_size == (frag_index + 1) * kBitmapSegmentBytes) {
+              // If the last fragment is full, `max_size % kBitmapSegmentBytes`
+              // would be 0. In this case, we should set `frag_maxlen` to
+              // `kBitmapSegmentBytes` to avoid writing an empty fragment.
+              frag_maxlen = kBitmapSegmentBytes;
+            } else {
+              frag_maxlen = max_size % kBitmapSegmentBytes;
+            }
           } else {
             frag_maxlen = kBitmapSegmentBytes;
           }
diff --git a/tests/cppunit/disk_test.cc b/tests/cppunit/disk_test.cc
index 6bfec413..e53fb7ae 100644
--- a/tests/cppunit/disk_test.cc
+++ b/tests/cppunit/disk_test.cc
@@ -168,6 +168,46 @@ TEST_F(RedisDiskTest, BitmapDisk) {
   bitmap->Del(key_);
 }
 
+TEST_F(RedisDiskTest, BitmapDisk2) {
+  const int64_t kGroupSize = 8192;
+  for (size_t num_bits : {8192, 16384}) {
+    for (bool set_op : {false, true}) {
+      std::unique_ptr<redis::Bitmap> bitmap = 
std::make_unique<redis::Bitmap>(storage_, "disk_ns_bitmap2");
+      std::unique_ptr<redis::Disk> disk = 
std::make_unique<redis::Disk>(storage_, "disk_ns_bitmap2");
+      key_ = "bitmapdisk_key2";
+      bitmap->Del(key_);
+      bool bit = false;
+
+      for (size_t i = 0; i < num_bits; i += kGroupSize) {
+        // Set all first bit of group to `!set_op`
+        EXPECT_TRUE(bitmap->SetBit(key_, i, !set_op, &bit).ok());
+        // Set all last bit of group to `set_op`.
+        EXPECT_TRUE(bitmap->SetBit(key_, i + kGroupSize - 1, set_op, 
&bit).ok());
+      }
+
+      auto bit_not_dest_key = "bit_op_not_dest_key";
+
+      int64_t len = 0;
+      EXPECT_TRUE(bitmap->BitOp(BitOpFlags::kBitOpNot, "NOT", 
bit_not_dest_key, {key_}, &len).ok());
+
+      for (size_t i = 0; i < num_bits; i += kGroupSize) {
+        bool result = false;
+        // Check all first bit of group is `set_op`
+        EXPECT_TRUE(bitmap->GetBit(bit_not_dest_key, i, &result).ok());
+        EXPECT_EQ(set_op, result);
+        // Check all last bit of group is `!set_op`
+        EXPECT_TRUE(bitmap->GetBit(bit_not_dest_key, i + kGroupSize - 1, 
&result).ok());
+        EXPECT_EQ(!set_op, result);
+        // Check bit in group between (first, last) is "1".
+        for (size_t j = i + 1; j < i + kGroupSize - 1; ++j) {
+          EXPECT_TRUE(bitmap->GetBit(bit_not_dest_key, j, &result).ok());
+          EXPECT_TRUE(result) << j << " is not true";
+        }
+      }
+    }
+  }
+}
+
 TEST_F(RedisDiskTest, SortedintDisk) {
   std::unique_ptr<redis::Sortedint> sortedint = 
std::make_unique<redis::Sortedint>(storage_, "disk_ns_sortedint");
   std::unique_ptr<redis::Disk> disk = std::make_unique<redis::Disk>(storage_, 
"disk_ns_sortedint");
diff --git a/tests/gocase/unit/type/bitmap/bitmap_test.go 
b/tests/gocase/unit/type/bitmap/bitmap_test.go
index bd978aed..192f638a 100644
--- a/tests/gocase/unit/type/bitmap/bitmap_test.go
+++ b/tests/gocase/unit/type/bitmap/bitmap_test.go
@@ -234,6 +234,14 @@ func TestBitmap(t *testing.T) {
                }
        })
 
+       t.Run("BITOP Boundary Check", func(t *testing.T) {
+               require.NoError(t, rdb.Del(ctx, "str").Err())
+               str := util.RandStringWithSeed(0, 1000, util.Binary, 2701)
+               Set2SetBit(t, rdb, ctx, "str", []byte(str))
+               require.NoError(t, rdb.BitOpNot(ctx, "target", "str").Err())
+               require.EqualValues(t, SimulateBitOp(NOT, []byte(str)), 
rdb.Get(ctx, "target").Val())
+       })
+
        t.Run("BITOP with non string source key", func(t *testing.T) {
                require.NoError(t, rdb.Del(ctx, "c").Err())
                Set2SetBit(t, rdb, ctx, "a", []byte("\xaa\x00\xff\x55"))
diff --git a/tests/gocase/util/random.go b/tests/gocase/util/random.go
index 4082a771..098e9df3 100644
--- a/tests/gocase/util/random.go
+++ b/tests/gocase/util/random.go
@@ -58,7 +58,11 @@ const (
 )
 
 func RandString(min, max int, typ RandStringType) string {
-       r := rand.New(rand.NewSource(time.Now().UnixNano()))
+       return RandStringWithSeed(min, max, typ, time.Now().UnixNano())
+}
+
+func RandStringWithSeed(min, max int, typ RandStringType, seed int64) string {
+       r := rand.New(rand.NewSource(seed))
        length := min + r.Intn(max-min+1)
 
        var minVal, maxVal int
@@ -71,7 +75,7 @@ func RandString(min, max int, typ RandStringType) string {
 
        var sb strings.Builder
        for ; length > 0; length-- {
-               s := fmt.Sprintf("%c", minVal+rand.Intn(maxVal-minVal+1))
+               s := fmt.Sprintf("%c", 
minVal+int(r.Int31n(int32(maxVal-minVal+1))))
                sb.WriteString(s)
        }
        return sb.String()

Reply via email to