On AIX a C++ exception is not caught when the catching function lives in a
translation unit other than the one holding main(), is reached only through a
function pointer, and that pointer is published by a static constructor.  The
handler in its own frame is never found and std::terminate runs.

collect2 leaves SCAN_DWEH out of the scan it performs before the first link:

    scanfilter this_filter = ld1_filter;
    #if HAVE_AS_REF
      if (!shared_obj)
        this_filter &= ~SCAN_DWEH;
    #endif

The stated reason is that referencing every frame table would drag in the
corresponding objects even when nothing else needs them.  That cost is real for
archive members.  It is not for the object files named on the command line,
which are linked unconditionally either way.

The guard also assumes a table left out here is still visible in the first
link's output, because ASM_OUTPUT_DWARF_TABLE_REF emits a .ref from each
function body to its table.  That holds only while the function body survives.
A unit whose sole entry point is a static constructor has nothing referenced
during the first link -- collect2 has not written the constructor table yet --
so the AIX linker discards the csect and the frame table with it.  collect2
then scans that output, finds one table where there were two, and registers
only the survivor:

    $ g++ -Wl,-debug -o t a.o b.o main.o | grep 'frame table'
    1 frame table found

Constructors are already collected from the objects; frame tables are not.
This removes the asymmetry for objects and leaves archives as they were.

Built and tested on powerpc-ibm-aix7.3.1.0 (--disable-bootstrap; the change
is in collect2 only).

The new test, run through the testsuite with only collect2 swapped:

    unpatched   4 expected passes, 4 unexpected failures
    patched     8 expected passes

It fails in gnu++98, gnu++14, gnu++17 and gnu++20 alike, so the language
standard is not involved.  With the fix, collect2 reports two frame tables
where it reported one.

A full check-g++ was run both ways on the same tree, again with only collect2
differing: 224411 result lines each and no differing line.  Tallies identical
in every category (211110 expected passes, 14 unexpected failures, 28
unexpected successes, 2090 expected failures, 11169 unsupported).

Shared-object and static-archive links were also compared: identical behaviour,
the archive case growing by 56 bytes, which is the one added table entry rather
than a dragged-in member.

The affected block is byte-identical in releases/gcc-13, 14, 15 and master, and
the patch applies cleanly to 13.3.0 and to master.

Signed-off-by: Bernard Ladenthin <[email protected]>

        PR target/60939

gcc/ChangeLog:

        * collect2.cc (main): Use separate scan filters for object files and
        for libraries.  Retain SCAN_DWEH when scanning objects; keep excluding
        it for libraries.

gcc/testsuite/ChangeLog:

        * g++.dg/eh/pr60939.C: New test.
        * g++.dg/eh/pr60939-a.cc: New test.

---
diff --git a/gcc/collect2.cc b/gcc/collect2.cc
index 63b9a0c23..a30873439 100644
--- a/gcc/collect2.cc
+++ b/gcc/collect2.cc
@@ -1517,21 +1517,27 @@ main (int argc, char **argv)
        control whether we need a first pass link later on or not, and what
        will remain to be scanned there.  */
 
-    scanfilter this_filter = ld1_filter;
+    scanfilter obj_filter = ld1_filter;
+    scanfilter lib_filter = ld1_filter;
 #if HAVE_AS_REF
     if (!shared_obj)
-      this_filter &= ~SCAN_DWEH;
+      /* Referencing every frame table found in an archive would drag the whole
+        member in even when nothing else needs it.  Objects named on the
+        command line are linked regardless, so nothing is saved by deferring
+        theirs to the first link -- and deferring loses the table whenever the
+        linker discards a csect that nothing references yet.  */
+      lib_filter &= ~SCAN_DWEH;
 #endif
 
     /* Scan object files.  */
     while (export_object_lst < object)
-      scan_prog_file (*export_object_lst++, PASS_OBJ, this_filter);
+      scan_prog_file (*export_object_lst++, PASS_OBJ, obj_filter);
 
     /* Scan libraries.  */
     for (; list; list = list->next)
-      scan_prog_file (list->name, PASS_FIRST, this_filter);
+      scan_prog_file (list->name, PASS_FIRST, lib_filter);
 
-    ld1_filter = ld1_filter & ~this_filter;
+    ld1_filter = ld1_filter & ~lib_filter;
   }
 
   if (exports.first)
diff --git a/gcc/testsuite/g++.dg/eh/pr60939.C 
b/gcc/testsuite/g++.dg/eh/pr60939.C
new file mode 100644
index 0000000..0000000
--- /dev/null
+++ b/gcc/testsuite/g++.dg/eh/pr60939.C
@@ -0,0 +1,24 @@
+// PR target/60939 - a C++ exception was not caught when the catching function
+// lived in another translation unit, was reached only through a function
+// pointer, and that pointer was published by a static constructor.
+//
+// On AIX the linker discards a csect nothing references during collect2's
+// first link, taking the unit's DWARF frame table with it; collect2 then
+// scanned that link's output and registered only the surviving table, so the
+// unwinder found no handler and std::terminate ran.
+//
+// The test is not AIX-only on purpose: it is a valid program everywhere, and
+// costs nothing to keep running on other targets.
+
+// { dg-do run }
+// { dg-additional-sources "pr60939-a.cc" }
+// { dg-options "-O2" }
+
+extern "C" void (*pfoo) (void);
+
+int
+main ()
+{
+  pfoo ();
+  return 0;
+}
diff --git a/gcc/testsuite/g++.dg/eh/pr60939-a.cc 
b/gcc/testsuite/g++.dg/eh/pr60939-a.cc
new file mode 100644
index 0000000..0000000
--- /dev/null
+++ b/gcc/testsuite/g++.dg/eh/pr60939-a.cc
@@ -0,0 +1,30 @@
+// Companion to pr60939.C.  Nothing here is referenced from main() except
+// through the pointer the static constructor stores, which is what makes the
+// unit a candidate for removal by a garbage-collecting linker.
+
+#include <stdexcept>
+#include <cstdlib>
+
+extern "C" void (*pfoo) (void);
+void (*pfoo) (void) = 0;
+
+static void
+foo (void)
+{
+  try
+    {
+      throw std::range_error ("pr60939");
+    }
+  catch (const std::range_error &)
+    {
+      return;
+    }
+  abort ();
+}
+
+struct init
+{
+  init () { pfoo = foo; }
+};
+
+static init the_init;

Reply via email to