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) {
|
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
||||||
auto result =
|
auto result =
|
||||||
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
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;
|
result[result.size() - 1] = 0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -50,6 +52,14 @@ using Version = int64_t;
|
|||||||
#define force_inline inline __attribute__((always_inline))
|
#define force_inline inline __attribute__((always_inline))
|
||||||
using StringRef = std::span<const uint8_t>;
|
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 {
|
struct KeyRangeRef {
|
||||||
StringRef begin;
|
StringRef begin;
|
||||||
StringRef end;
|
StringRef end;
|
||||||
@@ -180,7 +190,8 @@ void sortPoints(std::vector<KeyInfo> &points) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int compare(const StringRef &a, const StringRef &b) {
|
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)
|
if (c < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if (c > 0)
|
if (c > 0)
|
||||||
@@ -371,7 +382,7 @@ public:
|
|||||||
Node *n = finger[0]->getNext(
|
Node *n = finger[0]->getNext(
|
||||||
0); // or alreadyChecked, but that is more easily invalidated
|
0); // or alreadyChecked, but that is more easily invalidated
|
||||||
if (n && n->length() == value.size() &&
|
if (n && n->length() == value.size() &&
|
||||||
!memcmp(n->value(), value.data(), value.size()))
|
equalBytes(n->value(), value.data(), value.size()))
|
||||||
return n;
|
return n;
|
||||||
else
|
else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -695,7 +706,8 @@ private:
|
|||||||
s = nextS;
|
s = nextS;
|
||||||
if (start.finished()) {
|
if (start.finished()) {
|
||||||
if (nextS->length() == start.value.size() &&
|
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();
|
return noConflict();
|
||||||
else
|
else
|
||||||
return conflict();
|
return conflict();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import struct
|
import struct
|
||||||
|
|
||||||
|
import conflict_set
|
||||||
from conflict_set import *
|
from conflict_set import *
|
||||||
|
|
||||||
|
|
||||||
@@ -251,6 +253,25 @@ def test_merge_child_node48():
|
|||||||
cs.addWrites(1, write(b"\x00" * 8, b"\x00" * 10))
|
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():
|
def test_fixup_256_heterogeneous_versions():
|
||||||
# Regression test for issue #89: recomputing the maxVersion of a Node256
|
# Regression test for issue #89: recomputing the maxVersion of a Node256
|
||||||
# must consider all of its children, not just those with an index < 16.
|
# must consider all of its children, not just those with an index < 16.
|
||||||
|
|||||||
Reference in new issue
Block a user