diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cdeffe504..bdde42927e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -589,6 +589,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/Archive.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/Atom.zig" + "${CMAKE_SOURCE_DIR}/src/link/Elf/LdScript.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/LinkerDefined.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/Object.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf/SharedObject.zig" diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 49b95da7ea..f31df0dcba 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1383,8 +1383,15 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node // libc dep self.error_flags.missing_libc = false; if (self.base.options.link_libc) { - if (self.base.options.libc_installation != null) { - @panic("TODO explicit libc_installation"); + if (self.base.options.libc_installation) |lc| { + const flags = target_util.libcFullLinkFlags(target); + try system_libs.ensureUnusedCapacity(flags.len); + for (flags) |flag| { + const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so", .{ + lc.crt_dir.?, fs.path.sep, flag["-l".len..], + }); + system_libs.appendAssumeCapacity(.{ .path = lib_path }); + } } else if (target.isGnuLibC()) { try system_libs.ensureUnusedCapacity(glibc.libs.len + 1); for (glibc.libs) |lib| { @@ -1649,7 +1656,7 @@ const ParseError = error{ FileSystem, NotSupported, InvalidCharacter, -} || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError; +} || LdScript.Error || std.os.AccessError || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError; fn parsePositional( self: *Elf, @@ -1682,7 +1689,13 @@ fn parseLibrary( try self.parseArchive(in_file, lib.path, must_link, ctx); } else if (SharedObject.isSharedObject(in_file)) { try self.parseSharedObject(in_file, lib, ctx); - } else return error.UnknownFileType; + } else { + // TODO if the script has a top-level comment identifying it as GNU ld script, + // then report parse errors. Otherwise return UnknownFileType. + self.parseLdScript(in_file, lib, ctx) catch |err| switch (err) { + else => return error.UnknownFileType, + }; + } } fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseErrorCtx) ParseError!void { @@ -1693,7 +1706,7 @@ fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseEr const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .object = .{ - .path = path, + .path = try gpa.dupe(u8, path), .data = data, .index = index, } }); @@ -1718,11 +1731,14 @@ fn parseArchive( const gpa = self.base.allocator; const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); - var archive = Archive{ .path = path, .data = data }; + var archive = Archive{ .path = try gpa.dupe(u8, path), .data = data }; defer archive.deinit(gpa); try archive.parse(self); - for (archive.objects.items) |extracted| { + const objects = try archive.objects.toOwnedSlice(gpa); + defer gpa.free(objects); + + for (objects) |extracted| { const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .object = extracted }); const object = &self.files.items(.data)[index].object; @@ -1749,7 +1765,7 @@ fn parseSharedObject( const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .shared_object = .{ - .path = lib.path, + .path = try gpa.dupe(u8, lib.path), .data = data, .index = index, .needed = lib.needed, @@ -1764,6 +1780,123 @@ fn parseSharedObject( if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch; } +fn parseLdScript(self: *Elf, in_file: std.fs.File, lib: SystemLib, ctx: *ParseErrorCtx) ParseError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.allocator; + const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); + defer gpa.free(data); + + var script = LdScript{}; + defer script.deinit(gpa); + try script.parse(data, self); + + if (script.cpu_arch) |cpu_arch| { + ctx.detected_cpu_arch = cpu_arch; + if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch; + } + + const lib_dirs = self.base.options.lib_dirs; + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var test_path = std.ArrayList(u8).init(arena); + var checked_paths = std.ArrayList([]const u8).init(arena); + + for (script.args.items) |scr_obj| { + checked_paths.clearRetainingCapacity(); + + success: { + if (mem.startsWith(u8, scr_obj.path, "-l")) { + const lib_name = scr_obj.path["-l".len..]; + + // TODO I think technically we should re-use the mechanism used by the frontend here. + // Maybe we should hoist search-strategy all the way here? + for (lib_dirs) |lib_dir| { + if (!self.isStatic()) { + if (try self.accessLibPath(&test_path, &checked_paths, lib_dir, lib_name, .Dynamic)) + break :success; + } + if (try self.accessLibPath(&test_path, &checked_paths, lib_dir, lib_name, .Static)) + break :success; + } + + try self.reportMissingLibraryError( + checked_paths.items, + "missing library dependency: GNU ld script '{s}' requires '{s}', but file not found", + .{ + lib.path, + scr_obj.path, + }, + ); + } else { + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + if (fs.realpath(scr_obj.path, &buffer)) |path| { + test_path.clearRetainingCapacity(); + try test_path.writer().writeAll(path); + break :success; + } else |_| {} + + try checked_paths.append(try gpa.dupe(u8, scr_obj.path)); + for (lib_dirs) |lib_dir| { + if (try self.accessLibPath(&test_path, &checked_paths, lib_dir, scr_obj.path, null)) + break :success; + } + + try self.reportMissingLibraryError( + checked_paths.items, + "missing library dependency: GNU ld script '{s}' requires '{s}', but file not found", + .{ + lib.path, + scr_obj.path, + }, + ); + } + } + + const full_path = test_path.items; + const scr_file = try std.fs.cwd().openFile(full_path, .{}); + defer scr_file.close(); + + var scr_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; + self.parseLibrary(scr_file, .{ + .needed = scr_obj.needed, + .path = full_path, + }, false, &scr_ctx) catch |err| try self.handleAndReportParseError(full_path, err, &scr_ctx); + } +} + +fn accessLibPath( + self: *Elf, + test_path: *std.ArrayList(u8), + checked_paths: *std.ArrayList([]const u8), + lib_dir_path: []const u8, + lib_name: []const u8, + link_mode: ?std.builtin.LinkMode, +) !bool { + const sep = fs.path.sep_str; + const target = self.base.options.target; + test_path.clearRetainingCapacity(); + try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{ + lib_dir_path, + target.libPrefix(), + lib_name, + if (link_mode) |mode| switch (mode) { + .Static => target.staticLibSuffix(), + .Dynamic => target.dynamicLibSuffix(), + } else "", + }); + try checked_paths.append(try self.base.allocator.dupe(u8, test_path.items)); + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + return true; +} + /// When resolving symbols, we approach the problem similarly to `mold`. /// 1. Resolve symbols across all objects (including those preemptively extracted archives). /// 2. Resolve symbols across all shared objects. @@ -5886,6 +6019,19 @@ fn reportUndefined(self: *Elf, undefs: anytype) !void { } } +fn reportMissingLibraryError( + self: *Elf, + checked_paths: []const []const u8, + comptime format: []const u8, + args: anytype, +) error{OutOfMemory}!void { + var err = try self.addErrorWithNotes(checked_paths.len); + try err.addMsg(self, format, args); + for (checked_paths) |path| { + try err.addNote(self, "tried {s}", .{path}); + } +} + const ParseErrorCtx = struct { detected_cpu_arch: std.Target.Cpu.Arch, }; @@ -6182,7 +6328,7 @@ pub const null_shdr = elf.Elf64_Shdr{ .sh_entsize = 0, }; -const SystemLib = struct { +pub const SystemLib = struct { needed: bool = false, path: []const u8, }; @@ -6228,6 +6374,7 @@ const GnuHashSection = synthetic_sections.GnuHashSection; const GotSection = synthetic_sections.GotSection; const GotPltSection = synthetic_sections.GotPltSection; const HashSection = synthetic_sections.HashSection; +const LdScript = @import("Elf/LdScript.zig"); const LinkerDefined = @import("Elf/LinkerDefined.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; diff --git a/src/link/Elf/Archive.zig b/src/link/Elf/Archive.zig index f34b323206..94529a368c 100644 --- a/src/link/Elf/Archive.zig +++ b/src/link/Elf/Archive.zig @@ -71,6 +71,7 @@ pub fn isArchive(file: std.fs.File) bool { } pub fn deinit(self: *Archive, allocator: Allocator) void { + allocator.free(self.path); allocator.free(self.data); self.objects.deinit(allocator); } @@ -122,7 +123,7 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void { }; const object = Object{ - .archive = self.path, + .archive = try gpa.dupe(u8, self.path), .path = try gpa.dupe(u8, object_name[0 .. object_name.len - 1]), // To account for trailing '/' .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]), .index = undefined, diff --git a/src/link/Elf/LdScript.zig b/src/link/Elf/LdScript.zig new file mode 100644 index 0000000000..c901a8ecba --- /dev/null +++ b/src/link/Elf/LdScript.zig @@ -0,0 +1,533 @@ +cpu_arch: ?std.Target.Cpu.Arch = null, +args: std.ArrayListUnmanaged(Elf.SystemLib) = .{}, + +pub fn deinit(scr: *LdScript, allocator: Allocator) void { + scr.args.deinit(allocator); +} + +pub const Error = error{ + InvalidScript, + UnexpectedToken, + UnknownCpuArch, + OutOfMemory, +}; + +pub fn parse(scr: *LdScript, data: []const u8, elf_file: *Elf) Error!void { + const gpa = elf_file.base.allocator; + var tokenizer = Tokenizer{ .source = data }; + var tokens = std.ArrayList(Token).init(gpa); + defer tokens.deinit(); + var line_col = std.ArrayList(LineColumn).init(gpa); + defer line_col.deinit(); + + var line: usize = 0; + var prev_line_last_col: usize = 0; + + while (true) { + const tok = tokenizer.next(); + try tokens.append(tok); + const column = tok.start - prev_line_last_col; + try line_col.append(.{ .line = line, .column = column }); + switch (tok.id) { + .invalid => { + // TODO errors + // elf_file.base.fatal("invalid token in ld script: '{s}' ({d}:{d})", .{ + // tok.get(data), + // line, + // column, + // }); + return error.InvalidScript; + }, + .new_line => { + line += 1; + prev_line_last_col = tok.end; + }, + .eof => break, + else => {}, + } + } + + var it = TokenIterator{ .tokens = tokens.items }; + var parser = Parser{ .source = data, .it = &it }; + var args = std.ArrayList(Elf.SystemLib).init(gpa); + scr.doParse(.{ + .parser = &parser, + .args = &args, + }) catch |err| switch (err) { + error.UnexpectedToken => { + // const last_token_id = parser.it.pos - 1; + // const last_token = parser.it.get(last_token_id); + // const lcol = line_col.items[last_token_id]; + // TODO errors + // elf_file.base.fatal("unexpected token in ld script: {s} : '{s}' ({d}:{d})", .{ + // @tagName(last_token.id), + // last_token.get(data), + // lcol.line, + // lcol.column, + // }); + return error.InvalidScript; + }, + else => |e| return e, + }; + scr.args = args.moveToUnmanaged(); +} + +fn doParse(scr: *LdScript, ctx: struct { + parser: *Parser, + args: *std.ArrayList(Elf.SystemLib), +}) !void { + while (true) { + ctx.parser.skipAny(&.{ .comment, .new_line }); + + if (ctx.parser.maybe(.command)) |cmd_id| { + const cmd = ctx.parser.getCommand(cmd_id); + switch (cmd) { + .output_format => scr.cpu_arch = try ctx.parser.outputFormat(), + .group => try ctx.parser.group(ctx.args), + else => return error.UnexpectedToken, + } + } else break; + } + + if (ctx.parser.it.next()) |tok| switch (tok.id) { + .eof => {}, + else => return error.UnexpectedToken, + }; +} + +const LineColumn = struct { + line: usize, + column: usize, +}; + +const Command = enum { + output_format, + group, + as_needed, + + fn fromString(s: []const u8) ?Command { + inline for (@typeInfo(Command).Enum.fields) |field| { + comptime var buf: [field.name.len]u8 = undefined; + inline for (field.name, 0..) |c, i| { + buf[i] = comptime std.ascii.toUpper(c); + } + if (std.mem.eql(u8, &buf, s)) return @field(Command, field.name); + } + return null; + } +}; + +const Parser = struct { + source: []const u8, + it: *TokenIterator, + + fn outputFormat(p: *Parser) !std.Target.Cpu.Arch { + const value = value: { + if (p.skip(&.{.lparen})) { + const value_id = try p.require(.literal); + const value = p.it.get(value_id); + _ = try p.require(.rparen); + break :value value.get(p.source); + } else if (p.skip(&.{ .new_line, .lbrace })) { + const value_id = try p.require(.literal); + const value = p.it.get(value_id); + _ = p.skip(&.{.new_line}); + _ = try p.require(.rbrace); + break :value value.get(p.source); + } else return error.UnexpectedToken; + }; + if (std.mem.eql(u8, value, "elf64-x86-64")) return .x86_64; + return error.UnknownCpuArch; + } + + fn group(p: *Parser, args: *std.ArrayList(Elf.SystemLib)) !void { + if (!p.skip(&.{.lparen})) return error.UnexpectedToken; + + while (true) { + if (p.maybe(.literal)) |tok_id| { + const tok = p.it.get(tok_id); + const path = tok.get(p.source); + try args.append(.{ .path = path, .needed = true }); + } else if (p.maybe(.command)) |cmd_id| { + const cmd = p.getCommand(cmd_id); + switch (cmd) { + .as_needed => try p.asNeeded(args), + else => return error.UnexpectedToken, + } + } else break; + } + + _ = try p.require(.rparen); + } + + fn asNeeded(p: *Parser, args: *std.ArrayList(Elf.SystemLib)) !void { + if (!p.skip(&.{.lparen})) return error.UnexpectedToken; + + while (p.maybe(.literal)) |tok_id| { + const tok = p.it.get(tok_id); + const path = tok.get(p.source); + try args.append(.{ .path = path, .needed = false }); + } + + _ = try p.require(.rparen); + } + + fn skip(p: *Parser, comptime ids: []const Token.Id) bool { + const pos = p.it.pos; + inline for (ids) |id| { + const tok = p.it.next() orelse return false; + if (tok.id != id) { + p.it.seekTo(pos); + return false; + } + } + return true; + } + + fn skipAny(p: *Parser, comptime ids: []const Token.Id) void { + outer: while (p.it.next()) |tok| { + inline for (ids) |id| { + if (id == tok.id) continue :outer; + } + break p.it.seekBy(-1); + } + } + + fn maybe(p: *Parser, comptime id: Token.Id) ?Token.Index { + const pos = p.it.pos; + const tok = p.it.next() orelse return null; + if (tok.id == id) return pos; + p.it.seekBy(-1); + return null; + } + + fn require(p: *Parser, comptime id: Token.Id) !Token.Index { + return p.maybe(id) orelse return error.UnexpectedToken; + } + + fn getCommand(p: *Parser, index: Token.Index) Command { + const tok = p.it.get(index); + assert(tok.id == .command); + return Command.fromString(tok.get(p.source)).?; + } +}; + +const Token = struct { + id: Id, + start: usize, + end: usize, + + const Id = enum { + // zig fmt: off + eof, + invalid, + + new_line, + lparen, // ( + rparen, // ) + lbrace, // { + rbrace, // } + + comment, // /* */ + + command, // literal with special meaning, see Command + literal, + // zig fmt: on + }; + + const Index = usize; + + inline fn get(tok: Token, source: []const u8) []const u8 { + return source[tok.start..tok.end]; + } +}; + +const Tokenizer = struct { + source: []const u8, + index: usize = 0, + + fn matchesPattern(comptime pattern: []const u8, slice: []const u8) bool { + comptime var count: usize = 0; + inline while (count < pattern.len) : (count += 1) { + if (count >= slice.len) return false; + const c = slice[count]; + if (pattern[count] != c) return false; + } + return true; + } + + fn matches(tok: Tokenizer, comptime pattern: []const u8) bool { + return matchesPattern(pattern, tok.source[tok.index..]); + } + + fn isCommand(tok: Tokenizer, start: usize, end: usize) bool { + return if (Command.fromString(tok.source[start..end]) == null) false else true; + } + + fn next(tok: *Tokenizer) Token { + var result = Token{ + .id = .eof, + .start = tok.index, + .end = undefined, + }; + + var state: enum { + start, + comment, + literal, + } = .start; + + while (tok.index < tok.source.len) : (tok.index += 1) { + const c = tok.source[tok.index]; + switch (state) { + .start => switch (c) { + ' ', '\t' => result.start += 1, + + '\n' => { + result.id = .new_line; + tok.index += 1; + break; + }, + + '\r' => { + if (tok.matches("\r\n")) { + result.id = .new_line; + tok.index += "\r\n".len; + } else { + result.id = .invalid; + tok.index += 1; + } + break; + }, + + '/' => if (tok.matches("/*")) { + state = .comment; + tok.index += "/*".len; + } else { + state = .literal; + }, + + '(' => { + result.id = .lparen; + tok.index += 1; + break; + }, + + ')' => { + result.id = .rparen; + tok.index += 1; + break; + }, + + '{' => { + result.id = .lbrace; + tok.index += 1; + break; + }, + + '}' => { + result.id = .rbrace; + tok.index += 1; + break; + }, + + else => state = .literal, + }, + + .comment => switch (c) { + '*' => if (tok.matches("*/")) { + result.id = .comment; + tok.index += "*/".len; + break; + }, + else => {}, + }, + + .literal => switch (c) { + ' ', '(', '\n' => { + if (tok.isCommand(result.start, tok.index)) { + result.id = .command; + } else { + result.id = .literal; + } + break; + }, + + ')' => { + result.id = .literal; + break; + }, + + '\r' => { + if (tok.matches("\r\n")) { + if (tok.isCommand(result.start, tok.index)) { + result.id = .command; + } else { + result.id = .literal; + } + } else { + result.id = .invalid; + tok.index += 1; + } + break; + }, + + else => {}, + }, + } + } + + result.end = tok.index; + return result; + } +}; + +const TokenIterator = struct { + tokens: []const Token, + pos: Token.Index = 0, + + fn next(it: *TokenIterator) ?Token { + const token = it.peek() orelse return null; + it.pos += 1; + return token; + } + + fn peek(it: TokenIterator) ?Token { + if (it.pos >= it.tokens.len) return null; + return it.tokens[it.pos]; + } + + inline fn reset(it: *TokenIterator) void { + it.pos = 0; + } + + inline fn seekTo(it: *TokenIterator, pos: Token.Index) void { + it.pos = pos; + } + + fn seekBy(it: *TokenIterator, offset: isize) void { + const new_pos = @as(isize, @bitCast(it.pos)) + offset; + if (new_pos < 0) { + it.pos = 0; + } else { + it.pos = @as(usize, @intCast(new_pos)); + } + } + + inline fn get(it: *TokenIterator, pos: Token.Index) Token { + assert(pos < it.tokens.len); + return it.tokens[pos]; + } +}; + +const testing = std.testing; + +fn testExpectedTokens(input: []const u8, expected: []const Token.Id) !void { + var given = std.ArrayList(Token.Id).init(testing.allocator); + defer given.deinit(); + + var tokenizer = Tokenizer{ .source = input }; + while (true) { + const tok = tokenizer.next(); + if (tok.id == .invalid) { + std.debug.print(" {s} => '{s}'\n", .{ @tagName(tok.id), tok.get(input) }); + } + try given.append(tok.id); + if (tok.id == .eof) break; + } + + try testing.expectEqualSlices(Token.Id, expected, given.items); +} + +test "Tokenizer - just comments" { + try testExpectedTokens( + \\/* GNU ld script + \\ Use the shared library, but some functions are only in + \\ the static library, so try that secondarily. */ + , &.{ .comment, .eof }); +} + +test "Tokenizer - comments with a simple command" { + try testExpectedTokens( + \\/* GNU ld script + \\ Use the shared library, but some functions are only in + \\ the static library, so try that secondarily. */ + \\OUTPUT_FORMAT(elf64-x86-64) + , &.{ .comment, .new_line, .command, .lparen, .literal, .rparen, .eof }); +} + +test "Tokenizer - libc.so" { + try testExpectedTokens( + \\/* GNU ld script + \\ Use the shared library, but some functions are only in + \\ the static library, so try that secondarily. */ + \\OUTPUT_FORMAT(elf64-x86-64) + \\GROUP ( /a/b/c.so.6 /a/d/e.a AS_NEEDED ( /f/g/h.so.2 ) ) + , &.{ + .comment, .new_line, // GNU comment + .command, .lparen, .literal, .rparen, .new_line, // output format + .command, .lparen, .literal, .literal, // group start + .command, .lparen, .literal, .rparen, // as needed + .rparen, // group end + .eof, + }); +} + +test "Parser - output format" { + const source = + \\OUTPUT_FORMAT(elf64-x86-64) + ; + var tokenizer = Tokenizer{ .source = source }; + var tokens = std.ArrayList(Token).init(testing.allocator); + defer tokens.deinit(); + while (true) { + const tok = tokenizer.next(); + try testing.expect(tok.id != .invalid); + try tokens.append(tok); + if (tok.id == .eof) break; + } + var it = TokenIterator{ .tokens = tokens.items }; + var parser = Parser{ .source = source, .it = &it }; + const tok_id = try parser.require(.command); + try testing.expectEqual(parser.getCommand(tok_id), .output_format); + const cpu_arch = try parser.outputFormat(); + try testing.expectEqual(cpu_arch, .x86_64); +} + +test "Parser - group with as-needed" { + const source = + \\GROUP ( /a/b/c.so.6 /a/d/e.a AS_NEEDED ( /f/g/h.so.2 ) ) + ; + var tokenizer = Tokenizer{ .source = source }; + var tokens = std.ArrayList(Token).init(testing.allocator); + defer tokens.deinit(); + while (true) { + const tok = tokenizer.next(); + try testing.expect(tok.id != .invalid); + try tokens.append(tok); + if (tok.id == .eof) break; + } + var it = TokenIterator{ .tokens = tokens.items }; + var parser = Parser{ .source = source, .it = &it }; + + var args = std.ArrayList(Elf.LinkObject).init(testing.allocator); + defer args.deinit(); + const tok_id = try parser.require(.command); + try testing.expectEqual(parser.getCommand(tok_id), .group); + try parser.group(&args); + + try testing.expectEqualStrings("/a/b/c.so.6", args.items[0].path); + try testing.expect(args.items[0].needed); + try testing.expectEqualStrings("/a/d/e.a", args.items[1].path); + try testing.expect(args.items[1].needed); + try testing.expectEqualStrings("/f/g/h.so.2", args.items[2].path); + try testing.expect(!args.items[2].needed); +} + +const LdScript = @This(); + +const std = @import("std"); +const assert = std.debug.assert; + +const Allocator = std.mem.Allocator; +const Elf = @import("../Elf.zig"); diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 4edc5b62b1..c24025cadb 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -34,6 +34,8 @@ pub fn isObject(file: std.fs.File) bool { } pub fn deinit(self: *Object, allocator: Allocator) void { + if (self.archive) |path| allocator.free(path); + allocator.free(self.path); allocator.free(self.data); self.shdrs.deinit(allocator); self.strings.deinit(allocator); diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index 19e688b58d..0ff74edddf 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -33,6 +33,7 @@ pub fn isSharedObject(file: std.fs.File) bool { } pub fn deinit(self: *SharedObject, allocator: Allocator) void { + allocator.free(self.path); allocator.free(self.data); self.versyms.deinit(allocator); self.verstrings.deinit(allocator);