[Lldb-commits] [lldb] [LLDB] FindLibCppStdFunctionCallableInfo improvements (PR #111892)

Michael Buch via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 11 00:50:41 PDT 2024


================
@@ -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);
----------------
Michael137 wrote:

I think what you were seeing actually is that libc++ removed the `__compressed_pair` structure. So `__f_` gets you exactly the callable, not a wrapper template anymore. See e.g., what we did for the other data-formatters to support the new layout: https://github.com/llvm/llvm-project/pull/96538

https://github.com/llvm/llvm-project/pull/111892


More information about the lldb-commits mailing list