| /** |
| * The demangle module converts mangled D symbols to a representation similar |
| * to what would have existed in code. |
| * |
| * Copyright: Copyright Sean Kelly 2010 - 2014. |
| * License: Distributed under the |
| * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). |
| * (See accompanying file LICENSE) |
| * Authors: Sean Kelly |
| * Source: $(DRUNTIMESRC core/_demangle.d) |
| */ |
| |
| module core.demangle; |
| |
| version (OSX) |
| version = Darwin; |
| else version (iOS) |
| version = Darwin; |
| else version (TVOS) |
| version = Darwin; |
| else version (WatchOS) |
| version = Darwin; |
| |
| debug(trace) import core.stdc.stdio : printf; |
| debug(info) import core.stdc.stdio : printf; |
| |
| private struct NoHooks |
| { |
| // supported hooks |
| // static bool parseLName(ref Demangle); |
| // static char[] parseType(ref Demangle, char[]) |
| } |
| |
| private struct Demangle(Hooks = NoHooks) |
| { |
| // NOTE: This implementation currently only works with mangled function |
| // names as they exist in an object file. Type names mangled via |
| // the .mangleof property are effectively incomplete as far as the |
| // ABI is concerned and so are not considered to be mangled symbol |
| // names. |
| |
| // NOTE: This implementation builds the demangled buffer in place by |
| // writing data as it is decoded and then rearranging it later as |
| // needed. In practice this results in very little data movement, |
| // and the performance cost is more than offset by the gain from |
| // not allocating dynamic memory to assemble the name piecemeal. |
| // |
| // If the destination buffer is too small, parsing will restart |
| // with a larger buffer. Since this generally means only one |
| // allocation during the course of a parsing run, this is still |
| // faster than assembling the result piecemeal. |
| |
| pure @safe: |
| enum AddType { no, yes } |
| |
| |
| this( return scope const(char)[] buf_, return scope char[] dst_ = null ) |
| { |
| this( buf_, AddType.yes, dst_ ); |
| } |
| |
| |
| this( return scope const(char)[] buf_, AddType addType_, return scope char[] dst_ = null ) |
| { |
| buf = buf_; |
| addType = addType_; |
| dst = dst_; |
| } |
| |
| |
| enum size_t minBufSize = 4000; |
| |
| |
| const(char)[] buf = null; |
| char[] dst = null; |
| size_t pos = 0; |
| size_t len = 0; |
| size_t brp = 0; // current back reference pos |
| AddType addType = AddType.yes; |
| bool mute = false; |
| Hooks hooks; |
| |
| static class ParseException : Exception |
| { |
| this(string msg) @safe pure nothrow |
| { |
| super( msg ); |
| } |
| } |
| |
| |
| static class OverflowException : Exception |
| { |
| this(string msg) @safe pure nothrow |
| { |
| super( msg ); |
| } |
| } |
| |
| |
| static noreturn error( string msg = "Invalid symbol" ) @trusted /* exception only used in module */ |
| { |
| pragma(inline, false); // tame dmd inliner |
| |
| //throw new ParseException( msg ); |
| debug(info) printf( "error: %.*s\n", cast(int) msg.length, msg.ptr ); |
| throw __ctfe ? new ParseException(msg) |
| : cast(ParseException) __traits(initSymbol, ParseException).ptr; |
| |
| } |
| |
| |
| static noreturn overflow( string msg = "Buffer overflow" ) @trusted /* exception only used in module */ |
| { |
| pragma(inline, false); // tame dmd inliner |
| |
| //throw new OverflowException( msg ); |
| debug(info) printf( "overflow: %.*s\n", cast(int) msg.length, msg.ptr ); |
| throw cast(OverflowException) __traits(initSymbol, OverflowException).ptr; |
| } |
| |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // Type Testing and Conversion |
| ////////////////////////////////////////////////////////////////////////// |
| |
| |
| static bool isAlpha( char val ) |
| { |
| return ('a' <= val && 'z' >= val) || |
| ('A' <= val && 'Z' >= val) || |
| (0x80 & val); // treat all unicode as alphabetic |
| } |
| |
| |
| static bool isDigit( char val ) |
| { |
| return '0' <= val && '9' >= val; |
| } |
| |
| |
| static bool isHexDigit( char val ) |
| { |
| return ('0' <= val && '9' >= val) || |
| ('a' <= val && 'f' >= val) || |
| ('A' <= val && 'F' >= val); |
| } |
| |
| |
| static ubyte ascii2hex( char val ) |
| { |
| if (val >= 'a' && val <= 'f') |
| return cast(ubyte)(val - 'a' + 10); |
| if (val >= 'A' && val <= 'F') |
| return cast(ubyte)(val - 'A' + 10); |
| if (val >= '0' && val <= '9') |
| return cast(ubyte)(val - '0'); |
| error(); |
| } |
| |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // Data Output |
| ////////////////////////////////////////////////////////////////////////// |
| |
| |
| static bool contains( const(char)[] a, const(char)[] b ) @trusted |
| { |
| if (a.length && b.length) |
| { |
| auto bend = b.ptr + b.length; |
| auto aend = a.ptr + a.length; |
| return a.ptr <= b.ptr && bend <= aend; |
| } |
| return false; |
| } |
| |
| |
| // move val to the end of the dst buffer |
| char[] shift( const(char)[] val ) |
| { |
| pragma(inline, false); // tame dmd inliner |
| |
| if ( val.length && !mute ) |
| { |
| assert( contains( dst[0 .. len], val ) ); |
| debug(info) printf( "shifting (%.*s)\n", cast(int) val.length, val.ptr ); |
| |
| if (len + val.length > dst.length) |
| overflow(); |
| size_t v = &val[0] - &dst[0]; |
| dst[len .. len + val.length] = val[]; |
| for (size_t p = v; p < len; p++) |
| dst[p] = dst[p + val.length]; |
| |
| return dst[len - val.length .. len]; |
| } |
| return null; |
| } |
| |
| // remove val from dst buffer |
| void remove( const(char)[] val ) |
| { |
| pragma(inline, false); // tame dmd inliner |
| |
| if ( val.length ) |
| { |
| assert( contains( dst[0 .. len], val ) ); |
| debug(info) printf( "removing (%.*s)\n", cast(int) val.length, val.ptr ); |
| size_t v = &val[0] - &dst[0]; |
| assert( len >= val.length && len <= dst.length ); |
| len -= val.length; |
| for (size_t p = v; p < len; p++) |
| dst[p] = dst[p + val.length]; |
| } |
| } |
| |
| char[] append( const(char)[] val ) return scope |
| { |
| pragma(inline, false); // tame dmd inliner |
| |
| if ( val.length && !mute ) |
| { |
| if ( !dst.length ) |
| dst.length = minBufSize; |
| assert( !contains( dst[0 .. len], val ) ); |
| debug(info) printf( "appending (%.*s)\n", cast(int) val.length, val.ptr ); |
| |
| if ( dst.length - len >= val.length && &dst[len] == &val[0] ) |
| { |
| // data is already in place |
| auto t = dst[len .. len + val.length]; |
| len += val.length; |
| return t; |
| } |
| if ( dst.length - len >= val.length ) |
| { |
| dst[len .. len + val.length] = val[]; |
| auto t = dst[len .. len + val.length]; |
| len += val.length; |
| return t; |
| } |
| overflow(); |
| } |
| return null; |
| } |
| |
| void putComma(size_t n) |
| { |
| pragma(inline, false); |
| if (n) |
| put(", "); |
| } |
| |
| void put(char c) return scope |
| { |
| char[1] val = c; |
| put(val[]); |
| } |
| |
| void put(scope const(char)[] val) return scope |
| { |
| pragma(inline, false); // tame dmd inliner |
| |
| if (!val.length) return; |
| |
| if (!contains(dst[0 .. len], val)) |
| append(val); |
| else |
| shift(val); |
| } |
| |
| |
| void putAsHex( size_t val, int width = 0 ) |
| { |
| import core.internal.string; |
| |
| UnsignedStringBuf buf = void; |
| |
| auto s = unsignedToTempString!16(val, buf); |
| int slen = cast(int)s.length; |
| if (slen < width) |
| { |
| foreach (i; slen .. width) |
| put('0'); |
| } |
| put(s); |
| } |
| |
| |
| void pad( const(char)[] val ) |
| { |
| if ( val.length ) |
| { |
| append( " " ); |
| put( val ); |
| } |
| } |
| |
| |
| void silent( void delegate() pure @safe dg ) |
| { |
| debug(trace) printf( "silent+\n" ); |
| debug(trace) scope(success) printf( "silent-\n" ); |
| auto n = len; dg(); len = n; |
| } |
| |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // Parsing Utility |
| ////////////////////////////////////////////////////////////////////////// |
| |
| @property bool empty() |
| { |
| return pos >= buf.length; |
| } |
| |
| @property char front() |
| { |
| if ( pos < buf.length ) |
| return buf[pos]; |
| return char.init; |
| } |
| |
| char peek( size_t n ) |
| { |
| if ( pos + n < buf.length ) |
| return buf[pos + n]; |
| return char.init; |
| } |
| |
| |
| void test( char val ) |
| { |
| if ( val != front ) |
| error(); |
| } |
| |
| |
| void popFront() |
| { |
| if ( pos++ >= buf.length ) |
| error(); |
| } |
| |
| |
| void popFront(int i) |
| { |
| while (i--) |
| popFront(); |
| } |
| |
| |
| void match( char val ) |
| { |
| test( val ); |
| popFront(); |
| } |
| |
| |
| void match( const(char)[] val ) |
| { |
| foreach (char e; val ) |
| { |
| test( e ); |
| popFront(); |
| } |
| } |
| |
| |
| void eat( char val ) |
| { |
| if ( val == front ) |
| popFront(); |
| } |
| |
| bool isSymbolNameFront() |
| { |
| char val = front; |
| if ( isDigit( val ) || val == '_' ) |
| return true; |
| if ( val != 'Q' ) |
| return false; |
| |
| // check the back reference encoding after 'Q' |
| val = peekBackref(); |
| return isDigit( val ); // identifier ref |
| } |
| |
| // return the first character at the back reference |
| char peekBackref() |
| { |
| assert( front == 'Q' ); |
| auto n = decodeBackref!1(); |
| if (!n || n > pos) |
| error("invalid back reference"); |
| |
| return buf[pos - n]; |
| } |
| |
| size_t decodeBackref(size_t peekAt = 0)() |
| { |
| enum base = 26; |
| size_t n = 0; |
| for (size_t p; ; p++) |
| { |
| char t; |
| static if (peekAt > 0) |
| { |
| t = peek(peekAt + p); |
| } |
| else |
| { |
| t = front; |
| popFront(); |
| } |
| if (t < 'A' || t > 'Z') |
| { |
| if (t < 'a' || t > 'z') |
| error("invalid back reference"); |
| n = base * n + t - 'a'; |
| return n; |
| } |
| n = base * n + t - 'A'; |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////// |
| // Parsing Implementation |
| ////////////////////////////////////////////////////////////////////////// |
| |
| |
| /* |
| Number: |
| Digit |
| Digit Number |
| */ |
| const(char)[] sliceNumber() return scope |
| { |
| debug(trace) printf( "sliceNumber+\n" ); |
| debug(trace) scope(success) printf( "sliceNumber-\n" ); |
| |
| auto beg = pos; |
| |
| while ( true ) |
| { |
| auto t = front; |
| if (t >= '0' && t <= '9') |
| popFront(); |
| else |
| return buf[beg .. pos]; |
| } |
| } |
| |
| |
| size_t decodeNumber() scope |
| { |
| debug(trace) printf( "decodeNumber+\n" ); |
| debug(trace) scope(success) printf( "decodeNumber-\n" ); |
| |
| return decodeNumber( sliceNumber() ); |
| } |
| |
| |
| size_t decodeNumber( scope const(char)[] num ) scope |
| { |
| debug(trace) printf( "decodeNumber+\n" ); |
| debug(trace) scope(success) printf( "decodeNumber-\n" ); |
| |
| size_t val = 0; |
| |
| foreach ( c; num ) |
| { |
| import core.checkedint : mulu, addu; |
| |
| bool overflow = false; |
| val = mulu(val, 10, overflow); |
| val = addu(val, c - '0', overflow); |
| if (overflow) |
| error(); |
| } |
| return val; |
| } |
| |
| |
| void parseReal() scope |
| { |
| debug(trace) printf( "parseReal+\n" ); |
| debug(trace) scope(success) printf( "parseReal-\n" ); |
| |
| char[64] tbuf = void; |
| size_t tlen = 0; |
| real val = void; |
| |
| if ( 'I' == front ) |
| { |
| match( "INF" ); |
| put( "real.infinity" ); |
| return; |
| } |
| if ( 'N' == front ) |
| { |
| popFront(); |
| if ( 'I' == front ) |
| { |
| match( "INF" ); |
| put( "-real.infinity" ); |
| return; |
| } |
| if ( 'A' == front ) |
| { |
| match( "AN" ); |
| put( "real.nan" ); |
| return; |
| } |
| tbuf[tlen++] = '-'; |
| } |
| |
| tbuf[tlen++] = '0'; |
| tbuf[tlen++] = 'X'; |
| if ( !isHexDigit( front ) ) |
| error( "Expected hex digit" ); |
| tbuf[tlen++] = front; |
| tbuf[tlen++] = '.'; |
| popFront(); |
| |
| while ( isHexDigit( front ) ) |
| { |
| tbuf[tlen++] = front; |
| popFront(); |
| } |
| match( 'P' ); |
| tbuf[tlen++] = 'p'; |
| if ( 'N' == front ) |
| { |
| tbuf[tlen++] = '-'; |
| popFront(); |
| } |
| else |
| { |
| tbuf[tlen++] = '+'; |
| } |
| while ( isDigit( front ) ) |
| { |
| tbuf[tlen++] = front; |
| popFront(); |
| } |
| |
| tbuf[tlen] = 0; |
| debug(info) printf( "got (%s)\n", tbuf.ptr ); |
| pureReprintReal( tbuf[] ); |
| debug(info) printf( "converted (%.*s)\n", cast(int) tlen, tbuf.ptr ); |
| put( tbuf[0 .. tlen] ); |
| } |
| |
| |
| /* |
| LName: |
| Number Name |
| |
| Name: |
| Namestart |
| Namestart Namechars |
| |
| Namestart: |
| _ |
| Alpha |
| |
| Namechar: |
| Namestart |
| Digit |
| |
| Namechars: |
| Namechar |
| Namechar Namechars |
| */ |
| void parseLName() scope |
| { |
| debug(trace) printf( "parseLName+\n" ); |
| debug(trace) scope(success) printf( "parseLName-\n" ); |
| |
| static if (__traits(hasMember, Hooks, "parseLName")) |
| if (hooks.parseLName(this)) |
| return; |
| |
| if ( front == 'Q' ) |
| { |
| // back reference to LName |
| auto refPos = pos; |
| popFront(); |
| size_t n = decodeBackref(); |
| if ( !n || n > refPos ) |
| error( "Invalid LName back reference" ); |
| if ( !mute ) |
| { |
| auto savePos = pos; |
| scope(exit) pos = savePos; |
| pos = refPos - n; |
| parseLName(); |
| } |
| return; |
| } |
| auto n = decodeNumber(); |
| if ( n == 0 ) |
| { |
| put( "__anonymous" ); |
| return; |
| } |
| if ( n > buf.length || n > buf.length - pos ) |
| error( "LName must be at least 1 character" ); |
| if ( '_' != front && !isAlpha( front ) ) |
| error( "Invalid character in LName" ); |
| foreach (char e; buf[pos + 1 .. pos + n] ) |
| { |
| if ( '_' != e && !isAlpha( e ) && !isDigit( e ) ) |
| error( "Invalid character in LName" ); |
| } |
| |
| put( buf[pos .. pos + n] ); |
| pos += n; |
| } |
| |
| |
| /* |
| Type: |
| Shared |
| Const |
| Immutable |
| Wild |
| TypeArray |
| TypeVector |
| TypeStaticArray |
| TypeAssocArray |
| TypePointer |
| TypeFunction |
| TypeIdent |
| TypeClass |
| TypeStruct |
| TypeEnum |
| TypeTypedef |
| TypeDelegate |
| TypeNone |
| TypeVoid |
| TypeNoreturn |
| TypeByte |
| TypeUbyte |
| TypeShort |
| TypeUshort |
| TypeInt |
| TypeUint |
| TypeLong |
| TypeUlong |
| TypeCent |
| TypeUcent |
| TypeFloat |
| TypeDouble |
| TypeReal |
| TypeIfloat |
| TypeIdouble |
| TypeIreal |
| TypeCfloat |
| TypeCdouble |
| TypeCreal |
| TypeBool |
| TypeChar |
| TypeWchar |
| TypeDchar |
| TypeTuple |
| |
| Shared: |
| O Type |
| |
| Const: |
| x Type |
| |
| Immutable: |
| y Type |
| |
| Wild: |
| Ng Type |
| |
| TypeArray: |
| A Type |
| |
| TypeVector: |
| Nh Type |
| |
| TypeStaticArray: |
| G Number Type |
| |
| TypeAssocArray: |
| H Type Type |
| |
| TypePointer: |
| P Type |
| |
| TypeFunction: |
| CallConvention FuncAttrs Arguments ArgClose Type |
| |
| TypeIdent: |
| I LName |
| |
| TypeClass: |
| C LName |
| |
| TypeStruct: |
| S LName |
| |
| TypeEnum: |
| E LName |
| |
| TypeTypedef: |
| T LName |
| |
| TypeDelegate: |
| D TypeFunction |
| |
| TypeNone: |
| n |
| |
| TypeVoid: |
| v |
| |
| TypeNoreturn |
| Nn |
| |
| TypeByte: |
| g |
| |
| TypeUbyte: |
| h |
| |
| TypeShort: |
| s |
| |
| TypeUshort: |
| t |
| |
| TypeInt: |
| i |
| |
| TypeUint: |
| k |
| |
| TypeLong: |
| l |
| |
| TypeUlong: |
| m |
| |
| TypeCent |
| zi |
| |
| TypeUcent |
| zk |
| |
| TypeFloat: |
| f |
| |
| TypeDouble: |
| d |
| |
| TypeReal: |
| e |
| |
| TypeIfloat: |
| o |
| |
| TypeIdouble: |
| p |
| |
| TypeIreal: |
| j |
| |
| TypeCfloat: |
| q |
| |
| TypeCdouble: |
| r |
| |
| TypeCreal: |
| c |
| |
| TypeBool: |
| b |
| |
| TypeChar: |
| a |
| |
| TypeWchar: |
| u |
| |
| TypeDchar: |
| w |
| |
| TypeTuple: |
| B Number Arguments |
| */ |
| char[] parseType( char[] name = null ) return scope |
| { |
| static immutable string[23] primitives = [ |
| "char", // a |
| "bool", // b |
| "creal", // c |
| "double", // d |
| "real", // e |
| "float", // f |
| "byte", // g |
| "ubyte", // h |
| "int", // i |
| "ireal", // j |
| "uint", // k |
| "long", // l |
| "ulong", // m |
| null, // n |
| "ifloat", // o |
| "idouble", // p |
| "cfloat", // q |
| "cdouble", // r |
| "short", // s |
| "ushort", // t |
| "wchar", // u |
| "void", // v |
| "dchar", // w |
| ]; |
| |
| static if (__traits(hasMember, Hooks, "parseType")) |
| if (auto n = hooks.parseType(this, name)) |
| return n; |
| |
| debug(trace) printf( "parseType+\n" ); |
| debug(trace) scope(success) printf( "parseType-\n" ); |
| auto beg = len; |
| auto t = front; |
| |
| char[] parseBackrefType(scope char[] delegate() pure @safe parseDg) pure @safe |
| { |
| if (pos == brp) |
| error("recursive back reference"); |
| auto refPos = pos; |
| popFront(); |
| auto n = decodeBackref(); |
| if (n == 0 || n > pos) |
| error("invalid back reference"); |
| if ( mute ) |
| return null; |
| auto savePos = pos; |
| auto saveBrp = brp; |
| scope(success) { pos = savePos; brp = saveBrp; } |
| pos = refPos - n; |
| brp = refPos; |
| auto ret = parseDg(); |
| return ret; |
| } |
| |
| switch ( t ) |
| { |
| case 'Q': // Type back reference |
| return parseBackrefType( () => parseType( name ) ); |
| case 'O': // Shared (O Type) |
| popFront(); |
| put( "shared(" ); |
| parseType(); |
| put( ')' ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'x': // Const (x Type) |
| popFront(); |
| put( "const(" ); |
| parseType(); |
| put( ')' ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'y': // Immutable (y Type) |
| popFront(); |
| put( "immutable(" ); |
| parseType(); |
| put( ')' ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'N': |
| popFront(); |
| switch ( front ) |
| { |
| case 'n': // Noreturn |
| popFront(); |
| put("noreturn"); |
| return dst[beg .. len]; |
| case 'g': // Wild (Ng Type) |
| popFront(); |
| // TODO: Anything needed here? |
| put( "inout(" ); |
| parseType(); |
| put( ')' ); |
| return dst[beg .. len]; |
| case 'h': // TypeVector (Nh Type) |
| popFront(); |
| put( "__vector(" ); |
| parseType(); |
| put( ')' ); |
| return dst[beg .. len]; |
| default: |
| error(); |
| assert( 0 ); |
| } |
| case 'A': // TypeArray (A Type) |
| popFront(); |
| parseType(); |
| put( "[]" ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'G': // TypeStaticArray (G Number Type) |
| popFront(); |
| auto num = sliceNumber(); |
| parseType(); |
| put( '[' ); |
| put( num ); |
| put( ']' ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'H': // TypeAssocArray (H Type Type) |
| popFront(); |
| // skip t1 |
| auto tx = parseType(); |
| parseType(); |
| put( '[' ); |
| put( tx ); |
| put( ']' ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'P': // TypePointer (P Type) |
| popFront(); |
| parseType(); |
| put( '*' ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'F': case 'U': case 'W': case 'V': case 'R': // TypeFunction |
| return parseTypeFunction( name ); |
| case 'C': // TypeClass (C LName) |
| case 'S': // TypeStruct (S LName) |
| case 'E': // TypeEnum (E LName) |
| case 'T': // TypeTypedef (T LName) |
| popFront(); |
| parseQualifiedName(); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'D': // TypeDelegate (D TypeFunction) |
| popFront(); |
| auto modifiers = parseModifier(); |
| if ( front == 'Q' ) |
| parseBackrefType( () => parseTypeFunction( name, IsDelegate.yes ) ); |
| else |
| parseTypeFunction( name, IsDelegate.yes ); |
| if (modifiers) |
| { |
| // write modifiers behind the function arguments |
| while (auto str = typeCtors.toStringConsume(modifiers)) |
| { |
| put(' '); |
| put(str); |
| } |
| } |
| return dst[beg .. len]; |
| case 'n': // TypeNone (n) |
| popFront(); |
| // TODO: Anything needed here? |
| return dst[beg .. len]; |
| case 'B': // TypeTuple (B Number Arguments) |
| popFront(); |
| // TODO: Handle this. |
| return dst[beg .. len]; |
| case 'Z': // Internal symbol |
| // This 'type' is used for untyped internal symbols, i.e.: |
| // __array |
| // __init |
| // __vtbl |
| // __Class |
| // __Interface |
| // __ModuleInfo |
| popFront(); |
| return dst[beg .. len]; |
| default: |
| if (t >= 'a' && t <= 'w') |
| { |
| popFront(); |
| put( primitives[cast(size_t)(t - 'a')] ); |
| pad( name ); |
| return dst[beg .. len]; |
| } |
| else if (t == 'z') |
| { |
| popFront(); |
| switch ( front ) |
| { |
| case 'i': |
| popFront(); |
| put( "cent" ); |
| pad( name ); |
| return dst[beg .. len]; |
| case 'k': |
| popFront(); |
| put( "ucent" ); |
| pad( name ); |
| return dst[beg .. len]; |
| default: |
| error(); |
| assert( 0 ); |
| } |
| } |
| error(); |
| } |
| } |
| |
| |
| /* |
| TypeFunction: |
| CallConvention FuncAttrs Arguments ArgClose Type |
| |
| CallConvention: |
| F // D |
| U // C |
| W // Windows |
| R // C++ |
| |
| FuncAttrs: |
| FuncAttr |
| FuncAttr FuncAttrs |
| |
| FuncAttr: |
| empty |
| FuncAttrPure |
| FuncAttrNothrow |
| FuncAttrProperty |
| FuncAttrRef |
| FuncAttrReturn |
| FuncAttrScope |
| FuncAttrTrusted |
| FuncAttrSafe |
| |
| FuncAttrPure: |
| Na |
| |
| FuncAttrNothrow: |
| Nb |
| |
| FuncAttrRef: |
| Nc |
| |
| FuncAttrProperty: |
| Nd |
| |
| FuncAttrTrusted: |
| Ne |
| |
| FuncAttrSafe: |
| Nf |
| |
| FuncAttrNogc: |
| Ni |
| |
| FuncAttrReturn: |
| Nj |
| |
| FuncAttrScope: |
| Nl |
| |
| Arguments: |
| Argument |
| Argument Arguments |
| |
| Argument: |
| Argument2 |
| M Argument2 // scope |
| |
| Argument2: |
| Type |
| J Type // out |
| K Type // ref |
| L Type // lazy |
| |
| ArgClose |
| X // variadic T t,...) style |
| Y // variadic T t...) style |
| Z // not variadic |
| */ |
| void parseCallConvention() |
| { |
| // CallConvention |
| switch ( front ) |
| { |
| case 'F': // D |
| popFront(); |
| break; |
| case 'U': // C |
| popFront(); |
| put( "extern (C) " ); |
| break; |
| case 'W': // Windows |
| popFront(); |
| put( "extern (Windows) " ); |
| break; |
| case 'R': // C++ |
| popFront(); |
| put( "extern (C++) " ); |
| break; |
| default: |
| error(); |
| } |
| } |
| |
| /// Returns: Flags of `TypeCtor` |
| ushort parseModifier() |
| { |
| TypeCtor res = TypeCtor.None; |
| switch ( front ) |
| { |
| case 'y': |
| popFront(); |
| return TypeCtor.Immutable; |
| case 'O': |
| popFront(); |
| res |= TypeCtor.Shared; |
| if (front == 'x') |
| goto case 'x'; |
| if (front == 'N') |
| goto case 'N'; |
| return TypeCtor.Shared; |
| case 'N': |
| if (peek( 1 ) != 'g') |
| return res; |
| popFront(); |
| popFront(); |
| res |= TypeCtor.InOut; |
| if ( front == 'x' ) |
| goto case 'x'; |
| return res; |
| case 'x': |
| popFront(); |
| res |= TypeCtor.Const; |
| return res; |
| default: return TypeCtor.None; |
| } |
| } |
| |
| ushort parseFuncAttr() |
| { |
| // FuncAttrs |
| ushort result; |
| while ('N' == front) |
| { |
| popFront(); |
| switch ( front ) |
| { |
| case 'a': // FuncAttrPure |
| popFront(); |
| result |= FuncAttributes.Pure; |
| continue; |
| case 'b': // FuncAttrNoThrow |
| popFront(); |
| result |= FuncAttributes.Nothrow; |
| continue; |
| case 'c': // FuncAttrRef |
| popFront(); |
| result |= FuncAttributes.Ref; |
| continue; |
| case 'd': // FuncAttrProperty |
| popFront(); |
| result |= FuncAttributes.Property; |
| continue; |
| case 'e': // FuncAttrTrusted |
| popFront(); |
| result |= FuncAttributes.Trusted; |
| continue; |
| case 'f': // FuncAttrSafe |
| popFront(); |
| result |= FuncAttributes.Safe; |
| continue; |
| case 'g': |
| case 'h': |
| case 'k': |
| case 'n': |
| // NOTE: The inout parameter type is represented as "Ng". |
| // The vector parameter type is represented as "Nh". |
| // The return parameter type is represented as "Nk". |
| // The noreturn parameter type is represented as "Nn". |
| // These make it look like a FuncAttr, but infact |
| // if we see these, then we know we're really in |
| // the parameter list. Rewind and break. |
| pos--; |
| return result; |
| case 'i': // FuncAttrNogc |
| popFront(); |
| result |= FuncAttributes.NoGC; |
| continue; |
| case 'j': // FuncAttrReturn |
| popFront(); |
| if (this.peek(0) == 'N' && this.peek(1) == 'l') |
| { |
| result |= FuncAttributes.ReturnScope; |
| popFront(); |
| popFront(); |
| } else { |
| result |= FuncAttributes.Return; |
| } |
| continue; |
| case 'l': // FuncAttrScope |
| popFront(); |
| if (this.peek(0) == 'N' && this.peek(1) == 'j') |
| { |
| result |= FuncAttributes.ScopeReturn; |
| popFront(); |
| popFront(); |
| } else { |
| result |= FuncAttributes.Scope; |
| } |
| continue; |
| case 'm': // FuncAttrLive |
| popFront(); |
| result |= FuncAttributes.Live; |
| continue; |
| default: |
| error(); |
| } |
| } |
| return result; |
| } |
| |
| void parseFuncArguments() scope |
| { |
| // Arguments |
| for ( size_t n = 0; true; n++ ) |
| { |
| debug(info) printf( "tok (%c)\n", front ); |
| switch ( front ) |
| { |
| case 'X': // ArgClose (variadic T t...) style) |
| popFront(); |
| put( "..." ); |
| return; |
| case 'Y': // ArgClose (variadic T t,...) style) |
| popFront(); |
| put( ", ..." ); |
| return; |
| case 'Z': // ArgClose (not variadic) |
| popFront(); |
| return; |
| default: |
| break; |
| } |
| putComma(n); |
| |
| /* Do special return, scope, ref, out combinations |
| */ |
| int npops; |
| if ( 'M' == front && peek(1) == 'N' && peek(2) == 'k') |
| { |
| const c3 = peek(3); |
| if (c3 == 'J') |
| { |
| put("scope return out "); // MNkJ |
| npops = 4; |
| } |
| else if (c3 == 'K') |
| { |
| put("scope return ref "); // MNkK |
| npops = 4; |
| } |
| } |
| else if ('N' == front && peek(1) == 'k') |
| { |
| const c2 = peek(2); |
| if (c2 == 'J') |
| { |
| put("return out "); // NkJ |
| npops = 3; |
| } |
| else if (c2 == 'K') |
| { |
| put("return ref "); // NkK |
| npops = 3; |
| } |
| else if (c2 == 'M') |
| { |
| const c3 = peek(3); |
| if (c3 == 'J') |
| { |
| put("return scope out "); // NkMJ |
| npops = 4; |
| } |
| else if (c3 == 'K') |
| { |
| put("return scope ref "); // NkMK |
| npops = 4; |
| } |
| else |
| { |
| put("return scope "); // NkM |
| npops = 3; |
| } |
| } |
| } |
| popFront(npops); |
| |
| if ( 'M' == front ) |
| { |
| popFront(); |
| put( "scope " ); |
| } |
| if ( 'N' == front ) |
| { |
| popFront(); |
| if ( 'k' == front ) // Return (Nk Parameter2) |
| { |
| popFront(); |
| put( "return " ); |
| } |
| else |
| pos--; |
| } |
| switch ( front ) |
| { |
| case 'I': // in (I Type) |
| popFront(); |
| put("in "); |
| if (front == 'K') |
| goto case; |
| parseType(); |
| continue; |
| case 'K': // ref (K Type) |
| popFront(); |
| put( "ref " ); |
| parseType(); |
| continue; |
| case 'J': // out (J Type) |
| popFront(); |
| put( "out " ); |
| parseType(); |
| continue; |
| case 'L': // lazy (L Type) |
| popFront(); |
| put( "lazy " ); |
| parseType(); |
| continue; |
| default: |
| parseType(); |
| } |
| } |
| } |
| |
| enum IsDelegate { no, yes } |
| |
| /* |
| TypeFunction: |
| CallConvention FuncAttrs Arguments ArgClose Type |
| */ |
| char[] parseTypeFunction( char[] name = null, IsDelegate isdg = IsDelegate.no ) return scope |
| { |
| debug(trace) printf( "parseTypeFunction+\n" ); |
| debug(trace) scope(success) printf( "parseTypeFunction-\n" ); |
| auto beg = len; |
| |
| parseCallConvention(); |
| auto attributes = parseFuncAttr(); |
| |
| auto argbeg = len; |
| put( '(' ); |
| parseFuncArguments(); |
| put( ')' ); |
| if (attributes) |
| { |
| // write function attributes behind arguments |
| while (auto str = funcAttrs.toStringConsume(attributes)) |
| { |
| put(' '); |
| put(str); |
| } |
| } |
| auto retbeg = len; |
| parseType(); |
| put( ' ' ); |
| // append name/delegate/function |
| if ( name.length ) |
| { |
| if ( !contains( dst[0 .. len], name ) ) |
| put( name ); |
| else if ( shift( name ).ptr != name.ptr ) |
| { |
| argbeg -= name.length; |
| retbeg -= name.length; |
| } |
| } |
| else if ( IsDelegate.yes == isdg ) |
| put( "delegate" ); |
| else |
| put( "function" ); |
| // move arguments and attributes behind name |
| shift( dst[argbeg .. retbeg] ); |
| return dst[beg..len]; |
| } |
| |
| static bool isCallConvention( char ch ) |
| { |
| switch ( ch ) |
| { |
| case 'F', 'U', 'V', 'W', 'R': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /* |
| Value: |
| n |
| Number |
| i Number |
| N Number |
| e HexFloat |
| c HexFloat c HexFloat |
| A Number Value... |
| |
| HexFloat: |
| NAN |
| INF |
| NINF |
| N HexDigits P Exponent |
| HexDigits P Exponent |
| |
| Exponent: |
| N Number |
| Number |
| |
| HexDigits: |
| HexDigit |
| HexDigit HexDigits |
| |
| HexDigit: |
| Digit |
| A |
| B |
| C |
| D |
| E |
| F |
| */ |
| void parseValue(scope char[] name = null, char type = '\0' ) scope |
| { |
| debug(trace) printf( "parseValue+\n" ); |
| debug(trace) scope(success) printf( "parseValue-\n" ); |
| |
| // printf( "*** %c\n", front ); |
| switch ( front ) |
| { |
| case 'n': |
| popFront(); |
| put( "null" ); |
| return; |
| case 'i': |
| popFront(); |
| if ( '0' > front || '9' < front ) |
| error( "Number expected" ); |
| goto case; |
| case '0': .. case '9': |
| parseIntegerValue( name, type ); |
| return; |
| case 'N': |
| popFront(); |
| put( '-' ); |
| parseIntegerValue( name, type ); |
| return; |
| case 'e': |
| popFront(); |
| parseReal(); |
| return; |
| case 'c': |
| popFront(); |
| parseReal(); |
| put( '+' ); |
| match( 'c' ); |
| parseReal(); |
| put( 'i' ); |
| return; |
| case 'a': case 'w': case 'd': |
| char t = front; |
| popFront(); |
| auto n = decodeNumber(); |
| match( '_' ); |
| put( '"' ); |
| foreach (i; 0..n) |
| { |
| auto a = ascii2hex( front ); popFront(); |
| auto b = ascii2hex( front ); popFront(); |
| auto v = cast(char)((a << 4) | b); |
| if (' ' <= v && v <= '~') // ASCII printable |
| { |
| put(v); |
| } |
| else |
| { |
| put("\\x"); |
| putAsHex(v, 2); |
| } |
| } |
| put( '"' ); |
| if ( 'a' != t ) |
| put(t); |
| return; |
| case 'A': |
| // NOTE: This is kind of a hack. An associative array literal |
| // [1:2, 3:4] is represented as HiiA2i1i2i3i4, so the type |
| // is "Hii" and the value is "A2i1i2i3i4". Thus the only |
| // way to determine that this is an AA value rather than an |
| // array value is for the caller to supply the type char. |
| // Hopefully, this will change so that the value is |
| // "H2i1i2i3i4", rendering this unnecesary. |
| if ( 'H' == type ) |
| goto LassocArray; |
| // A Number Value... |
| // An array literal. Value is repeated Number times. |
| popFront(); |
| put( '[' ); |
| auto n = decodeNumber(); |
| foreach ( i; 0 .. n ) |
| { |
| putComma(i); |
| parseValue(); |
| } |
| put( ']' ); |
| return; |
| case 'H': |
| LassocArray: |
| // H Number Value... |
| // An associative array literal. Value is repeated 2*Number times. |
| popFront(); |
| put( '[' ); |
| auto n = decodeNumber(); |
| foreach ( i; 0 .. n ) |
| { |
| putComma(i); |
| parseValue(); |
| put(':'); |
| parseValue(); |
| } |
| put( ']' ); |
| return; |
| case 'S': |
| // S Number Value... |
| // A struct literal. Value is repeated Number times. |
| popFront(); |
| if ( name.length ) |
| put( name ); |
| put( '(' ); |
| auto n = decodeNumber(); |
| foreach ( i; 0 .. n ) |
| { |
| putComma(i); |
| parseValue(); |
| } |
| put( ')' ); |
| return; |
| case 'f': |
| // f MangledName |
| // A function literal symbol |
| popFront(); |
| parseMangledName(false, 1); |
| return; |
| default: |
| error(); |
| } |
| } |
| |
| |
| void parseIntegerValue( scope char[] name = null, char type = '\0' ) scope |
| { |
| debug(trace) printf( "parseIntegerValue+\n" ); |
| debug(trace) scope(success) printf( "parseIntegerValue-\n" ); |
| |
| switch ( type ) |
| { |
| case 'a': // char |
| case 'u': // wchar |
| case 'w': // dchar |
| { |
| auto val = sliceNumber(); |
| auto num = decodeNumber( val ); |
| |
| switch ( num ) |
| { |
| case '\'': |
| put( "'\\''" ); |
| return; |
| // \", \? |
| case '\\': |
| put( "'\\\\'" ); |
| return; |
| case '\a': |
| put( "'\\a'" ); |
| return; |
| case '\b': |
| put( "'\\b'" ); |
| return; |
| case '\f': |
| put( "'\\f'" ); |
| return; |
| case '\n': |
| put( "'\\n'" ); |
| return; |
| case '\r': |
| put( "'\\r'" ); |
| return; |
| case '\t': |
| put( "'\\t'" ); |
| return; |
| case '\v': |
| put( "'\\v'" ); |
| return; |
| default: |
| switch ( type ) |
| { |
| case 'a': |
| if ( num >= 0x20 && num < 0x7F ) |
| { |
| put( '\'' ); |
| put( cast(char)num ); |
| put( '\'' ); |
| return; |
| } |
| put( "\\x" ); |
| putAsHex( num, 2 ); |
| return; |
| case 'u': |
| put( "'\\u" ); |
| putAsHex( num, 4 ); |
| put( '\'' ); |
| return; |
| case 'w': |
| put( "'\\U" ); |
| putAsHex( num, 8 ); |
| put( '\'' ); |
| return; |
| default: |
| assert( 0 ); |
| } |
| } |
| } |
| case 'b': // bool |
| put( decodeNumber() ? "true" : "false" ); |
| return; |
| case 'h', 't', 'k': // ubyte, ushort, uint |
| put( sliceNumber() ); |
| put( 'u' ); |
| return; |
| case 'l': // long |
| put( sliceNumber() ); |
| put( 'L' ); |
| return; |
| case 'm': // ulong |
| put( sliceNumber() ); |
| put( "uL" ); |
| return; |
| default: |
| put( sliceNumber() ); |
| return; |
| } |
| } |
| |
| |
| /* |
| TemplateArgs: |
| TemplateArg |
| TemplateArg TemplateArgs |
| |
| TemplateArg: |
| TemplateArgX |
| H TemplateArgX |
| |
| TemplateArgX: |
| T Type |
| V Type Value |
| S Number_opt QualifiedName |
| X ExternallyMangledName |
| */ |
| void parseTemplateArgs() scope |
| { |
| debug(trace) printf( "parseTemplateArgs+\n" ); |
| debug(trace) scope(success) printf( "parseTemplateArgs-\n" ); |
| |
| L_nextArg: |
| for ( size_t n = 0; true; n++ ) |
| { |
| if ( front == 'H' ) |
| popFront(); |
| |
| switch ( front ) |
| { |
| case 'T': |
| popFront(); |
| putComma(n); |
| parseType(); |
| continue; |
| case 'V': |
| popFront(); |
| putComma(n); |
| // NOTE: In the few instances where the type is actually |
| // desired in the output it should precede the value |
| // generated by parseValue, so it is safe to simply |
| // decrement len and let put/append do its thing. |
| char t = front; // peek at type for parseValue |
| if ( t == 'Q' ) |
| t = peekBackref(); |
| char[] name; silent( delegate void() { name = parseType(); } ); |
| parseValue( name, t ); |
| continue; |
| case 'S': |
| popFront(); |
| putComma(n); |
| |
| if ( mayBeMangledNameArg() ) |
| { |
| auto l = len; |
| auto p = pos; |
| auto b = brp; |
| try |
| { |
| debug(trace) printf( "may be mangled name arg\n" ); |
| parseMangledNameArg(); |
| continue; |
| } |
| catch ( ParseException e ) |
| { |
| len = l; |
| pos = p; |
| brp = b; |
| debug(trace) printf( "not a mangled name arg\n" ); |
| } |
| } |
| if ( isDigit( front ) && isDigit( peek( 1 ) ) ) |
| { |
| // ambiguity: length followed by qualified name (starting with number) |
| // try all possible pairs of numbers |
| auto qlen = decodeNumber() / 10; // last digit needed for QualifiedName |
| pos--; |
| auto l = len; |
| auto p = pos; |
| auto b = brp; |
| while ( qlen > 0 ) |
| { |
| try |
| { |
| parseQualifiedName(); |
| if ( pos == p + qlen ) |
| continue L_nextArg; |
| } |
| catch ( ParseException e ) |
| { |
| } |
| qlen /= 10; // retry with one digit less |
| pos = --p; |
| len = l; |
| brp = b; |
| } |
| } |
| parseQualifiedName(); |
| continue; |
| case 'X': |
| popFront(); |
| putComma(n); |
| parseLName(); |
| continue; |
| default: |
| return; |
| } |
| } |
| } |
| |
| |
| bool mayBeMangledNameArg() |
| { |
| debug(trace) printf( "mayBeMangledNameArg+\n" ); |
| debug(trace) scope(success) printf( "mayBeMangledNameArg-\n" ); |
| |
| auto p = pos; |
| scope(exit) pos = p; |
| if ( isDigit( buf[pos] ) ) |
| { |
| auto n = decodeNumber(); |
| return n >= 4 && |
| pos < buf.length && '_' == buf[pos++] && |
| pos < buf.length && 'D' == buf[pos++] && |
| isDigit( buf[pos] ); |
| } |
| else |
| { |
| return pos < buf.length && '_' == buf[pos++] && |
| pos < buf.length && 'D' == buf[pos++] && |
| isSymbolNameFront(); |
| } |
| } |
| |
| |
| void parseMangledNameArg() |
| { |
| debug(trace) printf( "parseMangledNameArg+\n" ); |
| debug(trace) scope(success) printf( "parseMangledNameArg-\n" ); |
| |
| size_t n = 0; |
| if ( isDigit( front ) ) |
| n = decodeNumber(); |
| parseMangledName( false, n ); |
| } |
| |
| |
| /* |
| TemplateInstanceName: |
| Number __T LName TemplateArgs Z |
| */ |
| void parseTemplateInstanceName(bool hasNumber) scope |
| { |
| debug(trace) printf( "parseTemplateInstanceName+\n" ); |
| debug(trace) scope(success) printf( "parseTemplateInstanceName-\n" ); |
| |
| auto sav = pos; |
| auto saveBrp = brp; |
| scope(failure) |
| { |
| pos = sav; |
| brp = saveBrp; |
| } |
| auto n = hasNumber ? decodeNumber() : 0; |
| auto beg = pos; |
| match( "__T" ); |
| parseLName(); |
| put( "!(" ); |
| parseTemplateArgs(); |
| match( 'Z' ); |
| if ( hasNumber && pos - beg != n ) |
| error( "Template name length mismatch" ); |
| put( ')' ); |
| } |
| |
| |
| bool mayBeTemplateInstanceName() scope |
| { |
| debug(trace) printf( "mayBeTemplateInstanceName+\n" ); |
| debug(trace) scope(success) printf( "mayBeTemplateInstanceName-\n" ); |
| |
| auto p = pos; |
| scope(exit) pos = p; |
| auto n = decodeNumber(); |
| return n >= 5 && |
| pos < buf.length && '_' == buf[pos++] && |
| pos < buf.length && '_' == buf[pos++] && |
| pos < buf.length && 'T' == buf[pos++]; |
| } |
| |
| |
| /* |
| SymbolName: |
| LName |
| TemplateInstanceName |
| */ |
| void parseSymbolName() scope |
| { |
| debug(trace) printf( "parseSymbolName+\n" ); |
| debug(trace) scope(success) printf( "parseSymbolName-\n" ); |
| |
| // LName -> Number |
| // TemplateInstanceName -> Number "__T" |
| switch ( front ) |
| { |
| case '_': |
| // no length encoding for templates for new mangling |
| parseTemplateInstanceName(false); |
| return; |
| |
| case '0': .. case '9': |
| if ( mayBeTemplateInstanceName() ) |
| { |
| auto t = len; |
| |
| try |
| { |
| debug(trace) printf( "may be template instance name\n" ); |
| parseTemplateInstanceName(true); |
| return; |
| } |
| catch ( ParseException e ) |
| { |
| debug(trace) printf( "not a template instance name\n" ); |
| len = t; |
| } |
| } |
| goto case; |
| case 'Q': |
| parseLName(); |
| return; |
| default: |
| error(); |
| } |
| } |
| |
| // parse optional function arguments as part of a symbol name, i.e without return type |
| // if keepAttr, the calling convention and function attributes are not discarded, but returned |
| char[] parseFunctionTypeNoReturn( bool keepAttr = false ) return scope |
| { |
| // try to demangle a function, in case we are pointing to some function local |
| auto prevpos = pos; |
| auto prevlen = len; |
| auto prevbrp = brp; |
| |
| char[] attr; |
| try |
| { |
| if ( 'M' == front ) |
| { |
| // do not emit "needs this" |
| popFront(); |
| auto modifiers = parseModifier(); |
| while (auto str = typeCtors.toStringConsume(modifiers)) |
| { |
| put(str); |
| put(' '); |
| } |
| } |
| if ( isCallConvention( front ) ) |
| { |
| // we don't want calling convention and attributes in the qualified name |
| parseCallConvention(); |
| auto attributes = parseFuncAttr(); |
| if (keepAttr) { |
| while (auto str = funcAttrs.toStringConsume(attributes)) |
| { |
| put(str); |
| put(' '); |
| } |
| attr = dst[prevlen .. len]; |
| } |
| |
| put( '(' ); |
| parseFuncArguments(); |
| put( ')' ); |
| } |
| } |
| catch ( ParseException ) |
| { |
| // not part of a qualified name, so back up |
| pos = prevpos; |
| len = prevlen; |
| brp = prevbrp; |
| attr = null; |
| } |
| return attr; |
| } |
| |
| /* |
| QualifiedName: |
| SymbolName |
| SymbolName QualifiedName |
| */ |
| char[] parseQualifiedName() return scope |
| { |
| debug(trace) printf( "parseQualifiedName+\n" ); |
| debug(trace) scope(success) printf( "parseQualifiedName-\n" ); |
| size_t beg = len; |
| size_t n = 0; |
| |
| do |
| { |
| if ( n++ ) |
| put( '.' ); |
| parseSymbolName(); |
| parseFunctionTypeNoReturn(); |
| |
| } while ( isSymbolNameFront() ); |
| return dst[beg .. len]; |
| } |
| |
| |
| /* |
| MangledName: |
| _D QualifiedName Type |
| _D QualifiedName M Type |
| */ |
| void parseMangledName( bool displayType, size_t n = 0 ) scope |
| { |
| debug(trace) printf( "parseMangledName+\n" ); |
| debug(trace) scope(success) printf( "parseMangledName-\n" ); |
| char[] name = null; |
| |
| auto end = pos + n; |
| |
| eat( '_' ); |
| match( 'D' ); |
| do |
| { |
| size_t beg = len; |
| size_t nameEnd = len; |
| char[] attr; |
| do |
| { |
| if ( attr ) |
| remove( attr ); // dump attributes of parent symbols |
| if ( beg != len ) |
| put( '.' ); |
| parseSymbolName(); |
| nameEnd = len; |
| attr = parseFunctionTypeNoReturn( displayType ); |
| |
| } while ( isSymbolNameFront() ); |
| |
| if ( displayType ) |
| { |
| attr = shift( attr ); |
| nameEnd = len - attr.length; // name includes function arguments |
| } |
| name = dst[beg .. nameEnd]; |
| |
| debug(info) printf( "name (%.*s)\n", cast(int) name.length, name.ptr ); |
| if ( 'M' == front ) |
| popFront(); // has 'this' pointer |
| |
| auto lastlen = len; |
| auto type = parseType(); |
| if ( displayType ) |
| { |
| if ( type.length ) |
| put( ' ' ); |
| // sort (name,attr,type) -> (attr,type,name) |
| shift( name ); |
| } |
| else |
| { |
| // remove type |
| assert( attr.length == 0 ); |
| len = lastlen; |
| } |
| if ( pos >= buf.length || (n != 0 && pos >= end) ) |
| return; |
| |
| switch ( front ) |
| { |
| case 'T': // terminators when used as template alias parameter |
| case 'V': |
| case 'S': |
| case 'Z': |
| return; |
| default: |
| } |
| put( '.' ); |
| |
| } while ( true ); |
| } |
| |
| void parseMangledName() |
| { |
| parseMangledName( AddType.yes == addType ); |
| } |
| |
| char[] copyInput() return scope |
| { |
| if (dst.length < buf.length) |
| dst.length = buf.length; |
| char[] r = dst[0 .. buf.length]; |
| r[] = buf[]; |
| return r; |
| } |
| |
| char[] doDemangle(alias FUNC)() return scope |
| { |
| while ( true ) |
| { |
| try |
| { |
| debug(info) printf( "demangle(%.*s)\n", cast(int) buf.length, buf.ptr ); |
| FUNC(); |
| return dst[0 .. len]; |
| } |
| catch ( OverflowException e ) |
| { |
| debug(trace) printf( "overflow... restarting\n" ); |
| auto a = minBufSize; |
| auto b = 2 * dst.length; |
| auto newsz = a < b ? b : a; |
| debug(info) printf( "growing dst to %lu bytes\n", newsz ); |
| dst.length = newsz; |
| pos = len = brp = 0; |
| continue; |
| } |
| catch ( ParseException e ) |
| { |
| debug(info) |
| { |
| auto msg = e.toString(); |
| printf( "error: %.*s\n", cast(int) msg.length, msg.ptr ); |
| } |
| return copyInput(); |
| } |
| catch ( Exception e ) |
| { |
| assert( false ); // no other exceptions thrown |
| } |
| } |
| } |
| |
| char[] demangleName() nothrow |
| { |
| return doDemangle!parseMangledName(); |
| } |
| |
| char[] demangleType() nothrow |
| { |
| return doDemangle!parseType(); |
| } |
| } |
| |
| |
| /** |
| * Demangles D mangled names. If it is not a D mangled name, it returns its |
| * argument name. |
| * |
| * Params: |
| * buf = The string to demangle. |
| * dst = An optional destination buffer. |
| * |
| * Returns: |
| * The demangled name or the original string if the name is not a mangled D |
| * name. |
| */ |
| char[] demangle(return scope const(char)[] buf, return scope char[] dst = null ) nothrow pure @safe |
| { |
| auto d = Demangle!()(buf, dst); |
| // fast path (avoiding throwing & catching exception) for obvious |
| // non-D mangled names |
| if (buf.length < 2 || !(buf[0] == 'D' || buf[0..2] == "_D")) |
| return d.copyInput(); |
| return d.demangleName(); |
| } |
| |
| |
| /** |
| * Demangles a D mangled type. |
| * |
| * Params: |
| * buf = The string to demangle. |
| * dst = An optional destination buffer. |
| * |
| * Returns: |
| * The demangled type name or the original string if the name is not a |
| * mangled D type. |
| */ |
| char[] demangleType( const(char)[] buf, char[] dst = null ) nothrow pure @safe |
| { |
| auto d = Demangle!()(buf, dst); |
| return d.demangleType(); |
| } |
| |
| /** |
| * reencode a mangled symbol name that might include duplicate occurrences |
| * of the same identifier by replacing all but the first occurence with |
| * a back reference. |
| * |
| * Params: |
| * mangled = The mangled string representing the type |
| * |
| * Returns: |
| * The mangled name with deduplicated identifiers |
| */ |
| char[] reencodeMangled(return scope const(char)[] mangled) nothrow pure @safe |
| { |
| static struct PrependHooks |
| { |
| size_t lastpos; |
| char[] result; |
| size_t[const(char)[]] idpos; // identifier positions |
| |
| static struct Replacement |
| { |
| size_t pos; // postion in original mangled string |
| size_t respos; // postion in result string |
| } |
| Replacement [] replacements; |
| |
| pure @safe: |
| size_t positionInResult(size_t pos) scope |
| { |
| foreach_reverse (r; replacements) |
| if (pos >= r.pos) |
| return r.respos + pos - r.pos; |
| return pos; |
| } |
| |
| alias Remangle = Demangle!(PrependHooks); |
| |
| void flushPosition(ref Remangle d) scope |
| { |
| if (lastpos < d.pos) |
| { |
| result ~= d.buf[lastpos .. d.pos]; |
| } |
| else if (lastpos > d.pos) |
| { |
| // roll back to earlier position |
| while (replacements.length > 0 && replacements[$-1].pos > d.pos) |
| replacements = replacements[0 .. $-1]; |
| |
| if (replacements.length > 0) |
| result.length = replacements[$-1].respos + d.pos - replacements[$-1].pos; |
| else |
| result.length = d.pos; |
| } |
| } |
| |
| bool parseLName(scope ref Remangle d) scope |
| { |
| flushPosition(d); |
| |
| auto reslen = result.length; |
| auto refpos = d.pos; |
| if (d.front == 'Q') |
| { |
| size_t npos; |
| { |
| scope(exit) result.length = reslen; // remove all intermediate additions |
| // only support identifier back references |
| d.popFront(); |
| size_t n = d.decodeBackref(); |
| if (!n || n > refpos) |
| d.error("invalid back reference"); |
| |
| auto savepos = d.pos; |
| scope(exit) d.pos = savepos; |
| size_t srcpos = refpos - n; |
| |
| auto idlen = d.decodeNumber(); |
| if (d.pos + idlen > d.buf.length) |
| d.error("invalid back reference"); |
| auto id = d.buf[d.pos .. d.pos + idlen]; |
| auto pid = id in idpos; |
| if (!pid) |
| d.error("invalid back reference"); |
| npos = positionInResult(*pid); |
| } |
| encodeBackref(reslen - npos); |
| const pos = d.pos; // work around issues.dlang.org/show_bug.cgi?id=20675 |
| replacements ~= Replacement(pos, result.length); |
| } |
| else |
| { |
| auto n = d.decodeNumber(); |
| if (!n || n > d.buf.length || n > d.buf.length - d.pos) |
| d.error("LName too shot or too long"); |
| auto id = d.buf[d.pos .. d.pos + n]; |
| d.pos += n; |
| if (auto pid = id in idpos) |
| { |
| size_t npos = positionInResult(*pid); |
| result.length = reslen; |
| encodeBackref(reslen - npos); |
| const pos = d.pos; // work around issues.dlang.org/show_bug.cgi?id=20675 |
| replacements ~= Replacement(pos, result.length); |
| } |
| else |
| { |
| idpos[id] = refpos; |
| result ~= d.buf[refpos .. d.pos]; |
| } |
| } |
| lastpos = d.pos; |
| return true; |
| } |
| |
| char[] parseType( ref Remangle d, char[] name = null ) return scope |
| { |
| if (d.front != 'Q') |
| return null; |
| |
| flushPosition(d); |
| |
| auto refPos = d.pos; |
| d.popFront(); |
| auto n = d.decodeBackref(); |
| if (n == 0 || n > refPos) |
| d.error("invalid back reference"); |
| |
| size_t npos = positionInResult(refPos - n); |
| size_t reslen = result.length; |
| encodeBackref(reslen - npos); |
| |
| lastpos = d.pos; |
| return result[reslen .. $]; // anything but null |
| } |
| |
| void encodeBackref(size_t relpos) scope |
| { |
| result ~= 'Q'; |
| enum base = 26; |
| size_t div = 1; |
| while (relpos >= div * base) |
| div *= base; |
| while (div >= base) |
| { |
| auto dig = (relpos / div); |
| result ~= cast(char)('A' + dig); |
| relpos -= dig * div; |
| div /= base; |
| } |
| result ~= cast(char)('a' + relpos); |
| } |
| } |
| |
| auto d = Demangle!(PrependHooks)(mangled, null); |
| d.hooks = PrependHooks(); |
| d.mute = true; // no demangled output |
| try |
| { |
| d.parseMangledName(); |
| if (d.hooks.lastpos < d.pos) |
| d.hooks.result ~= d.buf[d.hooks.lastpos .. d.pos]; |
| return d.hooks.result; |
| } |
| catch (Exception) |
| { |
| // overflow exception cannot occur |
| return mangled.dup; |
| } |
| } |
| |
| /** |
| * Mangles a D symbol. |
| * |
| * Params: |
| * T = The type of the symbol. |
| * fqn = The fully qualified name of the symbol. |
| * dst = An optional destination buffer. |
| * |
| * Returns: |
| * The mangled name for a symbols of type T and the given fully |
| * qualified name. |
| */ |
| char[] mangle(T)(return scope const(char)[] fqn, return scope char[] dst = null) @safe pure nothrow |
| { |
| import core.internal.string : numDigits, unsignedToTempString; |
| |
| static struct DotSplitter |
| { |
| @safe pure nothrow: |
| const(char)[] s; |
| |
| @property bool empty() const { return !s.length; } |
| |
| @property const(char)[] front() const return scope |
| { |
| immutable i = indexOfDot(); |
| return i == -1 ? s[0 .. $] : s[0 .. i]; |
| } |
| |
| void popFront() scope |
| { |
| immutable i = indexOfDot(); |
| s = i == -1 ? s[$ .. $] : s[i+1 .. $]; |
| } |
| |
| private ptrdiff_t indexOfDot() const scope |
| { |
| foreach (i, c; s) if (c == '.') return i; |
| return -1; |
| } |
| } |
| |
| size_t len = "_D".length; |
| foreach (comp; DotSplitter(fqn)) |
| len += numDigits(comp.length) + comp.length; |
| len += T.mangleof.length; |
| if (dst.length < len) dst.length = len; |
| |
| size_t i = "_D".length; |
| dst[0 .. i] = "_D"; |
| foreach (comp; DotSplitter(fqn)) |
| { |
| const ndigits = numDigits(comp.length); |
| unsignedToTempString(comp.length, dst[i .. i + ndigits]); |
| i += ndigits; |
| dst[i .. i + comp.length] = comp[]; |
| i += comp.length; |
| } |
| dst[i .. i + T.mangleof.length] = T.mangleof[]; |
| i += T.mangleof.length; |
| |
| static if (hasTypeBackRef) |
| return reencodeMangled(dst[0 .. i]); |
| else |
| return dst[0 .. i]; |
| } |
| |
| |
| /// |
| @safe pure nothrow unittest |
| { |
| assert(mangle!int("a.b") == "_D1a1bi"); |
| assert(mangle!(char[])("test.foo") == "_D4test3fooAa"); |
| assert(mangle!(int function(int))("a.b") == "_D1a1bPFiZi"); |
| } |
| |
| @safe pure nothrow unittest |
| { |
| static assert(mangle!int("a.b") == "_D1a1bi"); |
| |
| auto buf = new char[](10); |
| buf = mangle!int("a.b", buf); |
| assert(buf == "_D1a1bi"); |
| buf = mangle!(char[])("test.foo", buf); |
| assert(buf == "_D4test3fooAa"); |
| buf = mangle!(real delegate(int))("modµ.dg"); |
| assert(buf == "_D5modµ2dgDFiZe", buf); |
| } |
| |
| |
| /** |
| * Mangles a D function. |
| * |
| * Params: |
| * T = function pointer type. |
| * fqn = The fully qualified name of the symbol. |
| * dst = An optional destination buffer. |
| * |
| * Returns: |
| * The mangled name for a function with function pointer type T and |
| * the given fully qualified name. |
| */ |
| char[] mangleFunc(T:FT*, FT)(return scope const(char)[] fqn, return scope char[] dst = null) @safe pure nothrow if (is(FT == function)) |
| { |
| static if (isExternD!FT) |
| { |
| return mangle!FT(fqn, dst); |
| } |
| else static if (hasPlainMangling!FT) |
| { |
| dst.length = fqn.length; |
| dst[] = fqn[]; |
| return dst; |
| } |
| else static if (isExternCPP!FT) |
| { |
| static assert(0, "Can't mangle extern(C++) functions."); |
| } |
| else |
| { |
| static assert(0, "Can't mangle function with unknown linkage ("~FT.stringof~")."); |
| } |
| } |
| |
| private enum hasTypeBackRef = (int function(void**,void**)).mangleof[$-4 .. $] == "QdZi"; |
| |
| @safe pure nothrow unittest |
| { |
| assert(mangleFunc!(int function(int))("a.b") == "_D1a1bFiZi"); |
| static if (hasTypeBackRef) |
| { |
| assert(mangleFunc!(int function(Object))("object.Object.opEquals") == "_D6object6Object8opEqualsFCQsZi"); |
| assert(mangleFunc!(int function(Object, Object))("object.Object.opEquals") == "_D6object6Object8opEqualsFCQsQdZi"); |
| } |
| else |
| { |
| auto mngl = mangleFunc!(int function(Object))("object.Object.opEquals"); |
| assert(mngl == "_D6object6Object8opEqualsFC6ObjectZi"); |
| auto remngl = reencodeMangled(mngl); |
| assert(remngl == "_D6object6Object8opEqualsFCQsZi"); |
| } |
| // trigger back tracking with ambiguity on '__T', template or identifier |
| assert(reencodeMangled("_D3std4conv4conv7__T3std4convi") == "_D3std4convQf7__T3stdQpi"); |
| } |
| |
| @safe pure nothrow unittest |
| { |
| int function(lazy int[], ...) fp; |
| assert(mangle!(typeof(fp))("demangle.test") == "_D8demangle4testPFLAiYi"); |
| assert(mangle!(typeof(*fp))("demangle.test") == "_D8demangle4testFLAiYi"); |
| } |
| |
| private template isExternD(FT) if (is(FT == function)) |
| { |
| enum isExternD = __traits(getLinkage, FT) == "D"; |
| } |
| |
| private template isExternCPP(FT) if (is(FT == function)) |
| { |
| enum isExternCPP = __traits(getLinkage, FT) == "C++"; |
| } |
| |
| private template hasPlainMangling(FT) if (is(FT == function)) |
| { |
| enum lnk = __traits(getLinkage, FT); |
| // C || Windows |
| enum hasPlainMangling = lnk == "C" || lnk == "Windows" || lnk == "System"; |
| } |
| |
| @safe pure nothrow unittest |
| { |
| static extern(D) void fooD(); |
| static extern(C) void fooC(); |
| static extern(Windows) void fooW(); |
| static extern(C++) void fooCPP(); |
| |
| bool check(FT)(bool isD, bool isCPP, bool isPlain) |
| { |
| return isExternD!FT == isD && isExternCPP!FT == isCPP && |
| hasPlainMangling!FT == isPlain; |
| } |
| static assert(check!(typeof(fooD))(true, false, false)); |
| static assert(check!(typeof(fooC))(false, false, true)); |
| static assert(check!(typeof(fooW))(false, false, true)); |
| static assert(check!(typeof(fooCPP))(false, true, false)); |
| |
| static assert(__traits(compiles, mangleFunc!(typeof(&fooD))(""))); |
| static assert(__traits(compiles, mangleFunc!(typeof(&fooC))(""))); |
| static assert(__traits(compiles, mangleFunc!(typeof(&fooW))(""))); |
| static assert(!__traits(compiles, mangleFunc!(typeof(&fooCPP))(""))); |
| } |
| |
| /*** |
| * C name mangling is done by adding a prefix on some platforms. |
| */ |
| version (Win32) |
| enum string cPrefix = "_"; |
| else version (Darwin) |
| enum string cPrefix = "_"; |
| else |
| enum string cPrefix = ""; |
| |
| @safe pure nothrow unittest |
| { |
| immutable string[2][] table = |
| [ |
| ["printf", "printf"], |
| ["_foo", "_foo"], |
| ["_D88", "_D88"], |
| ["_D3fooQeFIAyaZv", "void foo.foo(in immutable(char)[])" ], |
| ["_D3barQeFIKAyaZv", "void bar.bar(in ref immutable(char)[])" ], |
| ["_D4test3fooAa", "char[] test.foo"], |
| ["_D8demangle8demangleFAaZAa", "char[] demangle.demangle(char[])"], |
| ["_D6object6Object8opEqualsFC6ObjectZi", "int object.Object.opEquals(Object)"], |
| ["_D4test2dgDFiYd", "double delegate(int, ...) test.dg"], |
| ["_D4test2dgDxFNfiYd", "double delegate(int, ...) @safe const test.dg"], |
| //["_D4test58__T9factorialVde67666666666666860140VG5aa5_68656c6c6fVPvnZ9factorialf", ""], |
| //["_D4test101__T9factorialVde67666666666666860140Vrc9a999999999999d9014000000000000000c00040VG5aa5_68656c6c6fVPvnZ9factorialf", ""], |
| ["_D4test34__T3barVG3uw3_616263VG3wd3_646566Z1xi", "int test.bar!(\"abc\"w, \"def\"d).x"], |
| ["_D8demangle4testFLC6ObjectLDFLiZiZi", "int demangle.test(lazy Object, lazy int delegate(lazy int))"], |
| ["_D8demangle4testFAiXi", "int demangle.test(int[]...)"], |
| ["_D8demangle4testFAiYi", "int demangle.test(int[], ...)"], |
| ["_D8demangle4testFLAiXi", "int demangle.test(lazy int[]...)"], |
| ["_D8demangle4testFLAiYi", "int demangle.test(lazy int[], ...)"], |
| ["_D6plugin8generateFiiZAya", "immutable(char)[] plugin.generate(int, int)"], |
| ["_D6plugin8generateFiiZAxa", "const(char)[] plugin.generate(int, int)"], |
| ["_D6plugin8generateFiiZAOa", "shared(char)[] plugin.generate(int, int)"], |
| ["_D8demangle3fnAFZ3fnBMFZv", "void demangle.fnA().fnB()"], |
| ["_D8demangle4mainFZ1S3fnCMFZv", "void demangle.main().S.fnC()"], |
| ["_D8demangle4mainFZ1S3fnDMFZv", "void demangle.main().S.fnD()"], |
| ["_D8demangle20__T2fnVAiA4i1i2i3i4Z2fnFZv", "void demangle.fn!([1, 2, 3, 4]).fn()"], |
| ["_D8demangle10__T2fnVi1Z2fnFZv", "void demangle.fn!(1).fn()"], |
| ["_D8demangle26__T2fnVS8demangle1SS2i1i2Z2fnFZv", "void demangle.fn!(demangle.S(1, 2)).fn()"], |
| ["_D8demangle13__T2fnVeeNANZ2fnFZv", "void demangle.fn!(real.nan).fn()"], |
| ["_D8demangle14__T2fnVeeNINFZ2fnFZv", "void demangle.fn!(-real.infinity).fn()"], |
| ["_D8demangle13__T2fnVeeINFZ2fnFZv", "void demangle.fn!(real.infinity).fn()"], |
| ["_D8demangle21__T2fnVHiiA2i1i2i3i4Z2fnFZv", "void demangle.fn!([1:2, 3:4]).fn()"], |
| ["_D8demangle2fnFNgiZNgi", "inout(int) demangle.fn(inout(int))"], |
| ["_D8demangle29__T2fnVa97Va9Va0Vu257Vw65537Z2fnFZv", "void demangle.fn!('a', '\\t', \\x00, '\\u0101', '\\U00010001').fn()"], |
| ["_D2gc11gctemplates56__T8mkBitmapTS3std5range13__T4iotaTiTiZ4iotaFiiZ6ResultZ8mkBitmapFNbNiNfPmmZv", |
| "nothrow @nogc @safe void gc.gctemplates.mkBitmap!(std.range.iota!(int, int).iota(int, int).Result).mkBitmap(ulong*, ulong)"], |
| ["_D8serenity9persister6Sqlite69__T15SqlitePersisterTS8serenity9persister6Sqlite11__unittest6FZ4TestZ15SqlitePersister12__T7opIndexZ7opIndexMFmZS8serenity9persister6Sqlite11__unittest6FZ4Test", |
| "serenity.persister.Sqlite.__unittest6().Test serenity.persister.Sqlite.SqlitePersister!(serenity.persister.Sqlite.__unittest6().Test).SqlitePersister.opIndex!().opIndex(ulong)"], |
| ["_D8bug100274mainFZ5localMFZi","int bug10027.main().local()"], |
| ["_D8demangle4testFNhG16gZv", "void demangle.test(__vector(byte[16]))"], |
| ["_D8demangle4testFNhG8sZv", "void demangle.test(__vector(short[8]))"], |
| ["_D8demangle4testFNhG4iZv", "void demangle.test(__vector(int[4]))"], |
| ["_D8demangle4testFNhG2lZv", "void demangle.test(__vector(long[2]))"], |
| ["_D8demangle4testFNhG4fZv", "void demangle.test(__vector(float[4]))"], |
| ["_D8demangle4testFNhG2dZv", "void demangle.test(__vector(double[2]))"], |
| ["_D8demangle4testFNhG4fNhG4fZv", "void demangle.test(__vector(float[4]), __vector(float[4]))"], |
| ["_D8bug1119234__T3fooS23_D8bug111924mainFZ3bariZ3fooMFZv","void bug11192.foo!(bug11192.main().bar).foo()"], |
| ["_D13libd_demangle12__ModuleInfoZ", "libd_demangle.__ModuleInfo"], |
| ["_D15TypeInfo_Struct6__vtblZ", "TypeInfo_Struct.__vtbl"], |
| ["_D3std5stdio12__ModuleInfoZ", "std.stdio.__ModuleInfo"], |
| ["_D3std6traits15__T8DemangleTkZ8Demangle6__initZ", "std.traits.Demangle!(uint).Demangle.__init"], |
| ["_D3foo3Bar7__ClassZ", "foo.Bar.__Class"], |
| ["_D3foo3Bar6__vtblZ", "foo.Bar.__vtbl"], |
| ["_D3foo3Bar11__interfaceZ", "foo.Bar.__interface"], |
| ["_D3foo7__arrayZ", "foo.__array"], |
| ["_D8link657428__T3fooVE8link65746Methodi0Z3fooFZi", "int link6574.foo!(0).foo()"], |
| ["_D8link657429__T3fooHVE8link65746Methodi0Z3fooFZi", "int link6574.foo!(0).foo()"], |
| ["_D4test22__T4funcVAyaa3_610a62Z4funcFNaNbNiNmNfZAya", `pure nothrow @nogc @live @safe immutable(char)[] test.func!("a\x0ab").func()`], |
| ["_D3foo3barFzkZzi", "cent foo.bar(ucent)"], |
| ["_D5bug145Class3fooMFNlZPv", "scope void* bug14.Class.foo()"], |
| ["_D5bug145Class3barMFNjZPv", "return void* bug14.Class.bar()"], |
| ["_D5bug143fooFMPvZPv", "void* bug14.foo(scope void*)"], |
| ["_D5bug143barFMNkPvZPv", "void* bug14.bar(scope return void*)"], |
| ["_D3std5range15__T4iotaTtTtTtZ4iotaFtttZ6Result7opIndexMNgFNaNbNiNfmZNgt", |
| "inout pure nothrow @nogc @safe inout(ushort) std.range.iota!(ushort, ushort, ushort).iota(ushort, ushort, ushort).Result.opIndex(ulong)"], |
| ["_D3std6format77__T6getNthVAyaa13_696e7465676572207769647468S233std6traits10isIntegralTiTkTkZ6getNthFNaNfkkkZi", |
| "pure @safe int std.format.getNth!(\"integer width\", std.traits.isIntegral, int, uint, uint).getNth(uint, uint, uint)"], |
| ["_D3std11parallelism42__T16RoundRobinBufferTDFKAaZvTDxFNaNdNeZbZ16RoundRobinBuffer5primeMFZv", |
| "void std.parallelism.RoundRobinBuffer!(void delegate(ref char[]), bool delegate() pure @property @trusted const).RoundRobinBuffer.prime()"], |
| ["_D6mangle__T8fun21753VSQv6S21753S1f_DQBj10__lambda71MFNaNbNiNfZvZQCbQp", |
| "void function() pure nothrow @nogc @safe mangle.fun21753!(mangle.S21753(mangle.__lambda71())).fun21753"], |
| // Lname '0' |
| ["_D3std9algorithm9iteration__T9MapResultSQBmQBlQBe005stripTAAyaZQBi7opSliceMFNaNbNiNfmmZSQDiQDhQDa__TQCtSQDyQDxQDq00QCmTQCjZQDq", |
| "pure nothrow @nogc @safe std.algorithm.iteration.MapResult!(std.algorithm.iteration.__anonymous.strip, " |
| ~"immutable(char)[][]).MapResult std.algorithm.iteration.MapResult!(std.algorithm.iteration.strip, immutable(char)[][]).MapResult.opSlice(ulong, ulong)"], |
| |
| // back references |
| ["_D4core4stdc5errnoQgFZi", "int core.stdc.errno.errno()"], // identifier back reference |
| ["_D4testFS10structnameQnZb", "bool test(structname, structname)"], // type back reference |
| ["_D3std11parallelism__T4TaskS8unittest3cmpTAyaTQeZQBb6__dtorMFNfZv", |
| "@safe void std.parallelism.Task!(unittest.cmp, immutable(char)[], immutable(char)[]).Task.__dtor()"], |
| // 1.s.s.foo from https://issues.dlang.org/show_bug.cgi?id=15831 |
| ["_D13testexpansion44__T1sTS13testexpansion8__T1sTiZ1sFiZ6ResultZ1sFS13testexpansion8__T1sTiZ1sFiZ6ResultZ6Result3fooMFNaNfZv", |
| "pure @safe void testexpansion.s!(testexpansion.s!(int).s(int).Result).s(testexpansion.s!(int).s(int).Result).Result.foo()"], |
| ["_D13testexpansion__T1sTSQw__TQjTiZQoFiZ6ResultZQBbFQBcZQq3fooMFNaNfZv", |
| "pure @safe void testexpansion.s!(testexpansion.s!(int).s(int).Result).s(testexpansion.s!(int).s(int).Result).Result.foo()"], |
| // formerly ambiguous on 'V', template value argument or pascal function |
| // pascal functions have now been removed (in v2.095.0) |
| ["_D3std4conv__T7enumRepTyAaTEQBa12experimental9allocator15building_blocks15stats_collector7OptionsVQCti64ZQDnyQDh", |
| "immutable(char[]) std.conv.enumRep!(immutable(char[]), std.experimental.allocator.building_blocks.stats_collector.Options, 64).enumRep"], |
| // symbol back reference to location with symbol back reference |
| ["_D3std12experimental9allocator6common__T10reallocateTSQCaQBzQBo15building_blocks17kernighan_ritchie__T8KRRegionTSQEhQEgQDvQCh14null_allocator13NullAllocatorZQCdZQErFNaNbNiKQEpKAvmZb", |
| "pure nothrow @nogc bool std.experimental.allocator.common.reallocate!(std.experimental.allocator.building_blocks.kernighan_ritchie.KRRegion!(" |
| ~"std.experimental.allocator.building_blocks.null_allocator.NullAllocator).KRRegion).reallocate(ref " |
| ~"std.experimental.allocator.building_blocks.kernighan_ritchie.KRRegion!(std.experimental.allocator.building_blocks.null_allocator.NullAllocator).KRRegion, ref void[], ulong)"], |
| ["_D3std9exception__T11doesPointToTASQBh5regex8internal2ir10NamedGroupTQBkTvZQCeFNaNbNiNeKxASQDlQCeQCbQBvQBvKxQtZb", |
| "pure nothrow @nogc @trusted bool std.exception.doesPointTo!(std.regex.internal.ir.NamedGroup[], " |
| ~"std.regex.internal.ir.NamedGroup[], void).doesPointTo(ref const(std.regex.internal.ir.NamedGroup[]), ref const(std.regex.internal.ir.NamedGroup[]))"], |
| ["_D3std9algorithm9iteration__T14SplitterResultS_DQBu3uni7isWhiteFNaNbNiNfwZbTAyaZQBz9__xtoHashFNbNeKxSQDvQDuQDn__TQDgS_DQEnQCtQCsQCnTQCeZQEdZm", |
| "nothrow @trusted ulong std.algorithm.iteration.SplitterResult!(std.uni.isWhite(dchar), immutable(char)[]).SplitterResult." |
| ~"__xtoHash(ref const(std.algorithm.iteration.SplitterResult!(std.uni.isWhite, immutable(char)[]).SplitterResult))"], |
| ["_D3std8typecons__T7TypedefTCQBaQz19__unittestL6513_208FNfZ7MyClassVQBonVAyanZQCh6__ctorMFNaNbNcNiNfQCuZSQDyQDx__TQDrTQDmVQDqnVQCcnZQEj", |
| "pure nothrow ref @nogc @safe std.typecons.Typedef!(std.typecons.__unittestL6513_208().MyClass, null, null).Typedef " |
| ~"std.typecons.Typedef!(std.typecons.__unittestL6513_208().MyClass, null, null).Typedef.__ctor(std.typecons.__unittestL6513_208().MyClass)"], |
| ["_D3std6getopt__TQkTAyaTDFNaNbNiNfQoZvTQtTDQsZQBnFNfKAQBiQBlQBkQBrQyZSQCpQCo12GetoptResult", |
| "@safe std.getopt.GetoptResult std.getopt.getopt!(immutable(char)[], void delegate(immutable(char)[]) pure nothrow @nogc @safe, " |
| ~"immutable(char)[], void delegate(immutable(char)[]) pure nothrow @nogc @safe)." |
| ~"getopt(ref immutable(char)[][], immutable(char)[], void delegate(immutable(char)[]) pure nothrow @nogc @safe, " |
| ~"immutable(char)[], void delegate(immutable(char)[]) pure nothrow @nogc @safe)"], |
| ["_D3std5regex8internal9kickstart__T7ShiftOrTaZQl11ShiftThread__T3setS_DQCqQCpQCmQCg__TQBzTaZQCfQBv10setInvMaskMFNaNbNiNfkkZvZQCjMFNaNfwZv", |
| "pure @safe void std.regex.internal.kickstart.ShiftOr!(char).ShiftOr.ShiftThread.set!(std.regex.internal.kickstart.ShiftOr!(char).ShiftOr.ShiftThread.setInvMask(uint, uint)).set(dchar)"], |
| ["_D3std5stdio4File__T8lockImplX10LockFileExTykZQBaMFmmykZi", // C function as template alias parameter |
| "int std.stdio.File.lockImpl!(LockFileEx, immutable(uint)).lockImpl(ulong, ulong, immutable(uint))"], |
| // back reference for type in template AA parameter value |
| ["_D3std9algorithm9iteration__T12FilterResultSQBq8typecons__T5TupleTiVAyaa1_61TiVQla1_62TiVQva1_63ZQBm__T6renameVHiQBtA2i0a1_63i2a1_61ZQBeMFNcZ9__lambda1TAiZQEw9__xtoHashFNbNeKxSQGsQGrQGk__TQGdSQHiQFs__TQFmTiVQFja1_61TiVQFua1_62TiVQGfa1_63ZQGx__TQFlVQFhA2i0a1_63i2a1_61ZQGjMFNcZQFfTQEyZQJvZm", |
| `nothrow @trusted ulong std.algorithm.iteration.FilterResult!(std.typecons.Tuple!(int, "a", int, "b", int, "c").` |
| ~`Tuple.rename!([0:"c", 2:"a"]).rename().__lambda1, int[]).FilterResult.__xtoHash(ref const(std.algorithm.iteration.` |
| ~`FilterResult!(std.typecons.Tuple!(int, "a", int, "b", int, "c").Tuple.rename!([0:"c", 2:"a"]).rename().__lambda1, int[]).FilterResult))`], |
| |
| ["_D4test4rrs1FKPiZv", "void test.rrs1(ref int*)"], |
| ["_D4test4rrs1FMNkJPiZv", "void test.rrs1(scope return out int*)"], |
| ["_D4test4rrs1FMNkKPiZv", "void test.rrs1(scope return ref int*)"], |
| ["_D4test4rrs1FNkJPiZv", "void test.rrs1(return out int*)"], |
| ["_D4test4rrs1FNkKPiZv", "void test.rrs1(return ref int*)"], |
| ["_D4test4rrs1FNkMJPiZv", "void test.rrs1(return scope out int*)"], |
| ["_D4test4rrs1FNkMKPiZv", "void test.rrs1(return scope ref int*)"], |
| ["_D4test4rrs1FNkMPiZv", "void test.rrs1(return scope int*)"], |
| |
| // `scope` and `return` combinations |
| ["_D3foo3Foo3barMNgFNjNlNfZNgPv", "inout return scope @safe inout(void*) foo.Foo.bar()"], |
| ["_D3foo3FooQiMNgFNlNfZv", "inout scope @safe void foo.Foo.foo()"], |
| ["_D3foo3Foo4foorMNgFNjNfZv", "inout return @safe void foo.Foo.foor()"], |
| ["_D3foo3Foo3rabMNgFNlNjNfZv", "inout scope return @safe void foo.Foo.rab()"], |
| ]; |
| |
| |
| template staticIota(int x) |
| { |
| template Seq(T...){ alias Seq = T; } |
| |
| static if (x == 0) |
| alias staticIota = Seq!(); |
| else |
| alias staticIota = Seq!(staticIota!(x - 1), x - 1); |
| } |
| foreach ( i, name; table ) |
| { |
| auto r = demangle( name[0] ); |
| assert( r == name[1], |
| "demangled `" ~ name[0] ~ "` as `" ~ r ~ "` but expected `" ~ name[1] ~ "`"); |
| } |
| foreach ( i; staticIota!(table.length) ) |
| { |
| enum r = demangle( table[i][0] ); |
| static assert( r == table[i][1], |
| "demangled `" ~ table[i][0] ~ "` as `" ~ r ~ "` but expected `" ~ table[i][1] ~ "`"); |
| } |
| |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=18531 |
| auto symbol = `_D3std3uni__T6toCaseS_DQvQt12toLowerIndexFNaNbNiNewZtVii1043S_DQCjQCi10toLowerTabFNaNbNiNemZwSQDo5ascii7toLowerTAyaZQDzFNaNeQmZ14__foreachbody2MFNaNeKmKwZ14__foreachbody3MFNaNeKwZi`; |
| auto demangled = `pure @trusted int std.uni.toCase!(std.uni.toLowerIndex(dchar), 1043, std.uni.toLowerTab(ulong), std.ascii.toLower, immutable(char)[]).toCase(immutable(char)[]).__foreachbody2(ref ulong, ref dchar).__foreachbody3(ref dchar)`; |
| auto dst = new char[200]; |
| auto ret = demangle( symbol, dst); |
| assert( ret == demangled ); |
| } |
| } |
| |
| unittest |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=18300 |
| string s = demangle.mangleof; |
| foreach (i; 1..77) |
| { |
| char[] buf = new char[i]; |
| auto ds = demangle(s, buf); |
| assert(ds == "pure nothrow @safe char[] core.demangle.demangle(scope return const(char)[], scope return char[])" || |
| ds == "pure nothrow @safe char[] core.demangle.demangle(return scope const(char)[], return scope char[])"); |
| } |
| } |
| |
| unittest |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=18300 |
| string s = "_D1"; |
| string expected = "int "; |
| foreach (_; 0..10_000) |
| { |
| s ~= "a1"; |
| expected ~= "a."; |
| } |
| s ~= "FiZi"; |
| expected ~= "F"; |
| assert(s.demangle == expected); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=22235 |
| unittest |
| { |
| enum parent = __MODULE__ ~ '.' ~ __traits(identifier, __traits(parent, {})); |
| |
| static noreturn abort() { assert(false); } |
| assert(demangle(abort.mangleof) == "pure nothrow @nogc @safe noreturn " ~ parent ~ "().abort()"); |
| |
| static void accept(noreturn) {} |
| assert(demangle(accept.mangleof) == "pure nothrow @nogc @safe void " ~ parent ~ "().accept(noreturn)"); |
| |
| static void templ(T)(T, T) {} |
| assert(demangle(templ!noreturn.mangleof) == "pure nothrow @nogc @safe void " ~ parent ~ "().templ!(noreturn).templ(noreturn, noreturn)"); |
| |
| static struct S(T) {} |
| static void aggr(S!noreturn) { assert(0); } |
| assert(demangle(aggr.mangleof) == "pure nothrow @nogc @safe void " ~ parent ~ "().aggr(" ~ parent ~ "().S!(noreturn).S)"); |
| } |
| |
| /* |
| * Expand an OMF, DMD-generated compressed identifier into its full form |
| * |
| * This function only has a visible effect for OMF binaries (Win32), |
| * as compression is otherwise not used. |
| * |
| * See_Also: `compiler/src/dmd/backend/compress.d` |
| */ |
| string decodeDmdString( const(char)[] ln, ref size_t p ) nothrow pure @safe |
| { |
| string s; |
| uint zlen, zpos; |
| |
| // decompress symbol |
| while ( p < ln.length ) |
| { |
| int ch = cast(ubyte) ln[p++]; |
| if ( (ch & 0xc0) == 0xc0 ) |
| { |
| zlen = (ch & 0x7) + 1; |
| zpos = ((ch >> 3) & 7) + 1; // + zlen; |
| if ( zpos > s.length ) |
| break; |
| s ~= s[$ - zpos .. $ - zpos + zlen]; |
| } |
| else if ( ch >= 0x80 ) |
| { |
| if ( p >= ln.length ) |
| break; |
| int ch2 = cast(ubyte) ln[p++]; |
| zlen = (ch2 & 0x7f) | ((ch & 0x38) << 4); |
| if ( p >= ln.length ) |
| break; |
| int ch3 = cast(ubyte) ln[p++]; |
| zpos = (ch3 & 0x7f) | ((ch & 7) << 7); |
| if ( zpos > s.length ) |
| break; |
| s ~= s[$ - zpos .. $ - zpos + zlen]; |
| } |
| else if ( Demangle!().isAlpha(cast(char)ch) || Demangle!().isDigit(cast(char)ch) || ch == '_' ) |
| s ~= cast(char) ch; |
| else |
| { |
| p--; |
| break; |
| } |
| } |
| return s; |
| } |
| |
| // locally purified for internal use here only |
| extern (C) private |
| { |
| pure @trusted @nogc nothrow pragma(mangle, "fakePureReprintReal") void pureReprintReal(char[] nptr); |
| |
| void fakePureReprintReal(char[] nptr) |
| { |
| import core.stdc.stdlib : strtold; |
| import core.stdc.stdio : snprintf; |
| import core.stdc.errno : errno; |
| |
| const err = errno; |
| real val = strtold(nptr.ptr, null); |
| snprintf(nptr.ptr, nptr.length, "%#Lg", val); |
| errno = err; |
| } |
| } |
| |
| private struct ManglingFlagInfo |
| { |
| /// The flag value to use |
| ushort flag; |
| |
| /// Human-readable representation |
| string value; |
| } |
| |
| private enum TypeCtor : ushort { |
| None = 0, |
| //// 'x' |
| Const = (1 << 1), |
| /// 'y' |
| Immutable = (1 << 2), |
| /// 'O' |
| Shared = (1 << 3), |
| /// |
| InOut = (1 << 4), |
| } |
| |
| private immutable ManglingFlagInfo[] typeCtors = [ |
| ManglingFlagInfo(TypeCtor.Immutable, "immutable"), |
| ManglingFlagInfo(TypeCtor.Shared, "shared"), |
| ManglingFlagInfo(TypeCtor.InOut, "inout"), |
| ManglingFlagInfo(TypeCtor.Const, "const"), |
| ]; |
| |
| private enum FuncAttributes : ushort { |
| None = 0, |
| //// 'a' |
| Pure = (1 << 1), |
| //// 'b' |
| Nothrow = (1 << 2), |
| //// 'c' |
| Ref = (1 << 3), |
| //// 'd' |
| Property = (1 << 4), |
| //// 'e' |
| Trusted = (1 << 5), |
| //// 'f' |
| Safe = (1 << 6), |
| //// 'i' |
| NoGC = (1 << 7), |
| //// 'j' |
| Return = (1 << 8), |
| //// 'l' |
| Scope = (1 << 9), |
| //// 'm' |
| Live = (1 << 10), |
| |
| /// Their order matter |
| ReturnScope = (1 << 11), |
| ScopeReturn = (1 << 12), |
| } |
| |
| // The order in which we process is the same as in compiler/dmd/src/dmangle.d |
| private immutable ManglingFlagInfo[] funcAttrs = [ |
| ManglingFlagInfo(FuncAttributes.Pure, "pure"), |
| ManglingFlagInfo(FuncAttributes.Nothrow, "nothrow"), |
| ManglingFlagInfo(FuncAttributes.Ref, "ref"), |
| ManglingFlagInfo(FuncAttributes.Property, "@property"), |
| ManglingFlagInfo(FuncAttributes.NoGC, "@nogc"), |
| |
| ManglingFlagInfo(FuncAttributes.ReturnScope, "return scope"), |
| ManglingFlagInfo(FuncAttributes.ScopeReturn, "scope return"), |
| |
| ManglingFlagInfo(FuncAttributes.Return, "return"), |
| ManglingFlagInfo(FuncAttributes.Scope, "scope"), |
| |
| ManglingFlagInfo(FuncAttributes.Live, "@live"), |
| ManglingFlagInfo(FuncAttributes.Trusted, "@trusted"), |
| ManglingFlagInfo(FuncAttributes.Safe, "@safe"), |
| ]; |
| |
| private string toStringConsume (immutable ManglingFlagInfo[] infos, ref ushort base) |
| @safe pure nothrow @nogc |
| { |
| foreach (const ref info; infos) |
| { |
| if ((base & info.flag) == info.flag) |
| { |
| base &= ~info.flag; |
| return info.value; |
| } |
| } |
| return null; |
| } |