Skip to content
Draft
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
28 changes: 4 additions & 24 deletions include/mangle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@

namespace ctrace_tools
{
/**
* @brief Concept to define types that can be converted to `std::string_view`.
*
* The `StringLike` concept ensures that any type passed to functions
* requiring it can be implicitly converted to `std::string_view`.
*/
template <typename T> concept StringLike = std::convertible_to<T, std::string_view>;
// TODO: add mangling for windows

/**
Expand All @@ -23,32 +16,19 @@ namespace ctrace_tools
* This function determines whether a given name follows the Itanium C++ ABI
* mangling conventions (e.g., names starting with `_Z`).
*
* @tparam T A type satisfying the `StringLike` concept.
* @param name The name to check for mangling.
* @return `true` if the name is mangled, `false` otherwise.
*
* @note This function uses `abi::__cxa_demangle` to attempt demangling.
* If the demangling succeeds, the name is considered mangled.
* @note The name is mangled if a demangler reads it: the C++ runtime's
* (`abi::__cxa_demangle`), or LLVM's when the runtime's predates the
* name's mangling, as a C++23 explicit object member's on macOS 14.
* @note This implementation is specific to platforms using the Itanium C++ ABI
* (e.g., Linux, macOS). Windows mangling is not yet supported.
* @note The function is marked `[[nodiscard]]`, meaning the return value
* should not be ignored. It is also `noexcept`, indicating that it
* does not throw exceptions.
*/
[[nodiscard]] bool isMangled(StringLike auto name) noexcept
{
int status = 0;
std::string_view sv{name};

if (sv.length() < 2 || sv.substr(0, 2) != "_Z")
{
return false;
}

std::unique_ptr<char, void (*)(void*)> demangled(
abi::__cxa_demangle(sv.data(), nullptr, nullptr, &status), std::free);
return status == 0;
}
[[nodiscard]] bool isMangled(std::string_view name) noexcept;

/**
* @brief Generates a mangled name for a function.
Expand Down
31 changes: 23 additions & 8 deletions src/mangle.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
// SPDX-License-Identifier: Apache-2.0
#include "mangle.hpp"

#include <llvm/Demangle/Demangle.h>

namespace ctrace_tools
{
namespace
{
// A symbol, demangled by the C++ runtime's demangler, or by LLVM's when the runtime's
// predates the symbol's mangling (#240); empty when neither reads it.
std::string demangled(const std::string& name)
{
int status = 0;
std::unique_ptr<char, void (*)(void*)> out(
abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status), std::free);
if (status != 0 || !out)
out.reset(llvm::itaniumDemangle(name));
return out ? out.get() : "";
}
} // namespace

bool isMangled(std::string_view name) noexcept
{
return name.starts_with("_Z") && !demangled(std::string(name)).empty();
}

std::string mangleFunction(const std::string& namespaceName, const std::string& functionName,
const std::vector<std::string>& paramTypes)
Expand Down Expand Up @@ -71,14 +92,8 @@ namespace ctrace_tools

std::string demangle(const char* name)
{
int status = 0;
char* demangled = abi::__cxa_demangle(name, nullptr, nullptr, &status);

std::string result = (status == 0 && demangled) ? demangled : name;

free(demangled);

return result;
std::string result = demangled(name);
return result.empty() ? name : result;
}

std::string canonicalizeMangledName(std::string_view name)
Expand Down
28 changes: 28 additions & 0 deletions test/unit/analyzer_module_unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "analyzer/DiagnosticEmitter.hpp"
#include "analyzer/LocationResolver.hpp"
#include "analyzer/ModulePreparationService.hpp"
#include "mangle.hpp"

#include <algorithm>
#include <cstdio>
Expand Down Expand Up @@ -2596,6 +2597,32 @@ namespace
return report.failures == 0;
}

/// A symbol is recognized as mangled and shown demangled when a demangler reads it, whatever
/// the C++ runtime: one that predates a mangling, such as a C++23 explicit object member's
/// (macOS 14, GCC 11), leaves it to LLVM's (#240). A name that neither reads stays as it is.
bool testSymbolDemangling(TestReport& report)
{
using ctrace::stack::analysis::formatFunctionNameForMessage;
const std::string explicitObject = "_ZNH6Reader4peekERKS_Pi";
const std::string shown = "Reader::peek(this Reader const&, int*)";
report.expect(ctrace_tools::isMangled(explicitObject),
"Demangling: an explicit object member's symbol is a mangled name");
report.expect(ctrace_tools::demangle(explicitObject.c_str()) == shown,
"Demangling: an explicit object member's symbol is shown demangled");
report.expect(formatFunctionNameForMessage(explicitObject) == shown,
"Demangling: a message names an explicit object member demangled");
report.expect(ctrace_tools::isMangled("_ZN6Reader4peekEPi") &&
ctrace_tools::demangle("_ZN6Reader4peekEPi") == "Reader::peek(int*)",
"Demangling: a member's symbol is recognized and shown demangled");
report.expect(!ctrace_tools::isMangled("read_value") &&
formatFunctionNameForMessage("read_value") == "read_value",
"Demangling: a C function's name is no mangled name");
report.expect(!ctrace_tools::isMangled("_Z_not_a_symbol") &&
ctrace_tools::demangle("_Z_not_a_symbol") == "_Z_not_a_symbol",
"Demangling: a name that no demangler reads stays as it is");
return report.failures == 0;
}

/// resolveParameterDebugBinding: each parameter gets its own type, and its position among the
/// parameters the source declares. The others are artificial: `this`, the vtt that the
/// base-object constructor (C2) of a class with a virtual base takes, is_most_derived under the
Expand Down Expand Up @@ -2928,6 +2955,7 @@ int main(int argc, char** argv)
(void)testOwnershipCollectorExceptions(repoRoot, report);
(void)testOwnershipSummaryIndex(repoRoot, report);
(void)testCompileIRCacheWithoutCompilerRevision(report);
(void)testSymbolDemangling(report);

if (report.failures == 0)
{
Expand Down
Loading