16

I'm using libjpeg right now to save JPEG images. If there is an error, libjpeg's default behavior is to call exit(), which I want to avoid since it's not a fatal error for my program. libjpeg allows you to use your own error manager, and mandates that if you use your own error_exit() function (which calls exit() by default) you must not return control to the caller. libjpeg suggests using setjmp.h to meet this requirement and not exit() the program.

However, I am writing a C++ program, and I have access to exceptions. This question's answer states it's safe (as in well-defined behavior) to throw an exception from the callback. But it doesn't mention dynamic libraries, and there's a general rule of thumb that you don't throw exceptions across dynamic library boundaries.

Here's an example:

#include <iostream>
#include <jpeglib.h>
#include <cstdio>
#include <stdexcept>

static void handleLibJpegFatalError(j_common_ptr cinfo)
{
  (*cinfo->err->output_message)(cinfo);
  throw std::runtime_error("error in libjpeg, check stderr");
}

int main()
{
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  FILE* file = std::fopen("out.jpeg", "wb"); // assume this doesn't fail for this example

  try
    {
      cinfo.err = jpeg_std_error(&jerr);
      jerr.error_exit = handleLibJpegFatalError;

      // let's say this triggers a fatal error in libjpeg and handleLibJpegFatalError() is called
      // by libjpeg
      jpeg_create_compress(&cinfo);
    }
  catch (...)
    {
      std::cerr << "Error saving the JPEG!\n";
    }

  jpeg_destroy_compress(&cinfo);
  std::fclose(file);
}

What I would like to know is: can I throw an exception from this callback, and catch it back in my application, even if libjpeg is compiled as a dynamic library? libjpeg may be a static or dynamic library, and if it's a dynamic library it may possibly be built with a different compiler. However, the code that throws and catches the exception will certainly be in the same compilation unit. Is the above code safe?

FYI, I'm developing for OS X and Windows (and keeping the future of a Linux possibility in mind), so I'm more interested in if this is known to be well defined behavior in general, and not for a specific platform/compiler.

Community
  • 1
  • 1
Cornstalks
  • 37,137
  • 18
  • 79
  • 144
  • That's perfectly safe. Why wouldn't it be? Shared library calls still use the same call stack. – nw. Jun 08 '12 at 21:20
  • 1
    This might be related: http://stackoverflow.com/questions/10318363/is-it-safe-for-xs-error-handler-to-throw-exceptions – Pubby Jun 08 '12 at 21:24
  • 1
    @nw: I can't think of why it wouldn't be save; I just want to make sure nothing will get trashed when unwinding the stack. It may be perfectly safe, but I've been bitten in the butt by assuming things in the past so I'm playing it safe here and double checking. – Cornstalks Jun 08 '12 at 21:27
  • @nw. That's assuming the DLL uses the same conventions for stack management as the calling application, which is only guaranteed if both were compiled using the same compiler and runtime. In general, it's a bad idea to throw across DLL boundaries (at least in Windows). – ComicSansMS Jun 08 '12 at 22:05

3 Answers3

7

The other answer applies here. Nothing will get trashed when unwinding the stack. It doesn't even matter if the library uses some crazy calling convention internally, as long as it doesn't specifically mess with your C++ implementation's exception handling structures (which it wont as a C program). No C++ implementation that I know of finds the catch block by popping off stack frames (that would make optimization a nightmare), they all maintain internal structures for exception handling. As long as a call lower down in the call chain doesn't mess with those structures, stack unwinding will work perfectly fine for all of your personal code. Now, in general, it's quite possible that this would leave a library with a messed up internal state as you never return execution to the library for cleanup, but in the case of your error callback, libjpeg expects for control flow to not return and has presumably already cleaned up after itself.

In this case, I would go for it. In general, I would only throw fatal exceptions from a C callback.

Hope that helped.

nw.
  • 4,795
  • 8
  • 37
  • 42
  • So far as I can tell, exception handling either requires a means of locating everything on the stack, or being able to locate an object which is global to all code running on a thread, but won't conflict with other threads. The latter approach may be nicer when it's usable, but would be problematic for freestanding systems that know nothing about the underlying system's thread mechanics. – supercat Nov 22 '19 at 17:43
5

It's not safe. Depending on how the relevant non-C++ library code was compiled, the necessary unwind tables may not exist. This is just a practical reason why it might fail; the conceptual reason is that it's simply undefined behavior.

You should follow the documentation and use setjmp/longjmp to get just outside the call to libjpeg code, then throw an exception immediately in the if (setjmp(...)) { ... } body if you want to use exceptions.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Are you talking about unwinding the frames within the library call chain? Those won't get unwound with `setjmp`, either. C code would have to specifically overwrite the SEH frame/.eh_frame/etc to mess up unwinding for your C++ calls, which wouldn't happen unless someone was intentionally trying to mess stuff up. – nw. Jun 08 '12 at 23:30
  • 1
    It's not safe to throw an exception in a callback unless it will also be handled in the callback. As for where/how it can fail, if the C library code does not have unwind information at all (no `.eh_frame`) and does not use frame pointers, there is absolutely no way to backtrace beyond the C code and make sense of the earlier callframe where the exception is supposed to be handled. – R.. GitHub STOP HELPING ICE Jun 08 '12 at 23:57
0

As discussed in other answers, it ought to be safe as long as you are in control of all the modules and they will be built by the same toolchain (inc. statically linking).

But I want to add a caveat here that some toolchains require this support to be turned on, since libjpeg's functions are marked extern "C". By default, Visual Studio assumes that such functions will not propagate exceptions.

If you don't turn this on, expect much pain. I spent hours on a testcase almost identical to yours before I realised this.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055