diff --git a/clang/include/clang/CodeGen/ModuleBuilder.h b/clang/include/clang/CodeGen/ModuleBuilder.h index 59b9840d02e08..393f9f17551d5 100644 --- a/clang/include/clang/CodeGen/ModuleBuilder.h +++ b/clang/include/clang/CodeGen/ModuleBuilder.h @@ -114,6 +114,23 @@ CodeGenerator *CreateLLVMCodeGen(DiagnosticsEngine &Diags, llvm::LLVMContext &C, CoverageSourceInfo *CoverageInfo = nullptr); +namespace CodeGen { +/// Demangle the artificial function name (\param FuncName) used to encode trap +/// reasons used in debug info for traps (e.g. __builtin_verbose_trap). See +/// `CGDebugInfo::CreateTrapFailureMessageFor`. +/// +/// \param FuncName - The function name to demangle. +/// +/// \return A std::optional. If demangling succeeds the optional will contain +/// a pair of StringRefs where the first field is the trap category and the +/// second is the trap message. These can both be empty. If demangling fails the +/// optional will not contain a value. Note the returned StringRefs if non-empty +/// point into the underlying storage for \param FuncName and thus have the same +/// lifetime. +std::optional> +DemangleTrapReasonInDebugInfo(StringRef FuncName); +} // namespace CodeGen + } // end namespace clang #endif diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp b/clang/lib/CodeGen/ModuleBuilder.cpp index 971c81d9cbc25..ec36a440ce978 100644 --- a/clang/lib/CodeGen/ModuleBuilder.cpp +++ b/clang/lib/CodeGen/ModuleBuilder.cpp @@ -23,6 +23,7 @@ #include "llvm/IR/DataLayout.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/VirtualFileSystem.h" #include @@ -376,3 +377,31 @@ clang::CreateLLVMCodeGen(DiagnosticsEngine &Diags, llvm::StringRef ModuleName, HeaderSearchOpts, PreprocessorOpts, CGO, C, CoverageInfo); } + +namespace clang { +namespace CodeGen { +std::optional> +DemangleTrapReasonInDebugInfo(StringRef FuncName) { + static auto TrapRegex = + llvm::Regex(llvm::formatv("^{0}\\$(.*)\\$(.*)$", ClangTrapPrefix).str()); + llvm::SmallVector Matches; + std::string *ErrorPtr = nullptr; +#ifndef NDEBUG + std::string Error; + ErrorPtr = &Error; +#endif + if (!TrapRegex.match(FuncName, &Matches, ErrorPtr)) { + assert(ErrorPtr && ErrorPtr->empty() && "Invalid regex pattern"); + return {}; + } + + if (Matches.size() != 3) { + assert(0 && "Expected 3 matches from Regex::match"); + return {}; + } + + // Returns { Trap Category, Trap Message } + return std::make_pair(Matches[1], Matches[2]); +} +} // namespace CodeGen +} // namespace clang diff --git a/clang/unittests/CodeGen/CMakeLists.txt b/clang/unittests/CodeGen/CMakeLists.txt index f5bcecb0b08a3..d4efb2230a054 100644 --- a/clang/unittests/CodeGen/CMakeLists.txt +++ b/clang/unittests/CodeGen/CMakeLists.txt @@ -1,6 +1,7 @@ add_clang_unittest(ClangCodeGenTests BufferSourceTest.cpp CodeGenExternalTest.cpp + DemangleTrapReasonInDebugInfo.cpp TBAAMetadataTest.cpp CheckTargetFeaturesTest.cpp CLANG_LIBS diff --git a/clang/unittests/CodeGen/DemangleTrapReasonInDebugInfo.cpp b/clang/unittests/CodeGen/DemangleTrapReasonInDebugInfo.cpp new file mode 100644 index 0000000000000..17bfe17c31d65 --- /dev/null +++ b/clang/unittests/CodeGen/DemangleTrapReasonInDebugInfo.cpp @@ -0,0 +1,67 @@ +//=== unittests/CodeGen/DemangleTrapReasonInDebugInfo.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 "clang/CodeGen/ModuleBuilder.h" +#include "llvm/ADT/StringRef.h" +#include "gtest/gtest.h" + +using namespace clang::CodeGen; + +void CheckValidCommon(llvm::StringRef FuncName, const char *ExpectedCategory, + const char *ExpectedMessage) { + auto MaybeTrapReason = DemangleTrapReasonInDebugInfo(FuncName); + ASSERT_TRUE(MaybeTrapReason.has_value()); + auto [Category, Message] = MaybeTrapReason.value(); + ASSERT_STREQ(Category.str().c_str(), ExpectedCategory); + ASSERT_STREQ(Message.str().c_str(), ExpectedMessage); +} + +void CheckInvalidCommon(llvm::StringRef FuncName) { + auto MaybeTrapReason = DemangleTrapReasonInDebugInfo(FuncName); + ASSERT_TRUE(!MaybeTrapReason.has_value()); +} + +TEST(DemangleTrapReasonInDebugInfo, Valid) { + std::string FuncName(ClangTrapPrefix); + FuncName += "$trap category$trap message"; + CheckValidCommon(FuncName, "trap category", "trap message"); +} + +TEST(DemangleTrapReasonInDebugInfo, ValidEmptyCategory) { + std::string FuncName(ClangTrapPrefix); + FuncName += "$$trap message"; + CheckValidCommon(FuncName, "", "trap message"); +} + +TEST(DemangleTrapReasonInDebugInfo, ValidEmptyMessage) { + std::string FuncName(ClangTrapPrefix); + FuncName += "$trap category$"; + CheckValidCommon(FuncName, "trap category", ""); +} + +TEST(DemangleTrapReasonInDebugInfo, ValidAllEmpty) { + // `__builtin_verbose_trap` actually allows this + // currently. However, we should probably disallow this in Sema because having + // an empty category and message completely defeats the point of using the + // builtin (#165981). + std::string FuncName(ClangTrapPrefix); + FuncName += "$$"; + CheckValidCommon(FuncName, "", ""); +} + +TEST(DemangleTrapReasonInDebugInfo, InvalidOnlyPrefix) { + std::string FuncName(ClangTrapPrefix); + CheckInvalidCommon(FuncName); +} + +TEST(DemangleTrapReasonInDebugInfo, Invalid) { + std::string FuncName("foo"); + CheckInvalidCommon(FuncName); +} + +TEST(DemangleTrapReasonInDebugInfo, InvalidEmpty) { CheckInvalidCommon(""); } diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt index a27bceffe2e3a..727c8290bceb4 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt @@ -6,6 +6,8 @@ add_lldb_library(lldbPluginCPPRuntime lldbCore lldbSymbol lldbTarget + CLANG_LIBS + clangCodeGen ) add_subdirectory(ItaniumABI) diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp index 41bf10bbf42d7..08b7734d2a1e2 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp @@ -101,33 +101,14 @@ VerboseTrapFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) { if (func_name.empty()) return {}; - static auto trap_regex = - llvm::Regex(llvm::formatv("^{0}\\$(.*)\\$(.*)$", ClangTrapPrefix).str()); - SmallVector matches; - std::string regex_err_msg; - if (!trap_regex.match(func_name, &matches, ®ex_err_msg)) { - LLDB_LOGF(GetLog(LLDBLog::Unwind), - "Failed to parse match trap regex for '%s': %s", func_name.data(), - regex_err_msg.c_str()); - - return {}; - } - - // For `__clang_trap_msg$category$message$` we expect 3 matches: - // 1. entire string - // 2. category - // 3. message - if (matches.size() != 3) { - LLDB_LOGF(GetLog(LLDBLog::Unwind), - "Unexpected function name format. Expected '$$'$ but got: '%s'.", - func_name.data()); - + auto maybe_trap_reason = + clang::CodeGen::DemangleTrapReasonInDebugInfo(func_name); + if (!maybe_trap_reason.has_value()) { + LLDB_LOGF(GetLog(LLDBLog::Unwind), "Failed to demangle '%s' as trap reason", + func_name.str().c_str()); return {}; } - - auto category = matches[1]; - auto message = matches[2]; + auto [category, message] = maybe_trap_reason.value(); std::string stop_reason = category.empty() ? "" : category.str();