Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions collector/lib/NRadix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ bool NRadixTree::Insert(const IPNet& network) const {
const uint64_t* net_mask_p = net_mask.data();
uint64_t bit(0x8000000000000000ULL);

nRadixNode* node = this->root_;
nRadixNode* next = this->root_;
nRadixNode* node = this->root_.get();
nRadixNode* next = this->root_.get();

size_t i = 0;
// Traverse the tree for the bits that already exist in the tree.
while (bit & *net_mask_p) {
// If the bit is set, go right, otherwise left.
if (ntohll(*addr_p) & bit) {
next = node->right_;
next = node->right_.get();
} else {
next = node->left_;
next = node->left_.get();
}

if (!next) {
Expand Down Expand Up @@ -90,18 +90,19 @@ bool NRadixTree::Insert(const IPNet& network) const {
CLOG(ERROR) << "CIDR " << network << " already exists";
return false;
}
node->value_ = new IPNet(network);
node->value_ = std::make_unique<IPNet>(network);
return true;
}

// There still are bits to be walked, so go ahead and add them to the tree.
while (bit & *net_mask_p) {
next = new nRadixNode();
auto new_node = std::make_unique<nRadixNode>();
next = new_node.get();

if (ntohll(*addr_p) & bit) {
node->right_ = next;
node->right_ = std::move(new_node);
} else {
node->left_ = next;
node->left_ = std::move(new_node);
}

bit >>= 1;
Expand All @@ -121,7 +122,7 @@ bool NRadixTree::Insert(const IPNet& network) const {
}
}

node->value_ = new IPNet(network);
node->value_ = std::make_unique<IPNet>(network);
return true;
}

Expand All @@ -141,17 +142,17 @@ IPNet NRadixTree::Find(const IPNet& network) const {
uint64_t bit(0x8000000000000000ULL);

IPNet ret;
nRadixNode* node = this->root_;
nRadixNode* node = this->root_.get();
size_t i = 0;
while (node) {
if (node->value_) {
ret = *node->value_;
}

if (ntohll(*addr_p) & bit) {
node = node->right_;
node = node->right_.get();
} else {
node = node->left_;
node = node->left_.get();
}

// All network bits are traversed. If a supernet was found along the way, `ret` holds it,
Expand Down Expand Up @@ -195,13 +196,13 @@ void getAll(nRadixNode* node, std::vector<IPNet>& ret) {
ret.push_back(*node->value_);
}

getAll(node->left_, ret);
getAll(node->right_, ret);
getAll(node->left_.get(), ret);
getAll(node->right_.get(), ret);
}

std::vector<IPNet> NRadixTree::GetAll() const {
std::vector<IPNet> ret;
getAll(this->root_, ret);
getAll(this->root_.get(), ret);
return ret;
}

Expand All @@ -227,11 +228,11 @@ bool isAnyIPNetSubsetUtil(Address::Family family, const nRadixNode* n1, const nR
}

if (n1 && n1->value_) {
containing_net = n1->value_;
containing_net = n1->value_.get();
}

if (n2->value_) {
contained_net = n2->value_;
contained_net = n2->value_.get();
}

// If we find a network in first tree, that means it contains
Expand All @@ -240,11 +241,11 @@ bool isAnyIPNetSubsetUtil(Address::Family family, const nRadixNode* n1, const nR
// finding the smaller network down the path.

if (n1) {
return isAnyIPNetSubsetUtil(family, n1->left_, n2->left_, containing_net, contained_net) ||
isAnyIPNetSubsetUtil(family, n1->right_, n2->right_, containing_net, contained_net);
return isAnyIPNetSubsetUtil(family, n1->left_.get(), n2->left_.get(), containing_net, contained_net) ||
isAnyIPNetSubsetUtil(family, n1->right_.get(), n2->right_.get(), containing_net, contained_net);
}
return isAnyIPNetSubsetUtil(family, nullptr, n2->left_, containing_net, contained_net) ||
isAnyIPNetSubsetUtil(family, nullptr, n2->right_, containing_net, contained_net);
return isAnyIPNetSubsetUtil(family, nullptr, n2->left_.get(), containing_net, contained_net) ||
isAnyIPNetSubsetUtil(family, nullptr, n2->right_.get(), containing_net, contained_net);
}

bool NRadixTree::IsEmpty() const {
Expand All @@ -256,7 +257,7 @@ bool NRadixTree::IsAnyIPNetSubset(const NRadixTree& other) const {
}

bool NRadixTree::IsAnyIPNetSubset(Address::Family family, const NRadixTree& other) const {
return isAnyIPNetSubsetUtil(family, root_, other.root_, nullptr, nullptr);
return isAnyIPNetSubsetUtil(family, root_.get(), other.root_.get(), nullptr, nullptr);
}

} // namespace collector
40 changes: 16 additions & 24 deletions collector/lib/NRadix.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#pragma once

#include <memory>
#include <mutex>

#include "Logging.h"
Expand All @@ -38,45 +39,40 @@ namespace collector {

struct nRadixNode {
nRadixNode() : value_(nullptr), left_(nullptr), right_(nullptr) {}
explicit nRadixNode(const IPNet& value) : value_(new IPNet(value)), left_(nullptr), right_(nullptr) {}
explicit nRadixNode(const IPNet& value) : value_(std::make_unique<IPNet>(value)), left_(nullptr), right_(nullptr) {}

nRadixNode(const nRadixNode& other) : value_(nullptr), left_(nullptr), right_(nullptr) {
if (other.value_) {
value_ = new IPNet(*other.value_);
value_ = std::make_unique<IPNet>(*other.value_);
}
if (other.left_) {
left_ = new nRadixNode(*other.left_);
left_ = std::make_unique<nRadixNode>(*other.left_);
}
if (other.right_) {
right_ = new nRadixNode(*other.right_);
right_ = std::make_unique<nRadixNode>(*other.right_);
}
}

nRadixNode& operator=(const nRadixNode& other) {
if (this == &other) {
return *this;
}
auto* new_node = new nRadixNode(other);
auto new_node = std::make_unique<nRadixNode>(other);
std::swap(*new_node, *this);
delete new_node;
return *this;
}
Comment on lines 56 to 63
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Infinite recursion: std::swap falls back to copy assignment without move operations.

Since nRadixNode has a user-declared copy constructor and copy assignment, the move constructor and move assignment operator are not implicitly generated. When std::swap(*new_node, *this) executes, it attempts to use move semantics but falls back to copy operations. The internal a = std::move(b) call invokes this same operator=, causing infinite recursion and stack overflow.

Add move operations to enable proper swap semantics:

🐛 Proposed fix: add defaulted move operations
   nRadixNode(const nRadixNode& other) : value_(nullptr), left_(nullptr), right_(nullptr) {
     if (other.value_) {
       value_ = std::make_unique<IPNet>(*other.value_);
     }
     if (other.left_) {
       left_ = std::make_unique<nRadixNode>(*other.left_);
     }
     if (other.right_) {
       right_ = std::make_unique<nRadixNode>(*other.right_);
     }
   }

+  nRadixNode(nRadixNode&&) = default;
+  nRadixNode& operator=(nRadixNode&&) = default;
+
   nRadixNode& operator=(const nRadixNode& other) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@collector/lib/NRadix.h` around lines 56 - 63, The copy-assignment operator
for nRadixNode causes infinite recursion because std::swap falls back to copy
assignment when no move operations exist; declare and default the move
constructor and move-assignment operator for nRadixNode (e.g.,
nRadixNode(nRadixNode&&) noexcept = default; and nRadixNode&
operator=(nRadixNode&&) noexcept = default;) so std::swap will use move
semantics, or alternatively implement a noexcept member swap and call that from
operator=; update the class declaration to include these move operations (and
mark them noexcept) so the copy-and-swap implementation of operator= stops
recursing.


~nRadixNode() {
delete left_;
delete right_;
delete value_;
}
~nRadixNode() = default;

const IPNet* value_;
nRadixNode* left_;
nRadixNode* right_;
std::unique_ptr<const IPNet> value_;
std::unique_ptr<nRadixNode> left_;
std::unique_ptr<nRadixNode> right_;
};

class NRadixTree {
public:
NRadixTree() : root_(new nRadixNode()) {}
explicit NRadixTree(const std::vector<IPNet>& networks) : root_(new nRadixNode()) {
NRadixTree() : root_(std::make_unique<nRadixNode>()) {}
explicit NRadixTree(const std::vector<IPNet>& networks) : root_(std::make_unique<nRadixNode>()) {
for (const auto& network : networks) {
auto inserted = this->Insert(network);
if (!inserted) {
Expand All @@ -85,20 +81,16 @@ class NRadixTree {
}
}

NRadixTree(const NRadixTree& other) : root_(new nRadixNode(*other.root_)) {}
NRadixTree(const NRadixTree& other) : root_(std::make_unique<nRadixNode>(*other.root_)) {}

~NRadixTree() {
// This calls the node destructor which in turn cleans up all the nodes.
delete root_;
}
~NRadixTree() = default;

NRadixTree& operator=(const NRadixTree& other) {
if (this == &other) {
return *this;
}
delete root_;
// This calls the node copy constructor which in turn copies all the nodes.
root_ = new nRadixNode(*other.root_);
root_ = std::make_unique<nRadixNode>(*other.root_);
return *this;
}

Expand All @@ -120,7 +112,7 @@ class NRadixTree {
// Determines whether any network in `other` is fully contained by any network in this tree, for a given family.
bool IsAnyIPNetSubset(Address::Family family, const NRadixTree& other) const;

nRadixNode* root_;
std::unique_ptr<nRadixNode> root_;
};

} // namespace collector
Loading