From f4821d79e8dbf8ee8b0d4966d4f15bd7823649c0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 14 Mar 2026 19:32:16 +0100 Subject: [PATCH] fix(kqueue): avoid more potential leaks --- src/backend/KQueue.zig | 7 ++++++- src/backend/KQueueDir.zig | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/backend/KQueue.zig b/src/backend/KQueue.zig index a82011f..47a93f7 100644 --- a/src/backend/KQueue.zig +++ b/src/backend/KQueue.zig @@ -184,6 +184,11 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) var to_create: std.ArrayListUnmanaged([]const u8) = .empty; var to_delete: std.ArrayListUnmanaged([]const u8) = .empty; var new_dirs: std.ArrayListUnmanaged([]const u8) = .empty; + // to_delete items are owned (removed from snapshot) only after the fetchRemove + // loop inside the mutex block. Track this so the defer below only frees them + // once ownership has actually been transferred. + var to_delete_owned = false; + defer if (to_delete_owned) for (to_delete.items) |name| allocator.free(name); self.snapshots_mutex.lock(); errdefer self.snapshots_mutex.unlock(); @@ -222,6 +227,7 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) try to_delete.append(tmp, entry.key_ptr.*); } for (to_delete.items) |name| _ = snapshot.fetchRemove(name); + to_delete_owned = true; // ownership transferred; defer will free all items } self.snapshots_mutex.unlock(); @@ -231,7 +237,6 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) for (new_dirs.items) |full_path| try self.handler.change(full_path, EventType.created, .dir); for (to_delete.items) |name| { - defer allocator.free(name); // snapshot key, owned by allocator var path_buf: [std.fs.max_path_bytes]u8 = undefined; const full_path = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ dir_path, name }) catch continue; self.deregister_file_watch(allocator, full_path); diff --git a/src/backend/KQueueDir.zig b/src/backend/KQueueDir.zig index 835c8cf..94803c5 100644 --- a/src/backend/KQueueDir.zig +++ b/src/backend/KQueueDir.zig @@ -178,6 +178,11 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) var to_delete: std.ArrayListUnmanaged([]const u8) = .empty; var to_modify: std.ArrayListUnmanaged([]const u8) = .empty; var new_dirs: std.ArrayListUnmanaged([]const u8) = .empty; + // to_delete items are owned (removed from snapshot) only after the fetchRemove + // loop inside the mutex block. Track this so the defer below only frees them + // once ownership has actually been transferred. + var to_delete_owned = false; + defer if (to_delete_owned) for (to_delete.items) |name| allocator.free(name); self.snapshots_mutex.lock(); errdefer self.snapshots_mutex.unlock(); @@ -224,6 +229,7 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) try to_delete.append(tmp, entry.key_ptr.*); // borrow from snapshot } for (to_delete.items) |name| _ = snapshot.fetchRemove(name); + to_delete_owned = true; // ownership transferred; defer will free all items } self.snapshots_mutex.unlock(); @@ -232,7 +238,6 @@ fn scan_dir(self: *@This(), allocator: std.mem.Allocator, dir_path: []const u8) for (new_dirs.items) |full_path| try self.handler.change(full_path, EventType.created, .dir); for (to_delete.items) |name| { - defer allocator.free(name); // snapshot key, owned by allocator var path_buf: [std.fs.max_path_bytes]u8 = undefined; const full_path = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ dir_path, name }) catch continue; try self.handler.change(full_path, EventType.deleted, .file);