diff --git a/include/mangle.hpp b/include/mangle.hpp index bb17b2e..de229d1 100644 --- a/include/mangle.hpp +++ b/include/mangle.hpp @@ -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 concept StringLike = std::convertible_to; // TODO: add mangling for windows /** @@ -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 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. diff --git a/src/mangle.cpp b/src/mangle.cpp index bed2557..459ae7c 100644 --- a/src/mangle.cpp +++ b/src/mangle.cpp @@ -1,8 +1,29 @@ // SPDX-License-Identifier: Apache-2.0 #include "mangle.hpp" +#include + 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 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& paramTypes) @@ -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) diff --git a/test/unit/analyzer_module_unit_tests.cpp b/test/unit/analyzer_module_unit_tests.cpp index 698b961..67a4d61 100644 --- a/test/unit/analyzer_module_unit_tests.cpp +++ b/test/unit/analyzer_module_unit_tests.cpp @@ -21,6 +21,7 @@ #include "analyzer/DiagnosticEmitter.hpp" #include "analyzer/LocationResolver.hpp" #include "analyzer/ModulePreparationService.hpp" +#include "mangle.hpp" #include #include @@ -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 @@ -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) {