How to demangle std::string as std::string

2.5k views Asked by At

I have been using some demangling code already for while to help with some debugging without having to write thousands of lines with dynamic casts or having to implement virtual functions which return the class name.

template <class CLASS>
std::string getClassName(CLASS &theObject)
{
    int status = 0;

    // Convert real name to readable string
    char *realName = abi::__cxa_demangle(typeid(theObject).name(), nullptr,
                                         nullptr, &status);
    ASSERT(status == 0); // Assert for success
    VERIFY(realName, return std::string());
    // Return as string + prevent memory leaks!
    const std::string result(realName);
    free(realName);
    return result;
}

The idea behind this code is simple, to output the class we are actually using. Though after switching to Ubuntu 14.04 I was no longer able to compile using clang and the c++-11/c++-14 standard, so I've switched to using the libc++ instead of libstdc++.

After switching to libc++, I've noticed that when I demangle 'std::string' it no longer outputs 'std::string', though instead it outputs:

std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >

Off course this is correct, since std::string is a typedef for std::basic_string. Though as far as I can see in both libc++ as libstdc++ this defined the same way, using the typedef. So I don't really understand why this demangling has changed by switching to the libc++.

Has someone an idea why this is different and how to get 'std::string' if CLASS would be 'std::string', and 'myTemplate' when CLASS would be 'myTemplate'?

Tnx in advance!

JVApen

1

There are 1 answers

0
Howard Hinnant On BEST ANSWER

libc++ uses inline namespaces to version its ABI. The inline namespace it currently uses is std::__1. This was done so that Apple could ship the gcc libstdc++, and libc++ at the same time. Dylib A might link to libstdc++, and dylib B might link to libc++, and the application might link to both dylibs. When that happens, you don't want to accidentally get the libstdc++ std::string mixed up with the libc++ std::string.

They have the same API, so it would be easy to accidentally do by passing std::string across a dylib boundary. The solution is to tell the compiler to mangle them differently, and that is exactly what the inline namespace does (and was invented for). Now if they get accidentally mixed in an application, a link time error will result because the linker sees two different types, as evidenced by their different mangled names.

The demangler's job is to simply tell you the truth: what is the demangled name of the symbol you feed it. It is working perfectly.

There does exist a way to turn the ABI versioning off in libc++. Search <__config> for _LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD. You can see how some platforms define this to open the inlined namespace and some don't. This is a very big hammer to use for your problem. Everything that compiles and links against libc++ on your platform must be rebuilt if you change the ABI of libc++ in this way.

Here is a simpler partial solution to your problem that I sometimes use:

#include <iostream>
#include <type_traits>
#include <memory>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <cxxabi.h>

namespace
{

inline
void
filter(std::string& r, const char* b)
{
    const char* e = b;
    for (; *e; ++e)
        ;
    const char* pb = "std::__1::";
    const int pl = std::strlen(pb);
    const char* pe = pb + pl;
    while (true)
    {
        const char* x = std::search(b, e, pb, pe);
        r.append(b, x);
        if (x == e)
            break;
        r += "std::";
        b = x + pl;
    }
}

}  // unnamed namespace

template <typename T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
                __cxxabiv1::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
                std::free
           );
    std::string r;
    if (own)
    {
        if (std::is_const<TR>::value)
            r += "const ";
        if (std::is_volatile<TR>::value)
            r += "volatile ";
        filter(r, own.get());
        if (std::is_lvalue_reference<T>::value)
            r += "&";
        else if (std::is_rvalue_reference<T>::value)
            r += "&&";
    }
    else
        r = typeid(TR).name();
    return r;
}

This simply filters out the ::__1 in the mangled name before presenting it to you. You could also use the same technique to then transform std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > to std::string if you wanted to.

The Itanium ABI has only a handful of "compressions" that correspond to typedefs like this. They are std::string, std::istream, std::ostream and std::iostream.