Index: packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/function/TestLibCxxFunction.py =================================================================== --- packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/function/TestLibCxxFunction.py +++ packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/function/TestLibCxxFunction.py @@ -40,13 +40,17 @@ substrs=['stopped', 'stop reason = breakpoint']) - f1 = self.get_variable('f1') - f2 = self.get_variable('f2') + self.expect("frame variable f1", + substrs=['f1 = Function = foo(int, int)']) - if self.TraceOn(): - print(f1) - if self.TraceOn(): - print(f2) + self.expect("frame variable f2", + substrs=['f2 = Lambda in File main.cpp at Line 27']) - self.assertTrue(f1.GetValueAsUnsigned(0) != 0, 'f1 has a valid value') - self.assertTrue(f2.GetValueAsUnsigned(0) != 0, 'f2 has a valid value') + self.expect("frame variable f3", + substrs=['f3 = Lambda in File main.cpp at Line 31']) + + self.expect("frame variable f4", + substrs=['f4 = Function in File main.cpp at Line 17']) + + self.expect("frame variable f5", + substrs=['f5 = Function = Bar::add_num(int) const']) Index: packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/function/main.cpp =================================================================== --- packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/function/main.cpp +++ packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/function/main.cpp @@ -13,13 +13,28 @@ return x + y - 1; } -int main () +struct Bar { + int operator()() { + return 66 ; + } + int add_num(int i) const { return i + 3 ; } +} ; + +int main (int argc, char *argv[]) { int acc = 42; std::function f1 = foo; std::function f2 = [acc,f1] (int x) -> int { return x+f1(acc,x); }; - return f1(acc,acc) + f2(acc); // Set break point at this line. -} + auto f = [](int x, int y) { return x + y; }; + auto g = [](int x, int y) { return x * y; } ; + std::function f3 = argc %2 ? f : g ; + + Bar bar1 ; + std::function f4( bar1 ) ; + std::function f5 = &Bar::add_num; + + return f1(acc,acc) + f2(acc) + f3(acc+1,acc+2) + f4() + f5(bar1, 10); // Set break point at this line. +} Index: source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp =================================================================== --- source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -519,6 +519,11 @@ ConstString("^(std::__(ndk)?1::)weak_ptr<.+>(( )?&)?$"), stl_synth_flags, true); + AddCXXSummary( + cpp_category_sp, lldb_private::formatters::LibcxxFunctionSummaryProvider, + "libc++ std::function summary provider", + ConstString("^std::__(ndk)?1::function<.+>$"), stl_summary_flags, true); + stl_summary_flags.SetDontShowChildren(false); stl_summary_flags.SetSkipPointers(false); AddCXXSummary(cpp_category_sp, @@ -610,11 +615,6 @@ "std::map iterator synthetic children", ConstString("^std::__(ndk)?1::__map_iterator<.+>$"), stl_synth_flags, true); - - AddCXXSynthetic( - cpp_category_sp, lldb_private::formatters::LibcxxFunctionFrontEndCreator, - "std::function synthetic value provider", - ConstString("^std::__(ndk)?1::function<.+>$"), stl_synth_flags, true); #endif } Index: source/Plugins/Language/CPlusPlus/LibCxx.h =================================================================== --- source/Plugins/Language/CPlusPlus/LibCxx.h +++ source/Plugins/Language/CPlusPlus/LibCxx.h @@ -32,6 +32,10 @@ const TypeSummaryOptions &options); // libc++ std::shared_ptr<> and std::weak_ptr<> +bool LibcxxFunctionSummaryProvider( + ValueObject &valobj, Stream &stream, + const TypeSummaryOptions &options); // libc++ std::function<> + SyntheticChildrenFrontEnd * LibcxxVectorBoolSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); @@ -124,9 +128,6 @@ LibcxxInitializerListSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); -SyntheticChildrenFrontEnd *LibcxxFunctionFrontEndCreator(CXXSyntheticChildren *, - lldb::ValueObjectSP); - SyntheticChildrenFrontEnd *LibcxxQueueFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); Index: source/Plugins/Language/CPlusPlus/LibCxx.cpp =================================================================== --- source/Plugins/Language/CPlusPlus/LibCxx.cpp +++ source/Plugins/Language/CPlusPlus/LibCxx.cpp @@ -12,6 +12,7 @@ // C Includes // C++ Includes // Other libraries and framework includes +#include "llvm/ADT/ScopeExit.h" // Project includes #include "lldb/Core/Debugger.h" #include "lldb/Core/FormatEntity.h" @@ -23,6 +24,7 @@ #include "lldb/DataFormatters/VectorIterator.h" #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Target/ProcessStructReader.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/Endian.h" @@ -33,6 +35,225 @@ using namespace lldb_private; using namespace lldb_private::formatters; +bool lldb_private::formatters::LibcxxFunctionSummaryProvider( + ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { + + ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); + + if (!valobj_sp) + return false; + + // Member __f_ has type __base*, the contents of which will hold: + // 1) a vtable entry which may hold type information needed to discover the + // lambda being called + // 2) possibly hold a pointer to the callable object + // e.g. + // + // (lldb) frame var -R f_display + // (std::__1::function) f_display = { + // __buf_ = { + // … + // } + // __f_ = 0x00007ffeefbffa00 + // } + // (lldb) memory read -fA 0x00007ffeefbffa00 + // 0x7ffeefbffa00: ... `vtable for std::__1::__function::__funcGetChildMemberWithName(ConstString("__f_"), true)); + lldb::addr_t member__f_pointer_value = member__f_->GetValueAsUnsigned(0); + + ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + + if (process == nullptr) + return false; + + uint32_t address_size = process->GetAddressByteSize(); + Status status; + + // First item pointed to by __f_ should be the pointer to the vtable for + // a __base object. + lldb::addr_t vtable_address = + process->ReadPointerFromMemory(member__f_pointer_value, status); + + if (status.Fail()) + return false; + + bool found_wrapped_function = false; + + // Using scoped exit so we can use early return and still execute the default + // action in case we don't find the wrapper function. Otherwise we can't use + // early exit without duplicating code. + auto default_print_on_exit = llvm::make_scope_exit( + [&found_wrapped_function, &stream, &member__f_pointer_value]() { + if (!found_wrapped_function) + stream.Printf(" __f_ = %llu", member__f_pointer_value); + }); + + lldb::addr_t address_after_vtable = member__f_pointer_value + address_size; + // As commened above we may not have a function pointer but if we do we will + // need it. + lldb::addr_t possible_function_address = + process->ReadPointerFromMemory(address_after_vtable, status); + + if (status.Fail()) + return false; + + Target &target = process->GetTarget(); + + if (target.GetSectionLoadList().IsEmpty()) + return false; + + Address vtable_addr_resolved; + SymbolContext sc; + Symbol *symbol; + + if (!target.GetSectionLoadList().ResolveLoadAddress(vtable_address, + vtable_addr_resolved)) + return false; + + target.GetImages().ResolveSymbolContextForAddress( + vtable_addr_resolved, eSymbolContextEverything, sc); + symbol = sc.symbol; + + if (symbol == NULL) + return false; + + llvm::StringRef vtable_name(symbol->GetName().GetCString()); + bool found_expected_start_string = + vtable_name.startswith("vtable for std::__1::__function::__func<"); + + if (!found_expected_start_string) + return false; + + // Given a vtable name, we are want to extract the first template parameter + // + // ... __func ... + // ^^^^^^^^^ + // + // We do this by find the first < and , and extracting in between. + // + // This covers the case of the lambda known at compile time. + // + size_t first_open_angle_bracket = vtable_name.find('<') + 1; + size_t first_comma = vtable_name.find_first_of(','); + + llvm::StringRef first_template_parameter = + vtable_name.slice(first_open_angle_bracket, first_comma); + + Address function_address_resolved; + + // Setup for cases 2, 4 and 5 we have a pointer to a function after the + // vtable. We will use a process of elimination to drop through each case + // and obtain the data we need. + if (target.GetSectionLoadList().ResolveLoadAddress( + possible_function_address, function_address_resolved)) { + target.GetImages().ResolveSymbolContextForAddress( + function_address_resolved, eSymbolContextEverything, sc); + symbol = sc.symbol; + } + + auto get_name = [&first_template_parameter, &symbol]() { + // Given case 1: + // + // main::$_0 + // + // we want to append ::operator()() + if (first_template_parameter.contains("$_")) + return llvm::Regex::escape(first_template_parameter.str()) + + R"(::operator\(\)\(.*\))"; + + if (symbol != NULL && + symbol->GetName().GetStringRef().contains("__invoke")) { + + llvm::StringRef symbol_name = symbol->GetName().GetStringRef(); + size_t pos2 = symbol_name.find_last_of(':'); + + // Given case 2: + // + // main::$_1::__invoke(...) + // + // We want to slice off __invoke(...) and append operator()() + std::string lambda_operator = + llvm::Regex::escape(symbol_name.slice(0, pos2 + 1).str()) + + R"(operator\(\)\(.*\))"; + + return lambda_operator; + } + + // Case 3 + return first_template_parameter.str() + R"(::operator\(\)\(.*\))"; + ; + }; + + std::string func_to_match = get_name(); + + SymbolContextList scl; + + target.GetImages().FindFunctions(RegularExpression{func_to_match}, true, true, + true, scl); + + // Case 1,2 or 3 + if (scl.GetSize() >= 1) { + SymbolContext sc2 = scl[0]; + + AddressRange range; + sc2.GetAddressRange(eSymbolContextEverything, 0, false, range); + + Address address = range.GetBaseAddress(); + + Address addr; + if (target.ResolveLoadAddress(address.GetCallableLoadAddress(&target), + addr)) { + LineEntry line_entry; + addr.CalculateSymbolContextLineEntry(line_entry); + + found_wrapped_function = true; + if (first_template_parameter.contains("$_") || + (symbol != NULL && + symbol->GetName().GetStringRef().contains("__invoke"))) { + // Case 1 and 2 + stream.Printf(" Lambda in File %s at Line %u", + line_entry.file.GetFilename().GetCString(), + line_entry.line); + } else { + // Case 3 + stream.Printf(" Function in File %s at Line %u", + line_entry.file.GetFilename().GetCString(), + line_entry.line); + } + + return true; + } + } + + // Case 4 or 5 + if (!symbol->GetName().GetStringRef().startswith("vtable for")) { + found_wrapped_function = true; + stream.Printf(" Function = %s ", symbol->GetName().GetCString()); + return true; + } + + return false; +} + bool lldb_private::formatters::LibcxxSmartPointerSummaryProvider( ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); @@ -566,22 +787,3 @@ return true; } - -class LibcxxFunctionFrontEnd : public SyntheticValueProviderFrontEnd { -public: - LibcxxFunctionFrontEnd(ValueObject &backend) - : SyntheticValueProviderFrontEnd(backend) {} - - lldb::ValueObjectSP GetSyntheticValue() override { - static ConstString g___f_("__f_"); - return m_backend.GetChildMemberWithName(g___f_, true); - } -}; - -SyntheticChildrenFrontEnd * -lldb_private::formatters::LibcxxFunctionFrontEndCreator( - CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { - if (valobj_sp) - return new LibcxxFunctionFrontEnd(*valobj_sp); - return nullptr; -}