Commit 34f34dbe32

Jakub Konka <kubkon@jakubkonka.com>
2024-07-16 21:13:57
macho: reinstate duplicate definition checking
1 parent d19aab2
Changed files (5)
src/link/MachO/file.zig
@@ -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();
src/link/MachO/Object.zig
@@ -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();
src/link/MachO/ZigObject.zig
@@ -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;
src/link/MachO.zig
@@ -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);
test/link/macho.zig
@@ -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);