13

Using GNAT Ada and Gnu C++, I'm interfacing an Ada piece of code with a c++ wrapper and I'd like to catch Ada exceptions properly when running this (stupid) code:

with ada.text_io;

package body ada_throw is

   procedure ada_throw is
   begin
      ada.text_io.put_line ("hello");
      raise program_error;
   end ada_throw;       

end ada_throw;

relevant spec code is:

package ada_throw is

   procedure ada_throw;
   pragma export (convention => C, entity => ada_throw, external_name => "ada_throw");

end ada_throw;

Whe doing this on the C++ side:

#include <iostream>

extern "C"
{
  void ada_throw();
  void adainit();
}

int main()
{
  adainit();
  ada_throw();
  std::cout << "end of program" << std::endl;
  return 0;
}

I'm getting this:

hello

raised PROGRAM_ERROR : ada_throw.adb:8 explicit raise

So the exception mechanism works, I don't get the last print of my C++ program and the return code is non-zero.

Now I want to catch the exception. If I use catch(...) it works, but I can't get the explicit error message anymore, so I tried this:

#include <iostream>
#include <cxxabi.h>
extern "C"
{
  void ada_throw();
  void adainit();
}

int main()
{
  adainit();

  try
  {
    ada_throw();
  }
  catch (abi::__foreign_exception const &e)
  {
    std::cout << "exception" << std::endl;        
  }

  std::cout << "end of program" << std::endl;
  return 0;
}

it works properly, I get:

hello
exception
end of program

The only catch is that abi::__foreign_exception doesn't have a what() method, so I cannot get hold of the meaningful error message.

And debugging the program to try to hack into e is also a dead end since it's just a null pointer with the proper type:

(gdb) p &e
$2 = (const __cxxabiv1::__foreign_exception *) 0x0

Is there a way to get it from the C++ ?

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • This really depends on how your respective ada and c++ compilers implement exceptions. Both languages specify things differently, and it takes deliberate design choices - in both the compilers code-generation, and in use of library features - to ensure compatibility or, at least, a consistent mapping across the boundary between languages. Any solution will be specific to particular sets of compilers. – Peter May 21 '18 at 13:00
  • Yes, this question is tagged gnat as well. So it's using g++/gnat, I'll edit – Jean-François Fabre May 21 '18 at 13:12
  • 1
    The world of ABI is only very slowly evolving beyond a C interface. Exceptions are a compiler specific thing, although there might be a platform convention to which they adhere. – Khouri Giordano May 21 '18 at 13:27
  • Ada exception definition is given in `a-except.ads`. The file contains a line that I believed could be a clue :`Machine_Occurrence : System.Address; -- The underlying machine occurrence. For GCC, this corresponds to the _Unwind_Exception structure address.` But since you have no valid pointer ... – LoneWanderer May 21 '18 at 13:48
  • 1
    Also found this : `function Get_Exception_Machine_Occurrence (X : Exception_Occurrence) return System.Address; pragma Export (Ada, Get_Exception_Machine_Occurrence,"__gnat_get_exception_machine_occurrence"); -- Get the machine occurrence corresponding to an exception occurrence. It is Null_Address if there is no machine occurrence (in runtimes tha doesn't use GCC mechanism) or if it has been lost (Save_Occurrence doesn't save the machine occurrence).` – LoneWanderer May 21 '18 at 13:49
  • hmmm let me try our most recent Ada compiler to see if the pointer is still null – Jean-François Fabre May 21 '18 at 13:50
  • For ABI documentation, some clues could be found here: https://stackoverflow.com/questions/18672191/gcc-c-exception-handling-implementation (points to C++ Itanium exception handling https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html). Here is some clues for Ada exceptions https://www2.adacore.com/gap-static/GNAT_Book/html/node25.htm Figure 18.2 looks interesting. – LoneWanderer May 21 '18 at 13:57
  • Another clue : https://gcc.gnu.org/onlinedocs/gnat_ugn/Exception-Handling-Control.html states `The other approach is called ’zero cost’ exception handling. With this method, the compiler builds static tables to describe the exception ranges. No dynamic code is required when entering a frame containing an exception handler.[...] Note that in this mode and in the context of mixed Ada and C/C++ programming, to propagate an exception through a C/C++ code, the C/C++ code must be compiled with the -funwind-tables GCC’s option."` – LoneWanderer May 21 '18 at 14:31
  • I guess that `-funwind_tables` is set by default since the exception propagates to the C++ layer all right. It's just that the `e` variable is a null-reference (with latest GNAT 17.2 as well) – Jean-François Fabre May 21 '18 at 14:37
  • The rule of thumb is to never allow exceptions propagate across language boundaries. Throwing ada exception through C (which does not have exceptions at all) and trying to catch in C++ (which is not aware of ada exceptions existance) seems like a bizarre idea. Even if this accidentally works due to somewhat compatible underlaying exception implementation that would be a straight shot in the leg. The only way is to convert exception into the appropriate error form when it passes each language boundary. – user7860670 May 31 '18 at 20:05
  • 2
    I was talking with AdaCore GNAT maintainers and they have a lead for that though: catch the exception, then invoke some `GNAT.Last_Exception` (don't remember exactly) to get the last thrown exception. Not perfect in multithreaded environment, but would work most of the time (unfortunately the feature is broken, and being fixed at the current time, only for beta releases of the GNATPro edition, so not available to the public / GPL until 2019) – Jean-François Fabre May 31 '18 at 20:11

2 Answers2

3

In Ada you can get information about an exception occurrence using the functions Ada.Exceptions.Exception_Name and Ada.Exceptions.Exception_Message, which are a part of the standard library. One option is to provide a thin binding to these two functions, and call them, when you need information about the exception. (Exactly how to map abi::__foreign_exception to Ada.Exceptions.Exception_ID is left as an exercise for the reader.)

Another option is to make it explicit to the Ada compiler that you are writing C++ on the other side, and use CPlusPlus instead of C as your export convention. That may make the compiler provide exceptions, which C++ understands.

This is only a partial answer, as I'm very much out of practice with C++, but I hope it can help you in the right direction.

Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
  • 2
    `CPlusPlus` doesn't exist, but `CPP` does :) trying that now. ouch, the Ada compiler doesn't seem to be able to generate a proper C++ name. Still the same `_ada_throw` symbol... – Jean-François Fabre May 21 '18 at 12:16
  • 2
    Upvoted for your first option, but I suspect your alternative isn't going to work, and that this answer would be improved by removing it. –  May 21 '18 at 12:50
3

Depending on what you want to do with the exception in the C++ world, it might be easier/cleaner to write a wrapper in Ada, which calls ada_throw and handles any exception. Then simply call this wrapper from C++.

If the C++ code must see the exception and know what's going on, the wrapper could provide a what() method lookalike, and re-raise either the same or a new (custom) exception.

  • 1
    The `GNAT.CPP_Exceptions` seems to be the perfect thing to perform the wrapping. Quoting its .ads file `-- This package provides an interface for raising and handling C++ exceptions`. I'm giving a look at https://www.adacore.com/gems/gem-114-ada-and-c-exceptions – LoneWanderer May 21 '18 at 12:42
  • 1
    Unfortunatly, the Ada Gem shows how to pass exceptions across languages, but no info given on how to get the exception informations in the C++ part. – LoneWanderer May 21 '18 at 12:47
  • that's a good idea. But I have quite a few entrypoints, so I'd have to write a wrapper for each entrypoint. – Jean-François Fabre May 21 '18 at 12:53