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(...@@ -463,7 +463,6 @@ fn runResource(
463463
464 // Fetch and unpack a resource into a temporary directory.464 // Fetch and unpack a resource into a temporary directory.
465 var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);465 var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);
466 defer unpack_result.deinit();
467466
468 var pkg_path: Cache.Path = .{ .root_dir = tmp_directory, .sub_path = unpack_result.root_dir };467 var pkg_path: Cache.Path = .{ .root_dir = tmp_directory, .sub_path = unpack_result.root_dir };
469468
...@@ -487,11 +486,7 @@ fn runResource(...@@ -487,11 +486,7 @@ fn runResource(
487486
488 // Ignore errors that were excluded by manifest, such as failure to487 // Ignore errors that were excluded by manifest, such as failure to
489 // create symlinks that weren't supposed to be included anyway.488 // create symlinks that weren't supposed to be included anyway.
490 try unpack_result.filterErrors(filter);489 try unpack_result.validate(f, filter);
491 if (unpack_result.hasErrors()) {
492 try unpack_result.bundleErrors(eb, try f.srcLoc(f.location_tok));
493 return error.FetchFailed;
494 }
495490
496 // Apply the manifest's inclusion rules to the temporary directory by491 // Apply the manifest's inclusion rules to the temporary directory by
497 // deleting excluded files.492 // deleting excluded files.
...@@ -1117,8 +1112,7 @@ fn unpackResource(...@@ -1117,8 +1112,7 @@ fn unpackResource(
1117 .{ uri_path, @errorName(err) },1112 .{ uri_path, @errorName(err) },
1118 ));1113 ));
1119 };1114 };
1120 const gpa = f.arena.child_allocator;1115 return .{};
1121 return UnpackResult.init(gpa);
1122 },1116 },
1123 };1117 };
11241118
...@@ -1166,10 +1160,9 @@ fn unpackResource(...@@ -1166,10 +1160,9 @@ fn unpackResource(
11661160
1167fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackResult {1161fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackResult {
1168 const eb = &f.error_bundle;1162 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 };1165 var diagnostics: std.tar.Diagnostics = .{ .allocator = arena };
1172 defer diagnostics.deinit();
11731166
1174 std.tar.pipeToFileSystem(out_dir, reader, .{1167 std.tar.pipeToFileSystem(out_dir, reader, .{
1175 .diagnostics = &diagnostics,1168 .diagnostics = &diagnostics,
...@@ -1181,17 +1174,14 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes...@@ -1181,17 +1174,14 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes
1181 .{@errorName(err)},1174 .{@errorName(err)},
1182 ));1175 ));
11831176
1184 var res = UnpackResult.init(gpa);1177 var res: UnpackResult = .{ .root_dir = diagnostics.root_dir };
1185 if (diagnostics.root_dir.len > 0) {
1186 res.root_dir = try gpa.dupe(u8, diagnostics.root_dir);
1187 }
1188 if (diagnostics.errors.items.len > 0) {1178 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");
1190 for (diagnostics.errors.items) |item| {1180 for (diagnostics.errors.items) |item| {
1191 switch (item) {1181 switch (item) {
1192 .unable_to_create_file => |i| try res.unableToCreateFile(stripRoot(i.file_name, res.root_dir), i.code),1182 .unable_to_create_file => |i| 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),1183 .unable_to_create_sym_link => |i| 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)),1184 .unsupported_file_type => |i| res.unsupportedFileType(stripRoot(i.file_name, res.root_dir), @intFromEnum(i.file_type)),
1195 }1185 }
1196 }1186 }
1197 }1187 }
...@@ -1199,11 +1189,12 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes...@@ -1199,11 +1189,12 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: anytype) RunError!UnpackRes
1199}1189}
12001190
1201fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!UnpackResult {1191fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!UnpackResult {
1192 const arena = f.arena.allocator();
1202 const gpa = f.arena.child_allocator;1193 const gpa = f.arena.child_allocator;
1203 const want_oid = resource.git.want_oid;1194 const want_oid = resource.git.want_oid;
1204 const reader = resource.git.fetch_stream.reader();1195 const reader = resource.git.fetch_stream.reader();
12051196
1206 var res = UnpackResult.init(gpa);1197 var res: UnpackResult = .{};
1207 // The .git directory is used to store the packfile and associated index, but1198 // The .git directory is used to store the packfile and associated index, but
1208 // we do not attempt to replicate the exact structure of a real .git1199 // we do not attempt to replicate the exact structure of a real .git
1209 // directory, since that isn't relevant for fetching a package.1200 // 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...@@ -1234,16 +1225,15 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource) anyerror!Unpac
1234 checkout_prog_node.activate();1225 checkout_prog_node.activate();
1235 var repository = try git.Repository.init(gpa, pack_file, index_file);1226 var repository = try git.Repository.init(gpa, pack_file, index_file);
1236 defer repository.deinit();1227 defer repository.deinit();
1237 var diagnostics: git.Diagnostics = .{ .allocator = gpa };1228 var diagnostics: git.Diagnostics = .{ .allocator = arena };
1238 defer diagnostics.deinit();
1239 try repository.checkout(out_dir, want_oid, &diagnostics);1229 try repository.checkout(out_dir, want_oid, &diagnostics);
12401230
1241 if (diagnostics.errors.items.len > 0) {1231 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");
1243 for (diagnostics.errors.items) |item| {1233 for (diagnostics.errors.items) |item| {
1244 switch (item) {1234 switch (item) {
1245 .unable_to_create_file => |i| try res.unableToCreateFile(i.file_name, i.code),1235 .unable_to_create_file => |i| 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),1236 .unable_to_create_sym_link => |i| res.unableToCreateSymLink(i.file_name, i.link_name, i.code),
1247 }1237 }
1248 }1238 }
1249 }1239 }
...@@ -1701,6 +1691,7 @@ const native_os = builtin.os.tag;...@@ -1701,6 +1691,7 @@ const native_os = builtin.os.tag;
1701test {1691test {
1702 _ = Filter;1692 _ = Filter;
1703 _ = FileType;1693 _ = FileType;
1694 _ = UnpackResult;
1704}1695}
17051696
1706// Detects executable header: ELF magic header or shebang line.1697// Detects executable header: ELF magic header or shebang line.
...@@ -1741,8 +1732,8 @@ test FileHeader {...@@ -1741,8 +1732,8 @@ test FileHeader {
1741// tar/git diagnostic, filtering that errors by manifest inclusion rules and1732// tar/git diagnostic, filtering that errors by manifest inclusion rules and
1742// emitting remaining errors to an `ErrorBundle`.1733// emitting remaining errors to an `ErrorBundle`.
1743const UnpackResult = struct {1734const UnpackResult = struct {
1744 allocator: std.mem.Allocator,1735 errors: []Error = undefined,
1745 errors: std.ArrayListUnmanaged(Error) = .{},1736 errors_count: usize = 0,
1746 root_error_message: []const u8 = "",1737 root_error_message: []const u8 = "",
17471738
1748 // A non empty value means that the package contents are inside a1739 // A non empty value means that the package contents are inside a
...@@ -1772,97 +1763,82 @@ const UnpackResult = struct {...@@ -1772,97 +1763,82 @@ const UnpackResult = struct {
1772 };1763 };
1773 return !filter.includePath(file_name);1764 return !filter.includePath(file_name);
1774 }1765 }
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 }
1790 };1766 };
17911767
1792 fn init(allocator: std.mem.Allocator) UnpackResult {1768 fn allocErrors(self: *UnpackResult, arena: std.mem.Allocator, n: usize, root_error_message: []const u8) !void {
1793 return .{ .allocator = allocator };1769 self.root_error_message = try arena.dupe(u8, root_error_message);
1794 }1770 self.errors = try arena.alloc(UnpackResult.Error, n);
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;
1804 }1771 }
18051772
1806 fn hasErrors(self: *UnpackResult) bool {1773 fn hasErrors(self: *UnpackResult) bool {
1807 return self.errors.items.len > 0;1774 return self.errors_count > 0;
1808 }1775 }
18091776
1810 fn unableToCreateFile(self: *UnpackResult, file_name: []const u8, err: anyerror) !void {1777 fn unableToCreateFile(self: *UnpackResult, file_name: []const u8, err: anyerror) void {
1811 try self.errors.append(self.allocator, .{ .unable_to_create_file = .{1778 self.errors[self.errors_count] = .{ .unable_to_create_file = .{
1812 .code = err,1779 .code = err,
1813 .file_name = try self.allocator.dupe(u8, file_name),1780 .file_name = file_name,
1814 } });1781 } };
1782 self.errors_count += 1;
1815 }1783 }
18161784
1817 fn unableToCreateSymLink(self: *UnpackResult, file_name: []const u8, link_name: []const u8, err: anyerror) !void {1785 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 = .{1786 self.errors[self.errors_count] = .{ .unable_to_create_sym_link = .{
1819 .code = err,1787 .code = err,
1820 .file_name = try self.allocator.dupe(u8, file_name),1788 .file_name = file_name,
1821 .link_name = try self.allocator.dupe(u8, link_name),1789 .link_name = link_name,
1822 } });1790 } };
1791 self.errors_count += 1;
1823 }1792 }
18241793
1825 fn unsupportedFileType(self: *UnpackResult, file_name: []const u8, file_type: u8) !void {1794 fn unsupportedFileType(self: *UnpackResult, file_name: []const u8, file_type: u8) void {
1826 try self.errors.append(self.allocator, .{ .unsupported_file_type = .{1795 self.errors[self.errors_count] = .{ .unsupported_file_type = .{
1827 .file_name = try self.allocator.dupe(u8, file_name),1796 .file_name = file_name,
1828 .file_type = file_type,1797 .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 }
1830 }1809 }
18311810
1832 // Filter errors by manifest inclusion rules.1811 // Filter errors by manifest inclusion rules.
1833 fn filterErrors(self: *UnpackResult, filter: Filter) !void {1812 fn filterErrors(self: *UnpackResult, filter: Filter) void {
1834 var i = self.errors.items.len;1813 var i = self.errors_count;
1835 while (i > 0) {1814 while (i > 0) {
1836 i -= 1;1815 i -= 1;
1837 const item = self.errors.items[i];1816 if (self.errors[i].excluded(filter)) {
1838 if (item.excluded(filter)) {1817 self.errors_count -= 1;
1839 _ = self.errors.swapRemove(i);1818 const tmp = self.errors[i];
1840 item.free(self.allocator);1819 self.errors[i] = self.errors[self.errors_count];
1820 self.errors[self.errors_count] = tmp;
1841 }1821 }
1842 }1822 }
1843 }1823 }
18441824
1845 fn rootErrorMessage(self: *UnpackResult, msg: []const u8) !void {
1846 self.root_error_message = try self.allocator.dupe(u8, msg);
1847 }
1848
1849 // Emmit errors to an `ErrorBundle`.1825 // Emmit errors to an `ErrorBundle`.
1850 fn bundleErrors(1826 fn bundleErrors(
1851 self: *UnpackResult,1827 self: *UnpackResult,
1852 eb: *ErrorBundle.Wip,1828 eb: *ErrorBundle.Wip,
1853 src_loc: ErrorBundle.SourceLocationIndex,1829 src_loc: ErrorBundle.SourceLocationIndex,
1854 ) !void {1830 ) !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)
1856 return;1832 return;
18571833
1858 const notes_len: u32 = @intCast(self.errors.items.len);1834 const notes_len: u32 = @intCast(self.errors_count);
1859 try eb.addRootErrorMessage(.{1835 try eb.addRootErrorMessage(.{
1860 .msg = try eb.addString(self.root_error_message),1836 .msg = try eb.addString(self.root_error_message),
1861 .src_loc = src_loc,1837 .src_loc = src_loc,
1862 .notes_len = notes_len,1838 .notes_len = notes_len,
1863 });1839 });
1864 const notes_start = try eb.reserveNotes(notes_len);1840 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| {
1866 switch (item) {1842 switch (item) {
1867 .unable_to_create_sym_link => |info| {1843 .unable_to_create_sym_link => |info| {
1868 eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{1844 eb.extra.items[note_i] = @intFromEnum(try eb.addErrorMessage(.{
...@@ -1888,6 +1864,36 @@ const UnpackResult = struct {...@@ -1888,6 +1864,36 @@ const UnpackResult = struct {
1888 }1864 }
1889 }1865 }
1890 }1866 }
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 }
1891};1897};
18921898
1893test "tarball with duplicate paths" {1899test "tarball with duplicate paths" {