1 | //===-- LibCxxUnorderedMap.cpp --------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "LibCxx.h" |
10 | |
11 | #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" |
12 | #include "lldb/Core/ValueObject.h" |
13 | #include "lldb/Core/ValueObjectConstResult.h" |
14 | #include "lldb/DataFormatters/FormattersHelpers.h" |
15 | #include "lldb/Target/Target.h" |
16 | #include "lldb/Utility/ConstString.h" |
17 | #include "lldb/Utility/DataBufferHeap.h" |
18 | #include "lldb/Utility/Endian.h" |
19 | #include "lldb/Utility/Status.h" |
20 | #include "lldb/Utility/Stream.h" |
21 | #include "llvm/ADT/StringRef.h" |
22 | |
23 | using namespace lldb; |
24 | using namespace lldb_private; |
25 | using namespace lldb_private::formatters; |
26 | |
27 | namespace lldb_private { |
28 | namespace formatters { |
29 | class LibcxxStdUnorderedMapSyntheticFrontEnd |
30 | : public SyntheticChildrenFrontEnd { |
31 | public: |
32 | LibcxxStdUnorderedMapSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp); |
33 | |
34 | ~LibcxxStdUnorderedMapSyntheticFrontEnd() override = default; |
35 | |
36 | llvm::Expected<uint32_t> CalculateNumChildren() override; |
37 | |
38 | lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override; |
39 | |
40 | lldb::ChildCacheState Update() override; |
41 | |
42 | bool MightHaveChildren() override; |
43 | |
44 | size_t GetIndexOfChildWithName(ConstString name) override; |
45 | |
46 | private: |
47 | CompilerType m_element_type; |
48 | CompilerType m_node_type; |
49 | ValueObject *m_tree = nullptr; |
50 | size_t m_num_elements = 0; |
51 | ValueObject *m_next_element = nullptr; |
52 | std::vector<std::pair<ValueObject *, uint64_t>> m_elements_cache; |
53 | }; |
54 | } // namespace formatters |
55 | } // namespace lldb_private |
56 | |
57 | lldb_private::formatters::LibcxxStdUnorderedMapSyntheticFrontEnd:: |
58 | LibcxxStdUnorderedMapSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) |
59 | : SyntheticChildrenFrontEnd(*valobj_sp), m_element_type(), |
60 | m_elements_cache() { |
61 | if (valobj_sp) |
62 | Update(); |
63 | } |
64 | |
65 | llvm::Expected<uint32_t> lldb_private::formatters:: |
66 | LibcxxStdUnorderedMapSyntheticFrontEnd::CalculateNumChildren() { |
67 | return m_num_elements; |
68 | } |
69 | |
70 | static void consumeInlineNamespace(llvm::StringRef &name) { |
71 | // Delete past an inline namespace, if any: __[a-zA-Z0-9_]+:: |
72 | auto scratch = name; |
73 | if (scratch.consume_front(Prefix: "__" ) && std::isalnum(scratch[0])) { |
74 | scratch = scratch.drop_while(F: [](char c) { return std::isalnum(c); }); |
75 | if (scratch.consume_front(Prefix: "::" )) { |
76 | // Successfully consumed a namespace. |
77 | name = scratch; |
78 | } |
79 | } |
80 | } |
81 | |
82 | static bool isStdTemplate(ConstString type_name, llvm::StringRef type) { |
83 | llvm::StringRef name = type_name.GetStringRef(); |
84 | // The type name may be prefixed with `std::__<inline-namespace>::`. |
85 | if (name.consume_front(Prefix: "std::" )) |
86 | consumeInlineNamespace(name); |
87 | return name.consume_front(Prefix: type) && name.starts_with(Prefix: "<" ); |
88 | } |
89 | |
90 | static bool isUnorderedMap(ConstString type_name) { |
91 | return isStdTemplate(type_name, type: "unordered_map" ) || |
92 | isStdTemplate(type_name, type: "unordered_multimap" ); |
93 | } |
94 | |
95 | lldb::ValueObjectSP lldb_private::formatters:: |
96 | LibcxxStdUnorderedMapSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) { |
97 | if (idx >= CalculateNumChildrenIgnoringErrors()) |
98 | return lldb::ValueObjectSP(); |
99 | if (m_tree == nullptr) |
100 | return lldb::ValueObjectSP(); |
101 | |
102 | while (idx >= m_elements_cache.size()) { |
103 | if (m_next_element == nullptr) |
104 | return lldb::ValueObjectSP(); |
105 | |
106 | Status error; |
107 | ValueObjectSP node_sp = m_next_element->Dereference(error); |
108 | if (!node_sp || error.Fail()) |
109 | return lldb::ValueObjectSP(); |
110 | |
111 | ValueObjectSP value_sp = node_sp->GetChildMemberWithName(name: "__value_" ); |
112 | ValueObjectSP hash_sp = node_sp->GetChildMemberWithName(name: "__hash_" ); |
113 | if (!hash_sp || !value_sp) { |
114 | if (!m_element_type) { |
115 | auto p1_sp = m_backend.GetChildAtNamePath(names: {"__table_" , "__p1_" }); |
116 | if (!p1_sp) |
117 | return nullptr; |
118 | |
119 | ValueObjectSP first_sp = GetFirstValueOfLibCXXCompressedPair(pair&: *p1_sp); |
120 | if (!first_sp) |
121 | return nullptr; |
122 | |
123 | m_element_type = first_sp->GetCompilerType(); |
124 | m_element_type = m_element_type.GetTypeTemplateArgument(idx: 0); |
125 | m_element_type = m_element_type.GetPointeeType(); |
126 | m_node_type = m_element_type; |
127 | m_element_type = m_element_type.GetTypeTemplateArgument(idx: 0); |
128 | // This synthetic provider is used for both unordered_(multi)map and |
129 | // unordered_(multi)set. For unordered_map, the element type has an |
130 | // additional type layer, an internal struct (`__hash_value_type`) |
131 | // that wraps a std::pair. Peel away the internal wrapper type - whose |
132 | // structure is of no value to users, to expose the std::pair. This |
133 | // matches the structure returned by the std::map synthetic provider. |
134 | if (isUnorderedMap(type_name: m_backend.GetTypeName())) { |
135 | std::string name; |
136 | CompilerType field_type = m_element_type.GetFieldAtIndex( |
137 | idx: 0, name, bit_offset_ptr: nullptr, bitfield_bit_size_ptr: nullptr, is_bitfield_ptr: nullptr); |
138 | CompilerType actual_type = field_type.GetTypedefedType(); |
139 | if (isStdTemplate(type_name: actual_type.GetTypeName(), type: "pair" )) |
140 | m_element_type = actual_type; |
141 | } |
142 | } |
143 | if (!m_node_type) |
144 | return nullptr; |
145 | node_sp = m_next_element->Cast(compiler_type: m_node_type.GetPointerType()) |
146 | ->Dereference(error); |
147 | if (!node_sp || error.Fail()) |
148 | return nullptr; |
149 | |
150 | hash_sp = node_sp->GetChildMemberWithName(name: "__hash_" ); |
151 | if (!hash_sp) |
152 | return nullptr; |
153 | |
154 | value_sp = node_sp->GetChildMemberWithName(name: "__value_" ); |
155 | if (!value_sp) { |
156 | // clang-format off |
157 | // Since D101206 (ba79fb2e1f), libc++ wraps the `__value_` in an |
158 | // anonymous union. |
159 | // Child 0: __hash_node_base base class |
160 | // Child 1: __hash_ |
161 | // Child 2: anonymous union |
162 | // clang-format on |
163 | auto anon_union_sp = node_sp->GetChildAtIndex(idx: 2); |
164 | if (!anon_union_sp) |
165 | return nullptr; |
166 | |
167 | value_sp = anon_union_sp->GetChildMemberWithName(name: "__value_" ); |
168 | if (!value_sp) |
169 | return nullptr; |
170 | } |
171 | } |
172 | m_elements_cache.push_back( |
173 | x: {value_sp.get(), hash_sp->GetValueAsUnsigned(fail_value: 0)}); |
174 | m_next_element = node_sp->GetChildMemberWithName(name: "__next_" ).get(); |
175 | if (!m_next_element || m_next_element->GetValueAsUnsigned(fail_value: 0) == 0) |
176 | m_next_element = nullptr; |
177 | } |
178 | |
179 | std::pair<ValueObject *, uint64_t> val_hash = m_elements_cache[idx]; |
180 | if (!val_hash.first) |
181 | return lldb::ValueObjectSP(); |
182 | StreamString stream; |
183 | stream.Printf(format: "[%" PRIu64 "]" , (uint64_t)idx); |
184 | DataExtractor data; |
185 | Status error; |
186 | val_hash.first->GetData(data, error); |
187 | if (error.Fail()) |
188 | return lldb::ValueObjectSP(); |
189 | const bool thread_and_frame_only_if_stopped = true; |
190 | ExecutionContext exe_ctx = val_hash.first->GetExecutionContextRef().Lock( |
191 | thread_and_frame_only_if_stopped); |
192 | return CreateValueObjectFromData(name: stream.GetString(), data, exe_ctx, |
193 | type: m_element_type); |
194 | } |
195 | |
196 | lldb::ChildCacheState |
197 | lldb_private::formatters::LibcxxStdUnorderedMapSyntheticFrontEnd::Update() { |
198 | m_num_elements = 0; |
199 | m_next_element = nullptr; |
200 | m_elements_cache.clear(); |
201 | ValueObjectSP table_sp = m_backend.GetChildMemberWithName(name: "__table_" ); |
202 | if (!table_sp) |
203 | return lldb::ChildCacheState::eRefetch; |
204 | |
205 | ValueObjectSP p2_sp = table_sp->GetChildMemberWithName(name: "__p2_" ); |
206 | if (!p2_sp) |
207 | return lldb::ChildCacheState::eRefetch; |
208 | |
209 | ValueObjectSP num_elements_sp = GetFirstValueOfLibCXXCompressedPair(pair&: *p2_sp); |
210 | if (!num_elements_sp) |
211 | return lldb::ChildCacheState::eRefetch; |
212 | |
213 | ValueObjectSP p1_sp = table_sp->GetChildMemberWithName(name: "__p1_" ); |
214 | if (!p1_sp) |
215 | return lldb::ChildCacheState::eRefetch; |
216 | |
217 | ValueObjectSP value_sp = GetFirstValueOfLibCXXCompressedPair(pair&: *p1_sp); |
218 | if (!value_sp) |
219 | return lldb::ChildCacheState::eRefetch; |
220 | |
221 | m_tree = value_sp->GetChildMemberWithName(name: "__next_" ).get(); |
222 | if (m_tree == nullptr) |
223 | return lldb::ChildCacheState::eRefetch; |
224 | |
225 | m_num_elements = num_elements_sp->GetValueAsUnsigned(fail_value: 0); |
226 | |
227 | if (m_num_elements > 0) |
228 | m_next_element = m_tree; |
229 | |
230 | return lldb::ChildCacheState::eRefetch; |
231 | } |
232 | |
233 | bool lldb_private::formatters::LibcxxStdUnorderedMapSyntheticFrontEnd:: |
234 | MightHaveChildren() { |
235 | return true; |
236 | } |
237 | |
238 | size_t lldb_private::formatters::LibcxxStdUnorderedMapSyntheticFrontEnd:: |
239 | GetIndexOfChildWithName(ConstString name) { |
240 | return ExtractIndexFromString(item_name: name.GetCString()); |
241 | } |
242 | |
243 | SyntheticChildrenFrontEnd * |
244 | lldb_private::formatters::LibcxxStdUnorderedMapSyntheticFrontEndCreator( |
245 | CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { |
246 | return (valobj_sp ? new LibcxxStdUnorderedMapSyntheticFrontEnd(valobj_sp) |
247 | : nullptr); |
248 | } |
249 | |