1 | //===-- LibCxxVariant.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 "LibCxxVariant.h" |
10 | #include "LibCxx.h" |
11 | #include "lldb/DataFormatters/FormattersHelpers.h" |
12 | #include "lldb/Symbol/CompilerType.h" |
13 | #include "lldb/Utility/LLDBAssert.h" |
14 | |
15 | #include "llvm/ADT/ScopeExit.h" |
16 | #include <optional> |
17 | |
18 | using namespace lldb; |
19 | using namespace lldb_private; |
20 | |
21 | // libc++ variant implementation contains two members that we care about both |
22 | // are contained in the __impl member. |
23 | // - __index which tells us which of the variadic template types is the active |
24 | // type for the variant |
25 | // - __data is a variadic union which recursively contains itself as member |
26 | // which refers to the tailing variadic types. |
27 | // - __head which refers to the leading non pack type |
28 | // - __value refers to the actual value contained |
29 | // - __tail which refers to the remaining pack types |
30 | // |
31 | // e.g. given std::variant<int,double,char> v1 |
32 | // |
33 | // (lldb) frame var -R v1.__impl.__data |
34 | //(... __union<... 0, int, double, char>) v1.__impl.__data = { |
35 | // ... |
36 | // __head = { |
37 | // __value = ... |
38 | // } |
39 | // __tail = { |
40 | // ... |
41 | // __head = { |
42 | // __value = ... |
43 | // } |
44 | // __tail = { |
45 | // ... |
46 | // __head = { |
47 | // __value = ... |
48 | // ... |
49 | // |
50 | // So given |
51 | // - __index equal to 0 the active value is contained in |
52 | // |
53 | // __data.__head.__value |
54 | // |
55 | // - __index equal to 1 the active value is contained in |
56 | // |
57 | // __data.__tail.__head.__value |
58 | // |
59 | // - __index equal to 2 the active value is contained in |
60 | // |
61 | // __data.__tail.__tail.__head.__value |
62 | // |
63 | |
64 | namespace { |
65 | // libc++ std::variant index could have one of three states |
66 | // 1) Valid, we can obtain it and its not variant_npos |
67 | // 2) Invalid, we can't obtain it or it is not a type we expect |
68 | // 3) NPos, its value is variant_npos which means the variant has no value |
69 | enum class LibcxxVariantIndexValidity { Valid, Invalid, NPos }; |
70 | |
71 | uint64_t VariantNposValue(uint64_t index_byte_size) { |
72 | switch (index_byte_size) { |
73 | case 1: |
74 | return static_cast<uint8_t>(-1); |
75 | case 2: |
76 | return static_cast<uint16_t>(-1); |
77 | case 4: |
78 | return static_cast<uint32_t>(-1); |
79 | } |
80 | lldbassert(false && "Unknown index type size" ); |
81 | return static_cast<uint32_t>(-1); // Fallback to stable ABI type. |
82 | } |
83 | |
84 | LibcxxVariantIndexValidity |
85 | LibcxxVariantGetIndexValidity(ValueObjectSP &impl_sp) { |
86 | ValueObjectSP index_sp(impl_sp->GetChildMemberWithName(name: "__index" )); |
87 | |
88 | if (!index_sp) |
89 | return LibcxxVariantIndexValidity::Invalid; |
90 | |
91 | // In the stable ABI, the type of __index is just int. |
92 | // In the unstable ABI, where _LIBCPP_ABI_VARIANT_INDEX_TYPE_OPTIMIZATION is |
93 | // enabled, the type can either be unsigned char/short/int depending on |
94 | // how many variant types there are. |
95 | // We only need to do this here when comparing against npos, because npos is |
96 | // just `-1`, but that translates to different unsigned values depending on |
97 | // the byte size. |
98 | CompilerType index_type = index_sp->GetCompilerType(); |
99 | |
100 | llvm::Expected<uint64_t> index_type_bytes = index_type.GetByteSize(exe_scope: nullptr); |
101 | if (!index_type_bytes) { |
102 | LLDB_LOG_ERRORV(GetLog(LLDBLog::DataFormatters), |
103 | index_type_bytes.takeError(), "{0}" ); |
104 | if (!index_type_bytes) |
105 | return LibcxxVariantIndexValidity::Invalid; |
106 | } |
107 | uint64_t npos_value = VariantNposValue(index_byte_size: *index_type_bytes); |
108 | uint64_t index_value = index_sp->GetValueAsUnsigned(fail_value: 0); |
109 | |
110 | if (index_value == npos_value) |
111 | return LibcxxVariantIndexValidity::NPos; |
112 | |
113 | return LibcxxVariantIndexValidity::Valid; |
114 | } |
115 | |
116 | std::optional<uint64_t> LibcxxVariantIndexValue(ValueObjectSP &impl_sp) { |
117 | ValueObjectSP index_sp(impl_sp->GetChildMemberWithName(name: "__index" )); |
118 | |
119 | if (!index_sp) |
120 | return {}; |
121 | |
122 | return {index_sp->GetValueAsUnsigned(fail_value: 0)}; |
123 | } |
124 | |
125 | ValueObjectSP (ValueObjectSP &impl_sp, uint64_t index) { |
126 | ValueObjectSP data_sp(impl_sp->GetChildMemberWithName(name: "__data" )); |
127 | |
128 | if (!data_sp) |
129 | return ValueObjectSP{}; |
130 | |
131 | ValueObjectSP current_level = data_sp; |
132 | for (uint64_t n = index; n != 0; --n) { |
133 | ValueObjectSP tail_sp(current_level->GetChildMemberWithName(name: "__tail" )); |
134 | |
135 | if (!tail_sp) |
136 | return ValueObjectSP{}; |
137 | |
138 | current_level = tail_sp; |
139 | } |
140 | |
141 | return current_level->GetChildMemberWithName(name: "__head" ); |
142 | } |
143 | } // namespace |
144 | |
145 | namespace lldb_private { |
146 | namespace formatters { |
147 | bool LibcxxVariantSummaryProvider(ValueObject &valobj, Stream &stream, |
148 | const TypeSummaryOptions &options) { |
149 | ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); |
150 | if (!valobj_sp) |
151 | return false; |
152 | |
153 | ValueObjectSP impl_sp = GetChildMemberWithName( |
154 | obj&: *valobj_sp, alternative_names: {ConstString("__impl_" ), ConstString("__impl" )}); |
155 | |
156 | if (!impl_sp) |
157 | return false; |
158 | |
159 | LibcxxVariantIndexValidity validity = LibcxxVariantGetIndexValidity(impl_sp); |
160 | |
161 | if (validity == LibcxxVariantIndexValidity::Invalid) |
162 | return false; |
163 | |
164 | if (validity == LibcxxVariantIndexValidity::NPos) { |
165 | stream.Printf(format: " No Value" ); |
166 | return true; |
167 | } |
168 | |
169 | auto optional_index_value = LibcxxVariantIndexValue(impl_sp); |
170 | |
171 | if (!optional_index_value) |
172 | return false; |
173 | |
174 | uint64_t index_value = *optional_index_value; |
175 | |
176 | ValueObjectSP nth_head = LibcxxVariantGetNthHead(impl_sp, index: index_value); |
177 | |
178 | if (!nth_head) |
179 | return false; |
180 | |
181 | CompilerType head_type = nth_head->GetCompilerType(); |
182 | |
183 | if (!head_type) |
184 | return false; |
185 | |
186 | CompilerType template_type = head_type.GetTypeTemplateArgument(idx: 1); |
187 | |
188 | if (!template_type) |
189 | return false; |
190 | |
191 | stream << " Active Type = " << template_type.GetDisplayTypeName() << " " ; |
192 | |
193 | return true; |
194 | } |
195 | } // namespace formatters |
196 | } // namespace lldb_private |
197 | |
198 | namespace { |
199 | class VariantFrontEnd : public SyntheticChildrenFrontEnd { |
200 | public: |
201 | VariantFrontEnd(ValueObject &valobj) : SyntheticChildrenFrontEnd(valobj) { |
202 | Update(); |
203 | } |
204 | |
205 | llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override { |
206 | auto optional_idx = formatters::ExtractIndexFromString(item_name: name.GetCString()); |
207 | if (!optional_idx) { |
208 | return llvm::createStringError(Fmt: "Type has no child named '%s'" , |
209 | Vals: name.AsCString()); |
210 | } |
211 | return *optional_idx; |
212 | } |
213 | |
214 | lldb::ChildCacheState Update() override; |
215 | llvm::Expected<uint32_t> CalculateNumChildren() override { return m_size; } |
216 | ValueObjectSP GetChildAtIndex(uint32_t idx) override; |
217 | |
218 | private: |
219 | size_t m_size = 0; |
220 | }; |
221 | } // namespace |
222 | |
223 | lldb::ChildCacheState VariantFrontEnd::Update() { |
224 | m_size = 0; |
225 | ValueObjectSP impl_sp = formatters::GetChildMemberWithName( |
226 | obj&: m_backend, alternative_names: {ConstString("__impl_" ), ConstString("__impl" )}); |
227 | if (!impl_sp) |
228 | return lldb::ChildCacheState::eRefetch; |
229 | |
230 | LibcxxVariantIndexValidity validity = LibcxxVariantGetIndexValidity(impl_sp); |
231 | |
232 | if (validity == LibcxxVariantIndexValidity::Invalid) |
233 | return lldb::ChildCacheState::eRefetch; |
234 | |
235 | if (validity == LibcxxVariantIndexValidity::NPos) |
236 | return lldb::ChildCacheState::eReuse; |
237 | |
238 | m_size = 1; |
239 | |
240 | return lldb::ChildCacheState::eRefetch; |
241 | } |
242 | |
243 | ValueObjectSP VariantFrontEnd::GetChildAtIndex(uint32_t idx) { |
244 | if (idx >= m_size) |
245 | return {}; |
246 | |
247 | ValueObjectSP impl_sp = formatters::GetChildMemberWithName( |
248 | obj&: m_backend, alternative_names: {ConstString("__impl_" ), ConstString("__impl" )}); |
249 | if (!impl_sp) |
250 | return {}; |
251 | |
252 | auto optional_index_value = LibcxxVariantIndexValue(impl_sp); |
253 | |
254 | if (!optional_index_value) |
255 | return {}; |
256 | |
257 | uint64_t index_value = *optional_index_value; |
258 | |
259 | ValueObjectSP nth_head = LibcxxVariantGetNthHead(impl_sp, index: index_value); |
260 | |
261 | if (!nth_head) |
262 | return {}; |
263 | |
264 | CompilerType head_type = nth_head->GetCompilerType(); |
265 | |
266 | if (!head_type) |
267 | return {}; |
268 | |
269 | CompilerType template_type = head_type.GetTypeTemplateArgument(idx: 1); |
270 | |
271 | if (!template_type) |
272 | return {}; |
273 | |
274 | ValueObjectSP head_value(nth_head->GetChildMemberWithName(name: "__value" )); |
275 | |
276 | if (!head_value) |
277 | return {}; |
278 | |
279 | return head_value->Clone(new_name: ConstString("Value" )); |
280 | } |
281 | |
282 | SyntheticChildrenFrontEnd * |
283 | formatters::LibcxxVariantFrontEndCreator(CXXSyntheticChildren *, |
284 | lldb::ValueObjectSP valobj_sp) { |
285 | if (valobj_sp) |
286 | return new VariantFrontEnd(*valobj_sp); |
287 | return nullptr; |
288 | } |
289 | |