llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb @llvm/pr-subscribers-libcxxabi Author: Michael Buch (Michael137) <details> <summary>Changes</summary> This patch adds a new `frame-format` variable to allow highlighting function basenames when formatting frame names in LLDB ([see this RFC](https://discourse.llvm.org/t/rfc-lldb-highlighting-function-names-in-lldb-backtraces/85309)). This is done by tracking information about components of a demangled function name and plumbing that from the demangler through to LLDB. We expose this with a new `frame-format` variable to `${function.name-with-args}` (and can be easily extended to the other types of `${function.XXX}` variables. The new specifier looks as follows: ``` ${function.name-with-args:%highlight_basename(ansi.fg.cyan)} ``` This The plan is to eventually, after living on the option for some time, add it to the default `frame-format` setting. Example highlighted backtrace: <img width="1512" alt="Screenshot 2025-03-19 at 14 39 30" src="https://github.com/user-attachments/assets/b24f9c9f-029d-4a64-ad12-db2ad652ed71" /> **Changes** * Commit 1 (separately reviewed in https://github.com/llvm/llvm-project/pull/133249): required demangler interface changes to allow tracking of basename locations * Commit 2: implements the `TrackingOutputBuffer` * Commit 3: add version of `GetDemangledName` that will force re-demangling. This is required because LLDB will `SetDemangledName` without going through the demangler. So we need a way to force demangling to set the `m_demangled_info` member when we need it * Commit 4: changes `GetDemangledName` to use the `TrackingOutputBuffer` and populate the `m_demangled_info` member * Commit 5: implements the new `frame-format` specifier. This is what implements the actual highlighting --- Patch is 67.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131836.diff 21 Files Affected: - (modified) libcxxabi/src/demangle/ItaniumDemangle.h (+36-28) - (modified) libcxxabi/src/demangle/Utility.h (+7) - (added) lldb/include/lldb/Core/DemangledNameInfo.h (+151) - (modified) lldb/include/lldb/Core/FormatEntity.h (+28) - (modified) lldb/include/lldb/Core/Mangled.h (+23-3) - (modified) lldb/include/lldb/Symbol/Function.h (+4) - (modified) lldb/include/lldb/Target/Language.h (+5-4) - (modified) lldb/source/Core/CMakeLists.txt (+1) - (added) lldb/source/Core/DemangledNameInfo.cpp (+213) - (modified) lldb/source/Core/FormatEntity.cpp (+74-3) - (modified) lldb/source/Core/Mangled.cpp (+42-10) - (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+75-9) - (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h (+5-4) - (modified) lldb/source/Symbol/Function.cpp (+4) - (modified) lldb/source/Target/Language.cpp (+4-4) - (added) lldb/test/Shell/Settings/TestFrameFormatHighlight.test (+77) - (modified) lldb/unittests/Core/MangledTest.cpp (+214) - (modified) llvm/include/llvm/Demangle/Demangle.h (+7) - (modified) llvm/include/llvm/Demangle/ItaniumDemangle.h (+36-28) - (modified) llvm/include/llvm/Demangle/Utility.h (+7) - (modified) llvm/lib/Demangle/ItaniumDemangle.cpp (+14-3) ``````````diff diff --git a/libcxxabi/src/demangle/ItaniumDemangle.h b/libcxxabi/src/demangle/ItaniumDemangle.h index 3df41b5f4d7d0..89a24def830f2 100644 --- a/libcxxabi/src/demangle/ItaniumDemangle.h +++ b/libcxxabi/src/demangle/ItaniumDemangle.h @@ -281,9 +281,9 @@ class Node { } void print(OutputBuffer &OB) const { - printLeft(OB); + OB.printLeft(*this); if (RHSComponentCache != Cache::No) - printRight(OB); + OB.printRight(*this); } // Print the "left" side of this Node into OutputBuffer. @@ -458,11 +458,11 @@ class QualType final : public Node { } void printLeft(OutputBuffer &OB) const override { - Child->printLeft(OB); + OB.printLeft(*Child); printQuals(OB); } - void printRight(OutputBuffer &OB) const override { Child->printRight(OB); } + void printRight(OutputBuffer &OB) const override { OB.printRight(*Child); } }; class ConversionOperatorType final : public Node { @@ -491,7 +491,7 @@ class PostfixQualifiedType final : public Node { template<typename Fn> void match(Fn F) const { F(Ty, Postfix); } void printLeft(OutputBuffer &OB) const override { - Ty->printLeft(OB); + OB.printLeft(*Ty); OB += Postfix; } }; @@ -577,7 +577,7 @@ struct AbiTagAttr : Node { std::string_view getBaseName() const override { return Base->getBaseName(); } void printLeft(OutputBuffer &OB) const override { - Base->printLeft(OB); + OB.printLeft(*Base); OB += "[abi:"; OB += Tag; OB += "]"; @@ -644,7 +644,7 @@ class PointerType final : public Node { // We rewrite objc_object<SomeProtocol>* into id<SomeProtocol>. if (Pointee->getKind() != KObjCProtoName || !static_cast<const ObjCProtoName *>(Pointee)->isObjCObject()) { - Pointee->printLeft(OB); + OB.printLeft(*Pointee); if (Pointee->hasArray(OB)) OB += " "; if (Pointee->hasArray(OB) || Pointee->hasFunction(OB)) @@ -663,7 +663,7 @@ class PointerType final : public Node { !static_cast<const ObjCProtoName *>(Pointee)->isObjCObject()) { if (Pointee->hasArray(OB) || Pointee->hasFunction(OB)) OB += ")"; - Pointee->printRight(OB); + OB.printRight(*Pointee); } } }; @@ -729,7 +729,7 @@ class ReferenceType : public Node { std::pair<ReferenceKind, const Node *> Collapsed = collapse(OB); if (!Collapsed.second) return; - Collapsed.second->printLeft(OB); + OB.printLeft(*Collapsed.second); if (Collapsed.second->hasArray(OB)) OB += " "; if (Collapsed.second->hasArray(OB) || Collapsed.second->hasFunction(OB)) @@ -746,7 +746,7 @@ class ReferenceType : public Node { return; if (Collapsed.second->hasArray(OB) || Collapsed.second->hasFunction(OB)) OB += ")"; - Collapsed.second->printRight(OB); + OB.printRight(*Collapsed.second); } }; @@ -766,7 +766,7 @@ class PointerToMemberType final : public Node { } void printLeft(OutputBuffer &OB) const override { - MemberType->printLeft(OB); + OB.printLeft(*MemberType); if (MemberType->hasArray(OB) || MemberType->hasFunction(OB)) OB += "("; else @@ -778,7 +778,7 @@ class PointerToMemberType final : public Node { void printRight(OutputBuffer &OB) const override { if (MemberType->hasArray(OB) || MemberType->hasFunction(OB)) OB += ")"; - MemberType->printRight(OB); + OB.printRight(*MemberType); } }; @@ -798,7 +798,7 @@ class ArrayType final : public Node { bool hasRHSComponentSlow(OutputBuffer &) const override { return true; } bool hasArraySlow(OutputBuffer &) const override { return true; } - void printLeft(OutputBuffer &OB) const override { Base->printLeft(OB); } + void printLeft(OutputBuffer &OB) const override { OB.printLeft(*Base); } void printRight(OutputBuffer &OB) const override { if (OB.back() != ']') @@ -807,7 +807,7 @@ class ArrayType final : public Node { if (Dimension) Dimension->print(OB); OB += "]"; - Base->printRight(OB); + OB.printRight(*Base); } bool printInitListAsType(OutputBuffer &OB, @@ -851,7 +851,7 @@ class FunctionType final : public Node { // by printing out the return types's left, then print our parameters, then // finally print right of the return type. void printLeft(OutputBuffer &OB) const override { - Ret->printLeft(OB); + OB.printLeft(*Ret); OB += " "; } @@ -859,7 +859,7 @@ class FunctionType final : public Node { OB.printOpen(); Params.printWithComma(OB); OB.printClose(); - Ret->printRight(OB); + OB.printRight(*Ret); if (CVQuals & QualConst) OB += " const"; @@ -964,6 +964,8 @@ class FunctionEncoding final : public Node { FunctionRefQual getRefQual() const { return RefQual; } NodeArray getParams() const { return Params; } const Node *getReturnType() const { return Ret; } + const Node *getAttrs() const { return Attrs; } + const Node *getRequires() const { return Requires; } bool hasRHSComponentSlow(OutputBuffer &) const override { return true; } bool hasFunctionSlow(OutputBuffer &) const override { return true; } @@ -972,10 +974,11 @@ class FunctionEncoding final : public Node { void printLeft(OutputBuffer &OB) const override { if (Ret) { - Ret->printLeft(OB); + OB.printLeft(*Ret); if (!Ret->hasRHSComponent(OB)) OB += " "; } + Name->print(OB); } @@ -983,8 +986,9 @@ class FunctionEncoding final : public Node { OB.printOpen(); Params.printWithComma(OB); OB.printClose(); + if (Ret) - Ret->printRight(OB); + OB.printRight(*Ret); if (CVQuals & QualConst) OB += " const"; @@ -1324,14 +1328,14 @@ class NonTypeTemplateParamDecl final : public Node { template<typename Fn> void match(Fn F) const { F(Name, Type); } void printLeft(OutputBuffer &OB) const override { - Type->printLeft(OB); + OB.printLeft(*Type); if (!Type->hasRHSComponent(OB)) OB += " "; } void printRight(OutputBuffer &OB) const override { Name->print(OB); - Type->printRight(OB); + OB.printRight(*Type); } }; @@ -1376,11 +1380,11 @@ class TemplateParamPackDecl final : public Node { template<typename Fn> void match(Fn F) const { F(Param); } void printLeft(OutputBuffer &OB) const override { - Param->printLeft(OB); + OB.printLeft(*Param); OB += "..."; } - void printRight(OutputBuffer &OB) const override { Param->printRight(OB); } + void printRight(OutputBuffer &OB) const override { OB.printRight(*Param); } }; /// An unexpanded parameter pack (either in the expression or type context). If @@ -1445,13 +1449,13 @@ class ParameterPack final : public Node { initializePackExpansion(OB); size_t Idx = OB.CurrentPackIndex; if (Idx < Data.size()) - Data[Idx]->printLeft(OB); + OB.printLeft(*Data[Idx]); } void printRight(OutputBuffer &OB) const override { initializePackExpansion(OB); size_t Idx = OB.CurrentPackIndex; if (Idx < Data.size()) - Data[Idx]->printRight(OB); + OB.printRight(*Data[Idx]); } }; @@ -1609,13 +1613,13 @@ struct ForwardTemplateReference : Node { if (Printing) return; ScopedOverride<bool> SavePrinting(Printing, true); - Ref->printLeft(OB); + OB.printLeft(*Ref); } void printRight(OutputBuffer &OB) const override { if (Printing) return; ScopedOverride<bool> SavePrinting(Printing, true); - Ref->printRight(OB); + OB.printRight(*Ref); } }; @@ -1767,7 +1771,7 @@ class DtorName : public Node { void printLeft(OutputBuffer &OB) const override { OB += "~"; - Base->printLeft(OB); + OB.printLeft(*Base); } }; @@ -2047,7 +2051,7 @@ class CastExpr : public Node { { ScopedOverride<unsigned> LT(OB.GtIsGt, 0); OB += "<"; - To->printLeft(OB); + OB.printLeft(*To); OB += ">"; } OB.printOpen(); @@ -6176,6 +6180,10 @@ struct ManglingParser : AbstractManglingParser<ManglingParser<Alloc>, Alloc> { Alloc>::AbstractManglingParser; }; +inline void OutputBuffer::printLeft(const Node &N) { N.printLeft(*this); } + +inline void OutputBuffer::printRight(const Node &N) { N.printRight(*this); } + DEMANGLE_NAMESPACE_END #if defined(__clang__) diff --git a/libcxxabi/src/demangle/Utility.h b/libcxxabi/src/demangle/Utility.h index f1fad35d60d98..a38bef762dd57 100644 --- a/libcxxabi/src/demangle/Utility.h +++ b/libcxxabi/src/demangle/Utility.h @@ -27,6 +27,8 @@ DEMANGLE_NAMESPACE_BEGIN +class Node; + // Stream that AST nodes write their string representation into after the AST // has been parsed. class OutputBuffer { @@ -79,10 +81,15 @@ class OutputBuffer { OutputBuffer(const OutputBuffer &) = delete; OutputBuffer &operator=(const OutputBuffer &) = delete; + virtual ~OutputBuffer() {} + operator std::string_view() const { return std::string_view(Buffer, CurrentPosition); } + virtual void printLeft(const Node &N); + virtual void printRight(const Node &N); + /// If a ParameterPackExpansion (or similar type) is encountered, the offset /// into the pack that we're currently printing. unsigned CurrentPackIndex = std::numeric_limits<unsigned>::max(); diff --git a/lldb/include/lldb/Core/DemangledNameInfo.h b/lldb/include/lldb/Core/DemangledNameInfo.h new file mode 100644 index 0000000000000..8acb25518cdd3 --- /dev/null +++ b/lldb/include/lldb/Core/DemangledNameInfo.h @@ -0,0 +1,151 @@ +//===-- 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 <cstddef> +#include <utility> + +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<int>::someFunc<float>(int) const && + /// ^ ^ + /// start end + /// \endcode + std::pair<size_t, size_t> BasenameRange; + + /// A [start, end) pair for the function scope qualifiers. + /// E.g., for + /// \code{.cpp} + /// void foo::bar<int>::qux<float>(int) const && + /// ^ ^ + /// start end + /// \endcode + std::pair<size_t, size_t> ScopeRange; + + /// Indicates the [start, end) of the function argument lits. + /// E.g., + /// \code{.cpp} + /// int (*getFunc<float>(float, double))(int, int) + /// ^ ^ + /// start end + /// \endcode + std::pair<size_t, size_t> ArgumentsRange; + + /// Returns \c true if this object holds a valid basename range. + bool hasBasename() const { + return BasenameRange.first != BasenameRange.second && + BasenameRange.second > 0; + } + + friend bool operator==(const DemangledNameInfo &lhs, + const DemangledNameInfo &rhs) { + return std::tie(lhs.BasenameRange, lhs.ArgumentsRange, lhs.ScopeRange) == + std::tie(rhs.BasenameRange, rhs.ArgumentsRange, rhs.ScopeRange); + } + + 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<unsigned> + 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 alled to track beginning and end of the function + /// arguments. + void finalizeArgumentEnd(); + void finalizeStart(); + void finalizeEnd(); + + /// 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 51e9ce37e54e7..3ca744f4e047e 100644 --- a/lldb/include/lldb/Core/FormatEntity.h +++ b/lldb/include/lldb/Core/FormatEntity.h @@ -199,6 +199,26 @@ struct Entry { return true; } + /// Describes how a function name should be highlighted. + struct HighlightSettings { + /// ANSI prefix that will be printed before the function name. + std::string prefix; + + /// ANSI suffix that will be printed after the function name. + std::string suffix; + + /// What kind of highlighting the user asked to perform. + enum class Kind : uint8_t { + ///< Don't highlight. + None, + + ///< Highlight function basename + ///< (i.e., name without Scope and + ///< without template arguments). + Basename, + } kind = Kind::None; + }; + std::string string; std::string printf_format; std::vector<Entry> children; @@ -206,6 +226,14 @@ struct Entry { lldb::Format fmt = lldb::eFormatDefault; lldb::addr_t number = 0; bool deref = false; + + /// Set using the highlighting format specifiers to a + /// frame-format variable. + /// E.g., + /// \code + /// ${function.name-with-args:%highlight_basename(ansi.fg.green)} + /// \endcode + HighlightSettings highlight; }; bool Format(const Entry &entry, Stream &s, const SymbolContext *sc, diff --git a/lldb/include/lldb/Core/Mangled.h b/lldb/include/lldb/Core/Mangled.h index 5988d919a89b8..6f4ed741e8c5e 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 <cstddef> @@ -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. /// @@ -275,13 +282,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<DemangledNameInfo> &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<DemangledNameInfo> m_demangled_info = std::nullopt; }; Stream &operator<<(Stream &s, const Mangled &obj); diff --git a/lldb/include/lldb/Symbol/Function.h b/lldb/include/lldb/Symbol/Function.h index 21b3f9ab4a70c..62796b1538d26 100644 --- a/lldb/include/lldb/Symbol/Function.h +++ b/lldb/include/lldb/Symbol/Function.h @@ -11,6 +11,7 @@ #include "lldb/Core/AddressRange.h" #include "lldb/Core/Declaration.h" +#include "lldb/Core/DemangledNameInfo.h" #include "lldb/Core/Mangled.h" #include "lldb/Expression/DWARFExpressionList.h" #include "lldb/Symbol/Block.h" @@ -531,6 +532,9 @@ class Function : public UserID, public SymbolContextScope { ConstString GetDisplayName() const; + /// Retrieve \c DemangledNameInfo of the demangled name held by this object. + const std::optional<DemangledNameInfo> &GetDemangledInfo() const; + const Mangled &GetMangled() const { return m_mangled; } /// Get the DeclContext for this function, if available. diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h index b699a90aff8e4..088aa1a964ec7 100644 --- a/lldb/include/lldb/Target/Language.h +++ b/lldb/include/lldb/Target/Language.h @@ -268,10 +268,11 @@ class Language : public PluginInterface { // the reference has never been assigned virtual bool IsUninitializedReference(ValueObject &valobj); - virtual bool GetFunctionDisplayName(const SymbolContext *sc, - const ExecutionContext *exe_ctx, - FunctionNameRepresentation representation, - Stream &s); + virtual bool + GetFunctionDisplayName(const SymbolContext *sc, + const ExecutionContext *exe_ctx, + FunctionNameRepresentation representation, Stream &s, + const FormatEntity::Entry::HighlightSettings &); virtual ConstString GetDemangledFunctionNameWithoutArguments(Mangled mangled) const { diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index 0a08da0fec230... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/131836 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits