macho: reinstate duplicate definition checking

This commit is contained in:
Jakub Konka 2024-07-16 21:13:57 +02:00
parent d19aab2e87
commit 34f34dbe32
5 changed files with 86 additions and 69 deletions

View File

@ -26,6 +26,7 @@ resolver: SymbolResolver = .{},
/// This table will be populated after `scanRelocs` has run.
/// Key is symbol index.
undefs: std.AutoHashMapUnmanaged(SymbolResolver.Index, std.ArrayListUnmanaged(Ref)) = .{},
dupes: std.AutoHashMapUnmanaged(SymbolResolver.Index, std.ArrayListUnmanaged(File.Index)) = .{},
dyld_info_cmd: macho.dyld_info_command = .{},
symtab_cmd: macho.symtab_command = .{},
@ -311,6 +312,13 @@ pub fn deinit(self: *MachO) void {
}
self.undefs.deinit(gpa);
}
{
var it = self.dupes.iterator();
while (it.next()) |entry| {
entry.value_ptr.deinit(gpa);
}
self.dupes.deinit(gpa);
}
self.symtab.deinit(gpa);
self.strtab.deinit(gpa);
@ -518,14 +526,13 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n
try dead_strip.gcAtoms(self);
}
// TODO
// self.checkDuplicates() catch |err| switch (err) {
// error.HasDuplicates => return error.FlushFailure,
// else => |e| {
// try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{});
// return e;
// },
// };
self.checkDuplicates() catch |err| switch (err) {
error.HasDuplicates => return error.FlushFailure,
else => |e| {
try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{});
return e;
},
};
self.markImportsAndExports();
self.deadStripDylibs();
@ -1434,25 +1441,16 @@ fn claimUnresolved(self: *MachO) void {
}
fn checkDuplicates(self: *MachO) !void {
const gpa = self.base.comp.gpa;
var dupes = std.AutoArrayHashMap(Symbol.Index, std.ArrayListUnmanaged(File.Index)).init(gpa);
defer {
for (dupes.values()) |*list| {
list.deinit(gpa);
}
dupes.deinit();
}
if (self.getZigObject()) |zo| {
try zo.checkDuplicates(&dupes, self);
try zo.asFile().checkDuplicates(self);
}
for (self.objects.items) |index| {
try self.getFile(index).?.object.checkDuplicates(&dupes, self);
try self.getFile(index).?.checkDuplicates(self);
}
try self.reportDuplicates(dupes);
if (self.getInternalObject()) |obj| {
try obj.asFile().checkDuplicates(self);
}
try self.reportDuplicates();
}
fn markImportsAndExports(self: *MachO) void {
@ -3737,22 +3735,23 @@ pub fn reportUnexpectedError(self: *MachO, comptime format: []const u8, args: an
try err.addNote(self, "please report this as a linker bug on https://github.com/ziglang/zig/issues/new/choose", .{});
}
fn reportDuplicates(self: *MachO, dupes: anytype) error{ HasDuplicates, OutOfMemory }!void {
fn reportDuplicates(self: *MachO) error{ HasDuplicates, OutOfMemory }!void {
const tracy = trace(@src());
defer tracy.end();
const max_notes = 3;
var has_dupes = false;
var it = dupes.iterator();
var it = self.dupes.iterator();
while (it.next()) |entry| {
const sym = self.getSymbol(entry.key_ptr.*);
const sym = self.resolver.keys.items[entry.key_ptr.* - 1];
const notes = entry.value_ptr.*;
const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes);
var err = try self.addErrorWithNotes(nnotes + 1);
try err.addMsg(self, "duplicate symbol definition: {s}", .{sym.getName(self)});
try err.addNote(self, "defined by {}", .{sym.getFile(self).?.fmtPath()});
has_dupes = true;
var inote: usize = 0;
while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
@ -3764,10 +3763,7 @@ fn reportDuplicates(self: *MachO, dupes: anytype) error{ HasDuplicates, OutOfMem
const remaining = notes.items.len - max_notes;
try err.addNote(self, "defined {d} more times", .{remaining});
}
has_dupes = true;
}
if (has_dupes) return error.HasDuplicates;
}
@ -4452,6 +4448,11 @@ pub const SymbolResolver = struct {
return ref.getSymbol(macho_file).?.getName(macho_file);
}
pub fn getFile(key: Key, macho_file: *MachO) ?File {
const ref = Ref{ .index = key.index, .file = key.file };
return ref.getFile(macho_file);
}
fn eql(key: Key, other: Key, macho_file: *MachO) bool {
const key_name = key.getName(macho_file);
const other_name = other.getName(macho_file);

View File

@ -1550,25 +1550,6 @@ pub fn mergeSymbolVisibility(self: *Object, macho_file: *MachO) void {
}
}
// TODO
// pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void {
// for (self.symbols.items, 0..) |index, nlist_idx| {
// const sym = macho_file.getSymbol(index);
// if (sym.visibility != .global) continue;
// const file = sym.getFile(macho_file) orelse continue;
// if (file.getIndex() == self.index) continue;
// const nlist = self.symtab.items(.nlist)[nlist_idx];
// if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
// const gop = try dupes.getOrPut(index);
// if (!gop.found_existing) {
// gop.value_ptr.* = .{};
// }
// try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
// }
// }
// }
pub fn scanRelocs(self: *Object, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();

View File

@ -308,25 +308,6 @@ pub fn mergeSymbolVisibility(self: *ZigObject, macho_file: *MachO) void {
}
}
// TODO
// pub fn checkDuplicates(self: *ZigObject, dupes: anytype, macho_file: *MachO) !void {
// for (self.symbols.items, 0..) |index, nlist_idx| {
// const sym = macho_file.getSymbol(index);
// if (sym.visibility != .global) continue;
// const file = sym.getFile(macho_file) orelse continue;
// if (file.getIndex() == self.index) continue;
// const nlist = self.symtab.items(.nlist)[nlist_idx];
// if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) {
// const gop = try dupes.getOrPut(index);
// if (!gop.found_existing) {
// gop.value_ptr.* = .{};
// }
// try gop.value_ptr.append(macho_file.base.comp.gpa, self.index);
// }
// }
// }
pub fn resolveLiterals(self: *ZigObject, lp: *MachO.LiteralPool, macho_file: *MachO) !void {
_ = self;
_ = lp;

View File

@ -24,7 +24,7 @@ pub const File = union(enum) {
_ = options;
switch (file) {
.zig_object => |x| try writer.writeAll(x.path),
.internal => try writer.writeAll(""),
.internal => try writer.writeAll("internal"),
.object => |x| try writer.print("{}", .{x.fmtPath()}),
.dylib => |x| try writer.writeAll(x.path),
}
@ -57,7 +57,7 @@ pub const File = union(enum) {
weak: bool = false,
tentative: bool = false,
}) u32 {
if (file == .object and !args.archive) {
if (file != .dylib and !args.archive) {
const base: u32 = blk: {
if (args.tentative) break :blk 3;
break :blk if (args.weak) 2 else 1;
@ -254,6 +254,28 @@ pub const File = union(enum) {
}
}
pub fn checkDuplicates(file: File, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
for (file.getSymbols(), file.getNlists(), 0..) |sym, nlist, i| {
if (sym.visibility != .global) continue;
if (sym.flags.weak) continue;
if (nlist.undf()) continue;
const ref = file.getSymbolRef(@intCast(i), macho_file);
const ref_file = ref.getFile(macho_file) orelse continue;
if (ref_file.getIndex() == file.getIndex()) continue;
const gop = try macho_file.dupes.getOrPut(gpa, file.getGlobals()[i]);
if (!gop.found_existing) {
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(gpa, file.getIndex());
}
}
pub fn initOutputSections(file: File, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();

View File

@ -30,6 +30,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
// Exercise linker with LLVM backend
macho_step.dependOn(testDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testDuplicateDefinitions(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyObject(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyZig(b, .{ .target = default_target }));
macho_step.dependOn(testEntryPoint(b, .{ .target = default_target }));
@ -182,6 +183,37 @@ fn testDeadStrip(b: *Build, opts: Options) *Step {
return test_step;
}
fn testDuplicateDefinitions(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "duplicate-definitions", opts);
const obj = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\export fn weak() void { x += 1; }
});
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\comptime { @export(weakImpl, .{ .name = "weak", .linkage = .weak }); }
\\fn weakImpl() callconv(.C) void { x += 1; }
\\extern fn weak() void;
\\pub fn main() void {
\\ weak();
\\ strong();
\\}
});
exe.addObject(obj);
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: duplicate symbol definition: _strong",
"note: defined by /?/a.o",
"note: defined by /?/main.o",
} });
return test_step;
}
fn testDeadStripDylibs(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dead-strip-dylibs", opts);