[lldb][CPlusPlus] Implement CPlusPlusLanguage::GetFunctionDisplayName
authorMichael Buch <michaelbuch12@gmail.com>
Thu, 27 Oct 2022 10:10:28 +0000 (11:10 +0100)
committerMichael Buch <michaelbuch12@gmail.com>
Mon, 31 Oct 2022 12:25:19 +0000 (12:25 +0000)
This patch implements the `GetFunctionDisplayName` API which gets
used by the frame-formatting code to decide how to print a
function name.

Currently this API trivially returns `false`, so we try to parse
the demangled function base-name by hand. We try find the closing
parenthesis by doing a forward scan through the demangled name. However,
for arguments that contain parenthesis (e.g., function pointers)
this would leave garbage in the frame function name.

By re-using the `CPlusPlusLanguage` parser for this we offload the
need to parse function names to a component that knows how to do this
already.

We leave the existing parsing code in `FormatEntity` since it's used
in cases where a language-plugin is not available (and is not
necessarily C++ specific).

**Example**

For following function:
```
int foo(std::function<int(void)> const& func) { return 1; }
```

Before patch:
```
frame #0: 0x000000010000151c a.out`foo(func= Function = bar() )> const&) at sample.cpp:11:49
```

After patch:
```
frame #0: 0x000000010000151c a.out`foo(func= Function = bar() ) at sample.cpp:11:49
```

**Testing**

* Added shell test

lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
lldb/test/Shell/Settings/Inputs/names.cpp [new file with mode: 0644]
lldb/test/Shell/Settings/TestFrameFormatNameWithArgs.test [new file with mode: 0644]

index 3e94f55..752e68c 100644 (file)
 #include "lldb/Core/Module.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Core/UniqueCStringMap.h"
+#include "lldb/Core/ValueObjectVariable.h"
 #include "lldb/DataFormatters/CXXFunctionPointer.h"
 #include "lldb/DataFormatters/DataVisualization.h"
 #include "lldb/DataFormatters/FormattersHelpers.h"
 #include "lldb/DataFormatters/VectorType.h"
 #include "lldb/Symbol/SymbolFile.h"
+#include "lldb/Symbol/VariableList.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
@@ -171,6 +173,40 @@ static bool IsTrivialBasename(const llvm::StringRef &basename) {
   return idx == basename.size();
 }
 
+/// Writes out the function name in 'full_name' to 'out_stream'
+/// but replaces each argument type with the variable name
+/// and the corresponding pretty-printed value
+static bool PrettyPrintFunctionNameWithArgs(Stream &out_stream,
+                                            char const *full_name,
+                                            ExecutionContextScope *exe_scope,
+                                            VariableList const &args) {
+  CPlusPlusLanguage::MethodName cpp_method{ConstString(full_name)};
+
+  if (!cpp_method.IsValid())
+    return false;
+
+  llvm::StringRef return_type = cpp_method.GetReturnType();
+  if (!return_type.empty()) {
+    out_stream.PutCString(return_type);
+    out_stream.PutChar(' ');
+  }
+
+  out_stream.PutCString(cpp_method.GetScopeQualifiedName());
+  out_stream.PutChar('(');
+
+  FormatEntity::PrettyPrintFunctionArguments(out_stream, args, exe_scope);
+
+  out_stream.PutChar(')');
+
+  llvm::StringRef qualifiers = cpp_method.GetQualifiers();
+  if (!qualifiers.empty()) {
+    out_stream.PutChar(' ');
+    out_stream.PutCString(qualifiers);
+  }
+
+  return true;
+}
+
 bool CPlusPlusLanguage::MethodName::TrySimplifiedParse() {
   // This method tries to parse simple method definitions which are presumably
   // most comman in user programs. Definitions that can be parsed by this
@@ -1465,3 +1501,66 @@ bool CPlusPlusLanguage::IsSourceFile(llvm::StringRef file_path) const {
   // that we could check for.
   return file_path.contains("/usr/include/c++/");
 }
+
+bool CPlusPlusLanguage::GetFunctionDisplayName(
+    const SymbolContext *sc, const ExecutionContext *exe_ctx,
+    FunctionNameRepresentation representation, Stream &s) {
+  switch (representation) {
+  case FunctionNameRepresentation::eNameWithArgs: {
+    // Print the function name with arguments in it
+    if (sc->function) {
+      ExecutionContextScope *exe_scope =
+          exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr;
+      const char *cstr = sc->function->GetName().AsCString(nullptr);
+      if (cstr) {
+        const InlineFunctionInfo *inline_info = nullptr;
+        VariableListSP variable_list_sp;
+        bool get_function_vars = true;
+        if (sc->block) {
+          Block *inline_block = sc->block->GetContainingInlinedBlock();
+
+          if (inline_block) {
+            get_function_vars = false;
+            inline_info = sc->block->GetInlinedFunctionInfo();
+            if (inline_info)
+              variable_list_sp = inline_block->GetBlockVariableList(true);
+          }
+        }
+
+        if (get_function_vars) {
+          variable_list_sp =
+              sc->function->GetBlock(true).GetBlockVariableList(true);
+        }
+
+        if (inline_info) {
+          s.PutCString(cstr);
+          s.PutCString(" [inlined] ");
+          cstr = inline_info->GetName().GetCString();
+        }
+
+        VariableList args;
+        if (variable_list_sp)
+          variable_list_sp->AppendVariablesWithScope(eValueTypeVariableArgument,
+                                                     args);
+        if (args.GetSize() > 0) {
+          if (!PrettyPrintFunctionNameWithArgs(s, cstr, exe_scope, args))
+            return false;
+        } else {
+          s.PutCString(cstr);
+        }
+        return true;
+      }
+    } else if (sc->symbol) {
+      const char *cstr = sc->symbol->GetName().AsCString(nullptr);
+      if (cstr) {
+        s.PutCString(cstr);
+        return true;
+      }
+    }
+  } break;
+  default:
+    return false;
+  }
+
+  return false;
+}
index f9b3251..8099964 100644 (file)
@@ -136,6 +136,11 @@ public:
   ConstString
   GetDemangledFunctionNameWithoutArguments(Mangled mangled) const override;
 
+  bool GetFunctionDisplayName(const SymbolContext *sc,
+                              const ExecutionContext *exe_ctx,
+                              FunctionNameRepresentation representation,
+                              Stream &s) override;
+
   static bool IsCPPMangledName(llvm::StringRef name);
 
   // Extract C++ context and identifier from a string using heuristic matching
diff --git a/lldb/test/Shell/Settings/Inputs/names.cpp b/lldb/test/Shell/Settings/Inputs/names.cpp
new file mode 100644 (file)
index 0000000..461c6d0
--- /dev/null
@@ -0,0 +1,43 @@
+#include <functional>
+
+namespace detail {
+template <typename T> struct Quux {};
+} // namespace detail
+
+using FuncPtr = detail::Quux<double> (*(*)(int))(float);
+
+struct Foo {
+  template <typename T> void foo(T const &t) const noexcept(true) {}
+
+  template <size_t T> void operator<<(size_t) {}
+
+  template <typename T> FuncPtr returns_func_ptr(detail::Quux<int> &&) const noexcept(false) { return nullptr; }
+};
+
+namespace ns {
+template <typename T> int foo(T const &t) noexcept(false) { return 0; }
+
+template <typename T> FuncPtr returns_func_ptr(detail::Quux<int> &&) { return nullptr; }
+} // namespace ns
+
+int bar() { return 1; }
+
+namespace {
+int anon_bar() { return 1; }
+auto anon_lambda = [](std::function<int(int (*)(int))>) mutable {};
+} // namespace
+
+int main() {
+  ns::foo(bar);
+  ns::foo(std::function{bar});
+  ns::foo(anon_lambda);
+  ns::foo(std::function{anon_bar});
+  ns::foo(&Foo::foo<std::function<int(int)>>);
+  ns::returns_func_ptr<int>(detail::Quux<int>{});
+  Foo f;
+  f.foo(std::function{bar});
+  f.foo(std::function{anon_bar});
+  f.operator<< <(2 > 1)>(0);
+  f.returns_func_ptr<int>(detail::Quux<int>{});
+  return 0;
+}
diff --git a/lldb/test/Shell/Settings/TestFrameFormatNameWithArgs.test b/lldb/test/Shell/Settings/TestFrameFormatNameWithArgs.test
new file mode 100644 (file)
index 0000000..9cf3dba
--- /dev/null
@@ -0,0 +1,27 @@
+# RUN: %clangxx_host -g -O0 %S/Inputs/names.cpp -std=c++17 -o %t.out
+# RUN: %lldb -b -s %s %t.out | FileCheck %s
+settings set -f frame-format "frame ${function.name-with-args}\n"
+break set -n foo
+break set -n operator<<
+break set -n returns_func_ptr
+run
+# CHECK: frame int ns::foo<int ()>(t={{.*}})
+c
+# CHECK: frame int ns::foo<std::__1::function<int ()>>(t= Function = bar() )
+c
+# CHECK: frame int ns::foo<(anonymous namespace)::$_0>(t={{.*}})
+c
+# CHECK: frame int ns::foo<std::__1::function<int ()>>(t= Function = (anonymous namespace)::anon_bar() )
+c
+# CHECK: frame int ns::foo<void (Foo::*)(std::__1::function<int (int)> const&) const noexcept>(t={{.*}})
+c
+# CHECK: frame ns::returns_func_ptr<int>((null)={{.*}})
+c
+# CHECK: frame void Foo::foo<std::__1::function<int ()>>(this={{.*}}, t= Function = bar() ) const
+c
+# CHECK: frame void Foo::foo<std::__1::function<int ()>>(this={{.*}}, t= Function = (anonymous namespace)::anon_bar() ) const
+c
+# CHECK: frame void Foo::operator<<<1ul>(this={{.*}}, (null)=0)
+c
+# CHECK: frame Foo::returns_func_ptr<int>(this={{.*}}, (null)={{.*}})
+q