diff --git a/lldb/include/lldb/Core/DemangledNameInfo.h b/lldb/include/lldb/Core/DemangledNameInfo.h new file mode 100644 index 0000000000000..3926b45a96f3e --- /dev/null +++ b/lldb/include/lldb/Core/DemangledNameInfo.h @@ -0,0 +1,166 @@ +//===-- DemangledNameInfo.h -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_CORE_DEMANGLEDNAMEINFO_H +#define LLDB_CORE_DEMANGLEDNAMEINFO_H + +#include "llvm/Demangle/ItaniumDemangle.h" +#include "llvm/Demangle/Utility.h" + +#include +#include + +namespace lldb_private { + +/// Stores information about where certain portions of a demangled +/// function name begin and end. +struct DemangledNameInfo { + /// A [start, end) pair for the function basename. + /// The basename is the name without scope qualifiers + /// and without template parameters. E.g., + /// \code{.cpp} + /// void foo::bar::someFunc(int) const && + /// ^ ^ + /// start end + /// \endcode + std::pair BasenameRange; + + /// A [start, end) pair for the function scope qualifiers. + /// E.g., for + /// \code{.cpp} + /// void foo::bar::qux(int) const && + /// ^ ^ + /// start end + /// \endcode + std::pair ScopeRange; + + /// Indicates the [start, end) of the function argument lits. + /// E.g., + /// \code{.cpp} + /// int (*getFunc(float, double))(int, int) + /// ^ ^ + /// start end + /// \endcode + std::pair ArgumentsRange; + + /// Indicates the [start, end) of the function qualifiers + /// (e.g., CV-qualifiers, reference qualifiers, requires clauses). + /// + /// E.g., + /// \code{.cpp} + /// void foo::bar::qux(int) const && + /// ^ ^ + /// start end + /// \endcode + std::pair QualifiersRange; + + /// Returns \c true if this object holds a valid basename range. + bool hasBasename() const { + return BasenameRange.second > BasenameRange.first && + BasenameRange.second > 0; + } + + friend bool operator==(const DemangledNameInfo &lhs, + const DemangledNameInfo &rhs) { + return std::tie(lhs.BasenameRange, lhs.ArgumentsRange, lhs.ScopeRange, + lhs.QualifiersRange) == + std::tie(rhs.BasenameRange, rhs.ArgumentsRange, rhs.ScopeRange, + lhs.QualifiersRange); + } + + friend bool operator!=(const DemangledNameInfo &lhs, + const DemangledNameInfo &rhs) { + return !(lhs == rhs); + } +}; + +/// An OutputBuffer which keeps a record of where certain parts of a +/// demangled name begin/end (e.g., basename, scope, argument list, etc.). +/// The tracking occurs during printing of the Itanium demangle tree. +/// +/// Usage: +/// \code{.cpp} +/// +/// Node *N = mangling_parser.parseType(); +/// +/// TrackingOutputBuffer buffer; +/// N->printLeft(OB); +/// +/// assert (buffer.NameInfo.hasBasename()); +/// +/// \endcode +struct TrackingOutputBuffer : public llvm::itanium_demangle::OutputBuffer { + using OutputBuffer::OutputBuffer; + + /// Holds information about the demangled name that is + /// being printed into this buffer. + DemangledNameInfo NameInfo; + + void printLeft(const llvm::itanium_demangle::Node &N) override; + void printRight(const llvm::itanium_demangle::Node &N) override; + +private: + void printLeftImpl(const llvm::itanium_demangle::FunctionType &N); + void printRightImpl(const llvm::itanium_demangle::FunctionType &N); + + void printLeftImpl(const llvm::itanium_demangle::FunctionEncoding &N); + void printRightImpl(const llvm::itanium_demangle::FunctionEncoding &N); + + void printLeftImpl(const llvm::itanium_demangle::NestedName &N); + void printLeftImpl(const llvm::itanium_demangle::NameWithTemplateArgs &N); + + /// Called whenever we start printing a function type in the Itanium + /// mangling scheme. Examples include \ref FunctionEncoding, \ref + /// FunctionType, etc. + /// + /// \returns A ScopedOverride which will update the nesting depth of + /// currently printed function types on destruction. + [[nodiscard]] llvm::itanium_demangle::ScopedOverride + enterFunctionTypePrinting(); + + /// Returns \c true if we're not printing any nested function types, + /// just a \ref FunctionEncoding in the Itanium mangling scheme. + bool isPrintingTopLevelFunctionType() const; + + /// If this object \ref shouldTrack, then update the end of + /// the basename range to the current \c OB position. + void updateBasenameEnd(); + + /// If this object \ref shouldTrack, then update the beginning + /// of the scope range to the current \c OB position. + void updateScopeStart(); + + /// If this object \ref shouldTrack, then update the end of + /// the scope range to the current \c OB position. + void updateScopeEnd(); + + /// Returns \c true if the members of this object can be + /// updated. E.g., when we're printing nested template + /// arguments, we don't need to be tracking basename + /// locations. + bool shouldTrack() const; + + /// Helpers called to track beginning and end of the function + /// arguments. + void finalizeArgumentEnd(); + void finalizeStart(); + void finalizeEnd(); + void finalizeQualifiersStart(); + void finalizeQualifiersEnd(); + + /// Helper used in the finalize APIs. + bool canFinalize() const; + + /// Incremented each time we start printing a function type node + /// in the Itanium mangling scheme (e.g., \ref FunctionEncoding + /// or \ref FunctionType). + unsigned FunctionPrintingDepth = 0; +}; +} // namespace lldb_private + +#endif // LLDB_CORE_DEMANGLEDNAMEINFO_H diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h index f6c3bd981e03a..31a08b3e4add0 100644 --- a/lldb/include/lldb/Core/FormatEntity.h +++ b/lldb/include/lldb/Core/FormatEntity.h @@ -88,6 +88,13 @@ struct Entry { FunctionNameWithArgs, FunctionNameNoArgs, FunctionMangledName, + FunctionScope, + FunctionBasename, + FunctionTemplateArguments, + FunctionFormattedArguments, + FunctionReturnLeft, + FunctionReturnRight, + FunctionQualifiers, FunctionAddrOffset, FunctionAddrOffsetConcrete, FunctionLineOffset, diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h index 7db63eeeb6ee0..eb9a58c568896 100644 --- a/lldb/include/lldb/Core/Mangled.h +++ b/lldb/include/lldb/Core/Mangled.h @@ -9,10 +9,11 @@ #ifndef LLDB_CORE_MANGLED_H #define LLDB_CORE_MANGLED_H +#include "lldb/Core/DemangledNameInfo.h" +#include "lldb/Utility/ConstString.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-types.h" -#include "lldb/Utility/ConstString.h" #include "llvm/ADT/StringRef.h" #include @@ -134,9 +135,15 @@ class Mangled { /// A const reference to the display demangled name string object. ConstString GetDisplayDemangledName() const; - void SetDemangledName(ConstString name) { m_demangled = name; } + void SetDemangledName(ConstString name) { + m_demangled = name; + m_demangled_info.reset(); + } - void SetMangledName(ConstString name) { m_mangled = name; } + void SetMangledName(ConstString name) { + m_mangled = name; + m_demangled_info.reset(); + } /// Mangled name get accessor. /// @@ -277,13 +284,26 @@ class Mangled { /// table offsets in the cache data. void Encode(DataEncoder &encoder, ConstStringTable &strtab) const; + /// Retrieve \c DemangledNameInfo of the demangled name held by this object. + const std::optional &GetDemangledInfo() const; + private: + /// If \c force is \c false, this function will re-use the previously + /// demangled name (if any). If \c force is \c true (or the mangled name + /// on this object was not previously demangled), demangle and cache the + /// name. + ConstString GetDemangledNameImpl(bool force) const; + /// The mangled version of the name. ConstString m_mangled; /// Mutable so we can get it on demand with /// a const version of this object. mutable ConstString m_demangled; + + /// If available, holds information about where in \c m_demangled certain + /// parts of the name (e.g., basename, arguments, etc.) begin and end. + mutable std::optional m_demangled_info = std::nullopt; }; Stream &operator<<(Stream &s, const Mangled &obj); diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index a6dab045adf27..d73dd71d833f3 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -141,8 +141,10 @@ class PluginManager { GetOperatingSystemCreateCallbackForPluginName(llvm::StringRef name); // Language - static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, - LanguageCreateInstance create_callback); + static bool + RegisterPlugin(llvm::StringRef name, llvm::StringRef description, + LanguageCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback = nullptr); static bool UnregisterPlugin(LanguageCreateInstance create_callback); @@ -613,6 +615,14 @@ class PluginManager { static bool CreateSettingForStructuredDataPlugin( Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, llvm::StringRef description, bool is_global_property); + + static lldb::OptionValuePropertiesSP + GetSettingForCPlusPlusLanguagePlugin(Debugger &debugger, + llvm::StringRef setting_name); + + static bool CreateSettingForCPlusPlusLanguagePlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property); }; } // namespace lldb_private diff --git a/lldb/include/lldb/Symbol/SymbolContext.h b/lldb/include/lldb/Symbol/SymbolContext.h index 4f8405f1f0db5..af2f694e554de 100644 --- a/lldb/include/lldb/Symbol/SymbolContext.h +++ b/lldb/include/lldb/Symbol/SymbolContext.h @@ -311,8 +311,7 @@ class SymbolContext { /// mangling preference. If this object represents an inlined function, /// returns the name of the inlined function. Returns nullptr if no function /// name could be determined. - const char *GetPossiblyInlinedFunctionName( - Mangled::NamePreference mangling_preference) const; + Mangled GetPossiblyInlinedFunctionName() const; // Member variables lldb::TargetSP target_sp; ///< The Target for a given query diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h index fa856843871e3..d62871bd7ed70 100644 --- a/lldb/include/lldb/Target/Language.h +++ b/lldb/include/lldb/Target/Language.h @@ -15,6 +15,7 @@ #include #include +#include "lldb/Core/FormatEntity.h" #include "lldb/Core/Highlighter.h" #include "lldb/Core/PluginInterface.h" #include "lldb/DataFormatters/DumpValueObjectOptions.h" @@ -371,6 +372,13 @@ class Language : public PluginInterface { FunctionNameRepresentation representation, Stream &s); + virtual bool HandleFrameFormatVariable(const SymbolContext &sc, + const ExecutionContext *exe_ctx, + FormatEntity::Entry::Type type, + Stream &s) { + return false; + } + virtual ConstString GetDemangledFunctionNameWithoutArguments(Mangled mangled) const { if (ConstString demangled = mangled.GetDemangledName()) @@ -487,6 +495,10 @@ class Language : public PluginInterface { /// Python uses \b except. Defaults to \b catch. virtual llvm::StringRef GetCatchKeyword() const { return "catch"; } + virtual const FormatEntity::Entry *GetFunctionNameFormat() const { + return nullptr; + } + protected: // Classes that inherit from Language can see and modify these diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index f09b451ac414d..c4c442dcb2043 100644 --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -27,6 +27,7 @@ add_lldb_library(lldbCore NO_PLUGIN_DEPENDENCIES Debugger.cpp DebuggerEvents.cpp Declaration.cpp + DemangledNameInfo.cpp Disassembler.cpp DumpDataExtractor.cpp DumpRegisterValue.cpp diff --git a/lldb/source/Core/DemangledNameInfo.cpp b/lldb/source/Core/DemangledNameInfo.cpp new file mode 100644 index 0000000000000..54a06edc5ec1d --- /dev/null +++ b/lldb/source/Core/DemangledNameInfo.cpp @@ -0,0 +1,230 @@ +//===-- DemangledNameInfo.cpp ---------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/DemangledNameInfo.h" + +using namespace llvm::itanium_demangle; + +namespace lldb_private { + +bool TrackingOutputBuffer::shouldTrack() const { + if (!isPrintingTopLevelFunctionType()) + return false; + + if (isGtInsideTemplateArgs()) + return false; + + if (NameInfo.ArgumentsRange.first > 0) + return false; + + return true; +} + +bool TrackingOutputBuffer::canFinalize() const { + if (!isPrintingTopLevelFunctionType()) + return false; + + if (isGtInsideTemplateArgs()) + return false; + + if (NameInfo.ArgumentsRange.first == 0) + return false; + + return true; +} + +void TrackingOutputBuffer::updateBasenameEnd() { + if (!shouldTrack()) + return; + + NameInfo.BasenameRange.second = getCurrentPosition(); +} + +void TrackingOutputBuffer::updateScopeStart() { + if (!shouldTrack()) + return; + + NameInfo.ScopeRange.first = getCurrentPosition(); +} + +void TrackingOutputBuffer::updateScopeEnd() { + if (!shouldTrack()) + return; + + NameInfo.ScopeRange.second = getCurrentPosition(); +} + +void TrackingOutputBuffer::finalizeArgumentEnd() { + if (!canFinalize()) + return; + + NameInfo.ArgumentsRange.second = getCurrentPosition(); +} + +void TrackingOutputBuffer::finalizeQualifiersStart() { + if (!canFinalize()) + return; + + NameInfo.QualifiersRange.first = getCurrentPosition(); +} + +void TrackingOutputBuffer::finalizeQualifiersEnd() { + if (!canFinalize()) + return; + + NameInfo.QualifiersRange.second = getCurrentPosition(); +} + +void TrackingOutputBuffer::finalizeStart() { + if (!shouldTrack()) + return; + + NameInfo.ArgumentsRange.first = getCurrentPosition(); + + // If nothing has set the end of the basename yet (for example when + // printing templates), then the beginning of the arguments is the end of + // the basename. + if (NameInfo.BasenameRange.second == 0) + NameInfo.BasenameRange.second = getCurrentPosition(); + + assert(!shouldTrack()); + assert(canFinalize()); +} + +void TrackingOutputBuffer::finalizeEnd() { + if (!canFinalize()) + return; + + if (NameInfo.ScopeRange.first > NameInfo.ScopeRange.second) + NameInfo.ScopeRange.second = NameInfo.ScopeRange.first; + NameInfo.BasenameRange.first = NameInfo.ScopeRange.second; +} + +ScopedOverride TrackingOutputBuffer::enterFunctionTypePrinting() { + return {FunctionPrintingDepth, FunctionPrintingDepth + 1}; +} + +bool TrackingOutputBuffer::isPrintingTopLevelFunctionType() const { + return FunctionPrintingDepth == 1; +} + +void TrackingOutputBuffer::printLeft(const Node &N) { + switch (N.getKind()) { + case Node::KFunctionType: + printLeftImpl(static_cast(N)); + break; + case Node::KFunctionEncoding: + printLeftImpl(static_cast(N)); + break; + case Node::KNestedName: + printLeftImpl(static_cast(N)); + break; + case Node::KNameWithTemplateArgs: + printLeftImpl(static_cast(N)); + break; + default: + OutputBuffer::printLeft(N); + } +} + +void TrackingOutputBuffer::printRight(const Node &N) { + switch (N.getKind()) { + case Node::KFunctionType: + printRightImpl(static_cast(N)); + break; + case Node::KFunctionEncoding: + printRightImpl(static_cast(N)); + break; + default: + OutputBuffer::printRight(N); + } +} + +void TrackingOutputBuffer::printLeftImpl(const FunctionType &N) { + auto Scoped = enterFunctionTypePrinting(); + OutputBuffer::printLeft(N); +} + +void TrackingOutputBuffer::printRightImpl(const FunctionType &N) { + auto Scoped = enterFunctionTypePrinting(); + OutputBuffer::printRight(N); +} + +void TrackingOutputBuffer::printLeftImpl(const FunctionEncoding &N) { + auto Scoped = enterFunctionTypePrinting(); + + const Node *Ret = N.getReturnType(); + if (Ret) { + printLeft(*Ret); + if (!Ret->hasRHSComponent(*this)) + *this += " "; + } + + updateScopeStart(); + + N.getName()->print(*this); +} + +void TrackingOutputBuffer::printRightImpl(const FunctionEncoding &N) { + auto Scoped = enterFunctionTypePrinting(); + finalizeStart(); + + printOpen(); + N.getParams().printWithComma(*this); + printClose(); + + finalizeArgumentEnd(); + + const Node *Ret = N.getReturnType(); + + if (Ret) + printRight(*Ret); + + finalizeQualifiersStart(); + + auto CVQuals = N.getCVQuals(); + auto RefQual = N.getRefQual(); + auto *Attrs = N.getAttrs(); + auto *Requires = N.getRequires(); + + if (CVQuals & QualConst) + *this += " const"; + if (CVQuals & QualVolatile) + *this += " volatile"; + if (CVQuals & QualRestrict) + *this += " restrict"; + if (RefQual == FrefQualLValue) + *this += " &"; + else if (RefQual == FrefQualRValue) + *this += " &&"; + if (Attrs != nullptr) + Attrs->print(*this); + if (Requires != nullptr) { + *this += " requires "; + Requires->print(*this); + } + + finalizeQualifiersEnd(); + finalizeEnd(); +} + +void TrackingOutputBuffer::printLeftImpl(const NestedName &N) { + N.Qual->print(*this); + *this += "::"; + updateScopeEnd(); + N.Name->print(*this); + updateBasenameEnd(); +} + +void TrackingOutputBuffer::printLeftImpl(const NameWithTemplateArgs &N) { + N.Name->print(*this); + updateBasenameEnd(); + N.TemplateArgs->print(*this); +} + +} // namespace lldb_private diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp index 874478340003c..e352d07fe487d 100644 --- a/lldb/source/Core/FormatEntity.cpp +++ b/lldb/source/Core/FormatEntity.cpp @@ -122,7 +122,15 @@ constexpr Definition g_function_child_entries[] = { Definition("pc-offset", EntryType::FunctionPCOffset), Definition("initial-function", EntryType::FunctionInitial), Definition("changed", EntryType::FunctionChanged), - Definition("is-optimized", EntryType::FunctionIsOptimized)}; + Definition("is-optimized", EntryType::FunctionIsOptimized), + Definition("scope", EntryType::FunctionScope), + Definition("basename", EntryType::FunctionBasename), + Definition("template-arguments", EntryType::FunctionTemplateArguments), + Definition("formatted-arguments", EntryType::FunctionFormattedArguments), + Definition("return-left", EntryType::FunctionReturnLeft), + Definition("return-right", EntryType::FunctionReturnRight), + Definition("qualifiers", EntryType::FunctionQualifiers), +}; constexpr Definition g_line_child_entries[] = { Entry::DefinitionWithChildren("file", EntryType::LineEntryFile, @@ -353,6 +361,13 @@ const char *FormatEntity::Entry::TypeToCString(Type t) { ENUM_TO_CSTR(FunctionNameWithArgs); ENUM_TO_CSTR(FunctionNameNoArgs); ENUM_TO_CSTR(FunctionMangledName); + ENUM_TO_CSTR(FunctionScope); + ENUM_TO_CSTR(FunctionBasename); + ENUM_TO_CSTR(FunctionTemplateArguments); + ENUM_TO_CSTR(FunctionFormattedArguments); + ENUM_TO_CSTR(FunctionReturnLeft); + ENUM_TO_CSTR(FunctionReturnRight); + ENUM_TO_CSTR(FunctionQualifiers); ENUM_TO_CSTR(FunctionAddrOffset); ENUM_TO_CSTR(FunctionAddrOffsetConcrete); ENUM_TO_CSTR(FunctionLineOffset); @@ -1167,8 +1182,9 @@ static bool PrintFunctionNameWithArgs(Stream &s, ExecutionContextScope *exe_scope = exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr; - const char *cstr = - sc.GetPossiblyInlinedFunctionName(Mangled::ePreferDemangled); + const char *cstr = sc.GetPossiblyInlinedFunctionName() + .GetName(Mangled::ePreferDemangled) + .AsCString(); if (!cstr) return false; @@ -1186,7 +1202,8 @@ static bool PrintFunctionNameWithArgs(Stream &s, return true; } -static bool HandleFunctionNameWithArgs(Stream &s,const ExecutionContext *exe_ctx, +static bool HandleFunctionNameWithArgs(Stream &s, + const ExecutionContext *exe_ctx, const SymbolContext &sc) { Language *language_plugin = nullptr; bool language_plugin_handled = false; @@ -1220,6 +1237,35 @@ static bool HandleFunctionNameWithArgs(Stream &s,const ExecutionContext *exe_ctx return true; } +static bool FormatFunctionNameForLanguage(Stream &s, + const ExecutionContext *exe_ctx, + const SymbolContext *sc) { + assert(sc); + + Language *language_plugin = nullptr; + if (sc->function) + language_plugin = Language::FindPlugin(sc->function->GetLanguage()); + else if (sc->symbol) + language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); + + if (!language_plugin) + return false; + + const auto *format = language_plugin->GetFunctionNameFormat(); + if (!format) + return false; + + StreamString name_stream; + const bool success = + FormatEntity::Format(*format, name_stream, sc, exe_ctx, /*addr=*/nullptr, + /*valobj=*/nullptr, /*function_changed=*/false, + /*initial_function=*/false); + if (success) + s << name_stream.GetString(); + + return success; +} + bool FormatEntity::FormatStringRef(const llvm::StringRef &format_str, Stream &s, const SymbolContext *sc, const ExecutionContext *exe_ctx, @@ -1711,8 +1757,9 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, return true; } - const char *name = sc->GetPossiblyInlinedFunctionName( - Mangled::NamePreference::ePreferDemangled); + const char *name = sc->GetPossiblyInlinedFunctionName() + .GetName(Mangled::NamePreference::ePreferDemangled) + .AsCString(); if (!name) return false; @@ -1743,8 +1790,10 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, return true; } - const char *name = sc->GetPossiblyInlinedFunctionName( - Mangled::NamePreference::ePreferDemangledWithoutArguments); + const char *name = + sc->GetPossiblyInlinedFunctionName() + .GetName(Mangled::NamePreference::ePreferDemangledWithoutArguments) + .AsCString(); if (!name) return false; @@ -1753,19 +1802,41 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, return true; } + case Entry::Type::FunctionScope: + case Entry::Type::FunctionBasename: + case Entry::Type::FunctionTemplateArguments: + case Entry::Type::FunctionFormattedArguments: + case Entry::Type::FunctionReturnRight: + case Entry::Type::FunctionReturnLeft: + case Entry::Type::FunctionQualifiers: { + if (!sc->function) + return false; + + Language *language_plugin = + Language::FindPlugin(sc->function->GetLanguage()); + if (!language_plugin) + return false; + + return language_plugin->HandleFrameFormatVariable(*sc, exe_ctx, entry.type, + s); + } + case Entry::Type::FunctionNameWithArgs: { if (!sc) return false; + if (FormatFunctionNameForLanguage(s, exe_ctx, sc)) + return true; + return HandleFunctionNameWithArgs(s, exe_ctx, *sc); } - case Entry::Type::FunctionMangledName: { if (!sc) return false; - const char *name = sc->GetPossiblyInlinedFunctionName( - Mangled::NamePreference::ePreferMangled); + const char *name = sc->GetPossiblyInlinedFunctionName() + .GetName(Mangled::NamePreference::ePreferMangled) + .AsCString(); if (!name) return false; diff --git a/lldb/source/Core/Mangled.cpp b/lldb/source/Core/Mangled.cpp index 56836a6e5663e..ce4db4e0daa8b 100644 --- a/lldb/source/Core/Mangled.cpp +++ b/lldb/source/Core/Mangled.cpp @@ -9,6 +9,7 @@ #include "lldb/Core/Mangled.h" #include "lldb/Core/DataFileCache.h" +#include "lldb/Core/DemangledNameInfo.h" #include "lldb/Core/RichManglingContext.h" #include "lldb/Target/Language.h" #include "lldb/Utility/ConstString.h" @@ -111,6 +112,7 @@ Mangled::operator bool() const { return m_mangled || m_demangled; } void Mangled::Clear() { m_mangled.Clear(); m_demangled.Clear(); + m_demangled_info.reset(); } // Compare the string values. @@ -124,13 +126,16 @@ void Mangled::SetValue(ConstString name) { if (IsMangledName(name.GetStringRef())) { m_demangled.Clear(); m_mangled = name; + m_demangled_info.reset(); } else { m_demangled = name; m_mangled.Clear(); + m_demangled_info.reset(); } } else { m_demangled.Clear(); m_mangled.Clear(); + m_demangled_info.reset(); } } @@ -152,20 +157,26 @@ static char *GetMSVCDemangledStr(llvm::StringRef M) { return demangled_cstr; } -static char *GetItaniumDemangledStr(const char *M) { +static std::pair +GetItaniumDemangledStr(const char *M) { char *demangled_cstr = nullptr; + DemangledNameInfo info; llvm::ItaniumPartialDemangler ipd; bool err = ipd.partialDemangle(M); if (!err) { - // Default buffer and size (will realloc in case it's too small). + // Default buffer and size (OutputBuffer will realloc in case it's too + // small). size_t demangled_size = 80; - demangled_cstr = static_cast(std::malloc(demangled_size)); - demangled_cstr = ipd.finishDemangle(demangled_cstr, &demangled_size); + demangled_cstr = static_cast(std::malloc(80)); + + TrackingOutputBuffer OB(demangled_cstr, demangled_size); + demangled_cstr = ipd.finishDemangle(&OB); + info = std::move(OB.NameInfo); assert(demangled_cstr && "finishDemangle must always succeed if partialDemangle did"); - assert(demangled_cstr[demangled_size - 1] == '\0' && + assert(demangled_cstr[OB.getCurrentPosition() - 1] == '\0' && "Expected demangled_size to return length including trailing null"); } @@ -174,9 +185,14 @@ static char *GetItaniumDemangledStr(const char *M) { LLDB_LOGF(log, "demangled itanium: %s -> \"%s\"", M, demangled_cstr); else LLDB_LOGF(log, "demangled itanium: %s -> error: failed to demangle", M); + + if (!info.hasBasename()) + LLDB_LOGF(log, + "demangled itanium: %s -> error: failed to retrieve name info", + M); } - return demangled_cstr; + return {demangled_cstr, std::move(info)}; } static char *GetRustV0DemangledStr(llvm::StringRef M) { @@ -265,19 +281,31 @@ bool Mangled::GetRichManglingInfo(RichManglingContext &context, llvm_unreachable("Fully covered switch above!"); } +ConstString Mangled::GetDemangledName() const { + return GetDemangledNameImpl(/*force=*/false); +} + +std::optional const &Mangled::GetDemangledInfo() const { + if (!m_demangled_info) + GetDemangledNameImpl(/*force=*/true); + + return m_demangled_info; +} + // Generate the demangled name on demand using this accessor. Code in this // class will need to use this accessor if it wishes to decode the demangled // name. The result is cached and will be kept until a new string value is // supplied to this object, or until the end of the object's lifetime. -ConstString Mangled::GetDemangledName() const { +ConstString Mangled::GetDemangledNameImpl(bool force) const { if (!m_mangled) return m_demangled; // Re-use previously demangled names. - if (!m_demangled.IsNull()) + if (!force && !m_demangled.IsNull()) return m_demangled; - if (m_mangled.GetMangledCounterpart(m_demangled) && !m_demangled.IsNull()) + if (!force && m_mangled.GetMangledCounterpart(m_demangled) && + !m_demangled.IsNull()) return m_demangled; // We didn't already mangle this name, demangle it and if all goes well @@ -288,7 +316,10 @@ ConstString Mangled::GetDemangledName() const { demangled_name = GetMSVCDemangledStr(m_mangled); break; case eManglingSchemeItanium: { - demangled_name = GetItaniumDemangledStr(m_mangled.GetCString()); + std::pair demangled = + GetItaniumDemangledStr(m_mangled.GetCString()); + demangled_name = demangled.first; + m_demangled_info.emplace(std::move(demangled.second)); break; } case eManglingSchemeRustV0: @@ -447,6 +478,7 @@ bool Mangled::Decode(const DataExtractor &data, lldb::offset_t *offset_ptr, const StringTableReader &strtab) { m_mangled.Clear(); m_demangled.Clear(); + m_demangled_info.reset(); MangledEncoding encoding = (MangledEncoding)data.GetU8(offset_ptr); switch (encoding) { case Empty: diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index e6cb248ef31ce..73c018330a24e 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -564,11 +564,12 @@ static LanguageInstances &GetLanguageInstances() { return g_instances; } -bool PluginManager::RegisterPlugin(llvm::StringRef name, - llvm::StringRef description, - LanguageCreateInstance create_callback) { - return GetLanguageInstances().RegisterPlugin(name, description, - create_callback); +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + LanguageCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { + return GetLanguageInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback); } bool PluginManager::UnregisterPlugin(LanguageCreateInstance create_callback) { @@ -1682,6 +1683,7 @@ void PluginManager::DebuggerInitialize(Debugger &debugger) { GetStructuredDataPluginInstances().PerformDebuggerCallback(debugger); GetTracePluginInstances().PerformDebuggerCallback(debugger); GetScriptedInterfaceInstances().PerformDebuggerCallback(debugger); + GetLanguageInstances().PerformDebuggerCallback(debugger); } // This is the preferred new way to register plugin specific settings. e.g. @@ -1810,6 +1812,7 @@ static constexpr llvm::StringLiteral kSymbolLocatorPluginName("symbol-locator"); static constexpr llvm::StringLiteral kJITLoaderPluginName("jit-loader"); static constexpr llvm::StringLiteral kStructuredDataPluginName("structured-data"); +static constexpr llvm::StringLiteral kCPlusPlusLanguagePlugin("cplusplus"); lldb::OptionValuePropertiesSP PluginManager::GetSettingForDynamicLoaderPlugin(Debugger &debugger, @@ -1967,3 +1970,17 @@ bool PluginManager::CreateSettingForStructuredDataPlugin( "Settings for structured data plug-ins", properties_sp, description, is_global_property); } + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForCPlusPlusLanguagePlugin( + Debugger &debugger, llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kCPlusPlusLanguagePlugin); +} + +bool PluginManager::CreateSettingForCPlusPlusLanguagePlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kCPlusPlusLanguagePlugin, + "Settings for CPlusPlus language plug-ins", + properties_sp, description, is_global_property); +} diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index ccdc4d0ae99b3..9bb10c2a792a9 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -1,3 +1,11 @@ +lldb_tablegen(LanguageCPlusPlusProperties.inc -gen-lldb-property-defs + SOURCE LanguageCPlusPlusProperties.td + TARGET LLDBPluginLanguageCPlusPlusPropertiesGen) + +lldb_tablegen(LanguageCPlusPlusPropertiesEnum.inc -gen-lldb-property-enum-defs + SOURCE LanguageCPlusPlusProperties.td + TARGET LLDBPluginLanguageCPlusPlusPropertiesEnumGen) + add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN BlockPointer.cpp Coroutines.cpp @@ -41,3 +49,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN LINK_COMPONENTS Support ) + +add_dependencies(lldbPluginCPlusPlusLanguage + LLDBPluginLanguageCPlusPlusPropertiesGen + LLDBPluginLanguageCPlusPlusPropertiesEnumGen) diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 40b6563aeb410..943bc93348942 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -27,6 +27,7 @@ #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/DataFormatters/FormattersHelpers.h" #include "lldb/DataFormatters/VectorType.h" +#include "lldb/Interpreter/OptionValueProperties.h" #include "lldb/Symbol/SymbolFile.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Utility/ConstString.h" @@ -55,7 +56,7 @@ LLDB_PLUGIN_DEFINE(CPlusPlusLanguage) void CPlusPlusLanguage::Initialize() { PluginManager::RegisterPlugin(GetPluginNameStatic(), "C++ Language", - CreateInstance); + CreateInstance, &DebuggerInitialize); } void CPlusPlusLanguage::Terminate() { @@ -235,6 +236,151 @@ static bool PrettyPrintFunctionNameWithArgs(Stream &out_stream, return true; } +static std::optional +GetDemangledBasename(const SymbolContext &sc) { + Mangled mangled = sc.GetPossiblyInlinedFunctionName(); + if (!mangled) + return std::nullopt; + + auto demangled_name = mangled.GetDemangledName().GetStringRef(); + if (demangled_name.empty()) + return std::nullopt; + + const std::optional &info = mangled.GetDemangledInfo(); + if (!info) + return std::nullopt; + + // Function without a basename is nonsense. + if (!info->hasBasename()) + return std::nullopt; + + return demangled_name.slice(info->BasenameRange.first, + info->BasenameRange.second); +} + +static std::optional +GetDemangledTemplateArguments(const SymbolContext &sc) { + Mangled mangled = sc.GetPossiblyInlinedFunctionName(); + if (!mangled) + return std::nullopt; + + auto demangled_name = mangled.GetDemangledName().GetStringRef(); + if (demangled_name.empty()) + return std::nullopt; + + const std::optional &info = mangled.GetDemangledInfo(); + if (!info) + return std::nullopt; + + // Function without a basename is nonsense. + if (!info->hasBasename()) + return std::nullopt; + + if (info->ArgumentsRange.first < info->BasenameRange.second) + return std::nullopt; + + return demangled_name.slice(info->BasenameRange.second, + info->ArgumentsRange.first); +} + +static std::optional +GetDemangledReturnTypeLHS(const SymbolContext &sc) { + Mangled mangled = sc.GetPossiblyInlinedFunctionName(); + if (!mangled) + return std::nullopt; + + auto demangled_name = mangled.GetDemangledName().GetStringRef(); + if (demangled_name.empty()) + return std::nullopt; + + const std::optional &info = mangled.GetDemangledInfo(); + if (!info) + return std::nullopt; + + // Function without a basename is nonsense. + if (!info->hasBasename()) + return std::nullopt; + + if (info->ScopeRange.first >= demangled_name.size()) + return std::nullopt; + + return demangled_name.substr(0, info->ScopeRange.first); +} + +static std::optional +GetDemangledFunctionQualifiers(const SymbolContext &sc) { + Mangled mangled = sc.GetPossiblyInlinedFunctionName(); + if (!mangled) + return std::nullopt; + + auto demangled_name = mangled.GetDemangledName().GetStringRef(); + if (demangled_name.empty()) + return std::nullopt; + + const std::optional &info = mangled.GetDemangledInfo(); + if (!info) + return std::nullopt; + + // Function without a basename is nonsense. + if (!info->hasBasename()) + return std::nullopt; + + if (info->QualifiersRange.second < info->QualifiersRange.first) + return std::nullopt; + + return demangled_name.slice(info->QualifiersRange.first, + info->QualifiersRange.second); +} + +static std::optional +GetDemangledReturnTypeRHS(const SymbolContext &sc) { + Mangled mangled = sc.GetPossiblyInlinedFunctionName(); + if (!mangled) + return std::nullopt; + + auto demangled_name = mangled.GetDemangledName().GetStringRef(); + if (demangled_name.empty()) + return std::nullopt; + + const std::optional &info = mangled.GetDemangledInfo(); + if (!info) + return std::nullopt; + + // Function without a basename is nonsense. + if (!info->hasBasename()) + return std::nullopt; + + if (info->QualifiersRange.first < info->ArgumentsRange.second) + return std::nullopt; + + return demangled_name.slice(info->ArgumentsRange.second, + info->QualifiersRange.first); +} + +static std::optional +GetDemangledScope(const SymbolContext &sc) { + Mangled mangled = sc.GetPossiblyInlinedFunctionName(); + if (!mangled) + return std::nullopt; + + auto demangled_name = mangled.GetDemangledName().GetStringRef(); + if (demangled_name.empty()) + return std::nullopt; + + const std::optional &info = mangled.GetDemangledInfo(); + if (!info) + return std::nullopt; + + // Function without a basename is nonsense. + if (!info->hasBasename()) + return std::nullopt; + + if (info->ScopeRange.second < info->ScopeRange.first) + return std::nullopt; + + return demangled_name.slice(info->ScopeRange.first, info->ScopeRange.second); +} + bool CPlusPlusLanguage::CxxMethodName::TrySimplifiedParse() { // This method tries to parse simple method definitions which are presumably // most comman in user programs. Definitions that can be parsed by this @@ -1694,8 +1840,9 @@ static bool PrintFunctionNameWithArgs(Stream &s, ExecutionContextScope *exe_scope = exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr; - const char *cstr = sc.GetPossiblyInlinedFunctionName( - Mangled::NamePreference::ePreferDemangled); + const char *cstr = sc.GetPossiblyInlinedFunctionName() + .GetName(Mangled::NamePreference::ePreferDemangled) + .AsCString(); if (!cstr) return false; @@ -1739,3 +1886,130 @@ bool CPlusPlusLanguage::GetFunctionDisplayName( return false; } } +bool CPlusPlusLanguage::HandleFrameFormatVariable( + const SymbolContext &sc, const ExecutionContext *exe_ctx, + FormatEntity::Entry::Type type, Stream &s) { + assert(sc.function); + + switch (type) { + case FormatEntity::Entry::Type::FunctionScope: { + std::optional scope = GetDemangledScope(sc); + if (!scope) + return false; + + s << *scope; + + return true; + } + + case FormatEntity::Entry::Type::FunctionBasename: { + std::optional name = GetDemangledBasename(sc); + if (!name) + return false; + + s << *name; + + return true; + } + + case FormatEntity::Entry::Type::FunctionTemplateArguments: { + std::optional template_args = + GetDemangledTemplateArguments(sc); + if (!template_args) + return false; + + s << *template_args; + + return true; + } + + case FormatEntity::Entry::Type::FunctionFormattedArguments: { + VariableList args; + if (auto variable_list_sp = GetFunctionVariableList(sc)) + variable_list_sp->AppendVariablesWithScope(eValueTypeVariableArgument, + args); + + ExecutionContextScope *exe_scope = + exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr; + + s << '('; + FormatEntity::PrettyPrintFunctionArguments(s, args, exe_scope); + s << ')'; + + return true; + } + case FormatEntity::Entry::Type::FunctionReturnRight: { + std::optional return_rhs = GetDemangledReturnTypeRHS(sc); + if (!return_rhs) + return false; + + s << *return_rhs; + + return true; + } + case FormatEntity::Entry::Type::FunctionReturnLeft: { + std::optional return_lhs = GetDemangledReturnTypeLHS(sc); + if (!return_lhs) + return false; + + s << *return_lhs; + + return true; + } + case FormatEntity::Entry::Type::FunctionQualifiers: { + std::optional quals = GetDemangledFunctionQualifiers(sc); + if (!quals) + return false; + + s << *quals; + + return true; + } + default: + return false; + } +} + +#define LLDB_PROPERTIES_language_cplusplus +#include "LanguageCPlusPlusProperties.inc" + +enum { +#define LLDB_PROPERTIES_language_cplusplus +#include "LanguageCPlusPlusPropertiesEnum.inc" +}; + +namespace { +class PluginProperties : public Properties { +public: + static llvm::StringRef GetSettingName() { return "display"; } + + PluginProperties() { + m_collection_sp = std::make_shared(GetSettingName()); + m_collection_sp->Initialize(g_language_cplusplus_properties); + } + + const FormatEntity::Entry *GetFunctionNameFormat() const { + return GetPropertyAtIndexAs( + ePropertyFunctionNameFormat); + } +}; +} // namespace + +static PluginProperties &GetGlobalPluginProperties() { + static PluginProperties g_settings; + return g_settings; +} + +const FormatEntity::Entry *CPlusPlusLanguage::GetFunctionNameFormat() const { + return GetGlobalPluginProperties().GetFunctionNameFormat(); +} + +void CPlusPlusLanguage::DebuggerInitialize(Debugger &debugger) { + if (!PluginManager::GetSettingForCPlusPlusLanguagePlugin( + debugger, PluginProperties::GetSettingName())) { + PluginManager::CreateSettingForCPlusPlusLanguagePlugin( + debugger, GetGlobalPluginProperties().GetValueProperties(), + "Properties for the CPlusPlus language plug-in.", + /*is_global_property=*/true); + } +} diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h index b2b308f8e0c4d..575f76c3101ed 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h @@ -105,6 +105,13 @@ class CPlusPlusLanguage : public Language { FunctionNameRepresentation representation, Stream &s) override; + bool HandleFrameFormatVariable(const SymbolContext &sc, + const ExecutionContext *exe_ctx, + FormatEntity::Entry::Type type, + Stream &s) override; + + static bool IsCPPMangledName(llvm::StringRef name); + // Extract C++ context and identifier from a string using heuristic matching // (as opposed to // CPlusPlusLanguage::CxxMethodName which has to have a fully qualified C++ @@ -129,8 +136,13 @@ class CPlusPlusLanguage : public Language { llvm::StringRef GetInstanceVariableName() override { return "this"; } + const FormatEntity::Entry *GetFunctionNameFormat() const override; + // PluginInterface protocol llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + +private: + static void DebuggerInitialize(Debugger &); }; } // namespace lldb_private diff --git a/lldb/source/Plugins/Language/CPlusPlus/LanguageCPlusPlusProperties.td b/lldb/source/Plugins/Language/CPlusPlus/LanguageCPlusPlusProperties.td new file mode 100644 index 0000000000000..6ec87afe25758 --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/LanguageCPlusPlusProperties.td @@ -0,0 +1,8 @@ +include "../../../../include/lldb/Core/PropertiesBase.td" + +let Definition = "language_cplusplus" in { + def FunctionNameFormat: Property<"function-name-format", "FormatEntity">, + Global, + DefaultStringValue<"${function.return-left}${function.scope}${function.basename}${function.template-arguments}${function.formatted-arguments}${function.return-right}${function.qualifiers}">, + Desc<"C++ specific frame format string to use when displaying stack frame information for threads.">; +} diff --git a/lldb/source/Symbol/SymbolContext.cpp b/lldb/source/Symbol/SymbolContext.cpp index a9626bbc37777..3bbd1eff824e6 100644 --- a/lldb/source/Symbol/SymbolContext.cpp +++ b/lldb/source/Symbol/SymbolContext.cpp @@ -10,6 +10,7 @@ #include "lldb/Core/Address.h" #include "lldb/Core/Debugger.h" +#include "lldb/Core/DemangledNameInfo.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Host/Host.h" @@ -872,34 +873,36 @@ const Symbol *SymbolContext::FindBestGlobalDataSymbol(ConstString name, return nullptr; // no error; we just didn't find anything } -char const *SymbolContext::GetPossiblyInlinedFunctionName( - Mangled::NamePreference mangling_preference) const { - const char *name = nullptr; - if (function) - name = function->GetMangled().GetName(mangling_preference).AsCString(); - else if (symbol) - name = symbol->GetMangled().GetName(mangling_preference).AsCString(); +Mangled SymbolContext::GetPossiblyInlinedFunctionName() const { + auto get_mangled = [this]() { + if (function) + return function->GetMangled(); + + if (symbol) + return symbol->GetMangled(); + + return Mangled{}; + }; if (!block) - return name; + return get_mangled(); const Block *inline_block = block->GetContainingInlinedBlock(); if (!inline_block) - return name; + return get_mangled(); const InlineFunctionInfo *inline_info = inline_block->GetInlinedFunctionInfo(); if (!inline_info) - return name; + return get_mangled(); // If we do have an inlined frame name, return that. - if (char const *inline_name = - inline_info->GetMangled().GetName(mangling_preference).AsCString()) + if (const Mangled &inline_name = inline_info->GetMangled()) return inline_name; // Sometimes an inline frame may not have mangling information, // but does have a valid name. - return inline_info->GetName().AsCString(); + return Mangled{inline_info->GetName().AsCString()}; } // diff --git a/lldb/test/Shell/Settings/TestCxxFrameFormat.test b/lldb/test/Shell/Settings/TestCxxFrameFormat.test new file mode 100644 index 0000000000000..c26d339f57130 --- /dev/null +++ b/lldb/test/Shell/Settings/TestCxxFrameFormat.test @@ -0,0 +1,32 @@ +# Test the plugin.cplusplus.display.function-name-format setting. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +namespace ns::ns2 { +void custom(int x) asm("_Zinvalid_mangling"); +void custom(int x) {} + +void bar() { custom(5); } +void foo() { bar(); } +} + +int main(int argc, char const *argv[]) { + ns::ns2::foo(); + return 0; +} + +#--- commands.input +settings set plugin.cplusplus.display.function-name-format "${function.scope}${function.basename}" +settings set -f frame-format "custom-frame '${function.name-with-args}'\n" +break set -l 3 + +run +bt + +# CHECK: custom-frame '_Zinvalid_mangling(x=5)' +# CHECK: custom-frame 'ns::ns2::bar' +# CHECK: custom-frame 'ns::ns2::foo' diff --git a/lldb/test/Shell/Settings/TestCxxFrameFormatMixedLanguages.test b/lldb/test/Shell/Settings/TestCxxFrameFormatMixedLanguages.test new file mode 100644 index 0000000000000..854cf6384d8ee --- /dev/null +++ b/lldb/test/Shell/Settings/TestCxxFrameFormatMixedLanguages.test @@ -0,0 +1,51 @@ +# Test the plugin.cplusplus.display.function-name-format setting +# when interoperating multiple languages. + +# RUN: split-file %s %t +# RUN: %clangxx_host -x c -c -g %t/lib.c -o %t.clib.o +# RUN: %clangxx_host -c -g %t/lib.cpp -o %t.cxxlib.o +# RUN: %clangxx_host %t/main.m %t.cxxlib.o %t.clib.o -o %t.out +# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 | FileCheck %s + +#--- lib.c + +void foo(); + +void func() { + foo(); +} + +#--- lib.cpp + +namespace ns { +struct Foo { + void method() {} +}; +} + +extern "C" { +void foo() { + ns::Foo{}.method(); +} +} + +#--- main.m + +void func(); + +int main() { + func(); +} + +#--- commands.input +settings set plugin.cplusplus.display.function-name-format "this affects C++ only" +settings set -f frame-format "custom-frame '${function.name-with-args}'\n" +break set -n method + +run +bt + +# CHECK: custom-frame 'this affects C++ only' +# CHECK: custom-frame 'this affects C++ only' +# CHECK: custom-frame 'func' +# CHECK: custom-frame 'main' diff --git a/lldb/test/Shell/Settings/TestCxxFrameFormatObjC.test b/lldb/test/Shell/Settings/TestCxxFrameFormatObjC.test new file mode 100644 index 0000000000000..525ada2afe99c --- /dev/null +++ b/lldb/test/Shell/Settings/TestCxxFrameFormatObjC.test @@ -0,0 +1,24 @@ +# Test the plugin.cplusplus.display.function-name-format setting. + +# RUN: split-file %s %t +# RUN: %build %t/main.m -o %t.objc.out +# RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.m + +int func(int x) {} +int bar(int y) { func(y); } + +int main() { return bar(10); } + +#--- commands.input +settings set plugin.cplusplus.display.function-name-format "this affects C++ only" +settings set -f frame-format "custom-frame '${function.name-with-args}'\n" +break set -l 3 +run + +bt + +# CHECK: bt +# CHECK-NOT: this affects C++ only diff --git a/lldb/test/Shell/Settings/TestCxxFrameFormatPartialFailure.test b/lldb/test/Shell/Settings/TestCxxFrameFormatPartialFailure.test new file mode 100644 index 0000000000000..73564ae41837b --- /dev/null +++ b/lldb/test/Shell/Settings/TestCxxFrameFormatPartialFailure.test @@ -0,0 +1,29 @@ +# Test that the plugin.cplusplus.display.function-name-format setting +# doesn't print into the frame-format setting unless all its format variables +# were successful. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +template T gunc(int x = 10) { + return T{}; +} + +int main(int argc, const char *argv[]) { + gunc(); + return 0; +} + +#--- commands.input +settings set plugin.cplusplus.display.function-name-format "${function.basename}${script.target:invalid_func}" +settings set -f frame-format "custom-frame '${function.name-with-args}'\n" +break set -n gunc + +run +bt + +# CHECK: custom-frame 'int gunc(x=10)' +# CHECK: custom-frame 'main(argc=1, argv={{.*}})' diff --git a/lldb/test/Shell/Settings/TestCxxFrameFormatRecursive.test b/lldb/test/Shell/Settings/TestCxxFrameFormatRecursive.test new file mode 100644 index 0000000000000..90cd2d3e327c9 --- /dev/null +++ b/lldb/test/Shell/Settings/TestCxxFrameFormatRecursive.test @@ -0,0 +1,25 @@ +# XFAIL: * + +# Test disallowed variables inside the +# plugin.cplusplus.display.function-name-format setting. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +int main(int argc, char const *argv[]) { return 0; } + +#--- commands.input +settings set plugin.cplusplus.display.function-name-format "${function.name-with-args}" +settings set -f frame-format "custom-frame '${function.name-with-args}'\n" +b main +run + +bt + +# CHECK: bt +# CHECK-NOT: custom-frame +# CHECK: main diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test new file mode 100644 index 0000000000000..249a5fac5b55e --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionBasename.test @@ -0,0 +1,46 @@ +# Test the ${function.basename} frame-format variable. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s +# +#--- main.cpp +namespace ns { +template +struct Bar { + template + T bar(K k) const & { return 1.0f; } +}; + +template +struct Foo { + template + [[gnu::abi_tag("Test")]] void foo() const volatile && { + Bar b; + b.bar(b); + } +}; + +template +T func() { + ns::Foo{}.foo(); + return T{}; +} +} // namespace ns + +int main() { + ns::func>(); + return 0; +} + +#--- commands.input +settings set -f frame-format "custom-frame '${function.basename}'\n" +break set -n bar + +run +bt + +# CHECK: custom-frame 'bar' +# CHECK: custom-frame 'foo[abi:Test]' +# CHECK: custom-frame 'func' diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionBasenameObjC.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionBasenameObjC.test new file mode 100644 index 0000000000000..1a36d049db06c --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionBasenameObjC.test @@ -0,0 +1,24 @@ +# Check that we have an appropriate fallback for ${function.basename} in languages that +# don't implement this frame format variable (in this case Objective-C). +# +# RUN: split-file %s %t +# RUN: %build %t/main.m -o %t.objc.out +# RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.m + +int func() {} +int bar() { func(); } + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.basename}'\n" +break set -n bar + +run +bt + +# CHECK: bt +# CHECK-NOT: custom-frame diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test new file mode 100644 index 0000000000000..5554830d3a247 --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArguments.test @@ -0,0 +1,42 @@ +# Test the ${function.formatted-arguments} frame-format variable. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +struct Foo { + void func() {} +}; + +void bar() { + Foo{}.func(); +} + +void foo(int, int x) { + bar(); +} + +void myFunc(char const * str, + void (*fptr)(int, int)) { + fptr(5, 10); +} + +int main(int argc, char const *argv[]) { + myFunc("hello", &foo); + return 0; +} + +#--- commands.input +settings set -f frame-format "custom-frame '${function.formatted-arguments}'\n" +break set -n func + +run +bt + +# CHECK: custom-frame '(this={{.*}})' +# CHECK: custom-frame '()' +# CHECK: custom-frame '((null)=5, x=10)' +# CHECK: custom-frame '(str="hello", fptr=({{.*}}.out`foo(int, int) at main.cpp:{{[0-9]+}}))' +# CHECK: custom-frame '(argc=1, argv={{.*}})' diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArgumentsObjC.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArgumentsObjC.test new file mode 100644 index 0000000000000..fdafa65c6d05d --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionFormattedArgumentsObjC.test @@ -0,0 +1,24 @@ +# Check that we have an appropriate fallback for ${function.formatted-arguments} in languages that +# don't implement this frame format variable (in this case Objective-C). +# +# RUN: split-file %s %t +# RUN: %build %t/main.m -o %t.objc.out +# RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.m + +int func() {} +int bar() { func(); } + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.formatted-arguments}'\n" +break set -n func + +run +bt + +# CHECK: bt +# CHECK-NOT: custom-frame diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test new file mode 100644 index 0000000000000..95a3be3811d85 --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiers.test @@ -0,0 +1,24 @@ +# Test the ${function.qualifiers} frame-format variable. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +struct Foo { + void foo() const volatile && {} + void bar() { Foo{}.foo(); } +}; + +int main() { Foo{}.bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.qualifiers}'\n" +break set -n foo + +run +bt + +# CHECK: custom-frame ' const volatile &&' +# CHECK: custom-frame '' diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiersObjC.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiersObjC.test new file mode 100644 index 0000000000000..eff1b581c15dc --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionQualifiersObjC.test @@ -0,0 +1,25 @@ +# Check that we have an appropriate fallback for ${function.qualifiers} in +# languages that don't implement this frame format variable (in this case Objective-C). + +# RUN: split-file %s %t +# +# RUN: %build %t/main.m -o %t.objc.out +# RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.m + +int foo() {} +int bar() { foo(); } + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.qualifiers}'\n" +break set -n foo + +run +bt + +# CHECK: bt +# CHECK-NOT: custom-frame diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test new file mode 100644 index 0000000000000..a5e49a1054c86 --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturn.test @@ -0,0 +1,55 @@ +# Test the ${function.return-left} and ${function.return-right} +# frame-format variables. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +namespace ns::ns2 { +template +struct Foo {}; + +template +Foo qux(int) { + return {}; +} + +template +Foo (*bar(Foo))(int) { + qux(5); + return qux; +} + +struct Bar { + template + Foo (* (*foo(int) const &&)(Foo))(int) { + bar(Foo{}); + return bar; + } +}; +} + +int main(int argc, char const *argv[]) { + ns::ns2::Bar{}.foo(5); + return 0; +} + +#--- commands.input +settings set -f frame-format "custom-frame '${function.return-left}'\n" +break set -n qux + +run +bt + +# CHECK: custom-frame 'ns::ns2::Foo ' +# CHECK: custom-frame 'ns::ns2::Foo (*' +# CHECK: custom-frame 'ns::ns2::Foo (* (*' + +settings set -f frame-format "other-frame '${function.return-right}'\n" +bt + +# CHECK: other-frame '' +# CHECK: other-frame ')(int)' +# CHECK: other-frame ')(ns::ns2::Foo))(int)' diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionReturnObjC.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturnObjC.test new file mode 100644 index 0000000000000..69dd0fdbb0c19 --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturnObjC.test @@ -0,0 +1,31 @@ +# Check that we have an appropriate fallback for ${function.return-left} and +# ${function.return-right} in languages that don't implement this frame +# format variable (in this case Objective-C). +# +# RUN: split-file %s %t +# RUN: %build %t/main.m -o %t.objc.out +# RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.m + +int qux() {} +int bar() { qux(); } + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.return-left}'\n" +break set -n qux + +run +bt + +# CHECK: bt +# CHECK-NOT: custom-frame + +settings set -f frame-format "other-frame '${function.return-right}'\n" +bt + +# CHECK: bt +# CHECK-NOT: other-frame diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test new file mode 100644 index 0000000000000..28f0ab7ca39e3 --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionScope.test @@ -0,0 +1,41 @@ +# Test the ${function.scope} frame-format variable. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.out +# RUN: %lldb -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -x -b -s %t/commands.input %t.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +namespace ns::ns2 { +inline namespace ins { +template +struct Foo { + void func() {} +}; + +int foo() { + Foo{}.func(); + return 5; +} +} // namespace ins +} // namespace ns::ns2 + +using namespace ns::ns2; + +int bar() { + return ns::ns2::foo(); +} + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.scope}'\n" +break set -n func + +run +bt + +# CHECK: frame 'ns::ns2::ins::Foo::' +# CHECK: frame 'ns::ns2::ins::' +# CHECK: frame '' diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionScopeObjC.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionScopeObjC.test new file mode 100644 index 0000000000000..310b5e1992ab8 --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionScopeObjC.test @@ -0,0 +1,24 @@ +# Check that we have an appropriate fallback for ${function.scope} in languages that +# don't implement this frame format variable (in this case Objective-C). +# +# RUN: split-file %s %t +# RUN: %build %t/main.m -o %t.objc.out +# RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.m + +int func() {} +int bar() { func(); } + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.scope}'\n" +break set -n func + +run +bt + +# CHECK: bt +# CHECK-NOT: custom-frame diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test new file mode 100644 index 0000000000000..396dd29dfd02c --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArguments.test @@ -0,0 +1,37 @@ +# Test the ${function.template-arguments} frame-format variable. + +# RUN: split-file %s %t +# RUN: %build %t/main.cpp -o %t.cxx.out +# RUN: %lldb -x -b -s %t/commands.input %t.cxx.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.cpp +template +struct Foo { + template + void func() {} +}; + +template class K, + typename M> +int foo() { + Foo{}.func(); + return 5; +} + +int bar() { + return foo>(); +} + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.template-arguments}'\n" +break set -n func + +run +bt + +# CHECK: custom-frame '' +# CHECK: custom-frame '>' +# CHECK: custom-frame '' diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArgumentsObjC.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArgumentsObjC.test new file mode 100644 index 0000000000000..1726aebacb9eb --- /dev/null +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionTemplateArgumentsObjC.test @@ -0,0 +1,24 @@ +# Check that we have an appropriate fallback for ${function.template-arguments} in +# languages that don't implement this frame format variable (in this case Objective-C). +# +# RUN: split-file %s %t +# RUN: %build %t/main.m -o %t.objc.out +# RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ +# RUN: | FileCheck %s + +#--- main.m + +int func() {} +int bar() { func(); } + +int main() { return bar(); } + +#--- commands.input +settings set -f frame-format "custom-frame '${function.template-arguments}'\n" +break set -n func + +run +bt + +# CHECK: bt +# CHECK-NOT: custom-frame diff --git a/lldb/test/Shell/Settings/TestFrameFormatName.test b/lldb/test/Shell/Settings/TestFrameFormatName.test index 110daceb47b40..34eb2f4975315 100644 --- a/lldb/test/Shell/Settings/TestFrameFormatName.test +++ b/lldb/test/Shell/Settings/TestFrameFormatName.test @@ -22,13 +22,13 @@ c c # NAME_WITH_ARGS: frame int ns::foo(str="method") c -# NAME_WITH_ARGS: frame ns::returns_func_ptr((null)={{.*}}) +# NAME_WITH_ARGS: frame detail::Quux (* (*ns::returns_func_ptr((null)={{.*}}))(int))(float) c # NAME_WITH_ARGS: frame void Foo::foo(this={{.*}}, arg=({{.*}}`(anonymous namespace)::anon_bar() at {{.*}})) c # NAME_WITH_ARGS: frame void Foo::operator<<<1>(this={{.*}}, (null)=0) c -# NAME_WITH_ARGS: frame Foo::returns_func_ptr(this={{.*}}, (null)={{.*}}) +# NAME_WITH_ARGS: frame detail::Quux (* (*Foo::returns_func_ptr(this={{.*}}, (null)={{.*}}))(int))(float) const c # NAME_WITH_ARGS: frame inlined_foo(str="bar") q diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp index a3760ba43b3c9..e903cadd410f3 100644 --- a/lldb/unittests/Core/MangledTest.cpp +++ b/lldb/unittests/Core/MangledTest.cpp @@ -11,6 +11,7 @@ #include "TestingSupport/SubsystemRAII.h" #include "TestingSupport/TestUtilities.h" +#include "lldb/Core/DemangledNameInfo.h" #include "lldb/Core/Mangled.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" @@ -319,3 +320,255 @@ TEST(MangledTest, NameIndexes_FindFunctionSymbols) { EXPECT_EQ(0, Count("undemangable", eFunctionNameTypeBase)); EXPECT_EQ(0, Count("undemangable", eFunctionNameTypeMethod)); } + +TEST(MangledTest, DemangledNameInfo_SetMangledResets) { + Mangled mangled; + EXPECT_EQ(mangled.GetDemangledInfo(), std::nullopt); + + mangled.SetMangledName(ConstString("_Z3foov")); + ASSERT_TRUE(mangled); + + auto info1 = mangled.GetDemangledInfo(); + EXPECT_NE(info1, std::nullopt); + EXPECT_TRUE(info1->hasBasename()); + + mangled.SetMangledName(ConstString("_Z4funcv")); + + // Should have re-calculated demangled-info since mangled name changed. + auto info2 = mangled.GetDemangledInfo(); + ASSERT_NE(info2, std::nullopt); + EXPECT_TRUE(info2->hasBasename()); + + EXPECT_NE(info1.value(), info2.value()); + EXPECT_EQ(mangled.GetDemangledName(), "func()"); +} + +TEST(MangledTest, DemangledNameInfo_SetDemangledResets) { + Mangled mangled("_Z3foov"); + ASSERT_TRUE(mangled); + + mangled.SetDemangledName(ConstString("")); + + // Mangled name hasn't changed, so GetDemangledInfo causes re-demangling + // of previously set mangled name. + EXPECT_NE(mangled.GetDemangledInfo(), std::nullopt); + EXPECT_EQ(mangled.GetDemangledName(), "foo()"); +} + +TEST(MangledTest, DemangledNameInfo_Clear) { + Mangled mangled("_Z3foov"); + ASSERT_TRUE(mangled); + EXPECT_NE(mangled.GetDemangledInfo(), std::nullopt); + + mangled.Clear(); + + EXPECT_EQ(mangled.GetDemangledInfo(), std::nullopt); +} + +TEST(MangledTest, DemangledNameInfo_SetValue) { + Mangled mangled("_Z4funcv"); + ASSERT_TRUE(mangled); + + auto demangled_func = mangled.GetDemangledInfo(); + + // SetValue(mangled) resets demangled-info + mangled.SetValue(ConstString("_Z3foov")); + auto demangled_foo = mangled.GetDemangledInfo(); + EXPECT_NE(demangled_foo, std::nullopt); + EXPECT_NE(demangled_foo, demangled_func); + + // SetValue(demangled) resets demangled-info + mangled.SetValue(ConstString("_Z4funcv")); + EXPECT_EQ(mangled.GetDemangledInfo(), demangled_func); + + // SetValue(empty) resets demangled-info + mangled.SetValue(ConstString()); + EXPECT_EQ(mangled.GetDemangledInfo(), std::nullopt); + + // Demangling invalid mangled name will set demangled-info + // (without a valid basename). + mangled.SetValue(ConstString("_Zinvalid")); + ASSERT_NE(mangled.GetDemangledInfo(), std::nullopt); + EXPECT_FALSE(mangled.GetDemangledInfo()->hasBasename()); +} + +struct DemanglingPartsTestCase { + const char *mangled; + DemangledNameInfo expected_info; + std::string_view basename; + std::string_view scope; + std::string_view qualifiers; + bool valid_basename = true; +}; + +DemanglingPartsTestCase g_demangling_parts_test_cases[] = { + // clang-format off + { "_ZNVKO3BarIN2ns3QuxIiEEE1CIPFi3FooIS_IiES6_EEE6methodIS6_EENS5_IT_SC_E5InnerIiEESD_SD_", + { .BasenameRange = {92, 98}, .ScopeRange = {36, 92}, .ArgumentsRange = { 108, 158 }, + .QualifiersRange = {158, 176} }, + .basename = "method", + .scope = "Bar>::C, Bar>)>::", + .qualifiers = " const volatile &&" + }, + { "_Z7getFuncIfEPFiiiET_", + { .BasenameRange = {6, 13}, .ScopeRange = {6, 6}, .ArgumentsRange = { 20, 27 }, .QualifiersRange = {38, 38} }, + .basename = "getFunc", + .scope = "", + .qualifiers = "" + }, + { "_ZN1f1b1c1gEv", + { .BasenameRange = {9, 10}, .ScopeRange = {0, 9}, .ArgumentsRange = { 10, 12 }, + .QualifiersRange = {12, 12} }, + .basename = "g", + .scope = "f::b::c::", + .qualifiers = "" + }, + { "_ZN5test73fD1IiEEDTcmtlNS_1DEL_ZNS_1bEEEcvT__EES2_", + { .BasenameRange = {45, 48}, .ScopeRange = {38, 45}, .ArgumentsRange = { 53, 58 }, + .QualifiersRange = {58, 58} }, + .basename = "fD1", + .scope = "test7::", + .qualifiers = "" + }, + { "_ZN5test73fD1IiEEDTcmtlNS_1DEL_ZNS_1bINDT1cE1dEEEEEcvT__EES2_", + { .BasenameRange = {61, 64}, .ScopeRange = {54, 61}, .ArgumentsRange = { 69, 79 }, + .QualifiersRange = {79, 79} }, + .basename = "fD1", + .scope = "test7::", + .qualifiers = "" + }, + { "_ZN5test7INDT1cE1dINDT1cE1dEEEE3fD1INDT1cE1dINDT1cE1dEEEEEDTcmtlNS_1DEL_ZNS_1bINDT1cE1dEEEEEcvT__EES2_", + { .BasenameRange = {120, 123}, .ScopeRange = {81, 120}, .ArgumentsRange = { 155, 168 }, + .QualifiersRange = {168, 168} }, + .basename = "fD1", + .scope = "test7>::", + .qualifiers = "" + }, + { "_ZN8nlohmann16json_abi_v3_11_310basic_jsonINSt3__13mapENS2_6vectorENS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEEbxydS8_NS0_14adl_serializerENS4_IhNS8_IhEEEEvE5parseIRA29_KcEESE_OT_NS2_8functionIFbiNS0_6detail13parse_event_tERSE_EEEbb", + { .BasenameRange = {687, 692}, .ScopeRange = {343, 687}, .ArgumentsRange = { 713, 1174 }, + .QualifiersRange = {1174, 1174} }, + .basename = "parse", + .scope = "nlohmann::json_abi_v3_11_3::basic_json, std::__1::allocator>, bool, long long, unsigned long long, double, std::__1::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::__1::vector>, void>::", + .qualifiers = "" + }, + { "_ZN8nlohmann16json_abi_v3_11_310basic_jsonINSt3__13mapENS2_6vectorENS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEEbxydS8_NS0_14adl_serializerENS4_IhNS8_IhEEEEvEC1EDn", + { .BasenameRange = {344, 354}, .ScopeRange = {0, 344}, .ArgumentsRange = { 354, 370 }, + .QualifiersRange = {370, 370} }, + .basename = "basic_json", + .scope = "nlohmann::json_abi_v3_11_3::basic_json, std::__1::allocator>, bool, long long, unsigned long long, double, std::__1::allocator, nlohmann::json_abi_v3_11_3::adl_serializer, std::__1::vector>, void>::", + .qualifiers = "" + }, + { "_Z3fppIiEPFPFvvEiEf", + { .BasenameRange = {10, 13}, .ScopeRange = {10, 10}, .ArgumentsRange = { 18, 25 }, .QualifiersRange = {34,34} }, + .basename = "fpp", + .scope = "", + .qualifiers = "" + }, + { "_Z3fppIiEPFPFvvEN2ns3FooIiEEEf", + { .BasenameRange = {10, 13}, .ScopeRange = {10, 10}, .ArgumentsRange = { 18, 25 }, + .QualifiersRange = {43, 43} }, + .basename = "fpp", + .scope = "", + .qualifiers = "" + }, + { "_Z3fppIiEPFPFvPFN2ns3FooIiEENS2_3BarIfE3QuxEEEPFS2_S2_EEf", + { .BasenameRange = {10, 13}, .ScopeRange = {10, 10}, .ArgumentsRange = { 18, 25 }, + .QualifiersRange = {108, 108} }, + .basename = "fpp", + .scope = "", + .qualifiers = "" + }, + { "_ZN2ns8HasFuncsINS_3FooINS1_IiE3BarIfE3QuxEEEE3fppIiEEPFPFvvEiEf", + { .BasenameRange = {64, 67}, .ScopeRange = {10, 64}, .ArgumentsRange = { 72, 79 }, + .QualifiersRange = {88, 88} }, + .basename = "fpp", + .scope = "ns::HasFuncs::Bar::Qux>>::", + .qualifiers = "" + }, + { "_ZN2ns8HasFuncsINS_3FooINS1_IiE3BarIfE3QuxEEEE3fppIiEEPFPFvvES2_Ef", + { .BasenameRange = {64, 67}, .ScopeRange = {10, 64}, .ArgumentsRange = { 72, 79 }, + .QualifiersRange = {97, 97} }, + .basename = "fpp", + .scope = "ns::HasFuncs::Bar::Qux>>::", + .qualifiers = "", + }, + { "_ZN2ns8HasFuncsINS_3FooINS1_IiE3BarIfE3QuxEEEE3fppIiEEPFPFvPFS2_S5_EEPFS2_S2_EEf", + { .BasenameRange = {64, 67}, .ScopeRange = {10, 64}, .ArgumentsRange = { 72, 79 }, + .QualifiersRange = {162, 162} }, + .basename = "fpp", + .scope = "ns::HasFuncs::Bar::Qux>>::", + .qualifiers = "", + }, + { "_ZNKO2ns3ns23Bar3fooIiEEPFPFNS0_3FooIiEEiENS3_IfEEEi", + { .BasenameRange = {37, 40}, .ScopeRange = {23, 37}, .ArgumentsRange = { 45, 50 }, + .QualifiersRange = {78, 87} }, + .basename = "foo", + .scope = "ns::ns2::Bar::", + .qualifiers = " const &&", + }, + { "_ZTV11ImageLoader", + { .BasenameRange = {0, 0}, .ScopeRange = {0, 0}, .ArgumentsRange = { 0, 0 }, + .QualifiersRange = {0, 0} }, + .basename = "", + .scope = "", + .qualifiers = "", + .valid_basename = false + } + // clang-format on +}; + +struct DemanglingPartsTestFixture + : public ::testing::TestWithParam {}; + +namespace { +class TestAllocator { + llvm::BumpPtrAllocator Alloc; + +public: + void reset() { Alloc.Reset(); } + + template T *makeNode(Args &&...args) { + return new (Alloc.Allocate(sizeof(T), alignof(T))) + T(std::forward(args)...); + } + + void *allocateNodeArray(size_t sz) { + return Alloc.Allocate(sizeof(llvm::itanium_demangle::Node *) * sz, + alignof(llvm::itanium_demangle::Node *)); + } +}; +} // namespace + +TEST_P(DemanglingPartsTestFixture, DemanglingParts) { + const auto &[mangled, info, basename, scope, qualifiers, valid_basename] = + GetParam(); + + llvm::itanium_demangle::ManglingParser Parser( + mangled, mangled + ::strlen(mangled)); + + const auto *Root = Parser.parse(); + + ASSERT_NE(nullptr, Root); + + TrackingOutputBuffer OB; + Root->print(OB); + auto demangled = std::string_view(OB); + + ASSERT_EQ(OB.NameInfo.hasBasename(), valid_basename); + + EXPECT_EQ(OB.NameInfo.BasenameRange, info.BasenameRange); + EXPECT_EQ(OB.NameInfo.ScopeRange, info.ScopeRange); + EXPECT_EQ(OB.NameInfo.ArgumentsRange, info.ArgumentsRange); + EXPECT_EQ(OB.NameInfo.QualifiersRange, info.QualifiersRange); + + auto get_part = [&](const std::pair &loc) { + return demangled.substr(loc.first, loc.second - loc.first); + }; + + EXPECT_EQ(get_part(OB.NameInfo.BasenameRange), basename); + EXPECT_EQ(get_part(OB.NameInfo.ScopeRange), scope); + EXPECT_EQ(get_part(OB.NameInfo.QualifiersRange), qualifiers); +} + +INSTANTIATE_TEST_SUITE_P(DemanglingPartsTests, DemanglingPartsTestFixture, + ::testing::ValuesIn(g_demangling_parts_test_cases)); diff --git a/llvm/include/llvm/Demangle/Demangle.h b/llvm/include/llvm/Demangle/Demangle.h index 132e5088b5514..21e7457b6336f 100644 --- a/llvm/include/llvm/Demangle/Demangle.h +++ b/llvm/include/llvm/Demangle/Demangle.h @@ -93,6 +93,13 @@ struct ItaniumPartialDemangler { /// second and third parameters to __cxa_demangle. char *finishDemangle(char *Buf, size_t *N) const; + /// See \ref finishDemangle + /// + /// \param[in] OB A llvm::itanium_demangle::OutputBuffer that the demangled + /// name will be printed into. + /// + char *finishDemangle(void *OB) const; + /// Get the base name of a function. This doesn't include trailing template /// arguments, ie for "a::b" this function returns "b". char *getFunctionBaseName(char *Buf, size_t *N) const; diff --git a/llvm/lib/Demangle/ItaniumDemangle.cpp b/llvm/lib/Demangle/ItaniumDemangle.cpp index 5c21b06a1d095..1009cc91ca12a 100644 --- a/llvm/lib/Demangle/ItaniumDemangle.cpp +++ b/llvm/lib/Demangle/ItaniumDemangle.cpp @@ -411,9 +411,7 @@ bool ItaniumPartialDemangler::partialDemangle(const char *MangledName) { RootNode = Parser->parse(); return RootNode == nullptr; } - -static char *printNode(const Node *RootNode, char *Buf, size_t *N) { - OutputBuffer OB(Buf, N); +static char *printNode(const Node *RootNode, OutputBuffer &OB, size_t *N) { RootNode->print(OB); OB += '\0'; if (N != nullptr) @@ -421,6 +419,11 @@ static char *printNode(const Node *RootNode, char *Buf, size_t *N) { return OB.getBuffer(); } +static char *printNode(const Node *RootNode, char *Buf, size_t *N) { + OutputBuffer OB(Buf, N); + return printNode(RootNode, OB, N); +} + char *ItaniumPartialDemangler::getFunctionBaseName(char *Buf, size_t *N) const { if (!isFunction()) return nullptr; @@ -540,6 +543,14 @@ char *ItaniumPartialDemangler::finishDemangle(char *Buf, size_t *N) const { return printNode(static_cast(RootNode), Buf, N); } +char *ItaniumPartialDemangler::finishDemangle(void *OB) const { + assert(RootNode != nullptr && "must call partialDemangle()"); + assert(OB != nullptr && "valid OutputBuffer argument required"); + return printNode(static_cast(RootNode), + *static_cast(OB), + /*N=*/nullptr); +} + bool ItaniumPartialDemangler::hasFunctionQualifiers() const { assert(RootNode != nullptr && "must call partialDemangle()"); if (!isFunction())