[gcc(refs/users/aoliva/heads/testme)] GNAT exceptions in C++

Alexandre Oliva via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <20260710113829.478FF4BA2E10__26306.6346453101$1783683522$gmane$org@sourceware.org>
https://gcc.gnu.org/g:eab1653a1202027ec3111b239381ed97858d223f

commit eab1653a1202027ec3111b239381ed97858d223f
Author: Alexandre Oliva <[email protected]>
Date:   Sat Jun 27 00:37:01 2026 -0300

    GNAT exceptions in C++
    
    This patch introduces __gnu_cxx::__gnat_exception, a class derived
    from abi::__foreign_exception, that can be used to catch GNAT
    exceptions and obtain information from them, such as the exception
    name and message:
    
      try {
        try {
          raise_gnat_exception (1);
        } catch (__gnu_cxx::__gnat_exception& e) {
          __builtin_printf("caught gnat %p\n", &e);
          __builtin_printf("what: %s\n", e.what ());
          auto msg = e.__msg ();
          __builtin_printf("msg: %*s\n", msg.__len, msg.__ptr);
          throw;
        }
      } catch (abi::__foreign_exception& f) {
        __builtin_printf("caught foreign %p\n", &f);
      }
    
    prints:
    
      caught gnat 0x<_Unwind_Exception*>
      what: <GNAT Exception name>
      msg: <GNAT Exception message>
      caught foreign 0x<_Unwind_Exception*>
    
    I'm not entirely happy with the C++ API WRT how internal Ada strings
    are exposed as {len,ptr}, but something like std::string is probably
    undersirable in libsupc++, more so because it would require a hosted
    libstdc++.
    
    It would be cool to expose GNAT exceptions' backtraces as well, but
    System.Params.Default_Exception_Msg_Max_Length might conceivably vary,
    and that would offset the backtrace data, and we presumably don't want
    libstdc++ to depend on libgnat to get runtime access to that param and
    behave accordingly, and a #define that might go out of sync feels
    risky.
    
    for  libstdc++-v3/ChangeLog
    
            * config/abi/pre/gnu-versioned-namespace.ver (CXXABI_2.0):
            Drop duplicates.  Add __gnat_exception symbols.
            * config/abi/pre/gnu.ver (CXXABI_1.3.18): New.  Add
            __gnat_exception symbols.
            * libsupc++/Makefile.am (sources): Add gnat_exception.cc.
            * libsupc++/Makefile.in: Rebuild.
            * libsupc++/cxxabi.h (__gnu_cxx::__gnat_exception): New.
            (__cxxabiv1::__foreign_exception): Befriend it.
            * libsupc++/eh_personality.cc (PERSONALITY_FUNCTION): Select
            __gnat_exception for GNAT exceptions.
            * libsupc++/eh_arm.cc (__cxa_type_match): Likewise.
            * libsupc++/eh_catch.cc (__cxxabiv1::__cxa_begin_catch):
            Return unwindHeader for GNAT exceptions.
            * libsupc++/gnat_exception.cc: New.
            * libsupc++/unwind-cxx.h (__is_gnat_exception_class): New, for
            __ARM_EABI_UNWINDER__ and generic.
            (__gnat_exception_class) [!__ARM_EABI_UNWINDER__]: New.

Diff:
---
 .../config/abi/pre/gnu-versioned-namespace.ver     |  7 +-
 libstdc++-v3/config/abi/pre/gnu.ver                | 10 +++
 libstdc++-v3/libsupc++/Makefile.am                 |  1 +
 libstdc++-v3/libsupc++/Makefile.in                 |  5 +-
 libstdc++-v3/libsupc++/cxxabi.h                    | 16 ++++
 libstdc++-v3/libsupc++/eh_arm.cc                   |  6 +-
 libstdc++-v3/libsupc++/eh_catch.cc                 |  5 ++
 libstdc++-v3/libsupc++/eh_personality.cc           |  2 +
 libstdc++-v3/libsupc++/gnat_exception.cc           | 91 ++++++++++++++++++++++
 libstdc++-v3/libsupc++/unwind-cxx.h                | 31 ++++++++
 10 files changed, 169 insertions(+), 5 deletions(-)

diff --git a/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver b/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver
index 7c5059944272..6298a9ddeb28 100644
--- a/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver
+++ b/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver
@@ -286,8 +286,6 @@ CXXABI_2.0 {
     _ZTIN10__cxxabiv121__vmi_class_type_infoE;
     _ZTIN10__cxxabiv115__forced_unwindE;
     _ZTIN10__cxxabiv119__foreign_exceptionE;
-    _ZTIN10__cxxabiv115__forced_unwindE;
-    _ZTIN10__cxxabiv119__foreign_exceptionE;
 
     # typeinfo name
     _ZTS[a-z];
@@ -372,6 +370,11 @@ CXXABI_2.0 {
     _ZdaPvSt11align_val_t;
     _ZdaPvSt11align_val_tRKSt9nothrow_t;
     _ZdaPv[jmy]St11align_val_t;
+
+    # __gnu_cxx::__gnat_exception typeinfo and member functions
+    _ZNK9__gnu_cxx16__gnat_exception*;
+    _ZTIN9__gnu_cxx16__gnat_exceptionE;
+    _ZTSN9__gnu_cxx16__gnat_exceptionE;
 };
 
 # Symbols in the support library (libsupc++) supporting trans-mem.
diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver
index 3a6afac83087..ab26420f9a67 100644
--- a/libstdc++-v3/config/abi/pre/gnu.ver
+++ b/libstdc++-v3/config/abi/pre/gnu.ver
@@ -2999,6 +2999,16 @@ CXXABI_1.3.17 {
 } CXXABI_1.3.15;
 #endif
 
+CXXABI_1.3.18 {
+
+  global:
+    # __gnu_cxx::__gnat_exception typeinfo and member functions
+    _ZNK9__gnu_cxx16__gnat_exception*;
+    _ZTIN9__gnu_cxx16__gnat_exceptionE;
+    _ZTSN9__gnu_cxx16__gnat_exceptionE;
+
+} CXXABI_1.3.17;
+
 # Symbols in the support library (libsupc++) supporting transactional memory.
 CXXABI_TM_1 {
 
diff --git a/libstdc++-v3/libsupc++/Makefile.am b/libstdc++-v3/libsupc++/Makefile.am
index a3dc60f39458..3523366fc13f 100644
--- a/libstdc++-v3/libsupc++/Makefile.am
+++ b/libstdc++-v3/libsupc++/Makefile.am
@@ -80,6 +80,7 @@ sources = \
 	enum_type_info.cc \
 	function_type_info.cc \
 	fundamental_type_info.cc \
+	gnat_exception.cc \
 	guard.cc \
 	guard_error.cc \
 	hash_bytes.cc \
diff --git a/libstdc++-v3/libsupc++/Makefile.in b/libstdc++-v3/libsupc++/Makefile.in
index db5e83803d52..ec4dd4af3d29 100644
--- a/libstdc++-v3/libsupc++/Makefile.in
+++ b/libstdc++-v3/libsupc++/Makefile.in
@@ -161,8 +161,8 @@ am__objects_1 = array_type_info.lo atexit_arm.lo atexit_thread.lo \
 	eh_catch.lo eh_exception.lo eh_globals.lo eh_personality.lo \
 	eh_ptr.lo eh_term_handler.lo eh_terminate.lo eh_tm.lo \
 	eh_throw.lo eh_type.lo eh_unex_handler.lo enum_type_info.lo \
-	function_type_info.lo fundamental_type_info.lo guard.lo \
-	guard_error.lo hash_bytes.lo nested_exception.lo \
+	function_type_info.lo fundamental_type_info.lo gnat_exception.lo \
+	guard.lo guard_error.lo hash_bytes.lo nested_exception.lo \
 	new_handler.lo new_op.lo new_opnt.lo new_opv.lo new_opvnt.lo \
 	new_opa.lo new_opant.lo new_opva.lo new_opvant.lo del_opa.lo \
 	del_opant.lo del_opsa.lo del_opva.lo del_opvant.lo \
@@ -546,6 +546,7 @@ sources = \
 	enum_type_info.cc \
 	function_type_info.cc \
 	fundamental_type_info.cc \
+	gnat_exception.cc \
 	guard.cc \
 	guard_error.cc \
 	hash_bytes.cc \
diff --git a/libstdc++-v3/libsupc++/cxxabi.h b/libstdc++-v3/libsupc++/cxxabi.h
index 244b21fc9db3..09e48bd2a866 100644
--- a/libstdc++-v3/libsupc++/cxxabi.h
+++ b/libstdc++-v3/libsupc++/cxxabi.h
@@ -54,6 +54,10 @@
 #include <bits/cxxabi_init_exception.h>
 
 #ifdef __cplusplus
+namespace __gnu_cxx
+{
+  class __gnat_exception;
+}
 namespace __cxxabiv1
 {
   extern "C"
@@ -662,6 +666,7 @@ namespace __cxxabiv1
   {
     virtual ~__foreign_exception() throw();
     virtual void __pure_dummy() = 0; // prevent catch by value
+    friend class __gnu_cxx::__gnat_exception;
   };
 
 } // namespace __cxxabiv1
@@ -690,6 +695,17 @@ namespace abi = __cxxabiv1;
 
 namespace __gnu_cxx
 {
+  // A magic placeholder class that can be caught by reference
+  // to recognize GNAT exceptions.
+  class __gnat_exception : public abi::__foreign_exception
+  {
+    virtual ~__gnat_exception() throw();
+  public:
+    const char *what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW;
+    struct __ada_str { unsigned __len; const char *__ptr; };
+    __ada_str __msg() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW;
+  };
+
   /**
    *  @brief Exception thrown by __cxa_guard_acquire.
    *  @ingroup exceptions
diff --git a/libstdc++-v3/libsupc++/eh_arm.cc b/libstdc++-v3/libsupc++/eh_arm.cc
index 7008ce60f6df..5d0ed1d789fe 100644
--- a/libstdc++-v3/libsupc++/eh_arm.cc
+++ b/libstdc++-v3/libsupc++/eh_arm.cc
@@ -56,7 +56,11 @@ __cxa_type_match(_Unwind_Exception* ue_header,
   if (forced_unwind)
     throw_type = &typeid(abi::__forced_unwind);
   else if (foreign_exception)
-    throw_type = &typeid(abi::__foreign_exception);
+    {
+      throw_type = &typeid(abi::__foreign_exception);
+      if (__is_gnat_exception_class(ue_header->exception_class))
+	throw_type = &typeid(__gnu_cxx::__gnat_exception);
+    }
   else
     {
       if (dependent_exception)
diff --git a/libstdc++-v3/libsupc++/eh_catch.cc b/libstdc++-v3/libsupc++/eh_catch.cc
index 424dbc724b6a..650c75e177d0 100644
--- a/libstdc++-v3/libsupc++/eh_catch.cc
+++ b/libstdc++-v3/libsupc++/eh_catch.cc
@@ -58,6 +58,11 @@ __cxxabiv1::__cxa_begin_catch (void *exc_obj_in) _GLIBCXX_NOTHROW
       // Remember for end_catch and rethrow.
       globals->caughtExceptions = header;
 
+      // For GNAT exceptions, return the address of the unwind header.
+      // __gnat_exception member functions rely on that.
+      if (__is_gnat_exception_class(header->unwindHeader.exception_class))
+	return &header->unwindHeader;
+
       // ??? No sensible value to return; we don't know what the
       // object is, much less where it is in relation to the header.
       return 0;
diff --git a/libstdc++-v3/libsupc++/eh_personality.cc b/libstdc++-v3/libsupc++/eh_personality.cc
index 10458318f88e..ff242872d658 100644
--- a/libstdc++-v3/libsupc++/eh_personality.cc
+++ b/libstdc++-v3/libsupc++/eh_personality.cc
@@ -571,6 +571,8 @@ PERSONALITY_FUNCTION (int version,
       else if (foreign_exception)
 	{
 	  throw_type = &typeid(abi::__foreign_exception);
+	  if (__is_gnat_exception_class(exception_class))
+	    throw_type = &typeid(__gnu_cxx::__gnat_exception);
 	}
       else
 #endif
diff --git a/libstdc++-v3/libsupc++/gnat_exception.cc b/libstdc++-v3/libsupc++/gnat_exception.cc
new file mode 100644
index 000000000000..a1b6d1ee71fc
--- /dev/null
+++ b/libstdc++-v3/libsupc++/gnat_exception.cc
@@ -0,0 +1,91 @@
+// -*- C++ -*- __gnu_cxx::__gnat_exception implementation.
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of GCC.
+//
+// GCC is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3, or (at your option)
+// any later version.
+//
+// GCC is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+#include "exception"
+#include <cxxabi.h>
+#include "unwind.h"
+
+namespace {
+
+#if __ARM_EABI_UNWINDER__ && ! __USING_SJLJ_EXCEPTIONS__
+  typedef _Unwind_Control_Block GNAT_Unwind_Header;
+#else
+  typedef struct
+  {
+    _Unwind_Exception_Class exception_class;
+    _Unwind_Exception_Cleanup_Fn exception_cleanup;
+    _Unwind_Word private_[6];
+  } GNAT_Unwind_Header;
+#endif
+
+  struct GNAT_Exception_Data
+  {
+    bool not_handled_by_others;
+    char lang;
+    unsigned name_length;
+    const char *full_name; /* NULL terminated string.  */
+    /* ... */
+  };
+
+  struct GNAT_Exception_Occurrence
+  {
+    GNAT_Exception_Data *eid;
+    GNAT_Unwind_Header *header;
+    unsigned msg_length;
+    char msg[/*msg_[max_]length*/];
+    /* ... */
+  };
+
+  struct GNAT_Exception
+  {
+    GNAT_Unwind_Header header;
+    GNAT_Exception_Occurrence occurrence;
+  };
+}
+
+__gnu_cxx::__gnat_exception::~__gnat_exception() throw()
+{
+}
+
+const char*
+__gnu_cxx::__gnat_exception::what()
+  const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_USE_NOEXCEPT
+{
+  GNAT_Exception const *ex = reinterpret_cast<GNAT_Exception const *>(this);
+  if (ex->occurrence.eid)
+    return ex->occurrence.eid->full_name;
+  return "GNAT exception";
+}
+
+struct __gnu_cxx::__gnat_exception::__ada_str
+__gnu_cxx::__gnat_exception::__msg()
+  const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_USE_NOEXCEPT
+{
+  GNAT_Exception const *ex = reinterpret_cast<GNAT_Exception const *>(this);
+  __ada_str str = {
+    ex->occurrence.msg_length,
+    ex->occurrence.msg
+  };
+  return str;
+}
diff --git a/libstdc++-v3/libsupc++/unwind-cxx.h b/libstdc++-v3/libsupc++/unwind-cxx.h
index b6030f0d1978..6e69c1ff9c5b 100644
--- a/libstdc++-v3/libsupc++/unwind-cxx.h
+++ b/libstdc++-v3/libsupc++/unwind-cxx.h
@@ -250,6 +250,20 @@ __is_gxx_exception_class(_Unwind_Exception_Class c)
 	 && (c[7] == '\0' || c[7] == '\x01');
 }
 
+static inline bool
+__is_gnat_exception_class(_Unwind_Exception_Class c)
+{
+  // TODO: Take advantage of the fact that c will always be word aligned.
+  return c[0] == 'G'
+	 && c[1] == 'N'
+	 && c[2] == 'U'
+	 && c[3] == '-'
+	 && c[4] == 'A'
+	 && c[5] == 'd'
+	 && c[6] == 'a'
+	 && c[7] == '\0';
+}
+
 // Only checks for primary or dependent, but not that it is a C++ exception at
 // all.
 static inline bool
@@ -346,6 +360,23 @@ __is_gxx_exception_class(_Unwind_Exception_Class c)
       || c == __gxx_dependent_exception_class;
 }
 
+// GNAT reports exceptions as "GNU-Ada\0"
+const _Unwind_Exception_Class __gnat_exception_class
+= ((((((((_Unwind_Exception_Class) 'G'
+	 << 8 | (_Unwind_Exception_Class) 'N')
+	<< 8 | (_Unwind_Exception_Class) 'U')
+       << 8 | (_Unwind_Exception_Class) '-')
+      << 8 | (_Unwind_Exception_Class) 'A')
+     << 8 | (_Unwind_Exception_Class) 'd')
+    << 8 | (_Unwind_Exception_Class) 'a')
+   << 8 | (_Unwind_Exception_Class) '\0');
+
+static inline bool
+__is_gnat_exception_class(_Unwind_Exception_Class c)
+{
+  return c == __gnat_exception_class;
+}
+
 // Only checks for primary or dependent, but not that it is a C++ exception at
 // all.
 static inline bool
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.