7
\$\begingroup\$

Following is my hand-rolled implementation of name demangling for names generated by Microsoft's compiler, partially reimplementing the UnDecorateSymbolName WinAPI function.

static std::string getCallConv(char code)
{
    static const std::map<char, std::string> callConvs{
        { 'A', "__cdecl   " },
        { 'I', "__fastcall" },
        { 'E', "__thiscall" },
        { 'G', "__stdcall " }
    };
    auto iter = callConvs.find(code);
    return (iter != std::end(callConvs)) ? iter->second : "";
}

static std::string getTypeName(char code)
{
    static const std::map<char, std::string> types{
        { 'C', "signed char   " },
        { 'D', "char          " },
        { 'E', "unsigned char " },
        { 'F', "short         " },
        { 'G', "unsigned short" },
        { 'H', "int           " },
        { 'I', "unsigned int  " },
        { 'J', "long          " },
        { 'K', "unsigned long " },
        { 'M', "float         " },
        { 'N', "double        " },
        { 'O', "long double   " },
        // These are just placeholders. A better demangler
        // would replace them with the actual type names.
        { 'P', "void*         " },
        { 'Q', "void[]        " },
        { 'U', "struct*       " },
        { 'V', "class*        " },
        { 'X', "void          " },
        { 'Z', "...           " }
    };
    auto iter = types.find(code);
    return (iter != std::end(types)) ? iter->second : "";
}

std::string demangle(const char * mangledName, bool baseNameOnly)
{
    std::string demangledName;
    const char * ptr = mangledName;

    if (ptr == nullptr || *ptr == '\0')
    {
        return demangledName;
    }

    // MSFT C++ names always start with a question mark.
    if (*ptr != '?')
    {
        // Assume a C function with the default underscore prefix,
        // returning the original name minus the underscore. It might
        // also contain more name decoration at the end, so ignore
        // anything after the first '@' character.
        if (*ptr == '_')
        {
            for (++ptr; *ptr != '\0' && *ptr != '@'; ++ptr)
            {
                demangledName.push_back(*ptr);
            }
        }
        else
        {
            demangledName = ptr;
        }
        return demangledName + "()";
    }

    // Skip over the first '?'
    ++ptr;

    std::string funcName;
    std::string className;
    std::string callConv;
    std::string retType;

    // Now copy until an '@' or the end of the string to extract the function name:
    for (; *ptr != '\0' && *ptr != '@'; ++ptr)
    {
        funcName.push_back(*ptr);
    }

    // Same for the class name that follows if present:
    if (*ptr == '@')
    {
        for (++ptr; *ptr != '\0' && *ptr != '@'; ++ptr)
        {
            className.push_back(*ptr);
        }

        // Trailing '@'s after class name.
        for (; *ptr == '@' && *ptr != '\0'; ++ptr)
        {
        }
    }

    // NOTE: Parameter list info is available but it's not being handled!
    if (!className.empty())
    {
        // A special member function: operators or constructor/destructor
        // (from a nested subclass... I'm no 100% sure if that's it, but looks like it...)
        if (funcName.length() >= 2 && funcName[0] == '?')
        {
            if (funcName[1] == '0') // Constructor
            {
                funcName = funcName.substr(2);
                demangledName = className + "::" + funcName + "::" + funcName + "()";
            }
            else if (funcName[1] == '1') // Destructor
            {
                funcName = funcName.substr(2);
                demangledName = className + "::" + funcName + "::~" + funcName + "()";
            }
            else if (funcName[1] == '4') // operator =
            {
                demangledName = className + "::" + funcName.substr(2) + "::operator=()";
            }
            else // The rest is currently ignored, but there's one for each num until 9 + A to Z.
            {
                std::size_t i;
                for (i = 0; i < funcName.length(); ++i)
                {
                    if (funcName[i] != '?' && funcName[i] != '_' && std::isalpha(funcName[i]))
                    {
                        break;
                    }
                }
                demangledName = className + "::" + funcName.substr(i) + "::???";
            }
        }
        else
        {
            // Apparently this is a template class...
            if (className.length() >= 2 && className[0] == '?' && className[1] == '$')
            {
                className = className.substr(2);
                className += "<T>";
            }

            if (!baseNameOnly) // Just the Class::Method() part?
            {
                // 'Q' should follow the '@' that separated a class name. Apparently meaningless.
                // 'S'/'2' I'm not sure... Does it mean a static class method???
                for (; *ptr != '\0' && (*ptr == 'Q' || *ptr == 'S' || *ptr == '2'); ++ptr)
                {
                }
                callConv += getCallConv(*ptr++);

                // The '_' is a qualifiers for "extended types", whatever that means.
                // It might precede the return type character.
                if (*ptr == '_')
                {
                    ++ptr;
                }
                retType += getTypeName(*ptr++);

                if (!callConv.empty())
                {
                    callConv += " ";
                }
                if (!retType.empty())
                {
                    retType += " ";
                }
            }
            demangledName = retType + callConv + className + "::" + funcName + "()";
        }
    }
    else
    {
        // A special member function: operators or constructor/destructor
        if (funcName.length() >= 2 && funcName[0] == '?')
        {
            if (funcName[1] == '0') // Constructor
            {
                funcName = funcName.substr(2);
                demangledName = funcName + "::" + funcName + "()";
            }
            else if (funcName[1] == '1') // Destructor
            {
                funcName = funcName.substr(2);
                demangledName = funcName + "::~" + funcName + "()";
            }
            else if (funcName[1] == '4') // operator =
            {
                demangledName = funcName.substr(2) + "::operator=()";
            }
            else // The rest is currently ignored, but there's one for each num until 9 + A to Z.
            {
                std::size_t i;
                for (i = 0; i < funcName.length(); ++i)
                {
                    if (funcName[i] != '?' && funcName[i] != '_' && std::isalpha(funcName[i]))
                    {
                        break;
                    }
                }
                demangledName = className + "::" + funcName.substr(i) + "::???";
            }
        }
        else
        {
            if (!baseNameOnly) // Just the Function() part?
            {
                // 'Y' should follow the '@'.
                // Probably just to differentiate from a class method...
                if (*ptr == 'Y')
                {
                    ++ptr;
                }
                callConv += getCallConv(*ptr++);

                if (*ptr == '_')
                {
                    ++ptr;
                }
                retType += getTypeName(*ptr++);

                if (!callConv.empty())
                {
                    callConv += " ";
                }
                if (!retType.empty())
                {
                    retType += " ";
                }
            }
            demangledName = retType + callConv + funcName + "()";
        }
    }

    return demangledName;
}

As you might have noted, my attempt is very hardcoded. It was done mostly by analyzing samples from DLLs and from unofficial information found on the following links: 1, 2.

I only care about the base function and class names, but ended up also implementing enough to extract the return type and calling convention. Parameter lists are too complicated to be worth the effort.

A couple input samples:

std::cout << demangle("?SetFilePointer@SFile@@SGKPAV1@JPAJK@Z", true);
// SFile::SetFilePointer()

std::cout << demangle("?swprintf@@YAHPAGIPBGZZ", true);
// swprintf()

std::cout << demangle("?FromString@?$Traits@UtagSIZE@@@FuBi@@SA_NPBDAAUtagSIZE@@@Z", true);
// Traits<T>::FromString()

Suggestions on how to improve this are welcome.

\$\endgroup\$
3
\$\begingroup\$
  1. Your function is too long. It's ~200 lines long, and is already logically made up of disparate parts. Break it up into smaller chunks. For instance:

    if (*ptr != '?') {
        return demangle_c_function(mangledName);
    }
    
  2. Take the mangled name as a std::string const&. We're really not concerned about performance here, and it'll make many parts of the code easier to write.

  3. Use the standard algorithms. They will the intent of your loops much clearer. For instance, you have this loop:

    for (++ptr; *ptr != '\0' && *ptr != '@'; ++ptr)
    {
        demangledName.push_back(*ptr);
    }
    

    Which can become:

    auto start = mangledName.begin() + 1;
    auto end = std::find(start, mangledName.end(), '@');
    demangledName = std::string(start, end);
    

    Or, for a longer example, building up your names:

    auto it = mangledName.begin() + 1;
    auto func_end = std::find(it, mangledName.end(), '@');
    std::string funcName(it, func_end);
    it = func_end;
    
    if (it != mangledName.end()) {
        auto class_end = std::find(it+1, mangledName.end(), '@');
        std::string className(it+1, class_end);
        it = std::find_if(it, mangledName.end(), [](char c){ c != '@';});
    
        // do something with className in a different function
    }
    

    The one-liners are easier to understand than the loops.

  4. Use enumerations in a switch. Where you have:

    if (funcName[1] == '0') // Constructor
    {
        ...
    }
    else if (funcName[1] == '1') // Destructor
    

    Prefer instead to write:

    switch(funcName[1]) {
    case Mangled::CONSTRUCTOR:
        ...
    case Mangled::DESTRUCTOR:
        ...
    

    Expressed intent more clearly.

  5. In getCallConv() and getTypeName(), both makes can be std::unordered_map.
\$\endgroup\$
  • \$\begingroup\$ Thanks Barry, very insightful answer! Reminds me of avoiding raw loops, which is a principle I should try to follow more consistently ;) \$\endgroup\$ – glampert Nov 15 '15 at 17:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.