Skip to content

Commit 67e2b37

Browse files
mstdokumacijinzhongjia
authored andcommitted
fix(clonePayload): improve error handling for partial cloning of arrays and maps
1 parent a2df7d7 commit 67e2b37

2 files changed

Lines changed: 74 additions & 4 deletions

File tree

src/msgpack.zig

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -865,16 +865,38 @@ fn clonePayload(payload: Payload, allocator: Allocator) !Payload {
865865

866866
.arr => |arr| {
867867
const new_arr = try allocator.alloc(Payload, arr.len);
868-
errdefer allocator.free(new_arr);
868+
var cloned_count: usize = 0;
869+
var success = false;
870+
defer if (!success) {
871+
// cleanup partial clones on error
872+
for (0..cloned_count) |j| {
873+
new_arr[j].free(allocator);
874+
}
875+
allocator.free(new_arr);
876+
};
877+
869878
for (arr, 0..) |item, i| {
870-
new_arr[i] = try clonePayload(item, allocator);
879+
const cloned_item = try clonePayload(item, allocator);
880+
new_arr[i] = cloned_item;
881+
cloned_count += 1;
871882
}
883+
884+
success = true;
872885
return Payload{ .arr = new_arr };
873886
},
874887

875888
.map => |m| {
876889
var new_map = Map.init(allocator);
877-
errdefer new_map.deinit();
890+
var success = false;
891+
defer if (!success) {
892+
// Free any partially-inserted keys/values on error before deinit
893+
var it2 = new_map.map.iterator();
894+
while (it2.next()) |entry| {
895+
entry.key_ptr.*.free(allocator);
896+
entry.value_ptr.*.free(allocator);
897+
}
898+
new_map.deinit();
899+
};
878900

879901
// Clone all entries
880902
var it = m.map.iterator();
@@ -887,6 +909,8 @@ fn clonePayload(payload: Payload, allocator: Allocator) !Payload {
887909
// Use putInternal to insert without additional cloning
888910
try new_map.putInternal(cloned_key, cloned_value);
889911
}
912+
913+
success = true;
890914
return Payload{ .map = new_map };
891915
},
892916
};

src/test.zig

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ test "payload utility methods" {
11871187
const cloned_str_payload = try str_payload.deepClone(allocator);
11881188
defer cloned_str_payload.free(allocator);
11891189
try expect(u8eql("test", cloned_str_payload.str.value()));
1190-
try expect(@ptrToInt(str_payload.str.value().ptr) != @ptrToInt(cloned_str_payload.str.value().ptr));
1190+
try expect(@intFromPtr(str_payload.str.value().ptr) != @intFromPtr(cloned_str_payload.str.value().ptr));
11911191

11921192
const arr_payload = try Payload.arrPayload(3, allocator);
11931193
defer arr_payload.free(allocator);
@@ -3613,6 +3613,52 @@ test "iterative parser: deep nested maps" {
36133613
try expect(decoded == .map);
36143614
}
36153615

3616+
test "clonePayload arr partial fail path frees partially cloned elements" {
3617+
var src = try Payload.arrPayload(2, std.heap.page_allocator);
3618+
defer src.free(std.heap.page_allocator);
3619+
3620+
try src.setArrElement(0, try Payload.strToPayload("a", std.heap.page_allocator));
3621+
try src.setArrElement(1, try Payload.strToPayload("this-is-a-large-string-that-will-always-fail", std.heap.page_allocator));
3622+
3623+
var buffer: [@sizeOf(Payload) * 2 + 8]u8 = undefined;
3624+
var pool = std.heap.FixedBufferAllocator.init(&buffer);
3625+
const clone_alloc = pool.allocator();
3626+
3627+
const result = src.deepClone(clone_alloc);
3628+
if (result) |cloned| {
3629+
cloned.free(clone_alloc);
3630+
try std.testing.expect(false);
3631+
} else |err| {
3632+
try std.testing.expect(err == error.OutOfMemory);
3633+
}
3634+
3635+
const next_alloc = try clone_alloc.alloc(u8, 16);
3636+
clone_alloc.free(next_alloc);
3637+
}
3638+
3639+
test "clonePayload map partial fail path frees partially cloned entries" {
3640+
var src = Payload.mapPayload(std.heap.page_allocator);
3641+
defer src.free(std.heap.page_allocator);
3642+
3643+
try src.mapPut("k1", try Payload.strToPayload("v1", std.heap.page_allocator));
3644+
try src.mapPut("k2", try Payload.strToPayload("very-long-string-to-force-out-of-memory-on-clone", std.heap.page_allocator));
3645+
3646+
var buffer: [@sizeOf(Payload) * 2 + 16]u8 = undefined;
3647+
var pool = std.heap.FixedBufferAllocator.init(&buffer);
3648+
const clone_alloc = pool.allocator();
3649+
3650+
const result = src.deepClone(clone_alloc);
3651+
if (result) |cloned| {
3652+
cloned.free(clone_alloc);
3653+
try std.testing.expect(false);
3654+
} else |err| {
3655+
try std.testing.expect(err == error.OutOfMemory);
3656+
}
3657+
3658+
const next_alloc = try clone_alloc.alloc(u8, 16);
3659+
clone_alloc.free(next_alloc);
3660+
}
3661+
36163662
test "iterative free: deeply nested payload" {
36173663
// Create a deeply nested structure in memory
36183664
var root = try Payload.arrPayload(1, allocator);

0 commit comments

Comments
 (0)