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.

add errdefer for decode allocations, verify with checkAllAllocationFailures

Add errdefer allocator.free() for array items and map entries slices in
decodeAt so partial allocations are cleaned up on error. Add 3 tests
using std.testing.checkAllAllocationFailures to exhaustively verify that
every allocation failure in decode (flat map, nested record, array) is
handled without leaking — tests use ArenaAllocator over the failing
allocator to match the intended usage pattern.

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

jcalabro ece81a96 62e99128

+64
+2
src/internal/repo/cbor.zig
··· 315 315 // sanity check: each element is at least 1 byte 316 316 if (arg.val > data.len - pos.*) return error.UnexpectedEof; 317 317 const items = try allocator.alloc(Value, @intCast(arg.val)); 318 + errdefer allocator.free(items); 318 319 for (items) |*item| { 319 320 item.* = try decodeAt(allocator, data, pos, depth + 1); 320 321 } ··· 325 326 // sanity check: each entry is at least 2 bytes (key + value) 326 327 if (arg.val > (data.len - pos.*) / 2) return error.UnexpectedEof; 327 328 const entries = try allocator.alloc(Value.MapEntry, @intCast(arg.val)); 329 + errdefer allocator.free(entries); 328 330 for (entries, 0..) |*entry, i| { 329 331 // DAG-CBOR: map keys must be text strings — inline read to avoid 330 332 // a full decodeAt + Value union construction per key
+62
src/internal/repo/cbor_test.zig
··· 1209 1209 })); 1210 1210 } 1211 1211 1212 + // === allocation failure safety (checkAllAllocationFailures) === 1213 + 1214 + fn decodeSimpleImpl(backing: std.mem.Allocator, data: []const u8) !void { 1215 + // use an arena over the backing allocator — checkAllAllocationFailures 1216 + // tracks the backing allocator's alloc/free calls. the arena batches 1217 + // frees on deinit, so OOM from any arena allocation correctly frees 1218 + // everything allocated so far. 1219 + var arena = std.heap.ArenaAllocator.init(backing); 1220 + defer arena.deinit(); 1221 + _ = try cbor.decodeAll(arena.allocator(), data); 1222 + } 1223 + 1224 + test "checkAllAllocationFailures: decode flat map" { 1225 + var setup_arena = std.heap.ArenaAllocator.init(std.testing.allocator); 1226 + defer setup_arena.deinit(); 1227 + const encoded = try cbor.encodeAlloc(setup_arena.allocator(), .{ .map = &.{ 1228 + .{ .key = "a", .value = .{ .unsigned = 1 } }, 1229 + .{ .key = "b", .value = .{ .text = "hello" } }, 1230 + } }); 1231 + 1232 + try std.testing.checkAllAllocationFailures(std.testing.allocator, decodeSimpleImpl, .{encoded}); 1233 + } 1234 + 1235 + fn decodeNestedImpl(backing: std.mem.Allocator, data: []const u8) !void { 1236 + var arena = std.heap.ArenaAllocator.init(backing); 1237 + defer arena.deinit(); 1238 + _ = try cbor.decodeAll(arena.allocator(), data); 1239 + } 1240 + 1241 + test "checkAllAllocationFailures: decode nested record" { 1242 + var setup_arena = std.heap.ArenaAllocator.init(std.testing.allocator); 1243 + defer setup_arena.deinit(); 1244 + const sa = setup_arena.allocator(); 1245 + 1246 + const record: Value = .{ .map = &.{ 1247 + .{ .key = "$type", .value = .{ .text = "app.bsky.feed.post" } }, 1248 + .{ .key = "langs", .value = .{ .array = &.{.{ .text = "en" }} } }, 1249 + .{ .key = "text", .value = .{ .text = "hello" } }, 1250 + } }; 1251 + const encoded = try cbor.encodeAlloc(sa, record); 1252 + 1253 + try std.testing.checkAllAllocationFailures(std.testing.allocator, decodeNestedImpl, .{encoded}); 1254 + } 1255 + 1256 + fn decodeArrayImpl(backing: std.mem.Allocator, data: []const u8) !void { 1257 + var arena = std.heap.ArenaAllocator.init(backing); 1258 + defer arena.deinit(); 1259 + _ = try cbor.decodeAll(arena.allocator(), data); 1260 + } 1261 + 1262 + test "checkAllAllocationFailures: decode array" { 1263 + var setup_arena = std.heap.ArenaAllocator.init(std.testing.allocator); 1264 + defer setup_arena.deinit(); 1265 + const encoded = try cbor.encodeAlloc(setup_arena.allocator(), .{ .array = &.{ 1266 + .{ .unsigned = 1 }, 1267 + .{ .unsigned = 2 }, 1268 + .{ .text = "three" }, 1269 + } }); 1270 + 1271 + try std.testing.checkAllAllocationFailures(std.testing.allocator, decodeArrayImpl, .{encoded}); 1272 + } 1273 + 1212 1274 // === negative integer encode round-trip at min i64 === 1213 1275 1214 1276 test "round-trip encode min i64" {