From 98e703658745f4d7f6916e842843867082fd1fae Mon Sep 17 00:00:00 2001 From: Erik Pilkington Date: Tue, 6 Mar 2018 14:21:10 +0000 Subject: [PATCH] [demangler] Modernize the rest of the demangler. llvm-svn: 326797 --- libcxxabi/src/cxa_demangle.cpp | 745 +++++++++++++--------------------- libcxxabi/test/test_demangle.pass.cpp | 3 + 2 files changed, 290 insertions(+), 458 deletions(-) diff --git a/libcxxabi/src/cxa_demangle.cpp b/libcxxabi/src/cxa_demangle.cpp index 51952bd..d0362dc 100644 --- a/libcxxabi/src/cxa_demangle.cpp +++ b/libcxxabi/src/cxa_demangle.cpp @@ -2002,6 +2002,8 @@ struct Db { BumpPointerAllocator ASTAllocator; + Db(const char *First_, const char *Last_) : First(First_), Last(Last_) {} + template T *make(Args &&... args) { return new (ASTAllocator.allocate(sizeof(T))) T(std::forward(args)...); @@ -2054,6 +2056,12 @@ struct Db { bool parsePositiveInteger(size_t *Out); StringView parseBareSourceName(); + bool parseSeqId(size_t *Out); + Node *parseSubstitution(); + Node *parseTemplateParam(); + Node *parseTemplateArgs(); + Node *parseTemplateArg(); + /// Parse the production. Node *parseExpr(); Node *parsePrefixExpr(StringView Kind); @@ -2109,66 +2117,11 @@ struct Db { Node *parseUnresolvedType(); Node *parseDestructorName(); - // FIXME: remove this when all the parse_* functions have been rewritten. - template - Node *legacyParse() { - size_t BeforeType = Names.size(); - const char *OrigFirst = First; - const char *T = parse_fn(First, Last, *this); - if (T == OrigFirst || BeforeType + 1 != Names.size()) - return nullptr; - First = T; - Node *R = Names.back(); - Names.pop_back(); - return R; - } + /// Top-level entry point into the parser. + Node *parse(); }; -const char *parse_expression(const char *first, const char *last, Db &db) { - db.First = first; - db.Last = last; - Node *R = db.parseExpr(); - if (R == nullptr) - return first; - db.Names.push_back(R); - return db.First; -} - -const char *parse_expr_primary(const char *first, const char *last, Db &db) { - db.First = first; - db.Last = last; - Node *R = db.parseExprPrimary(); - if (R == nullptr) - return first; - db.Names.push_back(R); - return db.First; -} - -const char *parse_type(const char *first, const char *last, Db &db) { - db.First = first; - db.Last = last; - Node *R = db.parseType(); - if (R == nullptr) - return first; - db.Names.push_back(R); - return db.First; -} - -const char *parse_encoding(const char *first, const char *last, Db &db) { - db.First = first; - db.Last = last; - Node *R = db.parseEncoding(); - if (R == nullptr) - return first; - db.Names.push_back(R); - return db.First; -} - const char* parse_discriminator(const char* first, const char* last); -const char *parse_template_args(const char *first, const char *last, Db &db); -const char *parse_template_param(const char *, const char *, Db &); -const char *parse_substitution(const char *, const char *, Db &); - // ::= // N // ::= # See Scope Encoding below // Z @@ -2187,10 +2140,12 @@ Node *Db::parseName(NameState *State) { // ::= if (look() == 'S' && look(1) != 't') { - Node *S = legacyParse(); + Node *S = parseSubstitution(); + if (S == nullptr) + return nullptr; if (look() != 'I') return nullptr; - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; if (State) State->EndsWithTemplateArgs = true; @@ -2203,7 +2158,7 @@ Node *Db::parseName(NameState *State) { // ::= if (look() == 'I') { Subs.push_back(N); - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; if (State) State->EndsWithTemplateArgs = true; @@ -2693,7 +2648,7 @@ Node *Db::parseNestedName(NameState *State) { // ::= if (look() == 'T') { - Node *TP = legacyParse(); + Node *TP = parseTemplateParam(); if (TP == nullptr) return nullptr; PushComponent(TP); @@ -2703,7 +2658,7 @@ Node *Db::parseNestedName(NameState *State) { // ::= if (look() == 'I') { - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr || SoFar == nullptr) return nullptr; SoFar = make(SoFar, TA); @@ -2724,7 +2679,7 @@ Node *Db::parseNestedName(NameState *State) { // ::= if (look() == 'S' && look(1) != 't') { - Node *S = legacyParse(); + Node *S = parseSubstitution(); if (S == nullptr) return nullptr; PushComponent(S); @@ -2769,7 +2724,7 @@ Node *Db::parseSimpleId() { if (SN == nullptr) return nullptr; if (look() == 'I') { - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; return make(SN, TA); @@ -2795,7 +2750,7 @@ Node *Db::parseDestructorName() { // ::= Node *Db::parseUnresolvedType() { if (look() == 'T') { - Node *TP = legacyParse(); + Node *TP = parseTemplateParam(); if (TP == nullptr) return nullptr; Subs.push_back(TP); @@ -2808,7 +2763,7 @@ Node *Db::parseUnresolvedType() { Subs.push_back(DT); return DT; } - return legacyParse(); + return parseSubstitution(); } // ::= # unresolved name @@ -2831,7 +2786,7 @@ Node *Db::parseBaseUnresolvedName() { if (Oper == nullptr) return nullptr; if (look() == 'I') { - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; return make(Oper, TA); @@ -2861,7 +2816,7 @@ Node *Db::parseUnresolvedName() { return nullptr; if (look() == 'I') { - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; SoFar = make(SoFar, TA); @@ -2914,7 +2869,7 @@ Node *Db::parseUnresolvedName() { return nullptr; if (look() == 'I') { - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; SoFar = make(SoFar, TA); @@ -3436,7 +3391,7 @@ Node *Db::parseType() { break; } - Result = legacyParse(); + Result = parseTemplateParam(); if (Result == nullptr) return nullptr; @@ -3451,7 +3406,7 @@ Node *Db::parseType() { // parse them, take the second production. if (TryToParseTemplateArgs && look() == 'I') { - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; Result = make(Result, TA); @@ -3506,7 +3461,7 @@ Node *Db::parseType() { // ::= # See Compression below case 'S': { if (look(1) && look(1) != 't') { - Node *Sub = legacyParse(); + Node *Sub = parseSubstitution(); if (Sub == nullptr) return nullptr; @@ -3521,7 +3476,7 @@ Node *Db::parseType() { // parse them, take the second production. if (TryToParseTemplateArgs && look() == 'I') { - Node *TA = legacyParse(); + Node *TA = parseTemplateArgs(); if (TA == nullptr) return nullptr; Result = make(Sub, TA); @@ -3872,7 +3827,7 @@ Node *Db::parseExpr() { case 'L': return parseExprPrimary(); case 'T': - return legacyParse(); + return parseTemplateParam(); case 'f': return parseFunctionParam(); case 'a': @@ -4244,7 +4199,7 @@ Node *Db::parseExpr() { case 'Z': First += 2; if (look() == 'T') { - Node *R = legacyParse(); + Node *R = parseTemplateParam(); if (R == nullptr) return nullptr; return make(R); @@ -4572,6 +4527,28 @@ template Node *Db::parseFloatingLiteral() { return make>(Data); } +// ::= <0-9A-Z>+ +bool Db::parseSeqId(size_t *Out) { + if (!(look() >= '0' && look() <= '9') && + !(look() >= 'A' && look() <= 'Z')) + return true; + + size_t Id = 0; + while (true) { + if (look() >= '0' && look() <= '9') { + Id *= 36; + Id += static_cast(look() - '0'); + } else if (look() >= 'A' && look() <= 'Z') { + Id *= 36; + Id += static_cast(look() - 'A') + 10; + } else { + *Out = Id; + return false; + } + ++First; + } +} + // ::= S _ // ::= S_ // ::= Sa # ::std::allocator @@ -4582,243 +4559,172 @@ template Node *Db::parseFloatingLiteral() { // ::= Si # ::std::basic_istream > // ::= So # ::std::basic_ostream > // ::= Sd # ::std::basic_iostream > +Node *Db::parseSubstitution() { + if (!consumeIf('S')) + return nullptr; -const char* -parse_substitution(const char* first, const char* last, Db& db) -{ - if (last - first >= 2) - { - if (*first == 'S') - { - switch (first[1]) - { - case 'a': - db.Names.push_back( - db.make( - SpecialSubKind::allocator)); - first += 2; - break; - case 'b': - db.Names.push_back( - db.make(SpecialSubKind::basic_string)); - first += 2; - break; - case 's': - db.Names.push_back( - db.make( - SpecialSubKind::string)); - first += 2; - break; - case 'i': - db.Names.push_back(db.make(SpecialSubKind::istream)); - first += 2; - break; - case 'o': - db.Names.push_back(db.make(SpecialSubKind::ostream)); - first += 2; - break; - case 'd': - db.Names.push_back(db.make(SpecialSubKind::iostream)); - first += 2; - break; - case '_': - if (!db.Subs.empty()) - { - db.Names.push_back(db.Subs[0]); - first += 2; - } - break; - default: - if (std::isdigit(first[1]) || std::isupper(first[1])) - { - size_t sub = 0; - const char* t = first+1; - if (std::isdigit(*t)) - sub = static_cast(*t - '0'); - else - sub = static_cast(*t - 'A') + 10; - for (++t; t != last && (std::isdigit(*t) || std::isupper(*t)); ++t) - { - sub *= 36; - if (std::isdigit(*t)) - sub += static_cast(*t - '0'); - else - sub += static_cast(*t - 'A') + 10; - } - if (t == last || *t != '_') - return first; - ++sub; - if (sub < db.Subs.size()) - { - db.Names.push_back(db.Subs[sub]); - first = t+1; - } - } - break; - } - } + if (std::islower(look())) { + Node *SpecialSub; + switch (look()) { + case 'a': + ++First; + SpecialSub = make(SpecialSubKind::allocator); + break; + case 'b': + ++First; + SpecialSub = make(SpecialSubKind::basic_string); + break; + case 's': + ++First; + SpecialSub = make(SpecialSubKind::string); + break; + case 'i': + ++First; + SpecialSub = make(SpecialSubKind::istream); + break; + case 'o': + ++First; + SpecialSub = make(SpecialSubKind::ostream); + break; + case 'd': + ++First; + SpecialSub = make(SpecialSubKind::iostream); + break; + default: + return nullptr; } - return first; + // Itanium C++ ABI 5.1.2: If a name that would use a built-in + // has ABI tags, the tags are appended to the substitution; the result is a + // substitutable component. + Node *WithTags = parseAbiTags(SpecialSub); + if (WithTags != SpecialSub) { + Subs.push_back(WithTags); + SpecialSub = WithTags; + } + return SpecialSub; + } + + // ::= S_ + if (consumeIf('_')) { + if (Subs.empty()) + return nullptr; + return Subs[0]; + } + + // ::= S _ + size_t Index = 0; + if (parseSeqId(&Index)) + return nullptr; + ++Index; + if (!consumeIf('_') || Index >= Subs.size()) + return nullptr; + return Subs[Index]; } // ::= T_ # first template parameter // ::= T _ +Node *Db::parseTemplateParam() { + if (!consumeIf('T')) + return nullptr; -const char* -parse_template_param(const char* first, const char* last, Db& db) -{ - if (last - first >= 2) - { - if (*first == 'T') - { - if (first[1] == '_') - { - if (!db.TemplateParams.empty()) - { - db.Names.push_back(db.TemplateParams[0]); - first += 2; - } - else - { - db.Names.push_back(db.make("T_")); - first += 2; - db.FixForwardReferences = true; - } - } - else if (isdigit(first[1])) - { - const char* t = first+1; - size_t sub = static_cast(*t - '0'); - for (++t; t != last && isdigit(*t); ++t) - { - sub *= 10; - sub += static_cast(*t - '0'); - } - if (t == last || *t != '_') - return first; - ++sub; - if (sub < db.TemplateParams.size()) - { - db.Names.push_back(db.TemplateParams[sub]); - first = t+1; - } - else - { - db.Names.push_back( - db.make(StringView(first, t + 1))); - first = t+1; - db.FixForwardReferences = true; - } - } - } + if (consumeIf('_')) { + if (TemplateParams.empty()) { + FixForwardReferences = true; + return make("FORWARD_REFERENCE"); } - return first; + return TemplateParams[0]; + } + + size_t Index; + if (parsePositiveInteger(&Index)) + return nullptr; + ++Index; + if (!consumeIf('_')) + return nullptr; + if (Index >= TemplateParams.size()) { + FixForwardReferences = true; + return make("FORWARD_REFERENCE"); + } + return TemplateParams[Index]; } -// ::= # type or template -// ::= X E # expression -// ::= # simple expressions -// ::= J * E # argument pack -// ::= LZ E # extension -const char* -parse_template_arg(const char* first, const char* last, Db& db) -{ - if (first != last) - { - const char* t; - switch (*first) - { - case 'X': - t = parse_expression(first+1, last, db); - if (t != first+1) - { - if (t != last && *t == 'E') - first = t+1; - } - break; - case 'J': { - t = first+1; - if (t == last) - return first; - size_t ArgsBegin = db.Names.size(); - while (*t != 'E') - { - const char* t1 = parse_template_arg(t, last, db); - if (t1 == t) - return first; - t = t1; - } - NodeArray Args = db.popTrailingNodeArray(ArgsBegin); - db.Names.push_back(db.make(Args)); - first = t+1; - break; - } - case 'L': - // or LZ E - if (first+1 != last && first[1] == 'Z') - { - t = parse_encoding(first+2, last, db); - if (t != first+2 && t != last && *t == 'E') - first = t+1; - } - else - first = parse_expr_primary(first, last, db); - break; - default: - // - first = parse_type(first, last, db); - break; - } +// ::= # type or template +// ::= X E # expression +// ::= # simple expressions +// ::= J * E # argument pack +// ::= LZ E # extension +Node *Db::parseTemplateArg() { + switch (look()) { + case 'X': { + ++First; + Node *Arg = parseExpr(); + if (Arg == nullptr || !consumeIf('E')) + return nullptr; + return Arg; + } + case 'J': { + ++First; + size_t ArgsBegin = Names.size(); + while (!consumeIf('E')) { + Node *Arg = parseTemplateArg(); + if (Arg == nullptr) + return nullptr; + Names.push_back(Arg); } - return first; + NodeArray Args = popTrailingNodeArray(ArgsBegin); + return make(Args); + } + case 'L': { + // ::= LZ E # extension + if (look(1) == 'Z') { + First += 2; + Node *Arg = parseEncoding(); + if (Arg == nullptr || !consumeIf('E')) + return nullptr; + return Arg; + } + // ::= # simple expressions + return parseExprPrimary(); + } + default: + return parseType(); + } } // ::= I * E // extension, the abi says + -const char* -parse_template_args(const char* first, const char* last, Db& db) -{ - if (last - first >= 2 && *first == 'I') - { - if (db.TagTemplates) - db.TemplateParams.clear(); - const char* t = first+1; - size_t begin_idx = db.Names.size(); - while (*t != 'E') - { - if (db.TagTemplates) - { - auto TmpParams = std::move(db.TemplateParams); - size_t k0 = db.Names.size(); - const char* t1 = parse_template_arg(t, last, db); - size_t k1 = db.Names.size(); - db.TemplateParams = std::move(TmpParams); - if (t1 == t || t1 == last || k0 + 1 != k1) - return first; - Node *TableEntry = db.Names.back(); - if (TableEntry->getKind() == Node::KTemplateArgumentPack) - TableEntry = db.make( - static_cast(TableEntry) - ->getElements()); - db.TemplateParams.push_back(TableEntry); - t = t1; - continue; - } - size_t k0 = db.Names.size(); - const char* t1 = parse_template_arg(t, last, db); - size_t k1 = db.Names.size(); - if (t1 == t || t1 == last || k0 > k1) - return first; - t = t1; - } - if (begin_idx > db.Names.size()) - return first; - first = t + 1; - auto *tp = db.make( - db.popTrailingNodeArray(begin_idx)); - db.Names.push_back(tp); +Node *Db::parseTemplateArgs() { + if (!consumeIf('I')) + return nullptr; + + // refer to the innermost . Clear out any + // outer args that we may have inserted into TemplateParams. + if (TagTemplates) + TemplateParams.clear(); + + size_t ArgsBegin = Names.size(); + while (!consumeIf('E')) { + if (TagTemplates) { + auto OldParams = std::move(TemplateParams); + Node *Arg = parseTemplateArg(); + TemplateParams = std::move(OldParams); + if (Arg == nullptr) + return nullptr; + Names.push_back(Arg); + Node *TableEntry = Arg; + if (Arg->getKind() == Node::KTemplateArgumentPack) { + TableEntry = make( + static_cast(TableEntry)->getElements()); + } + TemplateParams.push_back(TableEntry); + } else { + Node *Arg = parseTemplateArg(); + if (Arg == nullptr) + return nullptr; + Names.push_back(Arg); } - return first; + } + return make(popTrailingNodeArray(ArgsBegin)); } // := _ # when number < 10 @@ -4859,183 +4765,106 @@ parse_discriminator(const char* first, const char* last) return first; } -// _block_invoke -// _block_invoke+ -// _block_invoke_+ - -const char* -parse_block_invoke(const char* first, const char* last, Db& db) -{ - if (last - first >= 13) - { - // FIXME: strcmp? - const char test[] = "_block_invoke"; - const char* t = first; - for (int i = 0; i < 13; ++i, ++t) - { - if (*t != test[i]) - return first; - } - if (t != last) - { - if (*t == '_') - { - // must have at least 1 decimal digit - if (++t == last || !std::isdigit(*t)) - return first; - ++t; - } - // parse zero or more digits - while (t != last && isdigit(*t)) - ++t; - } - if (db.Names.empty()) - return first; - db.Names.back() = - db.make("invocation function for block in ", - db.Names.back()); - first = t; +// ::= _Z +// ::= +// extension ::= ___Z _block_invoke +// extension ::= ___Z _block_invoke+ +// extension ::= ___Z _block_invoke_+ +Node *Db::parse() { + if (consumeIf("_Z")) { + Node *Encoding = parseEncoding(); + if (Encoding == nullptr) + return nullptr; + if (look() == '.') { + Encoding = make(Encoding, StringView(First, Last)); + First = Last; } - return first; -} + if (numLeft() != 0) + return nullptr; + return Encoding; + } -// extension -// := . + if (consumeIf("___Z")) { + Node *Encoding = parseEncoding(); + if (Encoding == nullptr || !consumeIf("_block_invoke")) + return nullptr; + consumeIf('_'); + if (parseNumber().empty()) + return nullptr; + if (numLeft() != 0) + return nullptr; + return make("invocation function for block in ", Encoding); + } -const char* -parse_dot_suffix(const char* first, const char* last, Db& db) -{ - if (first != last && *first == '.') - { - if (db.Names.empty()) - return first; - db.Names.back() = - db.make(db.Names.back(), StringView(first, last)); - first = last; - } - return first; + Node *Ty = parseType(); + if (numLeft() != 0) + return nullptr; + return Ty; } +} // unnamed namespace enum { - unknown_error = -4, - invalid_args = -3, - invalid_mangled_name, - memory_alloc_failure, - success + unknown_error = -4, + invalid_args = -3, + invalid_mangled_name = -2, + memory_alloc_failure = -1, + success = 0, }; -// ___Z_block_invoke -// ___Z_block_invoke+ -// ___Z_block_invoke_+ -// ::= _Z -// ::= -void -demangle(const char* first, const char* last, Db& db, int& status) -{ - if (first >= last) - { - status = invalid_mangled_name; - return; - } - if (*first == '_') - { - if (last - first >= 4) - { - if (first[1] == 'Z') - { - const char* t = parse_encoding(first+2, last, db); - if (t != first+2 && t != last && *t == '.') - t = parse_dot_suffix(t, last, db); - if (t != last) - status = invalid_mangled_name; - } - else if (first[1] == '_' && first[2] == '_' && first[3] == 'Z') - { - const char* t = parse_encoding(first+4, last, db); - if (t != first+4 && t != last) - { - const char* t1 = parse_block_invoke(t, last, db); - if (t1 != last) - status = invalid_mangled_name; - } - else - status = invalid_mangled_name; - } - else - status = invalid_mangled_name; - } - else - status = invalid_mangled_name; - } - else - { - const char* t = parse_type(first, last, db); - if (t != last) - status = invalid_mangled_name; - } - if (status == success && db.Names.empty()) - status = invalid_mangled_name; -} - -} // unnamed namespace - - namespace __cxxabiv1 { extern "C" _LIBCXXABI_FUNC_VIS char * -__cxa_demangle(const char *mangled_name, char *buf, size_t *n, int *status) { - if (mangled_name == nullptr || (buf != nullptr && n == nullptr)) - { - if (status) - *status = invalid_args; - return nullptr; - } +__cxa_demangle(const char *MangledName, char *Buf, size_t *N, int *Status) { + if (MangledName == nullptr || (Buf != nullptr && N == nullptr)) { + if (Status) + *Status = invalid_args; + return nullptr; + } - size_t internal_size = buf != nullptr ? *n : 0; - Db db; - int internal_status = success; - size_t len = std::strlen(mangled_name); - demangle(mangled_name, mangled_name + len, db, - internal_status); + size_t BufSize = Buf != nullptr ? *N : 0; + int InternalStatus = success; + size_t MangledNameLength = std::strlen(MangledName); - if (internal_status == success && db.FixForwardReferences && - !db.TemplateParams.empty()) - { - db.FixForwardReferences = false; - db.TagTemplates = false; - db.Names.clear(); - db.Subs.clear(); - demangle(mangled_name, mangled_name + len, db, internal_status); - if (db.FixForwardReferences) - internal_status = invalid_mangled_name; - } + Db Parser(MangledName, MangledName + MangledNameLength); + Node *AST = Parser.parse(); - if (internal_status == success && - db.Names.back()->containsUnexpandedParameterPack()) - internal_status = invalid_mangled_name; + if (AST == nullptr) + InternalStatus = invalid_mangled_name; - if (internal_status == success) - { - if (!buf) - { - internal_size = 1024; - buf = static_cast(std::malloc(internal_size)); - } + if (InternalStatus == success && Parser.FixForwardReferences && + !Parser.TemplateParams.empty()) { + Parser.FixForwardReferences = false; + Parser.TagTemplates = false; + Parser.Names.clear(); + Parser.Subs.clear(); + Parser.First = MangledName; + Parser.Last = MangledName + MangledNameLength; + AST = Parser.parse(); + if (AST == nullptr || Parser.FixForwardReferences) + InternalStatus = invalid_mangled_name; + } - if (buf) - { - OutputStream s(buf, internal_size); - db.Names.back()->print(s); - s += '\0'; - if (n) *n = s.getCurrentPosition(); - buf = s.getBuffer(); - } - else - internal_status = memory_alloc_failure; + if (InternalStatus == success && AST->containsUnexpandedParameterPack()) + InternalStatus = invalid_mangled_name; + + if (InternalStatus == success) { + if (Buf == nullptr) { + BufSize = 1024; + Buf = static_cast(std::malloc(BufSize)); } - else - buf = nullptr; - if (status) - *status = internal_status; - return buf; + + if (Buf) { + OutputStream Stream(Buf, BufSize); + AST->print(Stream); + Stream += '\0'; + if (N != nullptr) + *N = Stream.getCurrentPosition(); + Buf = Stream.getBuffer(); + } else + InternalStatus = memory_alloc_failure; + } + + if (Status) + *Status = InternalStatus; + return InternalStatus == success ? Buf : nullptr; } } // __cxxabiv1 diff --git a/libcxxabi/test/test_demangle.pass.cpp b/libcxxabi/test/test_demangle.pass.cpp index b57bda0..b54f8d3 100644 --- a/libcxxabi/test/test_demangle.pass.cpp +++ b/libcxxabi/test/test_demangle.pass.cpp @@ -29713,6 +29713,9 @@ const char* cases[][2] = {"_ZNKR4llvm8OptionalINS_11MCFixupKindEEdeEv", "llvm::Optional::operator*() const &"}, {"_ZZL23isValidCoroutineContextRN5clang4SemaENS_14SourceLocationEN4llvm9StringRefEENK3$_4clEZL23isValidCoroutineContextS1_S2_S4_E15InvalidFuncDiag", "isValidCoroutineContext(clang::Sema&, clang::SourceLocation, llvm::StringRef)::$_4::operator()(isValidCoroutineContext(clang::Sema&, clang::SourceLocation, llvm::StringRef)::InvalidFuncDiag) const"}, + + // ABI tags can apply to built-in substitutions. + {"_Z1fSsB1XS_", "f(std::string[abi:X], std::string[abi:X])"}, }; const unsigned N = sizeof(cases) / sizeof(cases[0]); -- 2.7.4