-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[LLDB] FindLibCppStdFunctionCallableInfo improvements #111892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
b1e6178
d976756
2e75bb0
248be8c
e7612f5
a6b489d
2cce936
a26c722
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -174,6 +174,233 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo( | |
| if (!valobj_sp) | ||
| return optional_info; | ||
|
|
||
| // std::function has many variants, try to disambiguate | ||
| ValueObjectSP func_as_base_ptr; | ||
| { | ||
| ValueObjectSP outer_f = valobj_sp->GetChildMemberWithName("__f_"); | ||
|
|
||
| if (!outer_f) | ||
| return optional_info; // Unrecognized implementation | ||
|
|
||
| if (outer_f->IsPointerType()) { | ||
| // git: 3e519524c118651123eecf60c2bbc5d65ad9bac3 | ||
| // | ||
| // class function<_Rp()> { | ||
| // aligned_storage<3*sizeof(void*)>::type __buf_; | ||
| // __base<_Rp>* __f_; | ||
| // } | ||
|
|
||
| func_as_base_ptr = std::move(outer_f); | ||
| } else if (auto inner_f = outer_f->GetChildMemberWithName("__f_")) { | ||
| // git: 050b064f15ee56ee0b42c9b957a3dd0f32532394 | ||
| // | ||
| // class function<_Rp(_ArgTypes...)> { | ||
| // __value_func<_Rp(_ArgTypes...)> __f_; | ||
| // } | ||
| // | ||
| // class __value_func<_Rp(_ArgTypes...)> { | ||
| // aligned_storage<3 * sizeof(void*)>::type __buf_; | ||
| // __base<_Rp(_ArgTypes...)>* __f_; | ||
| // } | ||
|
|
||
| func_as_base_ptr = std::move(inner_f); | ||
| } else | ||
| return optional_info; // Unrecognized implementation | ||
| } | ||
|
|
||
| // __base<...> is a pure virtual class with an interface to create/copy/destroy/invoke | ||
| // the underlying value. This interface is implemented by partial specializations of the | ||
| // __func<_Fp, _Alloc, ...> template where _Fp is the wrapped functor object | ||
| Status status; | ||
| ValueObjectSP func_as_base = func_as_base_ptr->Dereference(status); | ||
| if (status.Fail()) | ||
| return optional_info; | ||
|
|
||
| // First we'll try to extract the __func<...> template instantiation's type by looking up | ||
| // the declarations of the member function pointers in it's vtable | ||
| CompilerType func_type; | ||
| Address func_method_addr; | ||
| { | ||
| ValueObjectSP vtable = func_as_base->GetVTable(); | ||
|
|
||
| llvm::Expected<uint32_t> num_entries = vtable->GetNumChildren(); | ||
| if (num_entries.takeError()) | ||
| return optional_info; | ||
|
|
||
| // __base is pure virtual, __func is final. All member function pointers are equally | ||
| // good candidates to find the enclosing class. | ||
| // | ||
| // In practice the first two vtable entries point to artificial destructors which the | ||
| // type system refuses to elaborate as their artificial specifications are not added | ||
| // to the enclosing class' declaration context. This causes various warnings, and dont | ||
| // get us any closer to the concrete type thus we skip them. | ||
| for (uint32_t idx = 2; idx < *num_entries; idx++) { | ||
| ValueObjectSP entry = vtable->GetChildAtIndex(idx); | ||
|
|
||
| // Points to a potentially interesting member function | ||
| addr_t mfunc_load_addr = entry->GetValueAsUnsigned(0); | ||
| if (!mfunc_load_addr) | ||
| continue; | ||
|
|
||
| if (!valobj_sp->GetTargetSP()->ResolveLoadAddress(mfunc_load_addr, func_method_addr)) | ||
| continue; | ||
|
|
||
| Function* func = func_method_addr.CalculateSymbolContextFunction(); | ||
| if (!func) | ||
| continue; | ||
|
|
||
| CompilerDeclContext mfunc_decl_ctx = func->GetDeclContext(); | ||
| if (!mfunc_decl_ctx.IsClassMethod()) | ||
| continue; | ||
|
|
||
| // Member functions are contained in their enclosing class' decl context | ||
| CompilerDeclContext mfunc_parent = mfunc_decl_ctx.GetDecl().GetDeclContext(); | ||
| if (!mfunc_parent.IsValid()) | ||
| continue; | ||
|
|
||
| func_type = mfunc_parent.GetDecl().GetType(); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| CompilerType callable_type = func_type.GetTypeTemplateArgument(0); | ||
| if (!callable_type) | ||
| return optional_info; | ||
|
|
||
| // Now that the __func is a known type we can dig for the wrapped callable | ||
| ValueObjectSP callable; | ||
| { | ||
| // class __func<_Fp, _Alloc, _Rp(_ArgTypes...)> : __base<_Rp(_ArgTypes...)> { | ||
| // __alloc_func<_Fp, _Alloc, _Rp(_ArgTypes...)> __f_; | ||
| // } | ||
| // | ||
| // class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)> { | ||
| // __compressed_pair<_Fp, _Ap> __f_; | ||
| // } | ||
| // | ||
| // class __compressed_pair : __compressed_pair_elem<_T1, 0>, | ||
| // __compressed_pair_elem<_T2, 1> { | ||
| // } | ||
| // | ||
| // struct __compressed_pair_elem { | ||
| // _Tp __value_; | ||
| // } | ||
| ValueObjectSP alloc_func = func_as_base->Cast(func_type); | ||
| if (!alloc_func) | ||
| return optional_info; | ||
|
|
||
| ValueObjectSP pair = alloc_func->GetChildAtNamePath({"__f_", "__f_"}); | ||
| if (!pair) | ||
| return optional_info; | ||
|
|
||
| if (callable_type.IsRecordType() && callable_type.GetNumFields() == 0) { | ||
| // callable_type is an empty class, and has been optimized away! Serve a dummy | ||
| callable = valobj_sp->CreateValueObjectFromAddress("__value_", | ||
| pair->GetLoadAddress(), | ||
| pair->GetExecutionContextRef(), | ||
| callable_type); | ||
|
||
| } else { | ||
| ValueObjectSP elem0 = pair->GetChildAtIndex(0); | ||
| if (!elem0) | ||
| return optional_info; | ||
|
|
||
| callable = elem0->GetChildMemberWithName("__value_"); | ||
| if (!callable) | ||
| return optional_info; | ||
| } | ||
| } | ||
|
|
||
| if (callable_type.IsFunctionPointerType()) { | ||
| addr_t target_load_addr = callable->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); | ||
|
|
||
| ModuleSP mod = func_method_addr.CalculateSymbolContextModule(); | ||
|
|
||
| Address callable_addr; | ||
| if (!mod->ResolveFileAddress(target_load_addr, callable_addr)) | ||
| return optional_info; | ||
|
|
||
| SymbolContext sc; | ||
| mod->ResolveSymbolContextForAddress(callable_addr, eSymbolContextSymbol | eSymbolContextLineEntry, sc); | ||
|
|
||
| if (!sc.symbol) | ||
| return optional_info; | ||
|
|
||
| return LibCppStdFunctionCallableInfo { | ||
| .callable_symbol = *sc.symbol, | ||
| .callable_address = sc.symbol->GetAddress(), | ||
| .callable_line_entry = sc.line_entry, | ||
| .callable_case = LibCppStdFunctionCallableCase::FreeOrMemberFunction | ||
| }; | ||
| } else if (callable_type.IsMemberFunctionPointerType()) { | ||
| // TODO: Member function's unsigned value comes back as invalid! I am guessing | ||
| // the ValueObject wants to let me know that this is not necessarily as simple | ||
| // as that.. I remember reading something in the Itanium ABI about member | ||
| // pointers taking up two pointers of space. Perhaps that's why it is not legal | ||
| // to read their underlying value raw? | ||
|
|
||
| printf("Curious!"); | ||
|
||
| } else if (callable_type.IsRecordType()) { | ||
| // Target is a lambda, or a generic callable. Search for a single operator() overload | ||
| std::optional<ConstString> mangled_func_name; | ||
|
|
||
| for (uint32_t idx = 0; idx < callable_type.GetNumMemberFunctions(); idx++) { | ||
| TypeMemberFunctionImpl mfunc = callable_type.GetMemberFunctionAtIndex(idx); | ||
|
|
||
| if (mfunc.GetKind() != eMemberFunctionKindInstanceMethod) | ||
| continue; | ||
|
|
||
| if (mfunc.GetName() != "operator()") | ||
| continue; | ||
|
|
||
| if (mangled_func_name) | ||
| return optional_info; // Cannot resolve ambiguous target | ||
|
|
||
| mangled_func_name = mfunc.GetMangledName(); | ||
| } | ||
|
|
||
| // Locate the symbol context corresponding to the target function | ||
| SymbolContext sc; | ||
| { | ||
| // We'll assume that callable_type is in the same module as the vtable | ||
| ModuleSP mod = func_method_addr.CalculateSymbolContextModule(); | ||
|
|
||
| // Limit our lookup to callable_type | ||
| CompilerDeclContext decl_ctx = callable_type.GetTypeSystem()->GetCompilerDeclContextForType(callable_type); | ||
|
|
||
| SymbolContextList list; | ||
| mod->FindFunctions(*mangled_func_name, decl_ctx, eFunctionNameTypeFull, {}, list); | ||
|
|
||
| if (list.GetSize() != 1) | ||
| return optional_info; | ||
|
|
||
| list.GetContextAtIndex(0, sc); | ||
| } | ||
|
|
||
| // TODO: This feels a bit clunky, I am probably misusing the API? FindFunctions returns me | ||
| // SymbolContexts with the .function set but not .symbol ... At first glance it seemed like | ||
| // if we know the function there must be a symbol too! | ||
| if (!sc.function) | ||
| return optional_info; | ||
|
|
||
| Symbol* symbol = sc.function->GetAddressRange().GetBaseAddress().CalculateSymbolContextSymbol(); | ||
| if (!symbol) | ||
| return optional_info; | ||
|
|
||
| return LibCppStdFunctionCallableInfo { | ||
| .callable_symbol = *symbol, | ||
| .callable_address = symbol->GetAddress(), | ||
| .callable_line_entry = sc.GetFunctionStartLineEntry(), | ||
|
|
||
| // TODO: Can't tell lambdas apart from generic callables.. do we really need to? | ||
| // Is it important to have the correct qualification in the summary? | ||
| .callable_case = LibCppStdFunctionCallableCase::Lambda | ||
| }; | ||
| } | ||
|
|
||
| // Unrecognized callable type - skip the original implementation for now | ||
| if (!callable_type.IsVoidType()) | ||
| return optional_info; | ||
|
|
||
| // 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 | ||
|
|
@@ -232,7 +459,6 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo( | |
| return optional_info; | ||
|
|
||
| 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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -191,14 +191,6 @@ llvm::Error ItaniumABILanguageRuntime::TypeHasVTable(CompilerType type) { | |
| type = pointee_type; | ||
| } | ||
|
|
||
| // Make sure this is a class or a struct first by checking the type class | ||
| // bitfield that gets returned. | ||
| if ((type.GetTypeClass() & (eTypeClassStruct | eTypeClassClass)) == 0) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you elaborate on the removal here?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might have overgeneralized here a bit - |
||
| return llvm::createStringError(std::errc::invalid_argument, | ||
| "type \"%s\" is not a class or struct or a pointer to one", | ||
| original_type.GetTypeName().AsCString("<invalid>")); | ||
| } | ||
|
|
||
| // Check if the type has virtual functions by asking it if it is polymorphic. | ||
| if (!type.IsPolymorphicClass()) { | ||
| return llvm::createStringError(std::errc::invalid_argument, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, this is not the compressed pair representation of libc++ anymore. Nothing really that would affect the code here, but the comment could use an update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am developing this using the SDK shipped with Xcode 16 - that's where I mostly intend to use the improvements too. Perhaps it isn't worth putting library code excerpts into comments if they are doomed to get out of sync eventually?