authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-09 14:47:34+02:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-04-09 15:00:22+02:00
log4151e6c31b50ffaab0c9b68106f7903c3d99c810
treea93c7b4b946a3e3508ebfc3938a97dea16886d46
parentf8dd2a1064154e5e8223c4d52e123b582497a40d

fetch: use arena allocator for diagnostic/UnpackResult

Reference: https://github.com/ziglang/zig/pull/19500#discussion_r1556476973 Arena is now used for Diagnostic (tar and git). `deinit` is not called on Diagnostic allowing us to reference strings from Diagnostic in UnpackResult without dupe. That seamed reasonable to me. Instead of using gpa for Diagnostic, and then dupe to arena. Or using arena for both and making dupe so we can deinit Diagnostic.

1 files changed, 87 insertions(+), 81 deletions(-)

src/Package/Fetch.zig+87-81
......@@ -463,7 +463,6 @@ fn runResource(
463463
464464 // Fetch and unpack a resource into a temporary directory.
465465 var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);
466 defer unpack_result.deinit();
467466
468467 var pkg_path: Cache.Path = .{ .root_dir = tmp_directory, .sub_path = unpack_result.root_dir };
469468
......@@ -487,11 +486,7 @@ fn runResource(
487486
488487 // Ignore errors that were excluded by manifest, such as failure to
489488 // create symlinks that weren't supposed to be included anyway.
490 try unpack_result.filterErrors(filter);
491 if (unpack_result.hasErrors()) {
492 try unpack_result.bundleErrors(eb, try f.srcLoc(f.location_tok));
493 return error.FetchFailed;
494 }
489 try unpack_result.validate(f, filter);
495490
496491 // Apply the manifest's inclusion rules to the temporary directory by
497492 // deleting excluded files.
......@@ -1117,8 +1112,7 @@ fn unpackResource(
11171112 .{ uri_path, @errorName(err) },
11181113 ));
11191114 };
1120 const gpa = f.arena.child_allocator;
1121 return UnpackResult.init(gpa);
1115 return .{};
11221116 },
11231117 };
11241118
......@@ -1166,10 +1160,9 @@ fn unpackResource(
11661160
11671161fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackResult {
11681162 const eb = &f.error_bundle;
1169 const gpa = f.arena.child_allocator;
1163 const arena = f.arena.allocator();
11701164
1171 var diagnostics: std.tar.Diagnostics = .{ .allocator = gpa };
1172 defer diagnostics.deinit();
1165 var diagnostics: std.tar.Diagnostics = .{ .allocator = arena };
11731166
11741167 std.tar.pipeToFileSystem(out_dir, reader, .{
11751168 .diagnostics = &diagnostics,
......@@ -1181,17 +1174,14 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes
11811174 .{@errorName(err)},
11821175 ));
11831176
1184 var res = UnpackResult.init(gpa);
1185 if (diagnostics.root_dir.len > 0) {
1186 res.root_dir = try gpa.dupe(u8, diagnostics.root_dir);
1187 }
1177 var res: UnpackResult = .{ .root_dir = diagnostics.root_dir };
11881178 if (diagnostics.errors.items.len > 0) {
1189 try res.rootErrorMessage("unable to unpack tarball");
1179 try res.allocErrors(arena, diagnostics.errors.items.len, "unable to unpack tarball");
11901180 for (diagnostics.errors.items) |item| {
11911181 switch (item) {
1192 .unable_to_create_file => |i| try res.unableToCreateFile(stripRoot(i.file_name, res.root_dir), i.code),
1193 .unable_to_create_sym_link => |i| try res.unableToCreateSymLink(stripRoot(i.file_name, res.root_dir), i.link_name, i.code),
1194 .unsupported_file_type => |i| try res.unsupportedFileType(stripRoot(i.file_name, res.root_dir), @intFromEnum(i.file_type)),
1182 .unable_to_create_file => |i| res.unableToCreateFile(stripRoot(i.file_name, res.root_dir), i.code),
1183 .unable_to_create_sym_link => |i| res.unableToCreateSymLink(stripRoot(i.file_name, res.root_dir), i.link_name, i.code),
1184 .unsupported_file_type => |i| res.unsupportedFileType(stripRoot(i.file_name, res.root_dir), @intFromEnum(i.file_type)),
11951185 }
11961186 }
11971187 }
......@@ -1199,11 +1189,12 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes
11991189}
12001190
12011191fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!UnpackResult {
1192 const arena = f.arena.allocator();
12021193 const gpa = f.arena.child_allocator;
12031194 const want_oid = resource.git.want_oid;
12041195 const reader = resource.git.fetch_stream.reader();
12051196
1206 var res = UnpackResult.init(gpa);
1197 var res: UnpackResult = .{};
12071198 // The .git directory is used to store the packfile and associated index, but
12081199 // we do not attempt to replicate the exact structure of a real .git
12091200 // directory, since that isn't relevant for fetching a package.
......@@ -1234,16 +1225,15 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!Unpac
12341225 checkout_prog_node.activate();
12351226 var repository = try git.Repository.init(gpa, pack_file, index_file);
12361227 defer repository.deinit();
1237 var diagnostics: git.Diagnostics = .{ .allocator = gpa };
1238 defer diagnostics.deinit();
1228 var diagnostics: git.Diagnostics = .{ .allocator = arena };
12391229 try repository.checkout(out_dir, want_oid, &diagnostics);
12401230
12411231 if (diagnostics.errors.items.len > 0) {
1242 try res.rootErrorMessage("unable to unpack packfile");
1232 try res.allocErrors(arena, diagnostics.errors.items.len, "unable to unpack packfile");
12431233 for (diagnostics.errors.items) |item| {
12441234 switch (item) {
1245 .unable_to_create_file => |i| try res.unableToCreateFile(i.file_name, i.code),
1246 .unable_to_create_sym_link => |i| try res.unableToCreateSymLink(i.file_name, i.link_name, i.code),
1235 .unable_to_create_file => |i| res.unableToCreateFile(i.file_name, i.code),
1236 .unable_to_create_sym_link => |i| res.unableToCreateSymLink(i.file_name, i.link_name, i.code),
12471237 }
12481238 }
12491239 }
......@@ -1701,6 +1691,7 @@ const native_os = builtin.os.tag;
17011691test {
17021692 _ = Filter;
17031693 _ = FileType;
1694 _ = UnpackResult;
17041695}
17051696
17061697// Detects executable header: ELF magic header or shebang line.
......@@ -1741,8 +1732,8 @@ test FileHeader {
17411732// tar/git diagnostic, filtering that errors by manifest inclusion rules and
17421733// emitting remaining errors to an `ErrorBundle`.
17431734const UnpackResult = struct {
1744 allocator: std.mem.Allocator,
1745 errors: std.ArrayListUnmanaged(Error) = .{},
1735 errors: []Error = undefined,
1736 errors_count: usize = 0,
17461737 root_error_message: []const u8 = "",
17471738
17481739 // A non empty value means that the package contents are inside a
......@@ -1772,97 +1763,82 @@ const UnpackResult = struct {
17721763 };
17731764 return !filter.includePath(file_name);
17741765 }
1775
1776 fn free(self: Error, allocator: std.mem.Allocator) void {
1777 switch (self) {
1778 .unable_to_create_sym_link => |info| {
1779 allocator.free(info.file_name);
1780 allocator.free(info.link_name);
1781 },
1782 .unable_to_create_file => |info| {
1783 allocator.free(info.file_name);
1784 },
1785 .unsupported_file_type => |info| {
1786 allocator.free(info.file_name);
1787 },
1788 }
1789 }
17901766 };
17911767
1792 fn init(allocator: std.mem.Allocator) UnpackResult {
1793 return .{ .allocator = allocator };
1794 }
1795
1796 fn deinit(self: *UnpackResult) void {
1797 for (self.errors.items) |item| {
1798 item.free(self.allocator);
1799 }
1800 self.errors.deinit(self.allocator);
1801 self.allocator.free(self.root_error_message);
1802 self.allocator.free(self.root_dir);
1803 self.* = undefined;
1768 fn allocErrors(self: *UnpackResult, arena: std.mem.Allocator, n: usize, root_error_message: []const u8) !void {
1769 self.root_error_message = try arena.dupe(u8, root_error_message);
1770 self.errors = try arena.alloc(UnpackResult.Error, n);
18041771 }
18051772
18061773 fn hasErrors(self: *UnpackResult) bool {
1807 return self.errors.items.len > 0;
1774 return self.errors_count > 0;
18081775 }
18091776
1810 fn unableToCreateFile(self: *UnpackResult, file_name: []const u8, err: anyerror) !void {
1811 try self.errors.append(self.allocator, .{ .unable_to_create_file = .{
1777 fn unableToCreateFile(self: *UnpackResult, file_name: []const u8, err: anyerror) void {
1778 self.errors[self.errors_count] = .{ .unable_to_create_file = .{
18121779 .code = err,
1813 .file_name = try self.allocator.dupe(u8, file_name),
1814 } });
1780 .file_name = file_name,
1781 } };
1782 self.errors_count += 1;
18151783 }
18161784
1817 fn unableToCreateSymLink(self: *UnpackResult, file_name: []const u8, link_name: []const u8, err: anyerror) !void {
1818 try self.errors.append(self.allocator, .{ .unable_to_create_sym_link = .{
1785 fn unableToCreateSymLink(self: *UnpackResult, file_name: []const u8, link_name: []const u8, err: anyerror) void {
1786 self.errors[self.errors_count] = .{ .unable_to_create_sym_link = .{
18191787 .code = err,
1820 .file_name = try self.allocator.dupe(u8, file_name),
1821 .link_name = try self.allocator.dupe(u8, link_name),
1822 } });
1788 .file_name = file_name,
1789 .link_name = link_name,
1790 } };
1791 self.errors_count += 1;
18231792 }
18241793
1825 fn unsupportedFileType(self: *UnpackResult, file_name: []const u8, file_type: u8) !void {
1826 try self.errors.append(self.allocator, .{ .unsupported_file_type = .{
1827 .file_name = try self.allocator.dupe(u8, file_name),
1794 fn unsupportedFileType(self: *UnpackResult, file_name: []const u8, file_type: u8) void {
1795 self.errors[self.errors_count] = .{ .unsupported_file_type = .{
1796 .file_name = file_name,
18281797 .file_type = file_type,
1829 } });
1798 } };
1799 self.errors_count += 1;
1800 }
1801
1802 fn validate(self: *UnpackResult, f: *Fetch, filter: Filter) !void {
1803 self.filterErrors(filter);
1804 if (self.hasErrors()) {
1805 const eb = &f.error_bundle;
1806 try self.bundleErrors(eb, try f.srcLoc(f.location_tok));
1807 return error.FetchFailed;
1808 }
18301809 }
18311810
18321811 // Filter errors by manifest inclusion rules.
1833 fn filterErrors(self: *UnpackResult, filter: Filter) !void {
1834 var i = self.errors.items.len;
1812 fn filterErrors(self: *UnpackResult, filter: Filter) void {
1813 var i = self.errors_count;
18351814 while (i > 0) {
18361815 i -= 1;
1837 const item = self.errors.items[i];
1838 if (item.excluded(filter)) {
1839 _ = self.errors.swapRemove(i);
1840 item.free(self.allocator);
1816 if (self.errors[i].excluded(filter)) {
1817 self.errors_count -= 1;
1818 const tmp = self.errors[i];
1819 self.errors[i] = self.errors[self.errors_count];
1820 self.errors[self.errors_count] = tmp;
18411821 }
18421822 }
18431823 }
18441824
1845 fn rootErrorMessage(self: *UnpackResult, msg: []const u8) !void {
1846 self.root_error_message = try self.allocator.dupe(u8, msg);
1847 }
1848
18491825 // Emmit errors to an `ErrorBundle`.
18501826 fn bundleErrors(
18511827 self: *UnpackResult,
18521828 eb: *ErrorBundle.Wip,
18531829 src_loc: ErrorBundle.SourceLocationIndex,
18541830 ) !void {
1855 if (self.errors.items.len == 0 and self.root_error_message.len == 0)
1831 if (self.errors_count == 0 and self.root_error_message.len == 0)
18561832 return;
18571833
1858 const notes_len: u32 = @intCast(self.errors.items.len);
1834 const notes_len: u32 = @intCast(self.errors_count);
18591835 try eb.addRootErrorMessage(.{
18601836 .msg = try eb.addString(self.root_error_message),
18611837 .src_loc = src_loc,
18621838 .notes_len = notes_len,
18631839 });
18641840 const notes_start = try eb.reserveNotes(notes_len);
1865 for (self.errors.items, notes_start..) |item, note_i| {
1841 for (self.errors, notes_start..) |item, note_i| {
18661842 switch (item) {
18671843 .unable_to_create_sym_link => |info| {
18681844 eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{
......@@ -1888,6 +1864,36 @@ const UnpackResult = struct {
18881864 }
18891865 }
18901866 }
1867
1868 test filterErrors {
1869 var arena_instance = std.heap.ArenaAllocator.init(std.testing.allocator);
1870 defer arena_instance.deinit();
1871 const arena = arena_instance.allocator();
1872
1873 // init
1874 var res: UnpackResult = .{};
1875 try res.allocErrors(arena, 4, "error");
1876 try std.testing.expectEqual(0, res.errors_count);
1877
1878 // create errors
1879 res.unableToCreateFile("dir1/file1", error.File1);
1880 res.unableToCreateSymLink("dir2/file2", "", error.File2);
1881 res.unableToCreateFile("dir1/file3", error.File3);
1882 res.unsupportedFileType("dir2/file4", 'x');
1883 try std.testing.expectEqual(4, res.errors_count);
1884
1885 // filter errors
1886 var filter: Filter = .{};
1887 try filter.include_paths.put(arena, "dir2", {});
1888 res.filterErrors(filter);
1889
1890 try std.testing.expectEqual(2, res.errors_count);
1891 try std.testing.expect(res.errors[0] == Error.unsupported_file_type);
1892 try std.testing.expect(res.errors[1] == Error.unable_to_create_sym_link);
1893 // filtered: moved to the list end
1894 try std.testing.expect(res.errors[2] == Error.unable_to_create_file);
1895 try std.testing.expect(res.errors[3] == Error.unable_to_create_file);
1896 }
18911897};
18921898
18931899test "tarball with duplicate paths" {