C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Use the exception system to check for bases at runtime

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Sat, 6 Apr 2024 21:04:24 +0100
Earlier this week, I wrote:
> Before I go any further with this, I have to see if it can be
> implemented with the Microsoft compiler. Because if I can get it
> working with Microsoft, then I'll have the top 4 compilers.


Implementing this on the Microsoft compiler without compiler support
has been tricky.

On the GNU, LLVM and Intel compilers, you only need a 'type_info' in
order to throw and catch an exception. However on Microsoft, when you
throw an exception, they have a 'ThrowInfo' struct which contains an
array of pointers to 'type_info' structs for all the base classes of
the type being thrown.

So on Microsoft, if you throw a 'std::runtime_error', you have a
'ThrowInfo' struct which contains pointers to:
    typeid(std::exception)
    typeid(std::runtime_error)

Since I don't have compiler support to find the ThrowInfo's address
relative to the type_info's address, I had to write a function that
does a little gymnastics moving back and forth in memory to find it
(see my comments in the below code).

So then inside my 'base' function, I convert the supplied 'type_info'
to a 'ThrowInfo', I iterate throw all the contained type_info's, and
if there's a match, I adjust the 'this' pointer and return it. In
order to adjust the 'this' pointer, I use the "_PMD::mdisp" field
however I'm not sure if this is correct (I see that there's also a
'pdisp' and a 'vdisp' but I don't know what's what).

The following code is tested and working on 32-Bit and 64-Bit x86
MS-Windows 10. In order to make it work properly with complex virtual
inheritance, maybe you need to also factor in "_PMD::pdisp" and
"_PMD::vdisp". I don't know.

I went to the bother of coding this pretty much just as a 'proof of
concept' -- because realistically, if a compiler vendor were to
implement this, they'd probably just re-use the code they have for
'dynamic_cast'. Anyway here's the code:

#include <cstdint> // int32_t
#include <typeinfo> // type_info
#include <type_traits> // conditional, is_const

// The following struct is used on both 32-Bit and 64-Bit
// Microsoft Visual C++ compilers for holding info needed
// to catch C++ exceptions.
// On 32-Bit machines, each member is a pointer.
// On 64-Bit machines, each member is a 32-Bit offset which
// is to be added onto the program's
// base address (see 'addr0' below).
struct ThrowInfo {
    std::int32_t attributes,
                 pmfnUnwind,
                 pForwardCompat,
                 pCatchableTypeArray;
};

#ifdef _WIN64
    extern "C" char *GetModuleHandleA(char const*);
    char const *const addr0 = GetModuleHandleA(nullptr);
#else
    char const *const addr0 = nullptr;
#endif

// In the following function, I do a bit of gymnastics
// to try find the address of the 'ThrowInfo' relative
// to the address of the 'type_info'.
// See the comments inside the function.
ThrowInfo const *TypeInfo_To_ThrowInfo(std::type_info const *const pti)
{
    using std::int32_t;

    // Step 1: Moving backwards, find the address of
    // the type_info inside the CatchableType
    int32_t const n0 = (char*)pti - addr0, *p0 = (int32_t*)pti;
    for (;;)
    {
        if ( n0 != *--p0 ) continue;

        //cout << "Found the address of the type_info inside the
CatchableType: " << (void*)p0 << endl;

        // Step 2: Calculate the address of the CatchableType
        // (it's 4 bytes behind the address of the type_info);
        char const *const pCatchableType = (char*)(p0 - 1);

        // Step 3: Moving forwards, find the address of the
        // CatchableType inside the CatchableTypeArray
        int32_t const n1 = pCatchableType - addr0, *p1 =
(int32_t*)pCatchableType;
        for (;;)
        {
            if ( n1 != *++p1 ) continue;

            //cout << "Found the address of the CatchableType inside
the CatchableTypeArray" << (void*)p1 << endl;
            for (;;)
            {
                // Step 4: Calculate the address of the CatchableTypeArray
                // by moving backward until we find a small number
                if ( *--p1 > 16 ) continue;

                //cout << "Found the address of the
CatchableTypeArray: " << (void*)p1 << endl;

                // Step 4: Calculate the address of the _ThrowInfo by
                // subtracting 16 bytes (32-Bit) or 32 bytes (64-Bit)
                ThrowInfo *const pthrowinfo = (ThrowInfo*)(p1 - sizeof(void*));
                //cout << "Found the address of the ThrowInfo: " <<
(void*)pthrowinfo << endl;
                return pthrowinfo;
            }
        }
    }
}

template<class Base>
Base *base(std::conditional_t<std::is_const_v<Base>,void const,void>
*const parg, std::type_info const &ti)
{
    ThrowInfo const *const pthi = TypeInfo_To_ThrowInfo(&ti);

    struct CatchableTypeArray { int32_t nCatchableTypes,
arrayOfCatchableTypes[]; };
    struct CatchableType { int32_t properties, pType; _PMD
thisDisplacement; };

    CatchableType const *p = nullptr;

    CatchableTypeArray const *const pcta = (CatchableTypeArray*)(addr0
+ pthi->pCatchableTypeArray);
    for ( unsigned i = 0u; i < pcta->nCatchableTypes; ++i )
    {
        CatchableType const *const pct = (CatchableType *)(addr0 +
pcta->arrayOfCatchableTypes[i]);
        std::type_info const *const pti = (std::type_info*)(addr0 + pct->pType);
        if ( &ti == pti )
        {
            p = pct;
            break;
        }
    }

    if ( nullptr == p ) return nullptr;

    return (Base*) ((char*)parg + p->thisDisplacement.mdisp); // Or
pdisp? Or vdisp? I don't know.
}

// ========================== Here comes the test code
#include <cassert> // assert
#include <any> // any
#include <iostream> // cout, endl
#include <stdexcept> // exception, runtime_error

using std::cout, std::endl;

void *pointer_from_any(std::any const &a)
{
    if ( false == a.has_value() ) return nullptr;

    return (void*)&a;
}

char const *get_what(std::any const &a)
{
    void *const pderived = pointer_from_any(a);
    if ( nullptr == pderived ) return "<no info>";
    auto *const pbase = base<std::exception>( pderived, a.type() );
    if ( nullptr == pbase ) return "<no info>";
    cout << "Address of base inside function 'get_what': " <<
(void*)pbase << endl;
    return pbase->what();
}

int main(void)
{
    std::any a = std::runtime_error("Frogs don't have wings");
    auto &re = std::any_cast<std::runtime_error&>(a);

    cout << "The following three pointers should be equal:\n";
    cout << pointer_from_any(a) << endl;
    cout << (void*)&re << endl;
    cout << (void*)static_cast<std::exception*>(&re) << endl;

    cout << get_what(a) << endl;

    // The invocation of 'base' on the next line should fail
    // because 'logic_error' isn't a base of 'runtime_error'
    std::logic_error *const ple = base<std::logic_error>(&re, a.type());
    assert( nullptr == ple );

    // The next line is need to ensure that the compiler
    // writes the ThrowInfo for std::runtime_error into
    // the final executable file.
    try { throw std::runtime_error("frog"); } catch(...){}
}

Received on 2024-04-06 20:04:37