[binutils-gdb] ld/testsuite: Add comprehensive PE COFF weak external tests

Alan Modra via Binutils-cvs <[email protected]> Tue, 30 Jun 2026 23:48:59 +0000 (GMT)
Newsgroups gmane.comp.gnu.binutils.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=3f8a2c9722b1dcb5525ce3943c5c3f98404ace67

commit 3f8a2c9722b1dcb5525ce3943c5c3f98404ace67
Author: Peter Damianov <[email protected]>
Date:   Tue Jun 23 17:38:07 2026 -0400

    ld/testsuite: Add comprehensive PE COFF weak external tests
    
    Add tests covering the full matrix of weak/strong symbol interactions
    for PE COFF weak externals, based on testcases by Martin Storsjo.
    The archive test covers PE weak externals whose public name is backed
    by a real fallback definition, and ensures weak declarations with the
    null fallback are not advertised as archive providers.
    
    ld/testsuite/
    
            * ld-pe/pe-compile.exp (weak_ext_test): New proc.  Compiles
            source files, links via gcc, and runs natively if possible.
            (weak_ext_archive_test): New proc.
            Add tests: normal, weak-undef, weak-defined,
            weak-decl-weak-def, strong-undef-weak-def-archive, weak-use,
            weak-override, weak-duplicate, weak-def-override, weak-def-use.
            * ld-pe/weak-ext-main.c: New file.
            * ld-pe/weak-ext-main-weak.c: New file.
            * ld-pe/weak-ext-main-weak-def.c: New file.
            * ld-pe/weak-ext-add2.c: New file.
            * ld-pe/weak-ext-add1-weak-chained.c: New file.
            * ld-pe/weak-ext-dummy.c: New file.
            * ld-pe/weak-ext-expected1.c: New file.
            * ld-pe/weak-ext-expected3.c: New file.
            * ld-pe/weak-ext-expected5.c: New file.
            * ld-pe/weak-ext-expected3-add1-weak.c: New file.

Diff:
---
 ld/testsuite/ld-pe/pe-compile.exp                 | 167 ++++++++++++++++++++++
 ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c   |  13 ++
 ld/testsuite/ld-pe/weak-ext-add2.c                |   8 ++
 ld/testsuite/ld-pe/weak-ext-dummy.c               |   5 +
 ld/testsuite/ld-pe/weak-ext-expected1.c           |   1 +
 ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c |   9 ++
 ld/testsuite/ld-pe/weak-ext-expected3.c           |   1 +
 ld/testsuite/ld-pe/weak-ext-expected5.c           |   1 +
 ld/testsuite/ld-pe/weak-ext-main-weak-def.c       |  26 ++++
 ld/testsuite/ld-pe/weak-ext-main-weak.c           |  22 +++
 ld/testsuite/ld-pe/weak-ext-main.c                |  21 +++
 11 files changed, 274 insertions(+)

diff --git a/ld/testsuite/ld-pe/pe-compile.exp b/ld/testsuite/ld-pe/pe-compile.exp
index 041a1cb9344..aac8711dff2 100644
--- a/ld/testsuite/ld-pe/pe-compile.exp
+++ b/ld/testsuite/ld-pe/pe-compile.exp
@@ -138,3 +138,170 @@ set align_tests {
 }
 
 run_ld_link_tests $align_tests
+
+# Test PE COFF weak external symbol resolution.
+# These tests cover the full matrix of weak/strong interactions to verify
+# that the linker correctly resolves weak externals in PE COFF objects.
+# Based on testcases by Martin Storsjo.
+
+proc weak_ext_test { testname sources } {
+    global CC_FOR_TARGET
+    global srcdir
+    global subdir
+
+    set objfiles {}
+    foreach src $sources {
+	set fileroot [file rootname [file tail $src]]
+	set obj "tmpdir/$fileroot.o"
+	if ![ld_compile $CC_FOR_TARGET $srcdir/$subdir/$src $obj] {
+	    fail "$testname (compile $src)"
+	    return
+	}
+	lappend objfiles $obj
+    }
+
+    set output "tmpdir/weak-ext-test.exe"
+    if ![ld_link $CC_FOR_TARGET $output $objfiles] {
+	fail "$testname (link)"
+	return
+    }
+
+    if ![isnative] {
+	pass "$testname (link only)"
+	return
+    }
+
+    catch "exec $output" prog_output
+    if { $prog_output eq "" } {
+	pass $testname
+    } else {
+	verbose $prog_output
+	fail "$testname ($prog_output)"
+    }
+}
+
+proc weak_ext_archive_test { } {
+    global CC_FOR_TARGET
+    global ar
+    global nm
+    global srcdir
+    global subdir
+
+    set testname "weak external: archive strong undef + weak def"
+    set main_obj "tmpdir/weak-ext-main.o"
+    set weak_decl_obj "tmpdir/weak-ext-main-weak.o"
+    set weak_obj "tmpdir/weak-ext-add1-weak-chained.o"
+    set expected_obj "tmpdir/weak-ext-expected3.o"
+    set weak_decl_archive "tmpdir/weak-ext-decl-archive.a"
+    set archive "tmpdir/weak-ext-archive.a"
+    set output "tmpdir/weak-ext-archive-test.exe"
+
+    set sources {weak-ext-main.c weak-ext-main-weak.c \
+	weak-ext-add1-weak-chained.c weak-ext-expected3.c}
+    set objects [list $main_obj $weak_decl_obj $weak_obj $expected_obj]
+    foreach src $sources obj $objects {
+	if ![ld_compile $CC_FOR_TARGET $srcdir/$subdir/$src $obj] {
+	    fail "$testname (compile $src)"
+	    return
+	}
+    }
+
+    set got [run_host_cmd "$ar" "rcs $weak_decl_archive $weak_decl_obj"]
+    if ![string match "" $got] {
+	fail "$testname (weak decl archive)"
+	return
+    }
+
+    # A weak declaration with the null fallback must not advertise its
+    # public name as an archive-map provider.
+    set got [run_host_cmd "$nm" "--print-armap $weak_decl_archive"]
+    if [regexp "(^|\n)_?func in weak-ext-main-weak\\.o" $got] {
+	verbose -log $got
+	fail "$testname (weak decl armap)"
+	return
+    }
+
+    set got [run_host_cmd "$ar" "rcs $archive $weak_obj"]
+    if ![string match "" $got] {
+	fail "$testname (archive)"
+	return
+    }
+
+    # A weak external with a real fallback should advertise the public
+    # alias, so a strong undefined reference can extract the member.
+    set got [run_host_cmd "$nm" "--print-armap $archive"]
+    if ![regexp "(^|\n)_?func in weak-ext-add1-weak-chained\\.o" $got] {
+	verbose -log $got
+	fail "$testname (armap)"
+	return
+    }
+
+    # Search the archive twice to cover command lines where the driver
+    # adds a library implicitly and the user also names it explicitly.
+    if ![ld_link $CC_FOR_TARGET $output \
+	     [list $main_obj $expected_obj $archive $archive]] {
+	fail "$testname (link)"
+	return
+    }
+
+    if ![isnative] {
+	pass "$testname (link only)"
+	return
+    }
+
+    catch "exec $output" prog_output
+    if { $prog_output eq "" } {
+	pass $testname
+    } else {
+	verbose $prog_output
+	fail "$testname ($prog_output)"
+    }
+}
+
+# Strong undefined reference + strong definition: basic sanity check.
+weak_ext_test "weak external: normal (strong undef + strong def)" \
+    {weak-ext-main.c weak-ext-add2.c weak-ext-expected3.c weak-ext-dummy.c}
+
+# Weak declaration with no definition available: func remains NULL,
+# never called.
+weak_ext_test "weak external: weak undef (no def)" \
+    {weak-ext-main-weak.c weak-ext-expected1.c weak-ext-dummy.c}
+
+# Weak declaration resolved by a strong definition in another object.
+weak_ext_test "weak external: weak decl + strong def" \
+    {weak-ext-main-weak.c weak-ext-add2.c weak-ext-expected3.c weak-ext-dummy.c}
+
+# Two weak externals for the same symbol meet: the one whose fallback
+# alias points to an actual function body should win over the one whose
+# fallback is NULL.
+weak_ext_test "weak external: weak decl + weak def" \
+    {weak-ext-main-weak.c weak-ext-add1-weak-chained.c weak-ext-expected3.c}
+
+# Strong undefined reference is seen before the weak definition: the
+# linker must still store the weak external's aux record so the fallback
+# alias can be resolved later.
+weak_ext_test "weak external: strong undef + weak def" \
+    {weak-ext-main.c weak-ext-add1-weak-chained.c weak-ext-expected3.c}
+
+weak_ext_archive_test
+
+# Strong definition overrides a weak definition: both call sites (in
+# main and in dummy) should resolve to the strong def.
+weak_ext_test "weak external: strong override of weak" \
+    {weak-ext-main.c weak-ext-add1-weak-chained.c weak-ext-add2.c \
+     weak-ext-expected5.c}
+
+# Two identical weak definitions for the same symbol: no conflict,
+# the linker picks one (first wins) and discards the duplicate.
+weak_ext_test "weak external: duplicate weak defs" \
+    {weak-ext-main.c weak-ext-add1-weak-chained.c \
+     weak-ext-expected3-add1-weak.c}
+
+# Weak definition in main overridden by a strong definition elsewhere.
+weak_ext_test "weak external: weak def overridden by strong" \
+    {weak-ext-main-weak-def.c weak-ext-add2.c weak-ext-expected3.c \
+     weak-ext-dummy.c}
+
+# Weak definition in main used directly when no strong definition exists.
+weak_ext_test "weak external: weak def used (no strong)" \
+    {weak-ext-main-weak-def.c weak-ext-expected5.c weak-ext-dummy.c}
diff --git a/ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c b/ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c
new file mode 100644
index 00000000000..5b68b8435e2
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-add1-weak-chained.c
@@ -0,0 +1,13 @@
+/* Weak definition of func (adds 1), plus dummy that calls func.  */
+extern int value;
+
+__attribute__((weak)) void func (void)
+{
+  value += 1;
+}
+
+void
+dummy (void)
+{
+  func ();
+}
diff --git a/ld/testsuite/ld-pe/weak-ext-add2.c b/ld/testsuite/ld-pe/weak-ext-add2.c
new file mode 100644
index 00000000000..691554d17e3
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-add2.c
@@ -0,0 +1,8 @@
+/* Strong definition of func (adds 2).  */
+extern int value;
+
+void
+func (void)
+{
+  value += 2;
+}
diff --git a/ld/testsuite/ld-pe/weak-ext-dummy.c b/ld/testsuite/ld-pe/weak-ext-dummy.c
new file mode 100644
index 00000000000..1c97c3cf97f
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-dummy.c
@@ -0,0 +1,5 @@
+/* Empty dummy function.  */
+void
+dummy (void)
+{
+}
diff --git a/ld/testsuite/ld-pe/weak-ext-expected1.c b/ld/testsuite/ld-pe/weak-ext-expected1.c
new file mode 100644
index 00000000000..5b4138b0bb1
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-expected1.c
@@ -0,0 +1 @@
+int expected = 1;
diff --git a/ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c b/ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c
new file mode 100644
index 00000000000..585830bd304
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-expected3-add1-weak.c
@@ -0,0 +1,9 @@
+/* Second weak definition of func (adds 1) plus expected value.  */
+extern int value;
+
+__attribute__((weak)) void func (void)
+{
+  value += 1;
+}
+
+int expected = 3;
diff --git a/ld/testsuite/ld-pe/weak-ext-expected3.c b/ld/testsuite/ld-pe/weak-ext-expected3.c
new file mode 100644
index 00000000000..3d67d70bcb0
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-expected3.c
@@ -0,0 +1 @@
+int expected = 3;
diff --git a/ld/testsuite/ld-pe/weak-ext-expected5.c b/ld/testsuite/ld-pe/weak-ext-expected5.c
new file mode 100644
index 00000000000..fef6b322c03
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-expected5.c
@@ -0,0 +1 @@
+int expected = 5;
diff --git a/ld/testsuite/ld-pe/weak-ext-main-weak-def.c b/ld/testsuite/ld-pe/weak-ext-main-weak-def.c
new file mode 100644
index 00000000000..05f81d189b4
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-main-weak-def.c
@@ -0,0 +1,26 @@
+/* Weak definition of func in main (adds 4).  */
+#include <stdio.h>
+
+int value = 1;
+
+__attribute__((weak)) void func (void)
+{
+  value += 4;
+}
+
+void dummy (void);
+extern int expected;
+
+int
+main (void)
+{
+  if (func)
+    func ();
+  dummy ();
+  if (value != expected)
+    {
+      printf ("expected %d, got %d\n", expected, value);
+      return 1;
+    }
+  return 0;
+}
diff --git a/ld/testsuite/ld-pe/weak-ext-main-weak.c b/ld/testsuite/ld-pe/weak-ext-main-weak.c
new file mode 100644
index 00000000000..bc1501f62c0
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-main-weak.c
@@ -0,0 +1,22 @@
+/* Weak declaration of func (no definition).  */
+#include <stdio.h>
+
+__attribute__((weak)) void func (void);
+void dummy (void);
+
+int value = 1;
+extern int expected;
+
+int
+main (void)
+{
+  if (func)
+    func ();
+  dummy ();
+  if (value != expected)
+    {
+      printf ("expected %d, got %d\n", expected, value);
+      return 1;
+    }
+  return 0;
+}
diff --git a/ld/testsuite/ld-pe/weak-ext-main.c b/ld/testsuite/ld-pe/weak-ext-main.c
new file mode 100644
index 00000000000..916bb246e5f
--- /dev/null
+++ b/ld/testsuite/ld-pe/weak-ext-main.c
@@ -0,0 +1,21 @@
+/* Strong undefined reference to func.  */
+#include <stdio.h>
+
+void func (void);
+void dummy (void);
+
+int value = 1;
+extern int expected;
+
+int
+main (void)
+{
+  func ();
+  dummy ();
+  if (value != expected)
+    {
+      printf ("expected %d, got %d\n", expected, value);
+      return 1;
+    }
+  return 0;
+}