atproto utils for zig
0
fork

Configure Feed

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

add errdefer for decodeMstNode entries, verify with checkAllAllocationFailures

Add errdefer allocator.free(entries) in decodeMstNode so the entries
slice is freed if a subsequent readMstEntry call fails. Verified with
checkAllAllocationFailures using a hand-built MST node CBOR fixture.

Other MST functions (put, delete, merge, etc.) mutate persistent tree
state and are designed for arena allocators — they can't be verified
with checkAllAllocationFailures due to cumulative allocation patterns.

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

jcalabro 3fb4f448 4c1f212d

+47
+1
src/internal/repo/mst.zig
··· 858 858 const arr_hdr = cbor.readArrayHeader(data, pos) catch return error.InvalidMstNode; 859 859 pos = arr_hdr.end; 860 860 const entries = try allocator.alloc(MstEntryData, @intCast(arr_hdr.val)); 861 + errdefer allocator.free(entries); 861 862 for (entries) |*entry| { 862 863 const result = readMstEntry(data, pos) catch return error.InvalidMstNode; 863 864 entry.* = result.entry;
+46
src/internal/repo/mst_test.zig
··· 205 205 try std.testing.expectEqual(@as(u32, 1), mst.keyHeight("blue")); 206 206 try std.testing.expectEqual(@as(u32, 0), mst.keyHeight("asdf")); 207 207 } 208 + 209 + // === allocation failure safety === 210 + 211 + fn decodeMstNodeImpl(backing: std.mem.Allocator, data: []const u8) !void { 212 + var arena = std.heap.ArenaAllocator.init(backing); 213 + defer arena.deinit(); 214 + _ = try mst.decodeMstNode(arena.allocator(), data); 215 + } 216 + 217 + test "checkAllAllocationFailures: decodeMstNode" { 218 + // build a valid MST node CBOR by hand: 219 + // map(2) { "e": [ map(4){k,p,t,v}, map(4){k,p,t,v} ], "l": null } 220 + var setup_arena = std.heap.ArenaAllocator.init(std.testing.allocator); 221 + defer setup_arena.deinit(); 222 + const sa = setup_arena.allocator(); 223 + 224 + const val_cid = try Cid.forDagCbor(sa, "value"); 225 + 226 + // build entry maps (the "k","p","t","v" maps inside the "e" array) 227 + const entry1: cbor.Value = .{ .map = &.{ 228 + .{ .key = "k", .value = .{ .bytes = "app.bsky.feed.post/aaa" } }, 229 + .{ .key = "p", .value = .{ .unsigned = 0 } }, 230 + .{ .key = "t", .value = .null }, 231 + .{ .key = "v", .value = .{ .cid = val_cid } }, 232 + } }; 233 + const entry2: cbor.Value = .{ 234 + .map = &.{ 235 + .{ .key = "k", .value = .{ .bytes = "bbb" } }, 236 + .{ .key = "p", .value = .{ .unsigned = 22 } }, // prefix_len = shared with prev key 237 + .{ .key = "t", .value = .null }, 238 + .{ .key = "v", .value = .{ .cid = val_cid } }, 239 + }, 240 + }; 241 + 242 + const node: cbor.Value = .{ .map = &.{ 243 + .{ .key = "e", .value = .{ .array = &.{ entry1, entry2 } } }, 244 + .{ .key = "l", .value = .null }, 245 + } }; 246 + const encoded = try cbor.encodeAlloc(sa, node); 247 + 248 + try std.testing.checkAllAllocationFailures( 249 + std.testing.allocator, 250 + decodeMstNodeImpl, 251 + .{encoded}, 252 + ); 253 + }