xref: /llvm-project/lldb/source/Plugins/Language/CPlusPlus/LibCxxMap.cpp (revision fbd1b6567ae41d64f1dbb6b32c7cf8a1b710b8d9)
1 //===-- LibCxxMap.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/DataBufferHeap.h"
17 #include "lldb/Utility/Endian.h"
18 #include "lldb/Utility/Status.h"
19 #include "lldb/Utility/Stream.h"
20 #include "lldb/lldb-forward.h"
21 
22 using namespace lldb;
23 using namespace lldb_private;
24 using namespace lldb_private::formatters;
25 
26 class MapEntry {
27 public:
28   MapEntry() = default;
29   explicit MapEntry(ValueObjectSP entry_sp) : m_entry_sp(entry_sp) {}
30   explicit MapEntry(ValueObject *entry)
31       : m_entry_sp(entry ? entry->GetSP() : ValueObjectSP()) {}
32 
33   ValueObjectSP left() const {
34     if (!m_entry_sp)
35       return m_entry_sp;
36     return m_entry_sp->GetSyntheticChildAtOffset(
37         0, m_entry_sp->GetCompilerType(), true);
38   }
39 
40   ValueObjectSP right() const {
41     if (!m_entry_sp)
42       return m_entry_sp;
43     return m_entry_sp->GetSyntheticChildAtOffset(
44         m_entry_sp->GetProcessSP()->GetAddressByteSize(),
45         m_entry_sp->GetCompilerType(), true);
46   }
47 
48   ValueObjectSP parent() const {
49     if (!m_entry_sp)
50       return m_entry_sp;
51     return m_entry_sp->GetSyntheticChildAtOffset(
52         2 * m_entry_sp->GetProcessSP()->GetAddressByteSize(),
53         m_entry_sp->GetCompilerType(), true);
54   }
55 
56   uint64_t value() const {
57     if (!m_entry_sp)
58       return 0;
59     return m_entry_sp->GetValueAsUnsigned(0);
60   }
61 
62   bool error() const {
63     if (!m_entry_sp)
64       return true;
65     return m_entry_sp->GetError().Fail();
66   }
67 
68   bool null() const { return (value() == 0); }
69 
70   ValueObjectSP GetEntry() const { return m_entry_sp; }
71 
72   void SetEntry(ValueObjectSP entry) { m_entry_sp = entry; }
73 
74   bool operator==(const MapEntry &rhs) const {
75     return (rhs.m_entry_sp.get() == m_entry_sp.get());
76   }
77 
78 private:
79   ValueObjectSP m_entry_sp;
80 };
81 
82 class MapIterator {
83 public:
84   MapIterator(ValueObject *entry, size_t depth = 0)
85       : m_entry(entry), m_max_depth(depth), m_error(false) {}
86 
87   MapIterator() = default;
88 
89   ValueObjectSP value() { return m_entry.GetEntry(); }
90 
91   ValueObjectSP advance(size_t count) {
92     ValueObjectSP fail;
93     if (m_error)
94       return fail;
95     size_t steps = 0;
96     while (count > 0) {
97       next();
98       count--, steps++;
99       if (m_error || m_entry.null() || (steps > m_max_depth))
100         return fail;
101     }
102     return m_entry.GetEntry();
103   }
104 
105 private:
106   /// Mimicks libc++'s __tree_next algorithm, which libc++ uses
107   /// in its __tree_iteartor::operator++.
108   void next() {
109     if (m_entry.null())
110       return;
111     MapEntry right(m_entry.right());
112     if (!right.null()) {
113       m_entry = tree_min(std::move(right));
114       return;
115     }
116     size_t steps = 0;
117     while (!is_left_child(m_entry)) {
118       if (m_entry.error()) {
119         m_error = true;
120         return;
121       }
122       m_entry.SetEntry(m_entry.parent());
123       steps++;
124       if (steps > m_max_depth) {
125         m_entry = MapEntry();
126         return;
127       }
128     }
129     m_entry = MapEntry(m_entry.parent());
130   }
131 
132   /// Mimicks libc++'s __tree_min algorithm.
133   MapEntry tree_min(MapEntry x) {
134     if (x.null())
135       return MapEntry();
136     MapEntry left(x.left());
137     size_t steps = 0;
138     while (!left.null()) {
139       if (left.error()) {
140         m_error = true;
141         return MapEntry();
142       }
143       x = left;
144       left.SetEntry(x.left());
145       steps++;
146       if (steps > m_max_depth)
147         return MapEntry();
148     }
149     return x;
150   }
151 
152   bool is_left_child(const MapEntry &x) {
153     if (x.null())
154       return false;
155     MapEntry rhs(x.parent());
156     rhs.SetEntry(rhs.left());
157     return x.value() == rhs.value();
158   }
159 
160   MapEntry m_entry;
161   size_t m_max_depth = 0;
162   bool m_error = false;
163 };
164 
165 namespace lldb_private {
166 namespace formatters {
167 class LibcxxStdMapSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
168 public:
169   LibcxxStdMapSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
170 
171   ~LibcxxStdMapSyntheticFrontEnd() override = default;
172 
173   llvm::Expected<uint32_t> CalculateNumChildren() override;
174 
175   lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override;
176 
177   lldb::ChildCacheState Update() override;
178 
179   bool MightHaveChildren() override;
180 
181   size_t GetIndexOfChildWithName(ConstString name) override;
182 
183 private:
184   bool GetDataType();
185 
186   void GetValueOffset(const lldb::ValueObjectSP &node);
187 
188   /// Returns the ValueObject for the __tree_node type that
189   /// holds the key/value pair of the node at index \ref idx.
190   ///
191   /// \param[in] idx The child index that we're looking to get
192   ///                the key/value pair for.
193   ///
194   /// \param[in] max_depth The maximum search depth after which
195   ///                      we stop trying to find the key/value
196   ///                      pair for.
197   ///
198   /// \returns On success, returns the ValueObjectSP corresponding
199   ///          to the __tree_node's __value_ member (which holds
200   ///          the key/value pair the formatter wants to display).
201   ///          On failure, will return nullptr.
202   ValueObjectSP GetKeyValuePair(size_t idx, size_t max_depth);
203 
204   ValueObject *m_tree = nullptr;
205   ValueObject *m_root_node = nullptr;
206   CompilerType m_element_type;
207   uint32_t m_skip_size = UINT32_MAX;
208   size_t m_count = UINT32_MAX;
209   std::map<size_t, MapIterator> m_iterators;
210 };
211 
212 class LibCxxMapIteratorSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
213 public:
214   LibCxxMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
215 
216   llvm::Expected<uint32_t> CalculateNumChildren() override;
217 
218   lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override;
219 
220   lldb::ChildCacheState Update() override;
221 
222   bool MightHaveChildren() override;
223 
224   size_t GetIndexOfChildWithName(ConstString name) override;
225 
226   ~LibCxxMapIteratorSyntheticFrontEnd() override;
227 
228 private:
229   ValueObject *m_pair_ptr;
230   lldb::ValueObjectSP m_pair_sp;
231 };
232 } // namespace formatters
233 } // namespace lldb_private
234 
235 lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::
236     LibcxxStdMapSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
237     : SyntheticChildrenFrontEnd(*valobj_sp), m_element_type(), m_iterators() {
238   if (valobj_sp)
239     Update();
240 }
241 
242 llvm::Expected<uint32_t> lldb_private::formatters::
243     LibcxxStdMapSyntheticFrontEnd::CalculateNumChildren() {
244   if (m_count != UINT32_MAX)
245     return m_count;
246 
247   if (m_tree == nullptr)
248     return 0;
249 
250   ValueObjectSP size_node(m_tree->GetChildMemberWithName("__pair3_"));
251   if (!size_node)
252     return 0;
253 
254   size_node = GetFirstValueOfLibCXXCompressedPair(*size_node);
255 
256   if (!size_node)
257     return 0;
258 
259   m_count = size_node->GetValueAsUnsigned(0);
260   return m_count;
261 }
262 
263 bool lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::GetDataType() {
264   if (m_element_type.IsValid())
265     return true;
266   m_element_type.Clear();
267   ValueObjectSP deref;
268   Status error;
269   deref = m_root_node->Dereference(error);
270   if (!deref || error.Fail())
271     return false;
272   deref = deref->GetChildMemberWithName("__value_");
273   if (deref) {
274     m_element_type = deref->GetCompilerType();
275     return true;
276   }
277   deref = m_backend.GetChildAtNamePath({"__tree_", "__pair3_"});
278   if (!deref)
279     return false;
280   m_element_type = deref->GetCompilerType()
281                        .GetTypeTemplateArgument(1)
282                        .GetTypeTemplateArgument(1);
283   if (m_element_type) {
284     std::string name;
285     uint64_t bit_offset_ptr;
286     uint32_t bitfield_bit_size_ptr;
287     bool is_bitfield_ptr;
288     m_element_type = m_element_type.GetFieldAtIndex(
289         0, name, &bit_offset_ptr, &bitfield_bit_size_ptr, &is_bitfield_ptr);
290     m_element_type = m_element_type.GetTypedefedType();
291     return m_element_type.IsValid();
292   } else {
293     m_element_type = m_backend.GetCompilerType().GetTypeTemplateArgument(0);
294     return m_element_type.IsValid();
295   }
296 }
297 
298 void lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::GetValueOffset(
299     const lldb::ValueObjectSP &node) {
300   if (m_skip_size != UINT32_MAX)
301     return;
302   if (!node)
303     return;
304   CompilerType node_type(node->GetCompilerType());
305   uint64_t bit_offset;
306   if (node_type.GetIndexOfFieldWithName("__value_", nullptr, &bit_offset) !=
307       UINT32_MAX) {
308     // Old layout (pre d05b10ab4fc65)
309     m_skip_size = bit_offset / 8u;
310   } else {
311     auto ast_ctx = node_type.GetTypeSystem().dyn_cast_or_null<TypeSystemClang>();
312     if (!ast_ctx)
313       return;
314     CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier(
315         llvm::StringRef(),
316         {{"ptr0", ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()},
317          {"ptr1", ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()},
318          {"ptr2", ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()},
319          {"cw", ast_ctx->GetBasicType(lldb::eBasicTypeBool)},
320          {"payload", (m_element_type.GetCompleteType(), m_element_type)}});
321     std::string child_name;
322     uint32_t child_byte_size;
323     int32_t child_byte_offset = 0;
324     uint32_t child_bitfield_bit_size;
325     uint32_t child_bitfield_bit_offset;
326     bool child_is_base_class;
327     bool child_is_deref_of_parent;
328     uint64_t language_flags;
329     auto child_type =
330         llvm::expectedToStdOptional(tree_node_type.GetChildCompilerTypeAtIndex(
331             nullptr, 4, true, true, true, child_name, child_byte_size,
332             child_byte_offset, child_bitfield_bit_size,
333             child_bitfield_bit_offset, child_is_base_class,
334             child_is_deref_of_parent, nullptr, language_flags));
335     if (child_type && child_type->IsValid())
336       m_skip_size = (uint32_t)child_byte_offset;
337   }
338 }
339 
340 ValueObjectSP
341 lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::GetKeyValuePair(
342     size_t idx, size_t max_depth) {
343   MapIterator iterator(m_root_node, max_depth);
344 
345   const bool need_to_skip = (idx > 0);
346   size_t actual_advance = idx;
347   if (need_to_skip) {
348     // If we have already created the iterator for the previous
349     // index, we can start from there and advance by 1.
350     auto cached_iterator = m_iterators.find(idx - 1);
351     if (cached_iterator != m_iterators.end()) {
352       iterator = cached_iterator->second;
353       actual_advance = 1;
354     }
355   }
356 
357   ValueObjectSP iterated_sp(iterator.advance(actual_advance));
358   if (!iterated_sp)
359     // this tree is garbage - stop
360     return nullptr;
361 
362   if (!GetDataType())
363     return nullptr;
364 
365   if (!need_to_skip) {
366     Status error;
367     iterated_sp = iterated_sp->Dereference(error);
368     if (!iterated_sp || error.Fail())
369       return nullptr;
370 
371     GetValueOffset(iterated_sp);
372     auto child_sp = iterated_sp->GetChildMemberWithName("__value_");
373     if (child_sp) {
374       // Old layout (pre 089a7cc5dea)
375       iterated_sp = child_sp;
376     } else {
377       iterated_sp = iterated_sp->GetSyntheticChildAtOffset(
378           m_skip_size, m_element_type, true);
379     }
380 
381     if (!iterated_sp)
382       return nullptr;
383   } else {
384     // because of the way our debug info is made, we need to read item 0
385     // first so that we can cache information used to generate other elements
386     if (m_skip_size == UINT32_MAX)
387       GetChildAtIndex(0);
388 
389     if (m_skip_size == UINT32_MAX)
390       return nullptr;
391 
392     iterated_sp = iterated_sp->GetSyntheticChildAtOffset(m_skip_size,
393                                                          m_element_type, true);
394     if (!iterated_sp)
395       return nullptr;
396   }
397 
398   m_iterators[idx] = iterator;
399   assert(iterated_sp != nullptr &&
400          "Cached MapIterator for invalid ValueObject");
401 
402   return iterated_sp;
403 }
404 
405 lldb::ValueObjectSP
406 lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::GetChildAtIndex(
407     uint32_t idx) {
408   static ConstString g_cc_("__cc_"), g_cc("__cc");
409   static ConstString g_nc("__nc");
410   uint32_t num_children = CalculateNumChildrenIgnoringErrors();
411   if (idx >= num_children)
412     return nullptr;
413 
414   if (m_tree == nullptr || m_root_node == nullptr)
415     return nullptr;
416 
417   ValueObjectSP key_val_sp = GetKeyValuePair(idx, /*max_depth=*/num_children);
418   if (!key_val_sp) {
419     // this will stop all future searches until an Update() happens
420     m_tree = nullptr;
421     return nullptr;
422   }
423 
424   // at this point we have a valid
425   // we need to copy current_sp into a new object otherwise we will end up with
426   // all items named __value_
427   StreamString name;
428   name.Printf("[%" PRIu64 "]", (uint64_t)idx);
429   auto potential_child_sp = key_val_sp->Clone(ConstString(name.GetString()));
430   if (potential_child_sp) {
431     switch (potential_child_sp->GetNumChildrenIgnoringErrors()) {
432     case 1: {
433       auto child0_sp = potential_child_sp->GetChildAtIndex(0);
434       if (child0_sp &&
435           (child0_sp->GetName() == g_cc_ || child0_sp->GetName() == g_cc))
436         potential_child_sp = child0_sp->Clone(ConstString(name.GetString()));
437       break;
438     }
439     case 2: {
440       auto child0_sp = potential_child_sp->GetChildAtIndex(0);
441       auto child1_sp = potential_child_sp->GetChildAtIndex(1);
442       if (child0_sp &&
443           (child0_sp->GetName() == g_cc_ || child0_sp->GetName() == g_cc) &&
444           child1_sp && child1_sp->GetName() == g_nc)
445         potential_child_sp = child0_sp->Clone(ConstString(name.GetString()));
446       break;
447     }
448     }
449   }
450   return potential_child_sp;
451 }
452 
453 lldb::ChildCacheState
454 lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::Update() {
455   m_count = UINT32_MAX;
456   m_tree = m_root_node = nullptr;
457   m_iterators.clear();
458   m_tree = m_backend.GetChildMemberWithName("__tree_").get();
459   if (!m_tree)
460     return lldb::ChildCacheState::eRefetch;
461   m_root_node = m_tree->GetChildMemberWithName("__begin_node_").get();
462   return lldb::ChildCacheState::eRefetch;
463 }
464 
465 bool lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::
466     MightHaveChildren() {
467   return true;
468 }
469 
470 size_t lldb_private::formatters::LibcxxStdMapSyntheticFrontEnd::
471     GetIndexOfChildWithName(ConstString name) {
472   return ExtractIndexFromString(name.GetCString());
473 }
474 
475 SyntheticChildrenFrontEnd *
476 lldb_private::formatters::LibcxxStdMapSyntheticFrontEndCreator(
477     CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
478   return (valobj_sp ? new LibcxxStdMapSyntheticFrontEnd(valobj_sp) : nullptr);
479 }
480 
481 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::
482     LibCxxMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
483     : SyntheticChildrenFrontEnd(*valobj_sp), m_pair_ptr(), m_pair_sp() {
484   if (valobj_sp)
485     Update();
486 }
487 
488 lldb::ChildCacheState
489 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::Update() {
490   m_pair_sp.reset();
491   m_pair_ptr = nullptr;
492 
493   ValueObjectSP valobj_sp = m_backend.GetSP();
494   if (!valobj_sp)
495     return lldb::ChildCacheState::eRefetch;
496 
497   TargetSP target_sp(valobj_sp->GetTargetSP());
498 
499   if (!target_sp)
500     return lldb::ChildCacheState::eRefetch;
501 
502   // this must be a ValueObject* because it is a child of the ValueObject we
503   // are producing children for it if were a ValueObjectSP, we would end up
504   // with a loop (iterator -> synthetic -> child -> parent == iterator) and
505   // that would in turn leak memory by never allowing the ValueObjects to die
506   // and free their memory
507   m_pair_ptr = valobj_sp
508                    ->GetValueForExpressionPath(
509                        ".__i_.__ptr_->__value_", nullptr, nullptr,
510                        ValueObject::GetValueForExpressionPathOptions()
511                            .DontCheckDotVsArrowSyntax()
512                            .SetSyntheticChildrenTraversal(
513                                ValueObject::GetValueForExpressionPathOptions::
514                                    SyntheticChildrenTraversal::None),
515                        nullptr)
516                    .get();
517 
518   if (!m_pair_ptr) {
519     m_pair_ptr = valobj_sp
520                      ->GetValueForExpressionPath(
521                          ".__i_.__ptr_", nullptr, nullptr,
522                          ValueObject::GetValueForExpressionPathOptions()
523                              .DontCheckDotVsArrowSyntax()
524                              .SetSyntheticChildrenTraversal(
525                                  ValueObject::GetValueForExpressionPathOptions::
526                                      SyntheticChildrenTraversal::None),
527                          nullptr)
528                      .get();
529     if (m_pair_ptr) {
530       auto __i_(valobj_sp->GetChildMemberWithName("__i_"));
531       if (!__i_) {
532         m_pair_ptr = nullptr;
533         return lldb::ChildCacheState::eRefetch;
534       }
535       CompilerType pair_type(
536           __i_->GetCompilerType().GetTypeTemplateArgument(0));
537       std::string name;
538       uint64_t bit_offset_ptr;
539       uint32_t bitfield_bit_size_ptr;
540       bool is_bitfield_ptr;
541       pair_type = pair_type.GetFieldAtIndex(
542           0, name, &bit_offset_ptr, &bitfield_bit_size_ptr, &is_bitfield_ptr);
543       if (!pair_type) {
544         m_pair_ptr = nullptr;
545         return lldb::ChildCacheState::eRefetch;
546       }
547 
548       auto addr(m_pair_ptr->GetValueAsUnsigned(LLDB_INVALID_ADDRESS));
549       m_pair_ptr = nullptr;
550       if (addr && addr != LLDB_INVALID_ADDRESS) {
551         auto ts = pair_type.GetTypeSystem();
552         auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>();
553         if (!ast_ctx)
554           return lldb::ChildCacheState::eRefetch;
555 
556         // Mimick layout of std::__tree_iterator::__ptr_ and read it in
557         // from process memory.
558         //
559         // The following shows the contiguous block of memory:
560         //
561         //        +-----------------------------+ class __tree_end_node
562         // __ptr_ | pointer __left_;            |
563         //        +-----------------------------+ class __tree_node_base
564         //        | pointer __right_;           |
565         //        | __parent_pointer __parent_; |
566         //        | bool __is_black_;           |
567         //        +-----------------------------+ class __tree_node
568         //        | __node_value_type __value_; | <<< our key/value pair
569         //        +-----------------------------+
570         //
571         CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier(
572             llvm::StringRef(),
573             {{"ptr0",
574               ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()},
575              {"ptr1",
576               ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()},
577              {"ptr2",
578               ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()},
579              {"cw", ast_ctx->GetBasicType(lldb::eBasicTypeBool)},
580              {"payload", pair_type}});
581         std::optional<uint64_t> size = tree_node_type.GetByteSize(nullptr);
582         if (!size)
583           return lldb::ChildCacheState::eRefetch;
584         WritableDataBufferSP buffer_sp(new DataBufferHeap(*size, 0));
585         ProcessSP process_sp(target_sp->GetProcessSP());
586         Status error;
587         process_sp->ReadMemory(addr, buffer_sp->GetBytes(),
588                                buffer_sp->GetByteSize(), error);
589         if (error.Fail())
590           return lldb::ChildCacheState::eRefetch;
591         DataExtractor extractor(buffer_sp, process_sp->GetByteOrder(),
592                                 process_sp->GetAddressByteSize());
593         auto pair_sp = CreateValueObjectFromData(
594             "pair", extractor, valobj_sp->GetExecutionContextRef(),
595             tree_node_type);
596         if (pair_sp)
597           m_pair_sp = pair_sp->GetChildAtIndex(4);
598       }
599     }
600   }
601 
602   return lldb::ChildCacheState::eRefetch;
603 }
604 
605 llvm::Expected<uint32_t> lldb_private::formatters::
606     LibCxxMapIteratorSyntheticFrontEnd::CalculateNumChildren() {
607   return 2;
608 }
609 
610 lldb::ValueObjectSP
611 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::GetChildAtIndex(
612     uint32_t idx) {
613   if (m_pair_ptr)
614     return m_pair_ptr->GetChildAtIndex(idx);
615   if (m_pair_sp)
616     return m_pair_sp->GetChildAtIndex(idx);
617   return lldb::ValueObjectSP();
618 }
619 
620 bool lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::
621     MightHaveChildren() {
622   return true;
623 }
624 
625 size_t lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::
626     GetIndexOfChildWithName(ConstString name) {
627   if (name == "first")
628     return 0;
629   if (name == "second")
630     return 1;
631   return UINT32_MAX;
632 }
633 
634 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::
635     ~LibCxxMapIteratorSyntheticFrontEnd() {
636   // this will be deleted when its parent dies (since it's a child object)
637   // delete m_pair_ptr;
638 }
639 
640 SyntheticChildrenFrontEnd *
641 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEndCreator(
642     CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
643   return (valobj_sp ? new LibCxxMapIteratorSyntheticFrontEnd(valobj_sp)
644                     : nullptr);
645 }
646