views:

104

answers:

1

Hi,

I'm building a shared library with f-no-rtti. Internally, this library throws std:invalid_argument and catches std::exception, but the catch clause is never entered.

The following code reproduces the problem (g++ 4.2, Mac OS X 10.6):

// library.cpp: exports f(), compiled with -fno-rtti
#include <stdexcept>
#include <iostream>
extern "C" {
    void f() {
        try {
            throw std::invalid_argument("std::exception handler");
        } catch( std::exception& e) {
            std::cout << e.what() << "\n";
        } catch(...) {
            std::cout << "... handler\n";
        }
    }
}

// main.cpp: the main executable, dynamically loads the library
#include <dlfcn.h>
typedef void(*fPtr)();

int main() {
    void* handle = dlopen( "./libexception_problem.dylib", RTLD_LAZY );
    fPtr p_f = reinterpret_cast<fPtr>( dlsym( handle, "f" ) );
    p_f();
}

Output:

MacBook-Pro:teste pfranco$ # works fine with rtti
MacBook-Pro:teste pfranco$ g++ -c library.cpp && g++ -shared -o libexception_problem.dylib library.o && g++ main.cpp -o main && ./main
std::exception handler
MacBook-Pro:teste pfranco$ # breaks with -fno-rtti
MacBook-Pro:teste pfranco$ g++ -c -fno-rtti library.cpp && g++ -shared -o libexception_problem.dylib library.o && g++ -fno-rtti main.cpp -o main && ./main
... handler
MacBook-Pro:teste pfranco$ #-no_dead_strip_inits_and_terms doesn't change anything
MacBook-Pro:teste pfranco$ g++ -c -no_dead_strip_inits_and_terms -fno-rtti library.cpp && g++ -no_dead_strip_inits_and_terms -shared -o libexception_problem.dylib library.o && g++ -fno-rtti -no_dead_strip_inits_and_terms main.cpp -o main && ./main
... handler
MacBook-Pro:teste pfranco$ # linking against the shared library works, but this isn't always an option
MacBook-Pro:teste pfranco$ g++ -c -fno-rtti library.cpp && g++ -shared -o libexception_problem.dylib library.o && g++ -fno-rtti main.cpp -o main -L. -lexception_problem && ./main
std::exception handler

This only happens if the code that throws is in a shared library, and only if the caught type is a base class of the actual exception - catch(std::invalid_argument&) works fine, std::logic_error& doesn't.

Interestingly, this doesn't happen on Linux, even when running the exact same commands.

Questions:

  1. Why does this happen? Is this a bug, undefined behavior or by design?
  2. How could I make it work, short of linking against the library?

Thanks a lot.

A: 

From the info page for gcc (my highlighing).

-fno-rtti Disable generation of information about every class with virtual functions for use by the C++ runtime type identification features (dynamic_cast and typeid). If you don't use those parts of the language, you can save some space by using this flag. Note that exception handling uses the same information, but it will generate it as needed. The dynamic_cast operator can still be used for casts that do not require runtime type information, i.e. casts to void * or to unambiguous base classes.

RTTI is a core part of the language. If the compiler allows you to disable itm you're working outside of the rules of the language so aren't necessarily going to have everything work as you expect.

Charles Bailey
Yeah, I'd read that, but isn't "it will generate it as needed" the key part? Thanks.
Pedro d'Aquino
@Pedro d'Aquino: To be honest, I don't know. The difficulty is that you're now using a language variant that is defined implicitly by gcc's actually behaviour. I recommend against trying to disable RTTI.
Charles Bailey
I was about to post the same thing. It suggests to me that exception-handling is *supposed* to generate all necessary type information, regardless of whether `-fno-rtti` is specified, as far as GCC is concerned. But I didn't run `man` on an OSX or BSD system, so it might say something different there. It could be that they've deliberately changed the behaviour (and documented somewhere), it could be they've silently changed it (and not documented it anywhere - very naughty), or it could be a bug in their gcc fork and/or their C++ runtime support.
Steve Jessop
@Steve The `man` text is the same on my Mac.
Pedro d'Aquino
@Pedro: also comb the docs for dlls on OSX - it might mention something there, since apparently it only happens with dynamic-link. Otherwise, sounds like a candidate for a bug. Whether that's a bug in the compiler ("-fno-rtti should not break catch") or just in the documentation ("-fno-rtti should mention that it breaks catch") probably depends whether it's a mistake or a genuine limitation of their implementation.
Steve Jessop