From 8afe6210e95dd4608584535e8aed882285dc2078 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 7 Aug 2021 10:23:30 +0200 Subject: [PATCH 1/4] macho: add TAPI v3 parser This turns out needed to correctly support version back to macOS 10.14 (Mojave) --- src/link/MachO/Dylib.zig | 137 +++++++++++++++++++++++++------ src/link/tapi.zig | 168 +++++++++++++++++++++++++++------------ 2 files changed, 229 insertions(+), 76 deletions(-) diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 64be1fd2fa..d3de8827d7 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -10,9 +10,10 @@ const math = std.math; const mem = std.mem; const fat = @import("fat.zig"); const commands = @import("commands.zig"); +const tapi = @import("../tapi.zig"); const Allocator = mem.Allocator; -const LibStub = @import("../tapi.zig").LibStub; +const LibStub = tapi.LibStub; const LoadCommand = commands.LoadCommand; const MachO = @import("../MachO.zig"); @@ -315,9 +316,9 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void { } } -fn hasTarget(targets: []const []const u8, target: []const u8) bool { - for (targets) |t| { - if (mem.eql(u8, t, target)) return true; +fn hasValue(stack: []const []const u8, needle: []const u8) bool { + for (stack) |v| { + if (mem.eql(u8, v, needle)) return true; } return false; } @@ -334,6 +335,78 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8 } } +fn hasArch(archs: []const []const u8, arch: []const u8) bool { + for (archs) |x| { + if (mem.eql(u8, x, arch)) return true; + } + return false; +} + +fn parseFromStubV3(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { + var umbrella_libs = std.StringHashMap(void).init(allocator); + defer umbrella_libs.deinit(); + + const arch_string = @tagName(target.cpu.arch); + + for (lib_stub.inner) |elem, stub_index| { + const stub = elem.v3; + if (!hasArch(stub.archs, arch_string)) continue; + + if (stub_index > 0) { + // TODO I thought that we could switch on presence of `parent-umbrella` map; + // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib` + // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps? + try umbrella_libs.put(stub.install_name, .{}); + } + + if (stub.exports) |exports| { + for (exports) |exp| { + if (!hasArch(exp.archs, arch_string)) continue; + + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + if (self.symbols.contains(sym_name)) continue; + try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); + } + } + + if (exp.re_exports) |re_exports| { + for (re_exports) |reexp| { + if (self.symbols.contains(reexp)) continue; + try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, reexp), {}); + } + } + } + } + } + + log.debug("{s}", .{lib_stub.inner[0].installName()}); + + // // TODO track which libs were already parsed in different steps + // for (lib_stub.inner) |elem| { + // const stub = elem.v3; + // if (!archMatches(stub.archs, arch_string)) continue; + + // if (stub.reexported_libraries) |reexports| { + // for (reexports) |reexp| { + // if (!matcher.matches(reexp.targets)) continue; + + // for (reexp.libraries) |lib| { + // if (umbrella_libs.contains(lib)) { + // log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name }); + // continue; + // } + + // log.debug(" | {s}", .{lib}); + + // const dep_id = try Id.default(allocator, lib); + // try self.dependent_libs.append(allocator, dep_id); + // } + // } + // } + // } +} + fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { const arch = switch (target.cpu.arch) { .aarch64 => "arm64", @@ -380,6 +453,13 @@ const TargetMatcher = struct { self.target_strings.deinit(self.allocator); } + fn hasTarget(targets: []const []const u8, target: []const u8) bool { + for (targets) |x| { + if (mem.eql(u8, x, target)) return true; + } + return false; + } + fn matches(self: TargetMatcher, targets: []const []const u8) bool { for (self.target_strings.items) |t| { if (hasTarget(targets, t)) return true; @@ -388,29 +468,15 @@ const TargetMatcher = struct { } }; -pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { - if (lib_stub.inner.len == 0) return error.EmptyStubFile; - - log.debug("parsing shared library from stub '{s}'", .{self.name}); - - const umbrella_lib = lib_stub.inner[0]; - - var id = try Id.default(allocator, umbrella_lib.install_name); - if (umbrella_lib.current_version) |version| { - try id.parseCurrentVersion(version); - } - if (umbrella_lib.compatibility_version) |version| { - try id.parseCompatibilityVersion(version); - } - self.id = id; - +fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { var matcher = try TargetMatcher.init(allocator, target); defer matcher.deinit(); var umbrella_libs = std.StringHashMap(void).init(allocator); defer umbrella_libs.deinit(); - for (lib_stub.inner) |stub, stub_index| { + for (lib_stub.inner) |elem, stub_index| { + const stub = elem.v4; if (!matcher.matches(stub.targets)) continue; if (stub_index > 0) { @@ -465,10 +531,11 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li } } - log.debug("{s}", .{umbrella_lib.install_name}); + log.debug("{s}", .{lib_stub.inner[0].installName()}); // TODO track which libs were already parsed in different steps - for (lib_stub.inner) |stub| { + for (lib_stub.inner) |elem| { + const stub = elem.v4; if (!matcher.matches(stub.targets)) continue; if (stub.reexported_libraries) |reexports| { @@ -477,7 +544,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li for (reexp.libraries) |lib| { if (umbrella_libs.contains(lib)) { - log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name }); + log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() }); continue; } @@ -491,6 +558,28 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li } } +pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { + if (lib_stub.inner.len == 0) return error.EmptyStubFile; + + log.debug("parsing shared library from stub '{s}'", .{self.name}); + + const umbrella_lib = lib_stub.inner[0]; + + var id = try Id.default(allocator, umbrella_lib.installName()); + if (umbrella_lib.currentVersion()) |version| { + try id.parseCurrentVersion(version); + } + if (umbrella_lib.compatibilityVersion()) |version| { + try id.parseCompatibilityVersion(version); + } + self.id = id; + + switch (umbrella_lib) { + .v3 => try self.parseFromStubV3(allocator, target, lib_stub), + .v4 => try self.parseFromStubV4(allocator, target, lib_stub), + } +} + pub fn parseDependentLibs( self: *Dylib, allocator: *Allocator, diff --git a/src/link/tapi.zig b/src/link/tapi.zig index 35193b0eec..aa953cc76a 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -6,6 +6,88 @@ const log = std.log.scoped(.tapi); const Allocator = mem.Allocator; const Yaml = @import("tapi/yaml.zig").Yaml; +const VersionField = union(enum) { + string: []const u8, + float: f64, + int: u64, +}; + +pub const TbdV3 = struct { + archs: []const []const u8, + uuids: []const []const u8, + platform: []const u8, + install_name: []const u8, + current_version: ?VersionField, + compatibility_version: ?VersionField, + objc_constraint: []const u8, + exports: ?[]const struct { + archs: []const []const u8, + re_exports: ?[]const []const u8, + symbols: ?[]const []const u8, + }, +}; + +pub const TbdV4 = struct { + tbd_version: u3, + targets: []const []const u8, + uuids: []const struct { + target: []const u8, + value: []const u8, + }, + install_name: []const u8, + current_version: ?VersionField, + compatibility_version: ?VersionField, + reexported_libraries: ?[]const struct { + targets: []const []const u8, + libraries: []const []const u8, + }, + parent_umbrella: ?[]const struct { + targets: []const []const u8, + umbrella: []const u8, + }, + exports: ?[]const struct { + targets: []const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, + }, + reexports: ?[]const struct { + targets: []const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, + }, + allowable_clients: ?[]const struct { + targets: []const []const u8, + clients: []const []const u8, + }, + objc_classes: ?[]const []const u8, +}; + +pub const Tbd = union(enum) { + v3: TbdV3, + v4: TbdV4, + + pub fn currentVersion(self: Tbd) ?VersionField { + return switch (self) { + .v3 => |v3| v3.current_version, + .v4 => |v4| v4.current_version, + }; + } + + pub fn compatibilityVersion(self: Tbd) ?VersionField { + return switch (self) { + .v3 => |v3| v3.compatibility_version, + .v4 => |v4| v4.compatibility_version, + }; + } + + pub fn installName(self: Tbd) []const u8 { + return switch (self) { + .v3 => |v3| v3.install_name, + .v4 => |v4| v4.install_name, + }; + } +}; + pub const LibStub = struct { /// Underlying memory for stub's contents. yaml: Yaml, @@ -13,49 +95,6 @@ pub const LibStub = struct { /// Typed contents of the tbd file. inner: []Tbd, - const Tbd = struct { - tbd_version: u3, - targets: []const []const u8, - uuids: []const struct { - target: []const u8, - value: []const u8, - }, - install_name: []const u8, - current_version: ?union(enum) { - string: []const u8, - float: f64, - int: u64, - }, - compatibility_version: ?union(enum) { - string: []const u8, - float: f64, - int: u64, - }, - reexported_libraries: ?[]const struct { - targets: []const []const u8, - libraries: []const []const u8, - }, - parent_umbrella: ?[]const struct { - targets: []const []const u8, - umbrella: []const u8, - }, - exports: ?[]const struct { - targets: []const []const u8, - symbols: ?[]const []const u8, - objc_classes: ?[]const []const u8, - }, - reexports: ?[]const struct { - targets: []const []const u8, - symbols: ?[]const []const u8, - objc_classes: ?[]const []const u8, - }, - allowable_clients: ?[]const struct { - targets: []const []const u8, - clients: []const []const u8, - }, - objc_classes: ?[]const []const u8, - }; - pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub { const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32)); defer allocator.free(source); @@ -65,16 +104,41 @@ pub const LibStub = struct { .inner = undefined, }; - lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: { - switch (err) { - error.TypeMismatch => { - // TODO clean this up. - var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); - out[0] = try lib_stub.yaml.parse(Tbd); - break :blk out; - }, - else => |e| return e, + // TODO clean this up. + lib_stub.inner = blk: { + err: { + const inner = lib_stub.yaml.parse([]TbdV4) catch |err| switch (err) { + error.TypeMismatch => break :err, + else => |e| return e, + }; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, inner.len); + for (inner) |doc, i| { + out[i] = .{ .v4 = doc }; + } + break :blk out; } + + err: { + const inner = lib_stub.yaml.parse(TbdV4) catch |err| switch (err) { + error.TypeMismatch => break :err, + else => |e| return e, + }; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); + out[0] = .{ .v4 = inner }; + break :blk out; + } + + err: { + const inner = lib_stub.yaml.parse(TbdV3) catch |err| switch (err) { + error.TypeMismatch => break :err, + else => |e| return e, + }; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); + out[0] = .{ .v3 = inner }; + break :blk out; + } + + return error.TypeMismatch; }; return lib_stub; From d95e8bc5f8c48752aed73d077e2f9c87293a6617 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 10 Aug 2021 18:43:32 +0200 Subject: [PATCH 2/4] macho: simplify versioning logic for TAPI --- src/link/MachO/Dylib.zig | 47 +++++++++++++++------------------------- src/link/tapi.zig | 22 ++++++++----------- src/link/tapi/yaml.zig | 2 +- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index d3de8827d7..141e39f651 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -348,6 +348,8 @@ fn parseFromStubV3(self: *Dylib, allocator: *Allocator, target: std.Target, lib_ const arch_string = @tagName(target.cpu.arch); + log.debug("{s}", .{lib_stub.inner[0].installName()}); + for (lib_stub.inner) |elem, stub_index| { const stub = elem.v3; if (!hasArch(stub.archs, arch_string)) continue; @@ -370,41 +372,28 @@ fn parseFromStubV3(self: *Dylib, allocator: *Allocator, target: std.Target, lib_ } } + if (exp.objc_classes) |objc_classes| { + for (objc_classes) |class_name| { + try self.addObjCClassSymbols(allocator, class_name); + } + } + if (exp.re_exports) |re_exports| { - for (re_exports) |reexp| { - if (self.symbols.contains(reexp)) continue; - try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, reexp), {}); + for (re_exports) |lib| { + if (umbrella_libs.contains(lib)) { + log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() }); + continue; + } + + log.debug(" | {s}", .{lib}); + + const dep_id = try Id.default(allocator, lib); + try self.dependent_libs.append(allocator, dep_id); } } } } } - - log.debug("{s}", .{lib_stub.inner[0].installName()}); - - // // TODO track which libs were already parsed in different steps - // for (lib_stub.inner) |elem| { - // const stub = elem.v3; - // if (!archMatches(stub.archs, arch_string)) continue; - - // if (stub.reexported_libraries) |reexports| { - // for (reexports) |reexp| { - // if (!matcher.matches(reexp.targets)) continue; - - // for (reexp.libraries) |lib| { - // if (umbrella_libs.contains(lib)) { - // log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name }); - // continue; - // } - - // log.debug(" | {s}", .{lib}); - - // const dep_id = try Id.default(allocator, lib); - // try self.dependent_libs.append(allocator, dep_id); - // } - // } - // } - // } } fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { diff --git a/src/link/tapi.zig b/src/link/tapi.zig index aa953cc76a..e412651c08 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -19,11 +19,12 @@ pub const TbdV3 = struct { install_name: []const u8, current_version: ?VersionField, compatibility_version: ?VersionField, - objc_constraint: []const u8, + objc_constraint: ?[]const u8, exports: ?[]const struct { archs: []const []const u8, re_exports: ?[]const []const u8, symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, }, }; @@ -107,10 +108,8 @@ pub const LibStub = struct { // TODO clean this up. lib_stub.inner = blk: { err: { - const inner = lib_stub.yaml.parse([]TbdV4) catch |err| switch (err) { - error.TypeMismatch => break :err, - else => |e| return e, - }; + log.debug("trying to parse as []TbdV4", .{}); + const inner = lib_stub.yaml.parse([]TbdV4) catch break :err; var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, inner.len); for (inner) |doc, i| { out[i] = .{ .v4 = doc }; @@ -119,25 +118,22 @@ pub const LibStub = struct { } err: { - const inner = lib_stub.yaml.parse(TbdV4) catch |err| switch (err) { - error.TypeMismatch => break :err, - else => |e| return e, - }; + log.debug("trying to parse as TbdV4", .{}); + const inner = lib_stub.yaml.parse(TbdV4) catch break :err; var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); out[0] = .{ .v4 = inner }; break :blk out; } err: { - const inner = lib_stub.yaml.parse(TbdV3) catch |err| switch (err) { - error.TypeMismatch => break :err, - else => |e| return e, - }; + log.debug("trying to parse as TbdV3", .{}); + const inner = lib_stub.yaml.parse(TbdV3) catch break :err; var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); out[0] = .{ .v3 = inner }; break :blk out; } + // TODO this is clunky. Perhaps an optional would be better here? return error.TypeMismatch; }; diff --git a/src/link/tapi/yaml.zig b/src/link/tapi/yaml.zig index b58df7609f..25d2c73e82 100644 --- a/src/link/tapi/yaml.zig +++ b/src/link/tapi/yaml.zig @@ -371,7 +371,7 @@ pub const Yaml = struct { } const unwrapped = value orelse { - log.err("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) }); + log.debug("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) }); return error.StructFieldMissing; }; @field(parsed, field.name) = try self.parseValue(field.field_type, unwrapped); From 16bb5c05f15e1ec4cc1616c5c33e56f67ea0763e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 11 Aug 2021 10:52:21 +0200 Subject: [PATCH 3/4] macho: refactor stub parsing in Dylib --- src/link/MachO/Dylib.zig | 349 ++++++++++++++++++--------------------- src/link/tapi.zig | 5 +- 2 files changed, 163 insertions(+), 191 deletions(-) diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 141e39f651..bdf3cfe6bb 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -180,9 +180,9 @@ pub fn createAndParseFromPath( if (opts.id) |id| { if (dylib.id.?.current_version < id.compatibility_version) { log.warn("found dylib is incompatible with the required minimum version", .{}); - log.warn(" | dylib: {s}", .{id.name}); - log.warn(" | required minimum version: {}", .{id.compatibility_version}); - log.warn(" | dylib version: {}", .{dylib.id.?.current_version}); + log.warn(" dylib: {s}", .{id.name}); + log.warn(" required minimum version: {}", .{id.compatibility_version}); + log.warn(" dylib version: {}", .{dylib.id.?.current_version}); // TODO maybe this should be an error and facilitate auto-cleanup? dylib.deinit(allocator); @@ -316,14 +316,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void { } } -fn hasValue(stack: []const []const u8, needle: []const u8) bool { - for (stack) |v| { - if (mem.eql(u8, v, needle)) return true; - } - return false; -} - -fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { +fn addObjCClassSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { const expanded = &[_][]const u8{ try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}), try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}), @@ -335,91 +328,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8 } } -fn hasArch(archs: []const []const u8, arch: []const u8) bool { - for (archs) |x| { - if (mem.eql(u8, x, arch)) return true; - } - return false; -} - -fn parseFromStubV3(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { - var umbrella_libs = std.StringHashMap(void).init(allocator); - defer umbrella_libs.deinit(); - - const arch_string = @tagName(target.cpu.arch); - - log.debug("{s}", .{lib_stub.inner[0].installName()}); - - for (lib_stub.inner) |elem, stub_index| { - const stub = elem.v3; - if (!hasArch(stub.archs, arch_string)) continue; - - if (stub_index > 0) { - // TODO I thought that we could switch on presence of `parent-umbrella` map; - // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib` - // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps? - try umbrella_libs.put(stub.install_name, .{}); - } - - if (stub.exports) |exports| { - for (exports) |exp| { - if (!hasArch(exp.archs, arch_string)) continue; - - if (exp.symbols) |symbols| { - for (symbols) |sym_name| { - if (self.symbols.contains(sym_name)) continue; - try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); - } - } - - if (exp.objc_classes) |objc_classes| { - for (objc_classes) |class_name| { - try self.addObjCClassSymbols(allocator, class_name); - } - } - - if (exp.re_exports) |re_exports| { - for (re_exports) |lib| { - if (umbrella_libs.contains(lib)) { - log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() }); - continue; - } - - log.debug(" | {s}", .{lib}); - - const dep_id = try Id.default(allocator, lib); - try self.dependent_libs.append(allocator, dep_id); - } - } - } - } - } -} - -fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { - const arch = switch (target.cpu.arch) { - .aarch64 => "arm64", - .x86_64 => "x86_64", - else => unreachable, - }; - const os = @tagName(target.os.tag); - const abi: ?[]const u8 = switch (target.abi) { - .gnu => null, - .simulator => "simulator", - else => unreachable, - }; - if (abi) |x| { - return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x }); - } - return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os }); +fn addSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { + if (self.symbols.contains(sym_name)) return; + try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); } const TargetMatcher = struct { allocator: *Allocator, + target: std.Target, target_strings: std.ArrayListUnmanaged([]const u8) = .{}, fn init(allocator: *Allocator, target: std.Target) !TargetMatcher { - var self = TargetMatcher{ .allocator = allocator }; + var self = TargetMatcher{ + .allocator = allocator, + .target = target, + }; try self.target_strings.append(allocator, try targetToAppleString(allocator, target)); if (target.abi == .simulator) { @@ -442,111 +365,43 @@ const TargetMatcher = struct { self.target_strings.deinit(self.allocator); } - fn hasTarget(targets: []const []const u8, target: []const u8) bool { - for (targets) |x| { - if (mem.eql(u8, x, target)) return true; + fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { + const arch = switch (target.cpu.arch) { + .aarch64 => "arm64", + .x86_64 => "x86_64", + else => unreachable, + }; + const os = @tagName(target.os.tag); + const abi: ?[]const u8 = switch (target.abi) { + .gnu => null, + .simulator => "simulator", + else => unreachable, + }; + if (abi) |x| { + return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x }); + } + return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os }); + } + + fn hasValue(stack: []const []const u8, needle: []const u8) bool { + for (stack) |v| { + if (mem.eql(u8, v, needle)) return true; } return false; } - fn matches(self: TargetMatcher, targets: []const []const u8) bool { + fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool { for (self.target_strings.items) |t| { - if (hasTarget(targets, t)) return true; + if (hasValue(targets, t)) return true; } return false; } + + fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool { + return hasValue(archs, @tagName(self.target.cpu.arch)); + } }; -fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { - var matcher = try TargetMatcher.init(allocator, target); - defer matcher.deinit(); - - var umbrella_libs = std.StringHashMap(void).init(allocator); - defer umbrella_libs.deinit(); - - for (lib_stub.inner) |elem, stub_index| { - const stub = elem.v4; - if (!matcher.matches(stub.targets)) continue; - - if (stub_index > 0) { - // TODO I thought that we could switch on presence of `parent-umbrella` map; - // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib` - // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps? - try umbrella_libs.put(stub.install_name, .{}); - } - - if (stub.exports) |exports| { - for (exports) |exp| { - if (!matcher.matches(exp.targets)) continue; - - if (exp.symbols) |symbols| { - for (symbols) |sym_name| { - if (self.symbols.contains(sym_name)) continue; - try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); - } - } - - if (exp.objc_classes) |classes| { - for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); - } - } - } - } - - if (stub.reexports) |reexports| { - for (reexports) |reexp| { - if (!matcher.matches(reexp.targets)) continue; - - if (reexp.symbols) |symbols| { - for (symbols) |sym_name| { - if (self.symbols.contains(sym_name)) continue; - try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); - } - } - - if (reexp.objc_classes) |classes| { - for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); - } - } - } - } - - if (stub.objc_classes) |classes| { - for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); - } - } - } - - log.debug("{s}", .{lib_stub.inner[0].installName()}); - - // TODO track which libs were already parsed in different steps - for (lib_stub.inner) |elem| { - const stub = elem.v4; - if (!matcher.matches(stub.targets)) continue; - - if (stub.reexported_libraries) |reexports| { - for (reexports) |reexp| { - if (!matcher.matches(reexp.targets)) continue; - - for (reexp.libraries) |lib| { - if (umbrella_libs.contains(lib)) { - log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() }); - continue; - } - - log.debug(" | {s}", .{lib}); - - const dep_id = try Id.default(allocator, lib); - try self.dependent_libs.append(allocator, dep_id); - } - } - } - } -} - pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { if (lib_stub.inner.len == 0) return error.EmptyStubFile; @@ -563,9 +418,127 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li } self.id = id; - switch (umbrella_lib) { - .v3 => try self.parseFromStubV3(allocator, target, lib_stub), - .v4 => try self.parseFromStubV4(allocator, target, lib_stub), + var umbrella_libs = std.StringHashMap(void).init(allocator); + defer umbrella_libs.deinit(); + + log.debug("found umbrella lib '{s}'", .{umbrella_lib.installName()}); + + var matcher = try TargetMatcher.init(allocator, target); + defer matcher.deinit(); + + for (lib_stub.inner) |elem, stub_index| { + const is_match = switch (elem) { + .v3 => |stub| matcher.matchesArch(stub.archs), + .v4 => |stub| matcher.matchesTarget(stub.targets), + }; + if (!is_match) continue; + + if (stub_index > 0) { + // TODO I thought that we could switch on presence of `parent-umbrella` map; + // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib` + // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps? + try umbrella_libs.put(elem.installName(), .{}); + } + + switch (elem) { + .v3 => |stub| { + if (stub.exports) |exports| { + for (exports) |exp| { + if (!matcher.matchesArch(exp.archs)) continue; + + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } + + if (exp.objc_classes) |objc_classes| { + for (objc_classes) |class_name| { + try self.addObjCClassSymbol(allocator, class_name); + } + } + + // TODO track which libs were already parsed in different steps + if (exp.re_exports) |re_exports| { + for (re_exports) |lib| { + if (umbrella_libs.contains(lib)) continue; + + log.debug(" (found re-export '{s}')", .{lib}); + + const dep_id = try Id.default(allocator, lib); + try self.dependent_libs.append(allocator, dep_id); + } + } + } + } + }, + .v4 => |stub| { + if (stub.exports) |exports| { + for (exports) |exp| { + if (!matcher.matchesTarget(exp.targets)) continue; + + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } + + if (exp.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbol(allocator, sym_name); + } + } + } + } + + if (stub.reexports) |reexports| { + for (reexports) |reexp| { + if (!matcher.matchesTarget(reexp.targets)) continue; + + if (reexp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } + + if (reexp.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbol(allocator, sym_name); + } + } + } + } + + if (stub.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbol(allocator, sym_name); + } + } + }, + } + } + + // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include + // re-exports directly in the stub file. + for (lib_stub.inner) |elem, stub_index| { + if (elem == .v3) break; + const stub = elem.v4; + + // TODO track which libs were already parsed in different steps + if (stub.reexported_libraries) |reexports| { + for (reexports) |reexp| { + if (!matcher.matchesTarget(reexp.targets)) continue; + + for (reexp.libraries) |lib| { + if (umbrella_libs.contains(lib)) continue; + + log.debug(" (found re-export '{s}')", .{lib}); + + const dep_id = try Id.default(allocator, lib); + try self.dependent_libs.append(allocator, dep_id); + } + } + } } } @@ -619,7 +592,7 @@ pub fn parseDependentLibs( continue :outer; } else { - log.warn("unable to resolve dependency {s}", .{id.name}); + log.debug("unable to resolve dependency {s}", .{id.name}); } } } diff --git a/src/link/tapi.zig b/src/link/tapi.zig index e412651c08..d10942ea02 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -105,7 +105,7 @@ pub const LibStub = struct { .inner = undefined, }; - // TODO clean this up. + // TODO revisit this logic in the hope of simplifying it. lib_stub.inner = blk: { err: { log.debug("trying to parse as []TbdV4", .{}); @@ -133,8 +133,7 @@ pub const LibStub = struct { break :blk out; } - // TODO this is clunky. Perhaps an optional would be better here? - return error.TypeMismatch; + return error.NotLibStub; }; return lib_stub; From 5d548cc65125cc33ebf4840fb73a97030b1d0505 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 11 Aug 2021 17:09:13 +0200 Subject: [PATCH 4/4] macho: move parsing logic for Object, Archive and Dylib into MachO This way, the functionality is better segregated, and we finally do not unnecessarily reparse dynamic libraries that were already visited and parsed. --- src/link/MachO.zig | 174 +++++++++++++++++++++++++++++-------- src/link/MachO/Archive.zig | 26 ------ src/link/MachO/Dylib.zig | 119 ++++--------------------- src/link/MachO/Object.zig | 26 ------ 4 files changed, 155 insertions(+), 190 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d23b696cb8..6fbe3bc150 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -31,6 +31,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig"); const Dylib = @import("MachO/Dylib.zig"); const File = link.File; const Object = @import("MachO/Object.zig"); +const LibStub = @import("tapi.zig").LibStub; const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; const LoadCommand = commands.LoadCommand; @@ -65,6 +66,7 @@ objects: std.ArrayListUnmanaged(Object) = .{}, archives: std.ArrayListUnmanaged(Archive) = .{}, dylibs: std.ArrayListUnmanaged(Dylib) = .{}, +dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{}, load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, @@ -994,6 +996,133 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void { } } +fn parseObject(self: *MachO, path: []const u8) !bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var object = Object{ + .name = name, + .file = file, + }; + + object.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotObject => { + object.deinit(self.base.allocator); + return false; + }, + else => |e| return e, + }; + + try self.objects.append(self.base.allocator, object); + + return true; +} + +fn parseArchive(self: *MachO, path: []const u8) !bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var archive = Archive{ + .name = name, + .file = file, + }; + + archive.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotArchive => { + archive.deinit(self.base.allocator); + return false; + }, + else => |e| return e, + }; + + try self.archives.append(self.base.allocator, archive); + + return true; +} + +const ParseDylibError = error{ + OutOfMemory, + EmptyStubFile, + MismatchedCpuArchitecture, + UnsupportedCpuArchitecture, +} || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError; + +const DylibCreateOpts = struct { + syslibroot: ?[]const u8 = null, + id: ?Dylib.Id = null, + is_dependent: bool = false, +}; + +pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var dylib = Dylib{ + .name = name, + .file = file, + }; + + dylib.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotDylib => { + try file.seekTo(0); + + var lib_stub = LibStub.loadFromFile(self.base.allocator, file) catch { + dylib.deinit(self.base.allocator); + return false; + }; + defer lib_stub.deinit(); + + try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub); + }, + else => |e| return e, + }; + + if (opts.id) |id| { + if (dylib.id.?.current_version < id.compatibility_version) { + log.warn("found dylib is incompatible with the required minimum version", .{}); + log.warn(" dylib: {s}", .{id.name}); + log.warn(" required minimum version: {}", .{id.compatibility_version}); + log.warn(" dylib version: {}", .{dylib.id.?.current_version}); + + // TODO maybe this should be an error and facilitate auto-cleanup? + dylib.deinit(self.base.allocator); + return false; + } + } + + const dylib_id = @intCast(u16, self.dylibs.items.len); + try self.dylibs.append(self.base.allocator, dylib); + try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id); + + if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) { + try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); + } + + // TODO this should not be performed if the user specifies `-flat_namespace` flag. + // See ld64 manpages. + try dylib.parseDependentLibs(self, opts.syslibroot); + + return true; +} + fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void { for (files) |file_name| { const full_path = full_path: { @@ -1003,28 +1132,11 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const }; defer self.base.allocator.free(full_path); - if (try Object.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |object| { - try self.objects.append(self.base.allocator, object); - continue; - } - - if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |archive| { - try self.archives.append(self.base.allocator, archive); - continue; - } - - if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path, .{ + if (try self.parseObject(full_path)) continue; + if (try self.parseArchive(full_path)) continue; + if (try self.parseDylib(full_path, .{ .syslibroot = syslibroot, - })) |dylibs| { - defer self.base.allocator.free(dylibs); - const dylib_id = @intCast(u16, self.dylibs.items.len); - try self.dylibs.appendSlice(self.base.allocator, dylibs); - // We always have to add the dylib that was on the linker line. - if (!self.referenced_dylibs.contains(dylib_id)) { - try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); - } - continue; - } + })) continue; log.warn("unknown filetype for positional input file: '{s}'", .{file_name}); } @@ -1032,23 +1144,10 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void { for (libs) |lib| { - if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, lib, .{ + if (try self.parseDylib(lib, .{ .syslibroot = syslibroot, - })) |dylibs| { - defer self.base.allocator.free(dylibs); - const dylib_id = @intCast(u16, self.dylibs.items.len); - try self.dylibs.appendSlice(self.base.allocator, dylibs); - // We always have to add the dylib that was on the linker line. - if (!self.referenced_dylibs.contains(dylib_id)) { - try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); - } - continue; - } - - if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, lib)) |archive| { - try self.archives.append(self.base.allocator, archive); - continue; - } + })) continue; + if (try self.parseArchive(lib)) continue; log.warn("unknown filetype for a library: '{s}'", .{lib}); } @@ -3360,6 +3459,7 @@ pub fn deinit(self: *MachO) void { dylib.deinit(self.base.allocator); } self.dylibs.deinit(self.base.allocator); + self.dylibs_map.deinit(self.base.allocator); self.referenced_dylibs.deinit(self.base.allocator); for (self.load_commands.items) |*lc| { diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 3b01233e1f..1f370088f0 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -103,32 +103,6 @@ pub fn deinit(self: *Archive, allocator: *Allocator) void { allocator.free(self.name); } -pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Archive { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var archive = Archive{ - .name = name, - .file = file, - }; - - archive.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotArchive => { - archive.deinit(allocator); - return null; - }, - else => |e| return e, - }; - - return archive; -} - pub fn parse(self: *Archive, allocator: *Allocator, target: std.Target) !void { const reader = self.file.reader(); self.library_offset = try fat.getLibraryOffset(reader, target); diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index bdf3cfe6bb..05d44559ce 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -10,10 +10,9 @@ const math = std.math; const mem = std.mem; const fat = @import("fat.zig"); const commands = @import("commands.zig"); -const tapi = @import("../tapi.zig"); const Allocator = mem.Allocator; -const LibStub = tapi.LibStub; +const LibStub = @import("../tapi.zig").LibStub; const LoadCommand = commands.LoadCommand; const MachO = @import("../MachO.zig"); @@ -74,7 +73,7 @@ pub const Id = struct { allocator.free(id.name); } - const ParseError = fmt.ParseIntError || fmt.BufPrintError; + pub const ParseError = fmt.ParseIntError || fmt.BufPrintError; pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void { id.current_version = try parseVersion(version); @@ -110,7 +109,7 @@ pub const Id = struct { var count: u4 = 0; while (split.next()) |value| { if (count > 2) { - log.warn("malformed version field: {s}", .{string}); + log.debug("malformed version field: {s}", .{string}); return 0x10000; } values[count] = value; @@ -129,78 +128,6 @@ pub const Id = struct { } }; -pub const Error = error{ - OutOfMemory, - EmptyStubFile, - MismatchedCpuArchitecture, - UnsupportedCpuArchitecture, -} || fs.File.OpenError || std.os.PReadError || Id.ParseError; - -pub const CreateOpts = struct { - syslibroot: ?[]const u8 = null, - id: ?Id = null, - target: ?std.Target = null, -}; - -pub fn createAndParseFromPath( - allocator: *Allocator, - target: std.Target, - path: []const u8, - opts: CreateOpts, -) Error!?[]Dylib { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var dylib = Dylib{ - .name = name, - .file = file, - }; - - dylib.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotDylib => { - try file.seekTo(0); - - var lib_stub = LibStub.loadFromFile(allocator, file) catch { - dylib.deinit(allocator); - return null; - }; - defer lib_stub.deinit(); - - try dylib.parseFromStub(allocator, target, lib_stub); - }, - else => |e| return e, - }; - - if (opts.id) |id| { - if (dylib.id.?.current_version < id.compatibility_version) { - log.warn("found dylib is incompatible with the required minimum version", .{}); - log.warn(" dylib: {s}", .{id.name}); - log.warn(" required minimum version: {}", .{id.compatibility_version}); - log.warn(" dylib version: {}", .{dylib.id.?.current_version}); - - // TODO maybe this should be an error and facilitate auto-cleanup? - dylib.deinit(allocator); - return null; - } - } - - var dylibs = std.ArrayList(Dylib).init(allocator); - defer dylibs.deinit(); - - try dylibs.append(dylib); - // TODO this should not be performed if the user specifies `-flat_namespace` flag. - // See ld64 manpages. - try dylib.parseDependentLibs(allocator, target, &dylibs, opts.syslibroot); - - return dylibs.toOwnedSlice(); -} - pub fn deinit(self: *Dylib, allocator: *Allocator) void { for (self.load_commands.items) |*lc| { lc.deinit(allocator); @@ -421,7 +348,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li var umbrella_libs = std.StringHashMap(void).init(allocator); defer umbrella_libs.deinit(); - log.debug("found umbrella lib '{s}'", .{umbrella_lib.installName()}); + log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); var matcher = try TargetMatcher.init(allocator, target); defer matcher.deinit(); @@ -520,7 +447,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include // re-exports directly in the stub file. - for (lib_stub.inner) |elem, stub_index| { + for (lib_stub.inner) |elem| { if (elem == .v3) break; const stub = elem.v4; @@ -544,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li pub fn parseDependentLibs( self: *Dylib, - allocator: *Allocator, - target: std.Target, - out: *std.ArrayList(Dylib), + macho_file: *MachO, syslibroot: ?[]const u8, ) !void { outer: for (self.dependent_libs.items) |id| { + if (macho_file.dylibs_map.contains(id.name)) continue :outer; + const has_ext = blk: { const basename = fs.path.basename(id.name); break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; @@ -561,36 +488,26 @@ pub fn parseDependentLibs( } else id.name; for (&[_][]const u8{ extension, ".tbd" }) |ext| { - const with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ + const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{ without_ext, ext, }); - defer allocator.free(with_ext); + defer macho_file.base.allocator.free(with_ext); const full_path = if (syslibroot) |root| - try fs.path.join(allocator, &.{ root, with_ext }) + try fs.path.join(macho_file.base.allocator, &.{ root, with_ext }) else with_ext; - defer if (syslibroot) |_| allocator.free(full_path); + defer if (syslibroot) |_| macho_file.base.allocator.free(full_path); log.debug("trying dependency at fully resolved path {s}", .{full_path}); - const dylibs = (try createAndParseFromPath( - allocator, - target, - full_path, - .{ - .id = id, - .syslibroot = syslibroot, - }, - )) orelse { - continue; - }; - defer allocator.free(dylibs); - - try out.appendSlice(dylibs); - - continue :outer; + const did_parse_successfully = try macho_file.parseDylib(full_path, .{ + .id = id, + .syslibroot = syslibroot, + .is_dependent = true, + }); + if (!did_parse_successfully) continue; } else { log.debug("unable to resolve dependency {s}", .{id.name}); } diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index c6aa2fb631..06c76b259d 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -153,32 +153,6 @@ pub fn deinit(self: *Object, allocator: *Allocator) void { } } -pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Object { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var object = Object{ - .name = name, - .file = file, - }; - - object.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotObject => { - object.deinit(allocator); - return null; - }, - else => |e| return e, - }; - - return object; -} - pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void { const reader = self.file.reader(); if (self.file_offset) |offset| {