https://github.com/vogelsgesang created https://github.com/llvm/llvm-project/pull/105695
With this commit, we also hide the implementation details of `std::invoke`. To do so, the `LibCXXFrameRecognizer` got a couple more regular expressions. The regular expression passed into the `AddRecognizer` became problematic, as it was evaluated on the demangled name. Those names also included result types for C++ symbols. For `std::__invoke` the return type is a huge `decltype(...)`, making the regular expresison really hard to write. Instead, I added support for `AddRecognizer` to match on the demangled names without result type and argument types. By hiding the implementation details of `invoke`, also the back traces for `std::function` become even nicer, because `std::function` is using `__invoke` internally. >From 18365311d0cee76bced3ba26d1ed08d9f5c6cb28 Mon Sep 17 00:00:00 2001 From: Adrian Vogelsgesang <avogelsges...@salesforce.com> Date: Thu, 22 Aug 2024 10:50:13 +0000 Subject: [PATCH] [lldb-dap] Add frame recognizers for libc++ `std::invoke` With this commit, we also hide the implementation details of `std::invoke`. To do so, the `LibCXXFrameRecognizer` got a couple more regular expressions. The regular expression passed into the `AddRecognizer` became problematic, as it was evaluated on the demangled name. Those names also included result types for C++ symbols. For `std::__invoke` the return type is a huge `decltype(...)`, making the regular expresison really hard to write. Instead, I added support for `AddRecognizer` to match on the demangled names without result type and argument types. By hiding the implementation details of `invoke`, also the back traces for `std::function` become even nicer, because `std::function` is using `__invoke` internally. --- .../lldb/Target/StackFrameRecognizer.h | 9 +++- lldb/source/Commands/Options.td | 2 +- .../CPlusPlus/CPPLanguageRuntime.cpp | 48 ++++++++++++++----- lldb/source/Target/StackFrameRecognizer.cpp | 41 ++++++++++++++-- .../TestStdFunctionRecognizer.py | 21 +++++++- .../lang/cpp/std-invoke-recognizer/Makefile | 5 ++ .../TestStdInvokeRecognizer.py | 28 +++++++++++ .../lang/cpp/std-invoke-recognizer/main.cpp | 40 ++++++++++++++++ 8 files changed, 173 insertions(+), 21 deletions(-) create mode 100644 lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile create mode 100644 lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py create mode 100644 lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h index 8acebc12c4b1dc..585f0b0bb59009 100644 --- a/lldb/include/lldb/Target/StackFrameRecognizer.h +++ b/lldb/include/lldb/Target/StackFrameRecognizer.h @@ -107,12 +107,14 @@ class StackFrameRecognizerManager { public: void AddRecognizer(lldb::StackFrameRecognizerSP recognizer, ConstString module, llvm::ArrayRef<ConstString> symbols, - bool first_instruction_only = true); + bool first_instruction_only = true, + Mangled::NamePreference mangling_preference = Mangled::ePreferDemangled); void AddRecognizer(lldb::StackFrameRecognizerSP recognizer, lldb::RegularExpressionSP module, lldb::RegularExpressionSP symbol, - bool first_instruction_only = true); + bool first_instruction_only = true, + Mangled::NamePreference mangling_preference = Mangled::ePreferDemangled); void ForEach(std::function< void(uint32_t recognizer_id, std::string recognizer_name, @@ -143,10 +145,13 @@ class StackFrameRecognizerManager { std::vector<ConstString> symbols; lldb::RegularExpressionSP symbol_regexp; bool first_instruction_only; + Mangled::NamePreference mangling_preference; }; std::deque<RegisteredEntry> m_recognizers; uint16_t m_generation; + std::unordered_set<Mangled::NamePreference> m_used_manglings; + }; /// \class ValueObjectRecognizerSynthesizedValue diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 9c4dbed6939ba9..df906e9d7c808f 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1049,7 +1049,7 @@ let Command = "thread backtrace" in { def thread_backtrace_extended : Option<"extended", "e">, Group<1>, Arg<"Boolean">, Desc<"Show the extended backtrace, if available">; def thread_backtrace_unfiltered : Option<"unfiltered", "u">, Group<1>, - Desc<"Filter out frames according to installed frame recognizers">; + Desc<"Do not filter out frames according to installed frame recognizers">; } let Command = "thread step scope" in { diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp index c60200ab186d09..3665e1a4c77e55 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include <cstring> +#include <iostream> #include <memory> @@ -44,7 +45,7 @@ char CPPLanguageRuntime::ID = 0; /// A frame recognizer that is installed to hide libc++ implementation /// details from the backtrace. class LibCXXFrameRecognizer : public StackFrameRecognizer { - RegularExpression m_hidden_function_regex; + std::array<RegularExpression, 3> m_hidden_regex; RecognizedStackFrameSP m_hidden_frame; struct LibCXXHiddenFrame : public RecognizedStackFrame { @@ -53,10 +54,32 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer { public: LibCXXFrameRecognizer() - : m_hidden_function_regex( - R"(^std::__1::(__function.*::operator\(\)|__invoke))" - R"((\[.*\])?)" // ABI tag. - R"(( const)?$)"), // const. + : m_hidden_regex{ + // internal implementation details of std::function + // std::__1::__function::__alloc_func<void (*)(), std::__1::allocator<void (*)()>, void ()>::operator()[abi:ne200000] + // std::__1::__function::__func<void (*)(), std::__1::allocator<void (*)()>, void ()>::operator() + // std::__1::__function::__value_func<void ()>::operator()[abi:ne200000]() const + RegularExpression{"" + R"(^std::__[0-9]*::)" // Namespace. + R"(__function::.*::operator\(\))" + R"((\[.*\])?)" // ABI tag. + R"(( const)?$)"}, // const. + // internal implementation details of std::invoke + // std::__1::__invoke[abi:ne200000]<void (*&)()> + RegularExpression{ + R"(^std::__[0-9]*::)" // Namespace. + R"(__invoke)" + R"((\[.*\])?)" // ABI tag. + R"(<.*>)"}, // template argument. + // internal implementation details of std::invoke + // std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne200000]<void (*&)()> + RegularExpression{ + R"(^std::__[0-9]*::)" // Namespace. + R"(__invoke_void_return_wrapper<.*>::__call)" + R"((\[.*\])?)" // ABI tag. + R"(<.*>)"} // template argument. + + }, m_hidden_frame(new LibCXXHiddenFrame()) {} std::string GetName() override { return "libc++ frame recognizer"; } @@ -69,8 +92,9 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer { if (!sc.function) return {}; - if (m_hidden_function_regex.Execute(sc.function->GetNameNoArguments())) - return m_hidden_frame; + for (RegularExpression &r : m_hidden_regex) + if (r.Execute(sc.function->GetNameNoArguments())) + return m_hidden_frame; return {}; } @@ -81,8 +105,9 @@ CPPLanguageRuntime::CPPLanguageRuntime(Process *process) if (process) process->GetTarget().GetFrameRecognizerManager().AddRecognizer( StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {}, - std::make_shared<RegularExpression>("^std::__1::"), - /*first_instruction_only*/ false); + std::make_shared<RegularExpression>("std::__[0-9]*::"), + /*first_instruction_only=*/ false, + /*mangling_preference=*/ Mangled::ePreferDemangledWithoutArguments); } bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) { @@ -108,8 +133,7 @@ bool contains_lambda_identifier(llvm::StringRef &str_ref) { CPPLanguageRuntime::LibCppStdFunctionCallableInfo line_entry_helper(Target &target, const SymbolContext &sc, Symbol *symbol, - llvm::StringRef first_template_param_sref, - bool has_invoke) { + llvm::StringRef first_template_param_sref, bool has_invoke) { CPPLanguageRuntime::LibCppStdFunctionCallableInfo optional_info; @@ -190,7 +214,7 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo( ValueObjectSP sub_member_f_(member_f_->GetChildMemberWithName("__f_")); if (sub_member_f_) - member_f_ = sub_member_f_; + member_f_ = sub_member_f_; } if (!member_f_) diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp index 44411afc65dda9..37901a2ea526a8 100644 --- a/lldb/source/Target/StackFrameRecognizer.cpp +++ b/lldb/source/Target/StackFrameRecognizer.cpp @@ -62,19 +62,24 @@ void StackFrameRecognizerManager::BumpGeneration() { void StackFrameRecognizerManager::AddRecognizer( StackFrameRecognizerSP recognizer, ConstString module, - llvm::ArrayRef<ConstString> symbols, bool first_instruction_only) { + llvm::ArrayRef<ConstString> symbols, bool first_instruction_only, + Mangled::NamePreference mangling_preference) { m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, false, module, RegularExpressionSP(), symbols, RegularExpressionSP(), first_instruction_only}); + m_used_manglings.insert(mangling_preference); BumpGeneration(); } void StackFrameRecognizerManager::AddRecognizer( StackFrameRecognizerSP recognizer, RegularExpressionSP module, - RegularExpressionSP symbol, bool first_instruction_only) { + RegularExpressionSP symbol, bool first_instruction_only, + Mangled::NamePreference mangling_preference) { m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, true, ConstString(), module, std::vector<ConstString>(), - symbol, first_instruction_only}); + symbol, first_instruction_only, + mangling_preference}); + m_used_manglings.insert(mangling_preference); BumpGeneration(); } @@ -119,13 +124,30 @@ bool StackFrameRecognizerManager::RemoveRecognizerWithID( void StackFrameRecognizerManager::RemoveAllRecognizers() { BumpGeneration(); m_recognizers.clear(); + m_used_manglings.clear(); } StackFrameRecognizerSP StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) { const SymbolContext &symctx = frame->GetSymbolContext( eSymbolContextModule | eSymbolContextFunction | eSymbolContextSymbol); - ConstString function_name = symctx.GetFunctionName(); + ConstString function_name_mangled; + ConstString function_name_demangled; + ConstString function_name_noargs; + for (Mangled::NamePreference m : m_used_manglings) { + switch (m) { + case Mangled::ePreferMangled: + function_name_mangled = symctx.GetFunctionName(m); + break; + case Mangled::ePreferDemangled: + function_name_demangled = symctx.GetFunctionName(m); + break; + case Mangled::ePreferDemangledWithoutArguments: + function_name_noargs = symctx.GetFunctionName(m); + break; + } + } + ModuleSP module_sp = symctx.module_sp; if (!module_sp) return StackFrameRecognizerSP(); @@ -145,6 +167,17 @@ StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) { if (!entry.module_regexp->Execute(module_name.GetStringRef())) continue; + ConstString function_name = [&]() { + switch (entry.mangling_preference) { + case Mangled::ePreferMangled: + return function_name_mangled; + case Mangled::ePreferDemangled: + return function_name_demangled; + case Mangled::ePreferDemangledWithoutArguments: + return function_name_noargs; + } + }(); + if (!entry.symbols.empty()) if (!llvm::is_contained(entry.symbols, function_name)) continue; diff --git a/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py index 30fe3ecb1e4bf4..28004194dee07c 100644 --- a/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py +++ b/lldb/test/API/lang/cpp/std-function-recognizer/TestStdFunctionRecognizer.py @@ -7,6 +7,23 @@ class LibCxxStdFunctionRecognizerTestCase(TestBase): NO_DEBUG_INFO_TESTCASE = True + @add_test_categories(["libc++"]) + def test_frame_recognizer(self): + """Test that std::function all implementation details are hidden in SBFrame""" + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + self.assertIn("foo", thread.GetFrameAtIndex(0).GetFunctionName()) + # Skip all hidden frames + frame_id = 1 + while frame_id < thread.GetNumFrames() and thread.GetFrameAtIndex(frame_id).IsHidden(): + frame_id = frame_id + 1 + # Expect `std::function<...>::operator()` to be the direct parent of `foo` + self.assertIn("::operator()", thread.GetFrameAtIndex(frame_id).GetFunctionName()) + # And right above that, there should be the `main` frame + self.assertIn("main", thread.GetFrameAtIndex(frame_id + 1).GetFunctionName()) + @add_test_categories(["libc++"]) def test_backtrace(self): """Test that std::function implementation details are hidden in bt""" @@ -27,12 +44,12 @@ def test_backtrace(self): self.expect( "thread backtrace -u", ordered=True, - patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"], + patterns=["frame.*foo", "frame.*std::__[0-9]*::__function", "frame.*main"], ) self.expect( "thread backtrace --unfiltered", ordered=True, - patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"], + patterns=["frame.*foo", "frame.*std::__[0-9]*::__function", "frame.*main"], ) @add_test_categories(["libc++"]) diff --git a/lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile b/lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile new file mode 100644 index 00000000000000..69014eb9c0f2eb --- /dev/null +++ b/lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile @@ -0,0 +1,5 @@ +CXX_SOURCES := main.cpp +USE_LIBCPP := 1 +CXXFLAGS_EXTRAS := -std=c++17 + +include Makefile.rules diff --git a/lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py b/lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py new file mode 100644 index 00000000000000..f2f8c506630eac --- /dev/null +++ b/lldb/test/API/lang/cpp/std-invoke-recognizer/TestStdInvokeRecognizer.py @@ -0,0 +1,28 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class LibCxxStdFunctionRecognizerTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + @add_test_categories(["libc++"]) + def test_frame_recognizer(self): + """Test that implementation details details of `std::invoke`""" + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + + while process.GetState() != lldb.eStateExited: + self.assertIn("print_num", thread.GetFrameAtIndex(0).GetFunctionName()) + self.process.Continue() + # # Skip all hidden frames + # frame_id = 1 + # while frame_id < thread.GetNumFrames() and thread.GetFrameAtIndex(frame_id).IsHidden(): + # frame_id = frame_id + 1 + # # Expect `std::function<...>::operator()` to be the direct parent of `foo` + # self.assertIn("::operator()", thread.GetFrameAtIndex(frame_id).GetFunctionName()) + # # And right above that, there should be the `main` frame + # self.assertIn("main", thread.GetFrameAtIndex(frame_id + 1).GetFunctionName()) diff --git a/lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp b/lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp new file mode 100644 index 00000000000000..78497d2938fe8a --- /dev/null +++ b/lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp @@ -0,0 +1,40 @@ +#include <functional> +#include <iostream> + +void print_num(int i) { + // break here + std::cout << i << '\n'; +} + +int add(int i, int j) { + // break here + return i + j; +} + +struct PrintAdder { + PrintAdder(int num) : num_(num) {} + void operator()(int i) const { + // break here + std::cout << i << '\n'; + } + void print_add(int i) const { + // break here + std::cout << num_ + i << '\n'; + } + int num_; +}; + +int main() { + // Invoke a void-returning function + std::invoke(print_num, -9); + + // Invoke a non-void-returning function + std::cout << std::invoke(add, 1, 10) << '\n'; + + // Invoke a member function + const PrintAdder foo(314159); + std::invoke(&PrintAdder::print_add, foo, 1); + + // Invoke a function object + std::invoke(PrintAdder(12), 18); +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits