Undo: free and search the undo tree iteratively

Recursion depth equalled history length, so destroying or clearing a
buffer with a few hundred thousand undo steps overflowed the stack.
UndoSystem::clear() also freed only the first root branch, leaking the
root's siblings; it now frees the whole forest.

Audit finding M4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 09:16:04 -07:00
committed by Kyle Isom
parent 0f2cc41ed1
commit 52089d3564
3 changed files with 48 additions and 37 deletions
+17 -10
View File
@@ -1,24 +1,31 @@
// Undo logic is implemented in UndoSystem; this file only owns node lifetime.
#include "UndoTree.h"
namespace {
#include <vector>
void
free_node_graph(UndoNode *node)
FreeUndoForest(UndoNode *first)
{
// Walk the sibling (redo-branch) list; for each node, recursively free its
// child subtree first, then the node itself.
while (node) {
UndoNode *next = node->next;
free_node_graph(node->child);
// Nodes form a first-child/next-sibling tree whose depth equals the
// length of the edit history, so walk it with an explicit stack.
std::vector<UndoNode *> stack;
if (first)
stack.push_back(first);
while (!stack.empty()) {
UndoNode *node = stack.back();
stack.pop_back();
if (node->child)
stack.push_back(node->child);
if (node->next)
stack.push_back(node->next);
delete node;
node = next;
}
}
} // namespace
UndoTree::~UndoTree()
{
free_node_graph(root);
FreeUndoForest(root);
delete pending;
}