Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca1d3c546 |
No files matched your search
+16
-4
@@ -31,7 +31,9 @@
|
||||
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
||||
auto result =
|
||||
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
||||
memcpy(result.data(), key.data(), key.size());
|
||||
if (key.size() > 0) {
|
||||
memcpy(result.data(), key.data(), key.size());
|
||||
}
|
||||
result[result.size() - 1] = 0;
|
||||
return result;
|
||||
}
|
||||
@@ -50,6 +52,14 @@ using Version = int64_t;
|
||||
#define force_inline inline __attribute__((always_inline))
|
||||
using StringRef = std::span<const uint8_t>;
|
||||
|
||||
// memcmp, but safe for zero-length ranges: memcmp is undefined behavior unless
|
||||
// both pointers are valid, even when the count is 0, and spans for the empty
|
||||
// key are allowed to have a null data().
|
||||
static force_inline bool equalBytes(const uint8_t *a, const uint8_t *b,
|
||||
int size) {
|
||||
return size == 0 || memcmp(a, b, size) == 0;
|
||||
}
|
||||
|
||||
struct KeyRangeRef {
|
||||
StringRef begin;
|
||||
StringRef end;
|
||||
@@ -180,7 +190,8 @@ void sortPoints(std::vector<KeyInfo> &points) {
|
||||
}
|
||||
|
||||
static int compare(const StringRef &a, const StringRef &b) {
|
||||
int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size()));
|
||||
int n = std::min(a.size(), b.size());
|
||||
int c = n > 0 ? memcmp(a.data(), b.data(), n) : 0;
|
||||
if (c < 0)
|
||||
return -1;
|
||||
if (c > 0)
|
||||
@@ -371,7 +382,7 @@ public:
|
||||
Node *n = finger[0]->getNext(
|
||||
0); // or alreadyChecked, but that is more easily invalidated
|
||||
if (n && n->length() == value.size() &&
|
||||
!memcmp(n->value(), value.data(), value.size()))
|
||||
equalBytes(n->value(), value.data(), value.size()))
|
||||
return n;
|
||||
else
|
||||
return nullptr;
|
||||
@@ -695,7 +706,8 @@ private:
|
||||
s = nextS;
|
||||
if (start.finished()) {
|
||||
if (nextS->length() == start.value.size() &&
|
||||
!memcmp(nextS->value(), start.value.data(), start.value.size()))
|
||||
equalBytes(nextS->value(), start.value.data(),
|
||||
start.value.size()))
|
||||
return noConflict();
|
||||
else
|
||||
return conflict();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import struct
|
||||
|
||||
import conflict_set
|
||||
from conflict_set import *
|
||||
|
||||
|
||||
@@ -251,6 +253,25 @@ def test_merge_child_node48():
|
||||
cs.addWrites(1, write(b"\x00" * 8, b"\x00" * 10))
|
||||
|
||||
|
||||
def test_empty_key_null_pointer():
|
||||
# Regression test for issue #96: the header only constrains the length of a
|
||||
# key, so a zero-length key may be spelled with a null pointer. Both
|
||||
# implementations must agree on such a key, and must not pass the null
|
||||
# pointer to memcpy/memcmp, which is undefined behavior even when the length
|
||||
# is 0 (keyAfter() in SkipList.cpp did, on every empty-key point operation).
|
||||
null_key = conflict_set._Key(None, 0)
|
||||
with DebugConflictSet() as cs:
|
||||
cs.addWrites(1, WriteRange(null_key, null_key))
|
||||
assert cs.check(ReadRange(null_key, null_key, 0)) == [Result.CONFLICT]
|
||||
assert cs.check(ReadRange(null_key, null_key, 1)) == [Result.COMMIT]
|
||||
|
||||
# The same operations spelled with a non-null empty key must agree.
|
||||
with DebugConflictSet() as cs:
|
||||
cs.addWrites(1, write(b""))
|
||||
assert cs.check(read(0, b"")) == [Result.CONFLICT]
|
||||
assert cs.check(read(1, b"")) == [Result.COMMIT]
|
||||
|
||||
|
||||
def test_fixup_256_heterogeneous_versions():
|
||||
# Regression test for issue #89: recomputing the maxVersion of a Node256
|
||||
# must consider all of its children, not just those with an index < 16.
|
||||
|
||||
Reference in new issue
Block a user