atproto utils for zig zat.dev
atproto sdk zig
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix MST integer overflow: findKey/deleteFromNode underflow at layer 0

After deleting keys, the tree trim loop could reduce root_layer below
what remaining keys require. findKey and deleteFromNode then computed
layer - 1 with layer=0, causing u32 underflow. Fixed by changing the
height == layer check to height >= layer (handles keys above the current
layer) and adding a layer == 0 early-return guard before recursion.

The insert-50-delete-every-other stress test now passes, validating that
the tree structure remains consistent after bulk deletions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

jcalabro f24148b2 bfc2c69a

+7 -8
+5 -2
src/internal/repo/mst.zig
··· 172 172 fn findKey(maybe_node: ?*Node, layer: u32, key: []const u8, height: u32) ?cbor.Cid { 173 173 const node = maybe_node orelse return null; 174 174 175 - if (height == layer) { 175 + if (height >= layer) { 176 + // key belongs at this layer or above — scan entries 176 177 for (node.entries.items) |entry| { 177 178 const cmp = std.mem.order(u8, key, entry.key); 178 179 if (cmp == .eq) return entry.value; ··· 182 183 } 183 184 184 185 // height < layer: recurse into the subtree gap containing key 186 + if (layer == 0) return null; // can't go deeper 185 187 for (node.entries.items, 0..) |entry, i| { 186 188 if (std.mem.order(u8, key, entry.key) == .lt) { 187 189 const child = if (i == 0) node.left else node.entries.items[i - 1].right; ··· 234 236 fn deleteFromNode(self: *Mst, node: *Node, layer: u32, key: []const u8) !?cbor.Cid { 235 237 const height = keyHeight(key); 236 238 237 - if (height == layer) { 239 + if (height >= layer) { 238 240 // find and remove the entry 239 241 for (node.entries.items, 0..) |entry, i| { 240 242 if (std.mem.eql(u8, entry.key, key)) { ··· 259 261 } 260 262 261 263 // height < layer: recurse into the appropriate gap 264 + if (layer == 0) return null; // can't go deeper 262 265 if (node.entries.items.len == 0) { 263 266 switch (node.left) { 264 267 .node => |left| return try self.deleteFromNode(left, layer - 1, key),
+2 -6
src/internal/repo/mst_test.zig
··· 156 156 } 157 157 } 158 158 159 - // TODO: this test exposes an integer overflow bug in MST tree rebalancing 160 - // after deletions. The tree.get() call panics after removing keys. 161 - // Needs investigation in the delete/rebalance path of mst.zig. 162 - test "insert 10 keys then remove every other" { 163 - if (true) return error.SkipZigTest; // skip until MST delete bug is fixed 159 + test "insert 50 keys then remove every other" { 164 160 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 165 161 defer arena.deinit(); 166 162 const a = arena.allocator(); 167 163 168 - const n = 10; 164 + const n = 50; 169 165 var tree = Mst.init(a); 170 166 var key_bufs: [n][64]u8 = undefined; 171 167 var keys: [n][]const u8 = undefined;