Skip to content

Conversation

da-viper
Copy link
Contributor

test was failing because in some version of libc++ there is an extra struct on the stored pair in map. And it did not use the synthetic child.

// Create the synthetic child, which is a pair where the key and value can be
// retrieved by querying the synthetic frontend for
// GetIndexOfChildWithName("first") and GetIndexOfChildWithName("second")
// respectively.
//
// std::unordered_map stores the actual key/value pair in
// __hash_value_type::__cc_ (or previously __cc).
auto potential_child_sp = key_value_sp->Clone(ConstString("pair"));
if (potential_child_sp)
if (potential_child_sp->GetNumChildrenIgnoringErrors() == 1)
if (auto child0_sp = potential_child_sp->GetChildAtIndex(0);
child0_sp->GetName() == "__cc_" || child0_sp->GetName() == "__cc")
potential_child_sp = child0_sp->Clone(ConstString("pair"));
m_pair_sp = potential_child_sp;

template <class _Key, class _Tp>
struct __hash_value_type {
typedef _Key key_type;
typedef _Tp mapped_type;
typedef pair<const key_type, mapped_type> value_type;
private:
value_type __cc_;

@da-viper da-viper requested a review from JDevlieghere as a code owner August 29, 2025 14:31
@da-viper da-viper added the lldb label Aug 29, 2025
@da-viper da-viper requested review from Michael137 and removed request for JDevlieghere August 29, 2025 14:31
@llvmbot
Copy link
Member

llvmbot commented Aug 29, 2025

@llvm/pr-subscribers-lldb

Author: Ebuka Ezike (da-viper)

Changes

test was failing because in some version of libc++ there is an extra struct on the stored pair in map. And it did not use the synthetic child.

// Create the synthetic child, which is a pair where the key and value can be
// retrieved by querying the synthetic frontend for
// GetIndexOfChildWithName("first") and GetIndexOfChildWithName("second")
// respectively.
//
// std::unordered_map stores the actual key/value pair in
// __hash_value_type::__cc_ (or previously __cc).
auto potential_child_sp = key_value_sp->Clone(ConstString("pair"));
if (potential_child_sp)
if (potential_child_sp->GetNumChildrenIgnoringErrors() == 1)
if (auto child0_sp = potential_child_sp->GetChildAtIndex(0);
child0_sp->GetName() == "__cc_" || child0_sp->GetName() == "__cc")
potential_child_sp = child0_sp->Clone(ConstString("pair"));
m_pair_sp = potential_child_sp;

template <class _Key, class _Tp>
struct __hash_value_type {
typedef _Key key_type;
typedef _Tp mapped_type;
typedef pair<const key_type, mapped_type> value_type;
private:
value_type __cc_;


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

1 Files Affected:

  • (modified) lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unordered_map-iterator/TestDataFormatterStdUnorderedMap.py (+12-1)
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unordered_map-iterator/TestDataFormatterStdUnorderedMap.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unordered_map-iterator/TestDataFormatterStdUnorderedMap.py
index d2382373f4810..95b0395ebc486 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unordered_map-iterator/TestDataFormatterStdUnorderedMap.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unordered_map-iterator/TestDataFormatterStdUnorderedMap.py
@@ -16,6 +16,12 @@ def check_ptr_or_ref(self, var_name: str):
         pair = var.GetChildAtIndex(0)
         self.assertTrue(pair)
 
+        # std::unordered_map previously stores the actual key/value pair
+        # in  __hash_value_type::__cc_ (or previously __cc).
+        if pair.GetNumChildren() == 1:
+            pair = pair.GetChildAtIndex(0)
+            self.assertTrue(pair)
+
         self.assertEqual(pair.GetChildAtIndex(0).summary, '"Hello"')
         self.assertEqual(pair.GetChildAtIndex(1).summary, '"World"')
 
@@ -29,6 +35,12 @@ def check_ptr_ptr(self, var_name: str):
         pair = ptr.GetChildAtIndex(0)
         self.assertTrue(pair)
 
+        # std::unordered_map previously stores the actual key/value pair
+        # in  __hash_value_type::__cc_ (or previously __cc).
+        if pair.GetNumChildren() == 1:
+            pair = pair.GetChildAtIndex(0)
+            self.assertTrue(pair)
+
         self.assertEqual(pair.GetChildAtIndex(0).summary, '"Hello"')
         self.assertEqual(pair.GetChildAtIndex(1).summary, '"World"')
 
@@ -113,7 +125,6 @@ def do_test_ptr(self):
         Test that pointers to std::unordered_map are formatted correctly.
         """
 
-        self.build()
         (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
             self, "Stop here", lldb.SBFileSpec("main.cpp", False)
         )

@Michael137
Copy link
Member

Can you point me to the buildbot that this is failing on? We should probably fix the formatter to support the layout, as opposed to fixing up the test

@da-viper
Copy link
Contributor Author

da-viper commented Aug 29, 2025

Can you point me to the buildbot that this is failing on? We should probably fix the formatter to support the layout, as opposed to fixing up the test

There is no build bot failure for this yet.

The formatter works fine if it is used from frame variable (*ptr1) or p (*ptr1) but fails when accessing from the python SB-API.
Not sure if python is meant to use the synthetic value or change the test

@da-viper
Copy link
Contributor Author

this happens on ubuntu 25.10 and fedora 42

/home/da-viper/Dev/contribute/llvm-project/llvm/cmake-build-release/bin/lldb -O "settings set show-statusline false" -o "b 19" a.out
(lldb) settings set show-statusline false
(lldb) target create "a.out"
Current executable set to '/home/da-viper/Dev/contribute/llvm-project/llvm/cmake-build-debug-oft/lldb-test-build.noindex/functionalities/data-formatter/data-formatter-stl/generic/unordered_map-iterator/TestDataFormatterStdUnorderedMap.test_ptr_libcxx_dwarf/a.out' (x86_64).
(lldb) b 19
Breakpoint 1: where = a.out`check_pointer(std::__1::unordered_map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>>> const*, std::__1::unordered_map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>>>*, std::__1::unordered_map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>>> const*, std::__1::unordered_map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>>> const**, std::__1::unordered_map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>>> const* const*, std::__1::unordered_map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>>>**) + 32 at main.cpp:19:3, address = 0x00000000000018a0

(lldb) r
Process 1304321 launched: '/home/da-viper/Dev/contribute/llvm-project/llvm/cmake-build-debug-oft/lldb-test-build.noindex/functionalities/data-formatter/data-formatter-stl/generic/unordered_map-iterator/TestDataFormatterStdUnorderedMap.test_ptr_libcxx_dwarf/a.out' (x86_64)
Process 1304321 stopped
* thread #1, name = 'a.out', stop reason = breakpoint 1.1
    frame #0: 0x00005555555558a0 a.out`check_pointer(ptr1=size=1, ptr2=size=1, ptr3=size=1, ptr4=0x00007fffffffdcc0, ptr5=0x00007fffffffdcc8, ptr6=0x00007fffffffdcc8) at main.cpp:19:3
   16  	static void check_pointer(const StringMapT *ptr1, StringMapT *ptr2,
   17  	                          StringMapTPtr ptr3, StringMapTPtr *ptr4,
   18  	                          const StringMapT *const *ptr5, StringMapT **ptr6) {
-> 19  	  std::printf("Stop here");
    	  ^
   20  	}
   21  	
   22  	int main() {

(lldb) fr v -T (*ptr1) 
(const StringMapT) (*ptr1) = size=1 {
  (std::pair<const std::basic_string<char>, std::basic_string<char> >) [0] = {
    (const std::basic_string<char>) first = "Hello"
    (std::basic_string<char>) second = "World"
  }
}

(lldb) fr v -T (*ptr1)[0]
(std::pair<const std::basic_string<char>, std::basic_string<char> >) [0] = {
  (const std::basic_string<char>) first = "Hello"
  (std::basic_string<char>) second = "World"
}

(lldb) script 
>>> Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.
>>> lldb.frame.FindVariable("ptr1").GetChildAtIndex(0)
(std::__hash_value_type<std::basic_string<char>, std::basic_string<char> >) [0] = {
  __cc_ = (first = "Hello", second = "World")
}
>>> 

@Michael137
Copy link
Member

Can you point me to the buildbot that this is failing on? We should probably fix the formatter to support the layout, as opposed to fixing up the test

There is no build bot failure for this yet.

The formatter works fine if it is used from frame variable (*ptr1) or p (*ptr1) but fails when accessing from the python SB-API. Not sure if python is meant to use the synthetic value or change the test

Hmmm that's surprising. @jimingham might know off the top. I'll have a look at what's going on here. It'd be nice if we didn't have to adjust the test itself

@jimingham
Copy link
Collaborator

Whether to prefer synthetic values for a ValueObject is controlled by the general setting target.enable-synthetic-value which is true by default. You can also control this on a particular ValueObject with ValueObject.SetPreferSyntheticValue which should start out primed by the value of the target setting.
If those are both true then ValueObject::GetChildAtIndex should fetch the synthetic child at that index. GetNonSyntheticValue().GetChildAtIndex should force the "real" children, and GetSyntheticValue().GetChildAtIndex should force the synthetic children.

@jimingham
Copy link
Collaborator

But also note that if the synthetic child creation fails, then we will silently fall back to handing out the "real" children. So this one looks more like synthetic child generation is failing for some reason.

@labath
Copy link
Collaborator

labath commented Sep 4, 2025

The script code is not an exact equivalent of the "frame var" expression. Notice how in the "frame var", you explicitly dereference the object, while in the script, you call GetChildAtIndex directly on the pointer value. The "frame var" expression would be more similar to script lldb.frame.FindVariable("ptr1").Dereference().GetChildAtIndex(0), and I'd guess (I didn't try reproducing this) that this will print the map member correctly.

That doesn't quite explain why is this failing, it might give us a clue about what could be happening. I suspect the problem here is that the map data formatter is just misbehaving when given a pointer value, and the implementation happens to end up returning the parent of the actual pair value. According to grep the __cc_ member is used in the c++03 implementation of the map, and I don't think many people use/test the data formatters in c++03 mode, so it's kind of surprising that the formatter works at all.

@Michael137
Copy link
Member

@da-viper since you can repro this issue locally, mind confirming @labath's theory (#156033 (comment))?

@da-viper
Copy link
Contributor Author

da-viper commented Sep 11, 2025

@da-viper since you can repro this issue locally, mind confirming @labath's theory ([#156033 (comment)]
(#156033 (comment)))?

The "frame var" expression would be more similar to script lldb.frame.FindVariable("ptr1").Dereference().GetChildAtIndex(0)

This works in this case.

>>> lldb.frame.FindVariable("ptr1").Dereference().GetChildAtIndex(0)
(std::pair<const std::basic_string<char>, std::basic_string<char> >) [0] = (first = "Hello", second = "World")

@Michael137
Copy link
Member

@da-viper since you can repro this issue locally, mind confirming @labath's theory ([#156033 (comment)]
(#156033 (comment)))?

The "frame var" expression would be more similar to script lldb.frame.FindVariable("ptr1").Dereference().GetChildAtIndex(0)

This works in this case.

>>> lldb.frame.FindVariable("ptr1").Dereference().GetChildAtIndex(0)
(std::pair<const std::basic_string<char>, std::basic_string<char> >) [0] = (first = "Hello", second = "World")

What I'm a bit confused about is why the map even contains a __cc_ member in the first place. @da-viper could you attach LLDB to the test binary and dump the output of frame var --raw ptr1? If it's being compiled in C++03 mode, why? Could it be that on your setup we are not building a local libc++ for some reason. And the system libc++ is a really old one? That's my only guess at the moment. We should probably just remove all the __cc_ support from our data-formatters anyway. Since it's not being tested

@labath
Copy link
Collaborator

labath commented Sep 11, 2025

What I'm a bit confused about is why the map even contains a __cc_ member in the first place.

Maybe because the binary is built in c++03 mode, so it uses the c++03 implementation of the map? The c++ library doesn't have to be old, the member is still present at HEAD

@Michael137
Copy link
Member

What I'm a bit confused about is why the map even contains a __cc_ member in the first place.

Maybe because the binary is built in c++03 mode, so it uses the c++03 implementation of the map? The c++ library doesn't have to be old, the member is still present at HEAD

Ah right yes. If it's built explicitly in c++03 in our test-suite that would also be confusing to me

@Michael137
Copy link
Member

Michael137 commented Sep 11, 2025

Ok so I looked at this with @da-viper offline. This is an existing issue with formatting specifically const std::unordered_map<..> *. I added the test as part of this issue: #146040. The following is the discrepancy:

(lldb) frame var ptr -P 1
(std::unordered_map<int, int> *) ptr = 0x000000016fdff040 size=1 {
  [0] = (first = 1, second = 2)
}
(lldb) frame var const_ptr -P 1
(const std::unordered_map<int, int> *) const_ptr = 0x000000016fdff040 size=1 {
  [0] = {
    __cc_ = (first = 1, second = 2)
  }
}

The __cc_ member on hash_value_type was actually removed altogether in a very recent version of libc++. So, as I mentioned in #146040 (comment), this works just fine on top-of-tree, because we no longer have the __cc_ wrapper in libc++. And I decided not to give that issue more attention because we try to test the API-tests against the latest libc++ version, where the test failure wouldn't reproduce. The issue that @da-viper is seeing when running tests is explicitly when running a libc++ API test without a locally built libc++. Then it falls back to the system libc++, which doesn't contain the latest libc++ patch that makes this whole thing work.

So TL;DR, this is not a regression. This just didn't work prior to #143501, but we never noticed cause there was no test for it.

It would still be interesting to know why this fails when using older libc++. I think @da-viper is investigating further why our unordered_map formatter breaks on const pointers.

A separate question, should we allow running the libc++ API test category without a locally built libc++ in the first place?

@da-viper da-viper force-pushed the fix-test-std-unordered-map branch from ca31a05 to 134d45f Compare September 12, 2025 09:56
Copy link

github-actions bot commented Sep 12, 2025

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

@da-viper
Copy link
Contributor Author

It was failing because the typeName checked by isUnordered is const qualified so it does not match the template name.

@da-viper da-viper requested a review from labath September 12, 2025 10:09
Copy link
Member

Choose a reason for hiding this comment

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

Can we do this in a separate PR?

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

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

LGTM

Could you update the PR description and title before merging? Particularly, this will close #146040

Unfortunately won't have a good way of testing this but our support for older layouts is best effort. I once tried writing a libcxx-simulator test for std::map, but that still required us to reach into libc++ internals, which wasn't very robust. So decided not to test the old layouts that way.

the type that is checked in isUnordered may be const qualified.
@da-viper da-viper force-pushed the fix-test-std-unordered-map branch from 12c872a to 09ad3b0 Compare September 12, 2025 11:00
 the type that is checked in isUnordered may be const qualified.
@da-viper da-viper changed the title [lldb][test] Fix unordered-map test [lldb] Fix unordered-map data formatter for const types Sep 12, 2025
@da-viper da-viper merged commit f1a02c6 into llvm:main Sep 15, 2025
11 checks passed
Michael137 added a commit that referenced this pull request Oct 2, 2025
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Oct 2, 2025
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Oct 3, 2025
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.

5 participants