Skip to content

Implement data formatters for LibStdC++ std::variant #68012

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

Merged
merged 4 commits into from
Oct 3, 2023

Conversation

jeffreytan81
Copy link
Contributor

This patch implements the data formatters for LibStdC++ std::variant.

@github-actions
Copy link

github-actions bot commented Oct 2, 2023

✅ With the latest revision this PR passed the Python code formatter.

@github-actions
Copy link

github-actions bot commented Oct 2, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

@jeffreytan81 jeffreytan81 marked this pull request as ready for review October 2, 2023 21:18
@llvmbot llvmbot added the lldb label Oct 2, 2023
@llvmbot
Copy link
Member

llvmbot commented Oct 2, 2023

@llvm/pr-subscribers-lldb

Changes

This patch implements the data formatters for LibStdC++ std::variant.


Full diff: https://github.com/llvm/llvm-project/pull/68012.diff

5 Files Affected:

  • (modified) lldb/examples/synthetic/gnu_libstdcpp.py (+84)
  • (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+14-4)
  • (added) lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/Makefile (+5)
  • (added) lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/TestDataFormatterLibStdcxxVariant.py (+72)
  • (added) lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/main.cpp (+79)
diff --git a/lldb/examples/synthetic/gnu_libstdcpp.py b/lldb/examples/synthetic/gnu_libstdcpp.py
index 825b7f3787a010d..29c926167fb440c 100644
--- a/lldb/examples/synthetic/gnu_libstdcpp.py
+++ b/lldb/examples/synthetic/gnu_libstdcpp.py
@@ -892,3 +892,87 @@ def update(self):
         except:
             pass
         return False
+
+
+def VariantSummaryProvider(valobj, dict):
+    raw_obj = valobj.GetNonSyntheticValue()
+    index_obj = raw_obj.GetChildMemberWithName("_M_index")
+    data_obj = raw_obj.GetChildMemberWithName("_M_u")
+    if not (index_obj and index_obj.IsValid() and data_obj and data_obj.IsValid()):
+        return "<Can't find _M_index or _M_u>"
+
+    def get_variant_npos_value(index_byte_size):
+        if index_byte_size == 1:
+            return 0xFF
+        elif index_byte_size == 2:
+            return 0xFFFF
+        else:
+            return 0xFFFFFFFF
+
+    npos_value = get_variant_npos_value(index_obj.GetByteSize())
+    index = index_obj.GetValueAsUnsigned(0)
+    if index == npos_value:
+        return " No Value"
+
+    active_type = data_obj.GetType().GetTemplateArgumentType(index)
+    return f" Active Type = {active_type.GetDisplayTypeName()} "
+
+
+class VariantSynthProvider:
+    def __init__(self, valobj, dict):
+        self.raw_obj = valobj.GetNonSyntheticValue()
+        self.is_valid = False
+        self.index = None
+        self.data_obj = None
+
+    def update(self):
+        try:
+            self.index = self.raw_obj.GetChildMemberWithName(
+                "_M_index"
+            ).GetValueAsSigned(-1)
+            self.is_valid = self.index != -1
+            self.data_obj = self.raw_obj.GetChildMemberWithName("_M_u")
+        except:
+            self.is_valid = False
+        return False
+
+    def has_children(self):
+        return True
+
+    def num_children(self):
+        return 1 if self.is_valid else 0
+
+    def get_child_index(self, name):
+        return 0
+
+    def get_child_at_index(self, index):
+        if not self.is_valid:
+            return None
+        cur = 0
+        node = self.data_obj
+        while cur < self.index:
+            node = node.GetChildMemberWithName("_M_rest")
+            cur += 1
+
+        # _M_storage's type depends on variant field's type "_Type".
+        #  1. if '_Type' is literal type: _Type _M_storage.
+        #  2. otherwise, __gnu_cxx::__aligned_membuf<_Type> _M_storage.
+        #
+        # For 2. we have to cast it to underlying template _Type.
+
+        value = node.GetChildMemberWithName("_M_first").GetChildMemberWithName(
+            "_M_storage"
+        )
+        template_type = value.GetType().GetTemplateArgumentType(0)
+
+        # Literal type will return None for GetTemplateArgumentType(0)
+        if (
+            template_type
+            and "__gnu_cxx::__aligned_membuf" in value.GetType().GetDisplayTypeName()
+            and template_type.IsValid()
+        ):
+            value = value.Cast(template_type)
+
+        if value.IsValid():
+            return value.Clone("Value")
+        return None
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index c1743a5e0a418dd..a285864ca2e1229 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -332,11 +332,11 @@ bool CPlusPlusLanguage::MethodName::ContainsPath(llvm::StringRef path) {
   // If we can't parse the incoming name, then just check that it contains path.
   if (m_parse_error)
     return m_full.GetStringRef().contains(path);
-    
+
   llvm::StringRef identifier;
   llvm::StringRef context;
   std::string path_str = path.str();
-  bool success 
+  bool success
       = CPlusPlusLanguage::ExtractContextAndIdentifier(path_str.c_str(),
                                                        context,
                                                        identifier);
@@ -372,7 +372,7 @@ bool CPlusPlusLanguage::MethodName::ContainsPath(llvm::StringRef path) {
     return false;
   if (haystack.empty() || !isalnum(haystack.back()))
     return true;
-    
+
   return false;
 }
 
@@ -388,7 +388,7 @@ bool CPlusPlusLanguage::IsCPPMangledName(llvm::StringRef name) {
   return true;
 }
 
-bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path, 
+bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path,
                                                   ConstString demangled) const {
   MethodName demangled_name(demangled);
   return demangled_name.ContainsPath(path);
@@ -1104,6 +1104,11 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       SyntheticChildrenSP(new ScriptedSyntheticChildren(
           stl_synth_flags,
           "lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider")));
+  cpp_category_sp->AddTypeSynthetic(
+      "^std::variant<.+>$", eFormatterMatchRegex,
+      SyntheticChildrenSP(new ScriptedSyntheticChildren(
+          stl_synth_flags,
+          "lldb.formatters.cpp.gnu_libstdcpp.VariantSynthProvider")));
 
   stl_summary_flags.SetDontShowChildren(false);
   stl_summary_flags.SetSkipPointers(false);
@@ -1148,6 +1153,11 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       TypeSummaryImplSP(new ScriptSummaryFormat(
           stl_summary_flags,
           "lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider")));
+  cpp_category_sp->AddTypeSummary(
+      "^std::variant<.+>$", eFormatterMatchRegex,
+      TypeSummaryImplSP(new ScriptSummaryFormat(
+          stl_summary_flags,
+          "lldb.formatters.cpp.gnu_libstdcpp.VariantSummaryProvider")));
 
   AddCXXSynthetic(
       cpp_category_sp,
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/Makefile
new file mode 100644
index 000000000000000..104f82809c7a35b
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/Makefile
@@ -0,0 +1,5 @@
+CXX_SOURCES := main.cpp
+
+USE_LIBSTDCPP := 1
+CXXFLAGS_EXTRAS := -std=c++17
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/TestDataFormatterLibStdcxxVariant.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/TestDataFormatterLibStdcxxVariant.py
new file mode 100644
index 000000000000000..88be87a5469e196
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/TestDataFormatterLibStdcxxVariant.py
@@ -0,0 +1,72 @@
+"""
+Test lldb data formatter for LibStdC++ std::variant.
+"""
+
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+USE_LIBSTDCPP = "USE_LIBSTDCPP"
+
+
+class LibStdcxxVariantDataFormatterTestCase(TestBase):
+    @add_test_categories(["libstdcxx"])
+    def test_with_run_command(self):
+        """Test LibStdC++ std::variant data formatter works correctly."""
+        self.build(dictionary={USE_LIBSTDCPP: "1"})
+
+        (self.target, self.process, _, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp", False)
+        )
+
+        lldbutil.continue_to_breakpoint(self.process, bkpt)
+        self.assertEqual(3 + 4, 7)
+
+        self.expect(
+            "frame variable v1",
+            substrs=["v1 =  Active Type = int  {", "Value = 12", "}"],
+        )
+
+        self.expect(
+            "frame variable v1_ref",
+            substrs=["v1_ref =  Active Type = int : {", "Value = 12", "}"],
+        )
+
+        self.expect(
+            "frame variable v_v1",
+            substrs=[
+                "v_v1 =  Active Type = std::variant<int, double, char>  {",
+                "Value =  Active Type = int  {",
+                "Value = 12",
+                "}",
+                "}",
+            ],
+        )
+
+        lldbutil.continue_to_breakpoint(self.process, bkpt)
+
+        self.expect(
+            "frame variable v1",
+            substrs=["v1 =  Active Type = double  {", "Value = 2", "}"],
+        )
+
+        lldbutil.continue_to_breakpoint(self.process, bkpt)
+
+        self.expect(
+            "frame variable v2",
+            substrs=["v2 =  Active Type = double  {", "Value = 2", "}"],
+        )
+
+        self.expect(
+            "frame variable v3",
+            substrs=["v3 =  Active Type = char  {", "Value = 'A'", "}"],
+        )
+
+        self.expect("frame variable v_no_value", substrs=["v_no_value =  No Value"])
+
+        self.expect(
+            "frame variable v_many_types_no_value",
+            substrs=["v_many_types_no_value =  No Value"],
+        )
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/main.cpp
new file mode 100644
index 000000000000000..545318f9358b673
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libstdcpp/variant/main.cpp
@@ -0,0 +1,79 @@
+#include <cstdio>
+#include <string>
+#include <variant>
+#include <vector>
+
+struct S {
+  operator int() { throw 42; }
+};
+
+int main() {
+  bool has_variant = true;
+
+  printf("%d\n", has_variant); // break here
+
+  std::variant<int, double, char> v1;
+  std::variant<int, double, char> &v1_ref = v1;
+  std::variant<int, double, char> v2;
+  std::variant<int, double, char> v3;
+  std::variant<std::variant<int, double, char>> v_v1;
+  std::variant<int, double, char> v_no_value;
+  // The next variant has many types, meaning the type index does not fit in
+  // a byte and must be `unsigned short` instead of `unsigned char` when
+  // using the unstable libc++ ABI. With stable libc++ ABI, the type index
+  // is always just `unsigned int`.
+  std::variant<
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
+      int, int, int, int, int, int, int, int, int, int, int, int>
+      v_many_types_no_value;
+
+  v1 = 12; // v contains int
+  v_v1 = v1;
+  int i = std::get<int>(v1);
+  printf("%d\n", i); // break here
+
+  v2 = 2.0;
+  double d = std::get<double>(v2);
+  printf("%f\n", d);
+
+  v3 = 'A';
+  char c = std::get<char>(v3);
+  printf("%d\n", c);
+
+  // Checking v1 above and here to make sure we done maintain the incorrect
+  // state when we change its value.
+  v1 = 2.0;
+  d = std::get<double>(v1);
+  printf("%f\n", d); // break here
+
+  try {
+    v_no_value.emplace<0>(S());
+  } catch (...) {
+  }
+
+  printf("%zu\n", v_no_value.index());
+
+  try {
+    v_many_types_no_value.emplace<0>(S());
+  } catch (...) {
+  }
+
+  printf("%zu\n", v_many_types_no_value.index());
+
+  return 0; // break here
+}

@medismailben
Copy link
Member

LGTM with some comments.

@add_test_categories(["libstdcxx"])
def test_with_run_command(self):
"""Test LibStdC++ std::variant data formatter works correctly."""
self.build(dictionary={USE_LIBSTDCPP: "1"})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to specify this since you already set USE_LIBSTDCPP in the Makefile ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Will remove.

)

lldbutil.continue_to_breakpoint(self.process, bkpt)
self.assertEqual(3 + 4, 7)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, this is an old diff that I drafted several months ago so totally forgot why I added this in the first place :-)

@jeffreytan81 jeffreytan81 merged commit 1ec4330 into llvm:main Oct 3, 2023
@antmox
Copy link
Contributor

antmox commented Oct 3, 2023

Hello,
It looks like this broke 2 bots:
lldb-aarch64-ubuntu : https://lab.llvm.org/buildbot/#/builders/96/builds/46436
lldb-arm-ubuntu : https://lab.llvm.org/buildbot/#/builders/17/builds/44011
Could you please take a look ?

@jeffreytan81
Copy link
Contributor Author

Thanks for the heads-up. I have done some investigation. The test passes on my CentOS Linux and Macbook. It is unclear why it fails in this two bots.
Unfortunately, I do not have the specific ubuntu machines to reproduce and logs do not provide enough clue. I will go a head and draft a fix to disable the failing test lines to unblock.

Hello, It looks like this broke 2 bots: lldb-aarch64-ubuntu : https://lab.llvm.org/buildbot/#/builders/96/builds/46436 lldb-arm-ubuntu : https://lab.llvm.org/buildbot/#/builders/17/builds/44011 Could you please take a look ?

medismailben pushed a commit that referenced this pull request Oct 3, 2023
#68012 works on my CentOS Linux
and Macbook but seems to fail for certain build bots. The error log
complains "No Value" check failure for `std::variant` but not very
actionable without a reproduce.

To unblock the build bots, I am commenting out the "No Value" checks.

Co-authored-by: jeffreytan81 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants