简体   繁体   中英

abi::__dynamic_cast returns nullptr for upcasts

I need to hook into C++'s exception throwing mechanism like this:

namespace __cxxabiv1
{
    extern "C" void __cxa_throw(void* voidPointerToActualObject, std::type_info* stdTypeInfoOfActualObject, void (*destructor)(void *))
    {
        // If thrownException is a custom exception type or something deriving from it, poke a value into it.
    }
}

If you're wondering "Why would you do that?"

I have this simple example of throwing an exception that's part of a very simple class hierarchy:

#include <stdexcept>

class Upper : public std::exception
{
    public:
        int pokeMe = 111111;
};
class Lower : public Upper {};

int main()
{
    throw Lower();
}

#include <cxxabi.h>

namespace __cxxabiv1
{
    extern "C" void __cxa_throw(void* voidPointerToActualObject, std::type_info* stdTypeInfoOfActualObject, void (*destructor)(void *))
    {
        // The point is to do the equivalent of this:
        Lower* staticallyTypedPointerToActualObject = reinterpret_cast<Lower*>(voidPointerToActualObject);
        auto thisWorks = dynamic_cast<Upper*>(staticallyTypedPointerToActualObject);
        thisWorks->pokeMe = 222222;

        // But we don't know the actual static type, so we can't get a statically typed pointer. We only have a void* and a type_info:
        auto abiTypeInfoOfActualObject = dynamic_cast<const abi::__class_type_info*>(stdTypeInfoOfActualObject);
        auto abiTypeInfoOfUpper = dynamic_cast<const abi::__class_type_info*>(&typeid(Upper));
        Upper* thisDoesNotWork = reinterpret_cast<Upper*>(abi::__dynamic_cast(voidPointerToActualObject, abiTypeInfoOfActualObject, abiTypeInfoOfUpper, -1));
        thisDoesNotWork->pokeMe = 333333;

        // Elided for clarity: Call the original __cxa_throw function here
        // Instead, suppress these warnings:
        (void)destructor; // Unused parameter
        while (1) { } // Return from non-returning function
    }
}

I don't see a reason why __dynamic_cast shouldn't be able to upcast, but it returns nullptr .
Why? And how do I get it to work?

It seems to be able to do downcasts just fine, BTW:

auto abiTypeInfoOfActualObject = dynamic_cast<const abi::__class_type_info*>(&typeid(Upper)); // Plonking this here for testing
auto abiTypeInfoOfUpper = dynamic_cast<const abi::__class_type_info*>(&typeid(Lower)); // Casting to Lower instead of Upper
Lower* thisDoesNotWork = reinterpret_cast<Lower*>(abi::__dynamic_cast(voidPointerToActualObject, abiTypeInfoOfActualObject, abiTypeInfoOfUpper, -1));

I managed to dig up this archived conversation from 2004 :

The ABI document does not require that __dynamic_cast perform a derived-to-base cast. Those __dynamic_cast operations that can actually be performed statically by the compiler must be performed statically by the compiler -- the runtime library does not expect to be called in that situation.

So that answers that question. Greeeeeeat.
But the conversation luckily mentions:

Yes; the holder knows the static type; it can throw a pointer of that type. The cast operation can catch the pointer type it's looking for, or fail the cast with catch(...).

That gave me the idea to try this (simplified version):

namespace __cxxabiv1
{

    using ThrowFunction = decltype(__cxa_throw)*;
    ThrowFunction oldThrowFunction = nullptr;

    extern "C" void __cxa_throw(void* voidPointerToActualObject, std::type_info* stdTypeInfoOfActualObject, void (*destructor)(void *))
    {
        if (oldThrowFunction == nullptr)
        {
            oldThrowFunction = (ThrowFunction)dlsym(RTLD_NEXT, "__cxa_throw");
        }

        try
        {
            oldThrowFunction(voidPointerToActualObject, stdTypeInfoOfActualObject, destructor);
        }
        catch (Upper& ex)
        {
            ex.pokeMe = 333333;
        }
        catch (...)
        {
        }

        oldThrowFunction(voidPointerToActualObject, stdTypeInfoOfActualObject, destructor);
    }
}

And I can't believe it but it actually works!

Edit: Disclaimer: It seems that this way, the destructor callback is actually called twice, because if use std::string pokeMe , the string is trashed by the time I get to the second call to oldThrowFunction. I'll experiment around with over the next few days.

Edit2: That's indeed the case. I couldn't find anything indicating whether __cxa_throw accepts nullptr as the destructor argument (it didn't crash for me, at least), so the safest bet is to pass a pointer to an empty dummy function:

void dummyDestructor(void*)
{
}
//...
oldThrowFunction(voidPointerToActualObject, stdTypeInfoOfActualObject, &dummyDestructor);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM