Commit c102eb83e6

Isaac Freund <ifreund@ifreund.xyz>
2020-12-17 19:32:40
stage2: free Package resources
Without this commit we leak file descriptors and memory
1 parent 8591f30
Changed files (2)
src/main.zig
@@ -560,12 +560,15 @@ fn buildOutputType(
     var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
     defer test_exec_args.deinit();
 
-    var root_pkg_memory: Package = .{
-        .root_src_directory = undefined,
-        .root_src_path = undefined,
+    const pkg_tree_root = try gpa.create(Package);
+    // This package only exists to clean up the code parsing --pkg-begin and
+    // --pkg-end flags. Use dummy values that are safe for the destroy call.
+    pkg_tree_root.* = .{
+        .root_src_directory = .{ .path = null, .handle = fs.cwd() },
+        .root_src_path = &[0]u8{},
     };
-    defer root_pkg_memory.table.deinit(gpa);
-    var cur_pkg: *Package = &root_pkg_memory;
+    defer pkg_tree_root.destroy(gpa);
+    var cur_pkg: *Package = pkg_tree_root;
 
     switch (arg_mode) {
         .build, .translate_c, .zig_test, .run => {
@@ -619,22 +622,13 @@ fn buildOutputType(
                         i += 1;
                         const pkg_path = args[i];
 
-                        const new_cur_pkg = try arena.create(Package);
-                        new_cur_pkg.* = .{
-                            .root_src_directory = if (fs.path.dirname(pkg_path)) |dirname|
-                                .{
-                                    .path = dirname,
-                                    .handle = try fs.cwd().openDir(dirname, .{}), // TODO close this fd
-                                }
-                            else
-                                .{
-                                    .path = null,
-                                    .handle = fs.cwd(),
-                                },
-                            .root_src_path = fs.path.basename(pkg_path),
-                            .parent = cur_pkg,
-                        };
-                        try cur_pkg.table.put(gpa, pkg_name, new_cur_pkg);
+                        const new_cur_pkg = try Package.create(
+                            gpa,
+                            fs.path.dirname(pkg_path),
+                            fs.path.basename(pkg_path),
+                        );
+                        new_cur_pkg.parent = cur_pkg;
+                        try cur_pkg.add(gpa, pkg_name, new_cur_pkg);
                         cur_pkg = new_cur_pkg;
                     } else if (mem.eql(u8, arg, "--pkg-end")) {
                         cur_pkg = cur_pkg.parent orelse
@@ -1583,26 +1577,22 @@ fn buildOutputType(
         .yes => |p| p,
     };
 
-    var cleanup_root_dir: ?fs.Dir = null;
-    defer if (cleanup_root_dir) |*dir| dir.close();
-
     const root_pkg: ?*Package = if (root_src_file) |src_path| blk: {
         if (main_pkg_path) |p| {
-            const dir = try fs.cwd().openDir(p, .{});
-            cleanup_root_dir = dir;
-            root_pkg_memory.root_src_directory = .{ .path = p, .handle = dir };
-            root_pkg_memory.root_src_path = try fs.path.relative(arena, p, src_path);
-        } else if (fs.path.dirname(src_path)) |p| {
-            const dir = try fs.cwd().openDir(p, .{});
-            cleanup_root_dir = dir;
-            root_pkg_memory.root_src_directory = .{ .path = p, .handle = dir };
-            root_pkg_memory.root_src_path = fs.path.basename(src_path);
+            const rel_src_path = try fs.path.relative(gpa, p, src_path);
+            defer gpa.free(rel_src_path);
+            break :blk try Package.create(gpa, p, rel_src_path);
         } else {
-            root_pkg_memory.root_src_directory = .{ .path = null, .handle = fs.cwd() };
-            root_pkg_memory.root_src_path = src_path;
+            break :blk try Package.create(gpa, fs.path.dirname(src_path), fs.path.basename(src_path));
         }
-        break :blk &root_pkg_memory;
     } else null;
+    defer if (root_pkg) |p| p.destroy(gpa);
+
+    // Transfer packages added with --pkg-begin/--pkg-end to the root package
+    if (root_pkg) |pkg| {
+        pkg.table = pkg_tree_root.table;
+        pkg_tree_root.table = .{};
+    }
 
     const self_exe_path = try fs.selfExePathAlloc(arena);
     var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir|
src/Package.zig
@@ -1,3 +1,12 @@
+const Package = @This();
+
+const std = @import("std");
+const fs = std.fs;
+const mem = std.mem;
+const Allocator = mem.Allocator;
+
+const Compilation = @import("Compilation.zig");
+
 pub const Table = std.StringHashMapUnmanaged(*Package);
 
 root_src_directory: Compilation.Directory,
@@ -6,57 +15,60 @@ root_src_path: []const u8,
 table: Table = .{},
 parent: ?*Package = null,
 
-const std = @import("std");
-const mem = std.mem;
-const Allocator = std.mem.Allocator;
-const assert = std.debug.assert;
-const Package = @This();
-const Compilation = @import("Compilation.zig");
-
-/// No references to `root_src_dir` and `root_src_path` are kept.
+/// Allocate a Package. No references to the slices passed are kept.
 pub fn create(
     gpa: *Allocator,
-    base_directory: Compilation.Directory,
-    /// Relative to `base_directory`.
-    root_src_dir: []const u8,
-    /// Relative to `root_src_dir`.
+    /// Null indicates the current working directory
+    root_src_dir_path: ?[]const u8,
+    /// Relative to root_src_dir_path
     root_src_path: []const u8,
 ) !*Package {
     const ptr = try gpa.create(Package);
     errdefer gpa.destroy(ptr);
 
-    const root_src_dir_path = try base_directory.join(gpa, &[_][]const u8{root_src_dir});
-    errdefer gpa.free(root_src_dir_path);
+    const owned_dir_path = if (root_src_dir_path) |p| try gpa.dupe(u8, p) else null;
+    errdefer if (owned_dir_path) |p| gpa.free(p);
 
-    const root_src_path_dupe = try mem.dupe(gpa, u8, root_src_path);
-    errdefer gpa.free(root_src_path_dupe);
+    const owned_src_path = try gpa.dupe(u8, root_src_path);
+    errdefer gpa.free(owned_src_path);
 
     ptr.* = .{
         .root_src_directory = .{
-            .path = root_src_dir_path,
-            .handle = try base_directory.handle.openDir(root_src_dir, .{}),
+            .path = owned_dir_path,
+            .handle = if (owned_dir_path) |p| try fs.cwd().openDir(p, .{}) else fs.cwd(),
         },
-        .root_src_path = root_src_path_dupe,
+        .root_src_path = owned_src_path,
     };
+
     return ptr;
 }
 
+/// Free all memory associated with this package and recursively call destroy
+/// on all packages in its table
 pub fn destroy(pkg: *Package, gpa: *Allocator) void {
-    pkg.root_src_directory.handle.close();
     gpa.free(pkg.root_src_path);
-    if (pkg.root_src_directory.path) |p| gpa.free(p);
+
+    // If root_src_directory.path is null then the handle is the cwd()
+    // which shouldn't be closed.
+    if (pkg.root_src_directory.path) |p| {
+        gpa.free(p);
+        pkg.root_src_directory.handle.close();
+    }
+
     {
         var it = pkg.table.iterator();
         while (it.next()) |kv| {
+            kv.value.destroy(gpa);
             gpa.free(kv.key);
         }
     }
+
     pkg.table.deinit(gpa);
     gpa.destroy(pkg);
 }
 
 pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void {
-    try pkg.table.ensureCapacity(gpa, pkg.table.items().len + 1);
+    try pkg.table.ensureCapacity(gpa, pkg.table.count() + 1);
     const name_dupe = try mem.dupe(gpa, u8, name);
     pkg.table.putAssumeCapacityNoClobber(name_dupe, package);
 }