Skip to content

[Syntax Highlighting] Add name and parameters syntax highlighting in Swift backtraces #10710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: swift/release/6.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions lldb/source/Core/Mangled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,41 @@ void Mangled::SetValue(ConstString name) {
}
}

// BEGIN SWIFT
#ifdef LLDB_ENABLE_SWIFT
static ConstString GetSwiftDemangledStr(ConstString m_mangled,
const SymbolContext *sc,
ConstString &m_demangled) {
const char *mangled_name = m_mangled.GetCString();
Log *log = GetLog(LLDBLog::Demangle);
LLDB_LOGF(log, "demangle swift: %s", mangled_name);
auto [demangled, info] = SwiftLanguageRuntime::TrackedDemangleSymbolAsString(
mangled_name, SwiftLanguageRuntime::eSimplified, sc);
info.PrefixRange.second =
std::min(info.BasenameRange.first, info.ArgumentsRange.first);
info.SuffixRange.first =
std::max(info.BasenameRange.second, info.ArgumentsRange.second);
info.SuffixRange.second = demangled.length();

// Don't cache the demangled name if the function isn't available yet.
if (!sc || !sc->function) {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\" (not cached)", mangled_name,
demangled.c_str());
return ConstString(demangled);
}
if (demangled.empty()) {
LLDB_LOGF(log, "demangle swift: %s -> error: failed to demangle",
mangled_name);
} else {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\"", mangled_name,
demangled.c_str());
m_demangled.SetStringWithMangledCounterpart(demangled, m_mangled);
}
return m_demangled;
}
#endif // LLDB_ENABLE_SWIFT
// END SWIFT

// Local helpers for different demangling implementations.
static char *GetMSVCDemangledStr(llvm::StringRef M) {
char *demangled_cstr = llvm::microsoftDemangle(
Expand Down Expand Up @@ -349,28 +384,7 @@ ConstString Mangled::GetDemangledNameImpl(bool force, // BEGIN SWIFT
// Demangling a swift name requires the swift compiler. This is
// explicitly unsupported on llvm.org.
#ifdef LLDB_ENABLE_SWIFT
{
const char *mangled_name = m_mangled.GetCString();
Log *log = GetLog(LLDBLog::Demangle);
LLDB_LOGF(log, "demangle swift: %s", mangled_name);
std::string demangled(SwiftLanguageRuntime::DemangleSymbolAsString(
mangled_name, SwiftLanguageRuntime::eTypeName, sc));
// Don't cache the demangled name the function isn't available yet.
if (!sc || !sc->function) {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\" (not cached)", mangled_name,
demangled.c_str());
return ConstString(demangled);
}
if (demangled.empty()) {
LLDB_LOGF(log, "demangle swift: %s -> error: failed to demangle",
mangled_name);
} else {
LLDB_LOGF(log, "demangle swift: %s -> \"%s\"", mangled_name,
demangled.c_str());
m_demangled.SetStringWithMangledCounterpart(demangled, m_mangled);
}
return m_demangled;
}
return GetSwiftDemangledStr(m_mangled, sc, m_demangled);
#endif // LLDB_ENABLE_SWIFT
break;
case eManglingSchemeNone:
Expand Down
83 changes: 79 additions & 4 deletions lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1896,21 +1896,96 @@ SwiftLanguage::GetDemangledFunctionNameWithoutArguments(Mangled mangled) const {

static std::optional<llvm::StringRef>
Copy link

@adrian-prantl adrian-prantl Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an optional StringRef is redundant unless there is a semantic difference between std::nullopt and an empty string.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just return a StringRef here

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I did this in the upstream C++ plugin too. I did this because this way we can differentiate between "failed to extract this demangling info" versus "this part of the demangled string was not present, but nothing failed". In the failure case we return false from the frame-format variable handler, which is necessary to trigger the fallback format in case things went wrong.

I guess instead of optional I should've probably used Expected and logged an error. Might be a good improvement for upstream actually if @charles-zablit has time on his hands :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetDemangledBasename(const SymbolContext &sc) {
return std::nullopt;
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<DemangledNameInfo> &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<llvm::StringRef>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here...

Copy link
Author

@charles-zablit charles-zablit Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will port the changes of the following PR once it's merged:

GetDemangledFunctionPrefix(const SymbolContext &sc) {
return std::nullopt;
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<DemangledNameInfo> &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->PrefixRange.first,
info->PrefixRange.second);
}

static std::optional<llvm::StringRef>
GetDemangledFunctionSuffix(const SymbolContext &sc) {
return std::nullopt;
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<DemangledNameInfo> &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->SuffixRange.first,
info->SuffixRange.second);
}

static bool PrintDemangledArgumentList(Stream &s, const SymbolContext &sc) {
return false;
assert(sc.symbol);

Mangled mangled = sc.GetPossiblyInlinedFunctionName();
if (!mangled)
return false;

auto demangled_name = mangled.GetDemangledName().GetStringRef();
if (demangled_name.empty())
return false;

const std::optional<DemangledNameInfo> &info = mangled.GetDemangledInfo();
if (!info)
return false;

// Function without a basename is nonsense.
if (!info->hasBasename())
return false;

if (info->ArgumentsRange.second < info->ArgumentsRange.first)
return false;

s << demangled_name.slice(info->ArgumentsRange.first,
info->ArgumentsRange.second);

return true;
}

static VariableListSP GetFunctionVariableList(const SymbolContext &sc) {
Expand Down
94 changes: 94 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftMangled.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//===-- SwiftMangled.h ------------------------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_SwiftMangled_h_
#define liblldb_SwiftMangled_h_

#include "lldb/Core/DemangledNameInfo.h"
#include "swift/Demangling/Demangle.h"

using namespace swift::Demangle;

/// A NodePrinter class with range tracking capabilities.
///
/// When used instead of a regular NodePrinter, this class will store additional
/// range information of the demangled name in the `info` attribute, such as the
/// range of the name of a method.
class TrackingNodePrinter : public NodePrinter {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a oxygen comment here explaining very briefly what this class is used for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 👍

public:
TrackingNodePrinter(DemangleOptions options) : NodePrinter(options) {}

lldb_private::DemangledNameInfo takeInfo() { return std::move(info); }

private:
lldb_private::DemangledNameInfo info;
std::optional<unsigned> parametersDepth;

void startName() {
if (!info.hasBasename())
info.BasenameRange.first = getStreamLength();
}

void endName() {
if (!info.hasBasename())
info.BasenameRange.second = getStreamLength();
}

void startParameters(unsigned depth) {
if (parametersDepth || !info.hasBasename() || info.hasArguments()) {
return;
}
info.ArgumentsRange.first = getStreamLength();
parametersDepth = depth;
}

void endParameters(unsigned depth) {
if (!parametersDepth || *parametersDepth != depth || info.hasArguments()) {
return;
}
info.ArgumentsRange.second = getStreamLength();
}

bool shouldTrackNameRange(NodePointer Node) const {
switch (Node->getKind()) {
case Node::Kind::Function:
case Node::Kind::Constructor:
case Node::Kind::Allocator:
case Node::Kind::ExplicitClosure:
return true;
default:
return false;
}
}

void printFunctionName(bool hasName, llvm::StringRef &OverwriteName,
llvm::StringRef &ExtraName, bool MultiWordName,
int &ExtraIndex, NodePointer Entity,
unsigned int depth) override {
if (shouldTrackNameRange(Entity))
startName();
NodePrinter::printFunctionName(hasName, OverwriteName, ExtraName,
MultiWordName, ExtraIndex, Entity, depth);
if (shouldTrackNameRange(Entity))
endName();
}

void printFunctionParameters(NodePointer LabelList, NodePointer ParameterType,
unsigned depth, bool showTypes) override {
startParameters(depth);
NodePrinter::printFunctionParameters(LabelList, ParameterType, depth,
showTypes);
endParameters(depth);
}
};

#endif // liblldb_SwiftMangled_h_
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,33 @@ class SwiftLanguageRuntime : public LanguageRuntime {
IsSwiftAsyncAwaitResumePartialFunctionSymbol(llvm::StringRef name);

enum DemangleMode { eSimplified, eTypeName, eDisplayTypeName };

/// Demangle a symbol to a string.
///
/// \param symbol The mangled symbol to demangle.
/// \param mode The `DemangleMode` to use when demangling.
/// \param sc The associated `SymbolContext`.
/// \param exe_ctx The associated `ExecutionContext`.
///
/// \return The demangled symbol.
static std::string
DemangleSymbolAsString(llvm::StringRef symbol, DemangleMode mode,
const SymbolContext *sc = nullptr,
const ExecutionContext *exe_ctx = nullptr);

/// Demangle a symbol to a string with additional range information.
///
/// \param symbol The mangled symbol to demangle.
/// \param mode The `DemangleMode` to use when demangling.
/// \param sc The associated `SymbolContext`.
/// \param exe_ctx The associated `ExecutionContext`.
///
/// \return The demangled symbol as well as range tracking information.
static std::pair<std::string, DemangledNameInfo>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doxygen comment?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 👍

TrackedDemangleSymbolAsString(llvm::StringRef symbol, DemangleMode mode,
const SymbolContext *sc = nullptr,
const ExecutionContext *exe_ctx = nullptr);

static std::string GetParentNameIfClosure(llvm::StringRef mangled_name);

/// Demangle a symbol to a swift::Demangle node tree.
Expand Down Expand Up @@ -885,6 +907,11 @@ class SwiftLanguageRuntime : public LanguageRuntime {

/// Swift native NSError isa.
std::optional<lldb::addr_t> m_SwiftNativeNSErrorISA;

static std::pair<std::string, std::optional<DemangledNameInfo>>
DemangleSymbolAsString(llvm::StringRef symbol, DemangleMode mode,
bool tracking, const SymbolContext *sc,
const ExecutionContext *exe_ctx);
};

/// The target specific register numbers used for async unwinding.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "SwiftLanguageRuntime.h"

#include "lldb/Breakpoint/StoppointCallbackContext.h"
#include "lldb/Core/DemangledNameInfo.h"
#include "lldb/Symbol/Block.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/VariableList.h"
Expand All @@ -28,6 +29,7 @@
#include "swift/Demangling/Demangle.h"
#include "swift/Demangling/Demangler.h"

#include "Plugins/Language/Swift/SwiftMangled.h"
#include "Plugins/Process/Utility/RegisterContext_x86.h"
#include "Utility/ARM64_DWARF_Registers.h"
#include "swift/Demangling/ManglingFlavor.h"
Expand Down Expand Up @@ -816,9 +818,11 @@ void SwiftLanguageRuntime::GetGenericParameterNamesForFunction(
}
}

std::string SwiftLanguageRuntime::DemangleSymbolAsString(
llvm::StringRef symbol, DemangleMode mode, const SymbolContext *sc,
const ExecutionContext *exe_ctx) {
std::pair<std::string, std::optional<DemangledNameInfo>>
SwiftLanguageRuntime::DemangleSymbolAsString(llvm::StringRef symbol,
DemangleMode mode, bool tracking,
const SymbolContext *sc,
const ExecutionContext *exe_ctx) {
bool did_init = false;
llvm::DenseMap<ArchetypePath, llvm::StringRef> dict;
swift::Demangle::DemangleOptions options;
Expand Down Expand Up @@ -863,7 +867,7 @@ std::string SwiftLanguageRuntime::DemangleSymbolAsString(
return swift::Demangle::genericParameterName(depth, index);
};
} else {
// Print generic generic parameter names.
// Print generic parameter names.
options.GenericParameterName = [&](uint64_t depth, uint64_t index) {
std::string name;
{
Expand All @@ -873,7 +877,30 @@ std::string SwiftLanguageRuntime::DemangleSymbolAsString(
return name;
};
}
return swift::Demangle::demangleSymbolAsString(symbol, options);
if (tracking) {
TrackingNodePrinter printer = TrackingNodePrinter(options);
swift::Demangle::Context ctx;
ctx.demangleSymbolAsString(symbol, printer);
return std::pair<std::string, std::optional<DemangledNameInfo>>(
printer.takeString(), printer.takeInfo());
}
return std::pair<std::string, std::optional<DemangledNameInfo>>(
swift::Demangle::demangleSymbolAsString(symbol, options), std::nullopt);
}

std::string SwiftLanguageRuntime::DemangleSymbolAsString(
llvm::StringRef symbol, DemangleMode mode, const SymbolContext *sc,
const ExecutionContext *exe_ctx) {
return DemangleSymbolAsString(symbol, mode, false, sc, exe_ctx).first;
}

std::pair<std::string, DemangledNameInfo>
SwiftLanguageRuntime::TrackedDemangleSymbolAsString(
llvm::StringRef symbol, DemangleMode mode, const SymbolContext *sc,
const ExecutionContext *exe_ctx) {
auto demangledData = DemangleSymbolAsString(symbol, mode, true, sc, exe_ctx);
return std::pair<std::string, DemangledNameInfo>(demangledData.first,
*demangledData.second);
}

swift::Demangle::NodePointer
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_lldb_unittest(LLDBCoreTests
RichManglingContextTest.cpp
SourceLocationSpecTest.cpp
SourceManagerTest.cpp
SwiftDemanglingPartsTest.cpp
UniqueCStringMapTest.cpp

LINK_LIBS
Expand Down
Loading