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