Make SHOW_MEMORY builds work: fix deferred node capacity assert and eraseTree use-after-free #91
No files matched your search
+48
-13
@@ -80,7 +80,10 @@ using namespace weaselab;
|
|||||||
#define assume assert
|
#define assume assert
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SHOW_MEMORY
|
// The SHOW_MEMORY instrumentation. Declared unconditionally so that each of
|
||||||
|
// these has a single definition in every configuration (see the definitions at
|
||||||
|
// the end of this file), rather than a no-op stub standing in for the real
|
||||||
|
// thing.
|
||||||
void addNode(struct Node *);
|
void addNode(struct Node *);
|
||||||
void removeNode(struct Node *);
|
void removeNode(struct Node *);
|
||||||
void addKey(struct Node *);
|
void addKey(struct Node *);
|
||||||
@@ -90,14 +93,6 @@ void removeKey(struct Node *);
|
|||||||
// `parent` pointers invalid.
|
// `parent` pointers invalid.
|
||||||
void removeKey(struct Node *, int64_t searchPathLength);
|
void removeKey(struct Node *, int64_t searchPathLength);
|
||||||
int64_t getSearchPathLength(struct Node *);
|
int64_t getSearchPathLength(struct Node *);
|
||||||
#else
|
|
||||||
constexpr void addNode(struct Node *) {}
|
|
||||||
constexpr void removeNode(struct Node *) {}
|
|
||||||
constexpr void addKey(struct Node *) {}
|
|
||||||
constexpr void removeKey(struct Node *) {}
|
|
||||||
constexpr void removeKey(struct Node *, int64_t) {}
|
|
||||||
constexpr int64_t getSearchPathLength(struct Node *) { return 0; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ==================== BEGIN IMPLEMENTATION ====================
|
// ==================== BEGIN IMPLEMENTATION ====================
|
||||||
|
|
||||||
@@ -5492,8 +5487,9 @@ void internal_addWrites(ConflictSet::Impl *impl,
|
|||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
impl->memory_bytes.set(impl->totalBytes);
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
|
// Testing-only cross-check of the accounting.
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort(); // GCOVR_EXCL_LINE
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -5504,8 +5500,9 @@ void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
|||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
impl->memory_bytes.set(impl->totalBytes);
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
|
// Testing-only cross-check of the accounting.
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort(); // GCOVR_EXCL_LINE
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -5971,6 +5968,14 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// The SHOW_MEMORY instrumentation. Each of these functions is defined exactly
|
||||||
|
// once, with the accounting compiled out when SHOW_MEMORY is disabled. Defining
|
||||||
|
// them once (rather than defining no-op stubs when SHOW_MEMORY is disabled)
|
||||||
|
// keeps every configuration of this file reporting each function on the same
|
||||||
|
// line, which merging coverage from objects compiled with different options
|
||||||
|
// requires: gcovr, merging the SHOW_MEMORY test with the rest of the build,
|
||||||
|
// otherwise aborts with "Got function ... on multiple lines". The optimizer
|
||||||
|
// removes the resulting empty calls.
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
|
|
||||||
int64_t nodeBytes = 0;
|
int64_t nodeBytes = 0;
|
||||||
@@ -5994,12 +5999,15 @@ int64_t getNodeSize(struct Node *n) {
|
|||||||
return sizeof(Node48);
|
return sizeof(Node48);
|
||||||
case Type_Node256:
|
case Type_Node256:
|
||||||
return sizeof(Node256);
|
return sizeof(Node256);
|
||||||
default:
|
default: // GCOVR_EXCL_LINE
|
||||||
abort();
|
abort(); // GCOVR_EXCL_LINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
int64_t getSearchPathLength(Node *n) {
|
int64_t getSearchPathLength(Node *n) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
assert(n != nullptr);
|
assert(n != nullptr);
|
||||||
int64_t result = 0;
|
int64_t result = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -6011,9 +6019,14 @@ int64_t getSearchPathLength(Node *n) {
|
|||||||
n = n->parent;
|
n = n->parent;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
#else
|
||||||
|
(void)n;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void addNode(Node *n) {
|
void addNode(Node *n) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
nodeBytes += getNodeSize(n);
|
nodeBytes += getNodeSize(n);
|
||||||
partialCapacityBytes += n->getCapacity();
|
partialCapacityBytes += n->getCapacity();
|
||||||
if (nodeBytes > peakNodeBytes) {
|
if (nodeBytes > peakNodeBytes) {
|
||||||
@@ -6022,14 +6035,22 @@ void addNode(Node *n) {
|
|||||||
if (partialCapacityBytes > peakPartialCapacityBytes) {
|
if (partialCapacityBytes > peakPartialCapacityBytes) {
|
||||||
peakPartialCapacityBytes = partialCapacityBytes;
|
peakPartialCapacityBytes = partialCapacityBytes;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
(void)n;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeNode(Node *n) {
|
void removeNode(Node *n) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
nodeBytes -= getNodeSize(n);
|
nodeBytes -= getNodeSize(n);
|
||||||
partialCapacityBytes -= n->getCapacity();
|
partialCapacityBytes -= n->getCapacity();
|
||||||
|
#else
|
||||||
|
(void)n;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void addKey(Node *n) {
|
void addKey(Node *n) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
if (!n->entryPresent) {
|
if (!n->entryPresent) {
|
||||||
++totalKeys;
|
++totalKeys;
|
||||||
keyBytes += getSearchPathLength(n);
|
keyBytes += getSearchPathLength(n);
|
||||||
@@ -6040,22 +6061,36 @@ void addKey(Node *n) {
|
|||||||
peakKeyBytes = keyBytes;
|
peakKeyBytes = keyBytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
(void)n;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeKey(Node *n) {
|
void removeKey(Node *n) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
if (n->entryPresent) {
|
if (n->entryPresent) {
|
||||||
--totalKeys;
|
--totalKeys;
|
||||||
keyBytes -= getSearchPathLength(n);
|
keyBytes -= getSearchPathLength(n);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
(void)n;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeKey(Node *n, int64_t searchPathLength) {
|
void removeKey(Node *n, int64_t searchPathLength) {
|
||||||
|
#if SHOW_MEMORY
|
||||||
if (n->entryPresent) {
|
if (n->entryPresent) {
|
||||||
--totalKeys;
|
--totalKeys;
|
||||||
keyBytes -= searchPathLength;
|
keyBytes -= searchPathLength;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
(void)n;
|
||||||
|
(void)searchPathLength;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
|
||||||
struct __attribute__((visibility("default"))) PeakPrinter {
|
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||||
~PeakPrinter() {
|
~PeakPrinter() {
|
||||||
printf("--- radix_tree ---\n");
|
printf("--- radix_tree ---\n");
|
||||||
|
|||||||
@@ -86,4 +86,30 @@ int main() {
|
|||||||
assert(checkPoint(cs, "other/999", version) == ConflictSet::Conflict);
|
assert(checkPoint(cs, "other/999", version) == ConflictSet::Conflict);
|
||||||
}
|
}
|
||||||
checkFullyReleased();
|
checkFullyReleased();
|
||||||
|
|
||||||
|
// Grow a node through every node type, so that the accounting for each node
|
||||||
|
// type's size is exercised, then erase the entries one at a time so that
|
||||||
|
// nodes are released individually rather than as part of a whole tree.
|
||||||
|
{
|
||||||
|
ConflictSet cs{0};
|
||||||
|
std::string key(1, '\0');
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
key[0] = char(i);
|
||||||
|
writePoint(cs, key, i + 1);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
key[0] = char(i);
|
||||||
|
assert(checkPoint(cs, key, i) == ConflictSet::Conflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Garbage collection erases entries that can only fail with TooOld anyway.
|
||||||
|
// Doing it one version at a time walks the tree, erasing, downsizing and
|
||||||
|
// releasing nodes as it goes.
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
cs.setOldestVersion(i + 1);
|
||||||
|
}
|
||||||
|
key[0] = '\0';
|
||||||
|
assert(checkPoint(cs, key, 0) == ConflictSet::TooOld);
|
||||||
|
}
|
||||||
|
checkFullyReleased();
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user