1 //===-- LibCxx.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 "lldb/Core/Debugger.h" 12 #include "lldb/Core/FormatEntity.h" 13 #include "lldb/Core/ValueObject.h" 14 #include "lldb/Core/ValueObjectConstResult.h" 15 #include "lldb/DataFormatters/FormattersHelpers.h" 16 #include "lldb/DataFormatters/StringPrinter.h" 17 #include "lldb/DataFormatters/TypeSummary.h" 18 #include "lldb/DataFormatters/VectorIterator.h" 19 #include "lldb/Target/SectionLoadList.h" 20 #include "lldb/Target/Target.h" 21 #include "lldb/Utility/ConstString.h" 22 #include "lldb/Utility/DataBufferHeap.h" 23 #include "lldb/Utility/Endian.h" 24 #include "lldb/Utility/Status.h" 25 #include "lldb/Utility/Stream.h" 26 27 #include "Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h" 28 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" 29 #include "lldb/lldb-enumerations.h" 30 #include <optional> 31 #include <tuple> 32 33 using namespace lldb; 34 using namespace lldb_private; 35 using namespace lldb_private::formatters; 36 37 lldb::ValueObjectSP lldb_private::formatters::GetChildMemberWithName( 38 ValueObject &obj, llvm::ArrayRef<ConstString> alternative_names) { 39 for (ConstString name : alternative_names) { 40 lldb::ValueObjectSP child_sp = obj.GetChildMemberWithName(name); 41 42 if (child_sp) 43 return child_sp; 44 } 45 return {}; 46 } 47 48 lldb::ValueObjectSP 49 lldb_private::formatters::GetFirstValueOfLibCXXCompressedPair( 50 ValueObject &pair) { 51 ValueObjectSP value; 52 ValueObjectSP first_child = pair.GetChildAtIndex(0); 53 if (first_child) 54 value = first_child->GetChildMemberWithName("__value_"); 55 if (!value) { 56 // pre-r300140 member name 57 value = pair.GetChildMemberWithName("__first_"); 58 } 59 return value; 60 } 61 62 lldb::ValueObjectSP 63 lldb_private::formatters::GetSecondValueOfLibCXXCompressedPair( 64 ValueObject &pair) { 65 ValueObjectSP value; 66 if (pair.GetNumChildren() > 1) { 67 ValueObjectSP second_child = pair.GetChildAtIndex(1); 68 if (second_child) { 69 value = second_child->GetChildMemberWithName("__value_"); 70 } 71 } 72 if (!value) { 73 // pre-r300140 member name 74 value = pair.GetChildMemberWithName("__second_"); 75 } 76 return value; 77 } 78 79 bool lldb_private::formatters::LibcxxFunctionSummaryProvider( 80 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 81 82 ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); 83 84 if (!valobj_sp) 85 return false; 86 87 ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef()); 88 Process *process = exe_ctx.GetProcessPtr(); 89 90 if (process == nullptr) 91 return false; 92 93 CPPLanguageRuntime *cpp_runtime = CPPLanguageRuntime::Get(*process); 94 95 if (!cpp_runtime) 96 return false; 97 98 CPPLanguageRuntime::LibCppStdFunctionCallableInfo callable_info = 99 cpp_runtime->FindLibCppStdFunctionCallableInfo(valobj_sp); 100 101 switch (callable_info.callable_case) { 102 case CPPLanguageRuntime::LibCppStdFunctionCallableCase::Invalid: 103 stream.Printf(" __f_ = %" PRIu64, callable_info.member_f_pointer_value); 104 return false; 105 break; 106 case CPPLanguageRuntime::LibCppStdFunctionCallableCase::Lambda: 107 stream.Printf( 108 " Lambda in File %s at Line %u", 109 callable_info.callable_line_entry.file.GetFilename().GetCString(), 110 callable_info.callable_line_entry.line); 111 break; 112 case CPPLanguageRuntime::LibCppStdFunctionCallableCase::CallableObject: 113 stream.Printf( 114 " Function in File %s at Line %u", 115 callable_info.callable_line_entry.file.GetFilename().GetCString(), 116 callable_info.callable_line_entry.line); 117 break; 118 case CPPLanguageRuntime::LibCppStdFunctionCallableCase::FreeOrMemberFunction: 119 stream.Printf(" Function = %s ", 120 callable_info.callable_symbol.GetName().GetCString()); 121 break; 122 } 123 124 return true; 125 } 126 127 bool lldb_private::formatters::LibcxxSmartPointerSummaryProvider( 128 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 129 ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); 130 if (!valobj_sp) 131 return false; 132 ValueObjectSP ptr_sp(valobj_sp->GetChildMemberWithName("__ptr_")); 133 ValueObjectSP count_sp( 134 valobj_sp->GetChildAtNamePath({"__cntrl_", "__shared_owners_"})); 135 ValueObjectSP weakcount_sp( 136 valobj_sp->GetChildAtNamePath({"__cntrl_", "__shared_weak_owners_"})); 137 138 if (!ptr_sp) 139 return false; 140 141 if (ptr_sp->GetValueAsUnsigned(0) == 0) { 142 stream.Printf("nullptr"); 143 return true; 144 } else { 145 bool print_pointee = false; 146 Status error; 147 ValueObjectSP pointee_sp = ptr_sp->Dereference(error); 148 if (pointee_sp && error.Success()) { 149 if (pointee_sp->DumpPrintableRepresentation( 150 stream, ValueObject::eValueObjectRepresentationStyleSummary, 151 lldb::eFormatInvalid, 152 ValueObject::PrintableRepresentationSpecialCases::eDisable, 153 false)) 154 print_pointee = true; 155 } 156 if (!print_pointee) 157 stream.Printf("ptr = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0)); 158 } 159 160 if (count_sp) 161 stream.Printf(" strong=%" PRIu64, 1 + count_sp->GetValueAsUnsigned(0)); 162 163 if (weakcount_sp) 164 stream.Printf(" weak=%" PRIu64, 1 + weakcount_sp->GetValueAsUnsigned(0)); 165 166 return true; 167 } 168 169 bool lldb_private::formatters::LibcxxUniquePointerSummaryProvider( 170 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 171 ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); 172 if (!valobj_sp) 173 return false; 174 175 ValueObjectSP ptr_sp(valobj_sp->GetChildMemberWithName("__ptr_")); 176 if (!ptr_sp) 177 return false; 178 179 ptr_sp = GetFirstValueOfLibCXXCompressedPair(*ptr_sp); 180 if (!ptr_sp) 181 return false; 182 183 if (ptr_sp->GetValueAsUnsigned(0) == 0) { 184 stream.Printf("nullptr"); 185 return true; 186 } else { 187 bool print_pointee = false; 188 Status error; 189 ValueObjectSP pointee_sp = ptr_sp->Dereference(error); 190 if (pointee_sp && error.Success()) { 191 if (pointee_sp->DumpPrintableRepresentation( 192 stream, ValueObject::eValueObjectRepresentationStyleSummary, 193 lldb::eFormatInvalid, 194 ValueObject::PrintableRepresentationSpecialCases::eDisable, 195 false)) 196 print_pointee = true; 197 } 198 if (!print_pointee) 199 stream.Printf("ptr = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0)); 200 } 201 202 return true; 203 } 204 205 /* 206 (lldb) fr var ibeg --raw --ptr-depth 1 207 (std::__1::__map_iterator<std::__1::__tree_iterator<std::__1::pair<int, 208 std::__1::basic_string<char, std::__1::char_traits<char>, 209 std::__1::allocator<char> > >, std::__1::__tree_node<std::__1::pair<int, 210 std::__1::basic_string<char, std::__1::char_traits<char>, 211 std::__1::allocator<char> > >, void *> *, long> >) ibeg = { 212 __i_ = { 213 __ptr_ = 0x0000000100103870 { 214 std::__1::__tree_node_base<void *> = { 215 std::__1::__tree_end_node<std::__1::__tree_node_base<void *> *> = { 216 __left_ = 0x0000000000000000 217 } 218 __right_ = 0x0000000000000000 219 __parent_ = 0x00000001001038b0 220 __is_black_ = true 221 } 222 __value_ = { 223 first = 0 224 second = { std::string } 225 */ 226 227 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd:: 228 LibCxxMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) 229 : SyntheticChildrenFrontEnd(*valobj_sp), m_pair_ptr(), m_pair_sp() { 230 if (valobj_sp) 231 Update(); 232 } 233 234 bool lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::Update() { 235 m_pair_sp.reset(); 236 m_pair_ptr = nullptr; 237 238 ValueObjectSP valobj_sp = m_backend.GetSP(); 239 if (!valobj_sp) 240 return false; 241 242 TargetSP target_sp(valobj_sp->GetTargetSP()); 243 244 if (!target_sp) 245 return false; 246 247 if (!valobj_sp) 248 return false; 249 250 // this must be a ValueObject* because it is a child of the ValueObject we 251 // are producing children for it if were a ValueObjectSP, we would end up 252 // with a loop (iterator -> synthetic -> child -> parent == iterator) and 253 // that would in turn leak memory by never allowing the ValueObjects to die 254 // and free their memory 255 m_pair_ptr = valobj_sp 256 ->GetValueForExpressionPath( 257 ".__i_.__ptr_->__value_", nullptr, nullptr, 258 ValueObject::GetValueForExpressionPathOptions() 259 .DontCheckDotVsArrowSyntax() 260 .SetSyntheticChildrenTraversal( 261 ValueObject::GetValueForExpressionPathOptions:: 262 SyntheticChildrenTraversal::None), 263 nullptr) 264 .get(); 265 266 if (!m_pair_ptr) { 267 m_pair_ptr = valobj_sp 268 ->GetValueForExpressionPath( 269 ".__i_.__ptr_", nullptr, nullptr, 270 ValueObject::GetValueForExpressionPathOptions() 271 .DontCheckDotVsArrowSyntax() 272 .SetSyntheticChildrenTraversal( 273 ValueObject::GetValueForExpressionPathOptions:: 274 SyntheticChildrenTraversal::None), 275 nullptr) 276 .get(); 277 if (m_pair_ptr) { 278 auto __i_(valobj_sp->GetChildMemberWithName("__i_")); 279 if (!__i_) { 280 m_pair_ptr = nullptr; 281 return false; 282 } 283 CompilerType pair_type( 284 __i_->GetCompilerType().GetTypeTemplateArgument(0)); 285 std::string name; 286 uint64_t bit_offset_ptr; 287 uint32_t bitfield_bit_size_ptr; 288 bool is_bitfield_ptr; 289 pair_type = pair_type.GetFieldAtIndex( 290 0, name, &bit_offset_ptr, &bitfield_bit_size_ptr, &is_bitfield_ptr); 291 if (!pair_type) { 292 m_pair_ptr = nullptr; 293 return false; 294 } 295 296 auto addr(m_pair_ptr->GetValueAsUnsigned(LLDB_INVALID_ADDRESS)); 297 m_pair_ptr = nullptr; 298 if (addr && addr != LLDB_INVALID_ADDRESS) { 299 auto ts = pair_type.GetTypeSystem(); 300 auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>(); 301 if (!ast_ctx) 302 return false; 303 304 // Mimick layout of std::__tree_iterator::__ptr_ and read it in 305 // from process memory. 306 // 307 // The following shows the contiguous block of memory: 308 // 309 // +-----------------------------+ class __tree_end_node 310 // __ptr_ | pointer __left_; | 311 // +-----------------------------+ class __tree_node_base 312 // | pointer __right_; | 313 // | __parent_pointer __parent_; | 314 // | bool __is_black_; | 315 // +-----------------------------+ class __tree_node 316 // | __node_value_type __value_; | <<< our key/value pair 317 // +-----------------------------+ 318 // 319 CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier( 320 llvm::StringRef(), 321 {{"ptr0", 322 ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()}, 323 {"ptr1", 324 ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()}, 325 {"ptr2", 326 ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()}, 327 {"cw", ast_ctx->GetBasicType(lldb::eBasicTypeBool)}, 328 {"payload", pair_type}}); 329 std::optional<uint64_t> size = tree_node_type.GetByteSize(nullptr); 330 if (!size) 331 return false; 332 WritableDataBufferSP buffer_sp(new DataBufferHeap(*size, 0)); 333 ProcessSP process_sp(target_sp->GetProcessSP()); 334 Status error; 335 process_sp->ReadMemory(addr, buffer_sp->GetBytes(), 336 buffer_sp->GetByteSize(), error); 337 if (error.Fail()) 338 return false; 339 DataExtractor extractor(buffer_sp, process_sp->GetByteOrder(), 340 process_sp->GetAddressByteSize()); 341 auto pair_sp = CreateValueObjectFromData( 342 "pair", extractor, valobj_sp->GetExecutionContextRef(), 343 tree_node_type); 344 if (pair_sp) 345 m_pair_sp = pair_sp->GetChildAtIndex(4); 346 } 347 } 348 } 349 350 return false; 351 } 352 353 size_t lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd:: 354 CalculateNumChildren() { 355 return 2; 356 } 357 358 lldb::ValueObjectSP 359 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd::GetChildAtIndex( 360 size_t idx) { 361 if (m_pair_ptr) 362 return m_pair_ptr->GetChildAtIndex(idx); 363 if (m_pair_sp) 364 return m_pair_sp->GetChildAtIndex(idx); 365 return lldb::ValueObjectSP(); 366 } 367 368 bool lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd:: 369 MightHaveChildren() { 370 return true; 371 } 372 373 size_t lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd:: 374 GetIndexOfChildWithName(ConstString name) { 375 if (name == "first") 376 return 0; 377 if (name == "second") 378 return 1; 379 return UINT32_MAX; 380 } 381 382 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEnd:: 383 ~LibCxxMapIteratorSyntheticFrontEnd() { 384 // this will be deleted when its parent dies (since it's a child object) 385 // delete m_pair_ptr; 386 } 387 388 SyntheticChildrenFrontEnd * 389 lldb_private::formatters::LibCxxMapIteratorSyntheticFrontEndCreator( 390 CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { 391 return (valobj_sp ? new LibCxxMapIteratorSyntheticFrontEnd(valobj_sp) 392 : nullptr); 393 } 394 395 lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: 396 LibCxxUnorderedMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) 397 : SyntheticChildrenFrontEnd(*valobj_sp) { 398 if (valobj_sp) 399 Update(); 400 } 401 402 bool lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: 403 Update() { 404 m_pair_sp.reset(); 405 m_iter_ptr = nullptr; 406 407 ValueObjectSP valobj_sp = m_backend.GetSP(); 408 if (!valobj_sp) 409 return false; 410 411 TargetSP target_sp(valobj_sp->GetTargetSP()); 412 413 if (!target_sp) 414 return false; 415 416 if (!valobj_sp) 417 return false; 418 419 auto exprPathOptions = ValueObject::GetValueForExpressionPathOptions() 420 .DontCheckDotVsArrowSyntax() 421 .SetSyntheticChildrenTraversal( 422 ValueObject::GetValueForExpressionPathOptions:: 423 SyntheticChildrenTraversal::None); 424 425 // This must be a ValueObject* because it is a child of the ValueObject we 426 // are producing children for it if were a ValueObjectSP, we would end up 427 // with a loop (iterator -> synthetic -> child -> parent == iterator) and 428 // that would in turn leak memory by never allowing the ValueObjects to die 429 // and free their memory. 430 m_iter_ptr = 431 valobj_sp 432 ->GetValueForExpressionPath(".__i_.__node_", nullptr, nullptr, 433 exprPathOptions, nullptr) 434 .get(); 435 436 if (m_iter_ptr) { 437 auto iter_child(valobj_sp->GetChildMemberWithName("__i_")); 438 if (!iter_child) { 439 m_iter_ptr = nullptr; 440 return false; 441 } 442 443 CompilerType node_type(iter_child->GetCompilerType() 444 .GetTypeTemplateArgument(0) 445 .GetPointeeType()); 446 447 CompilerType pair_type(node_type.GetTypeTemplateArgument(0)); 448 449 std::string name; 450 uint64_t bit_offset_ptr; 451 uint32_t bitfield_bit_size_ptr; 452 bool is_bitfield_ptr; 453 454 pair_type = pair_type.GetFieldAtIndex( 455 0, name, &bit_offset_ptr, &bitfield_bit_size_ptr, &is_bitfield_ptr); 456 if (!pair_type) { 457 m_iter_ptr = nullptr; 458 return false; 459 } 460 461 uint64_t addr = m_iter_ptr->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); 462 m_iter_ptr = nullptr; 463 464 if (addr == 0 || addr == LLDB_INVALID_ADDRESS) 465 return false; 466 467 auto ts = pair_type.GetTypeSystem(); 468 auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>(); 469 if (!ast_ctx) 470 return false; 471 472 // Mimick layout of std::__hash_iterator::__node_ and read it in 473 // from process memory. 474 // 475 // The following shows the contiguous block of memory: 476 // 477 // +-----------------------------+ class __hash_node_base 478 // __node_ | __next_pointer __next_; | 479 // +-----------------------------+ class __hash_node 480 // | size_t __hash_; | 481 // | __node_value_type __value_; | <<< our key/value pair 482 // +-----------------------------+ 483 // 484 CompilerType tree_node_type = ast_ctx->CreateStructForIdentifier( 485 llvm::StringRef(), 486 {{"__next_", 487 ast_ctx->GetBasicType(lldb::eBasicTypeVoid).GetPointerType()}, 488 {"__hash_", ast_ctx->GetBasicType(lldb::eBasicTypeUnsignedLongLong)}, 489 {"__value_", pair_type}}); 490 std::optional<uint64_t> size = tree_node_type.GetByteSize(nullptr); 491 if (!size) 492 return false; 493 WritableDataBufferSP buffer_sp(new DataBufferHeap(*size, 0)); 494 ProcessSP process_sp(target_sp->GetProcessSP()); 495 Status error; 496 process_sp->ReadMemory(addr, buffer_sp->GetBytes(), 497 buffer_sp->GetByteSize(), error); 498 if (error.Fail()) 499 return false; 500 DataExtractor extractor(buffer_sp, process_sp->GetByteOrder(), 501 process_sp->GetAddressByteSize()); 502 auto pair_sp = CreateValueObjectFromData( 503 "pair", extractor, valobj_sp->GetExecutionContextRef(), tree_node_type); 504 if (pair_sp) 505 m_pair_sp = pair_sp->GetChildAtIndex(2); 506 } 507 508 return false; 509 } 510 511 size_t lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: 512 CalculateNumChildren() { 513 return 2; 514 } 515 516 lldb::ValueObjectSP lldb_private::formatters:: 517 LibCxxUnorderedMapIteratorSyntheticFrontEnd::GetChildAtIndex(size_t idx) { 518 if (m_pair_sp) 519 return m_pair_sp->GetChildAtIndex(idx); 520 return lldb::ValueObjectSP(); 521 } 522 523 bool lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: 524 MightHaveChildren() { 525 return true; 526 } 527 528 size_t lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEnd:: 529 GetIndexOfChildWithName(ConstString name) { 530 if (name == "first") 531 return 0; 532 if (name == "second") 533 return 1; 534 return UINT32_MAX; 535 } 536 537 SyntheticChildrenFrontEnd * 538 lldb_private::formatters::LibCxxUnorderedMapIteratorSyntheticFrontEndCreator( 539 CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { 540 return (valobj_sp ? new LibCxxUnorderedMapIteratorSyntheticFrontEnd(valobj_sp) 541 : nullptr); 542 } 543 544 /* 545 (lldb) fr var ibeg --raw --ptr-depth 1 -T 546 (std::__1::__wrap_iter<int *>) ibeg = { 547 (std::__1::__wrap_iter<int *>::iterator_type) __i = 0x00000001001037a0 { 548 (int) *__i = 1 549 } 550 } 551 */ 552 553 SyntheticChildrenFrontEnd * 554 lldb_private::formatters::LibCxxVectorIteratorSyntheticFrontEndCreator( 555 CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { 556 return (valobj_sp ? new VectorIteratorSyntheticFrontEnd( 557 valobj_sp, {ConstString("__i_"), ConstString("__i")}) 558 : nullptr); 559 } 560 561 lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: 562 LibcxxSharedPtrSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) 563 : SyntheticChildrenFrontEnd(*valobj_sp), m_cntrl(nullptr) { 564 if (valobj_sp) 565 Update(); 566 } 567 568 size_t lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: 569 CalculateNumChildren() { 570 return (m_cntrl ? 1 : 0); 571 } 572 573 lldb::ValueObjectSP 574 lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd::GetChildAtIndex( 575 size_t idx) { 576 if (!m_cntrl) 577 return lldb::ValueObjectSP(); 578 579 ValueObjectSP valobj_sp = m_backend.GetSP(); 580 if (!valobj_sp) 581 return lldb::ValueObjectSP(); 582 583 if (idx == 0) 584 return valobj_sp->GetChildMemberWithName("__ptr_"); 585 586 if (idx == 1) { 587 if (auto ptr_sp = valobj_sp->GetChildMemberWithName("__ptr_")) { 588 Status status; 589 auto value_type_sp = 590 valobj_sp->GetCompilerType() 591 .GetTypeTemplateArgument(0).GetPointerType(); 592 ValueObjectSP cast_ptr_sp = ptr_sp->Cast(value_type_sp); 593 ValueObjectSP value_sp = cast_ptr_sp->Dereference(status); 594 if (status.Success()) { 595 return value_sp; 596 } 597 } 598 } 599 600 return lldb::ValueObjectSP(); 601 } 602 603 bool lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd::Update() { 604 m_cntrl = nullptr; 605 606 ValueObjectSP valobj_sp = m_backend.GetSP(); 607 if (!valobj_sp) 608 return false; 609 610 TargetSP target_sp(valobj_sp->GetTargetSP()); 611 if (!target_sp) 612 return false; 613 614 lldb::ValueObjectSP cntrl_sp(valobj_sp->GetChildMemberWithName("__cntrl_")); 615 616 m_cntrl = cntrl_sp.get(); // need to store the raw pointer to avoid a circular 617 // dependency 618 return false; 619 } 620 621 bool lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: 622 MightHaveChildren() { 623 return true; 624 } 625 626 size_t lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: 627 GetIndexOfChildWithName(ConstString name) { 628 if (name == "__ptr_") 629 return 0; 630 if (name == "$$dereference$$") 631 return 1; 632 return UINT32_MAX; 633 } 634 635 lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEnd:: 636 ~LibcxxSharedPtrSyntheticFrontEnd() = default; 637 638 SyntheticChildrenFrontEnd * 639 lldb_private::formatters::LibcxxSharedPtrSyntheticFrontEndCreator( 640 CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { 641 return (valobj_sp ? new LibcxxSharedPtrSyntheticFrontEnd(valobj_sp) 642 : nullptr); 643 } 644 645 lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: 646 LibcxxUniquePtrSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) 647 : SyntheticChildrenFrontEnd(*valobj_sp) { 648 if (valobj_sp) 649 Update(); 650 } 651 652 lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: 653 ~LibcxxUniquePtrSyntheticFrontEnd() = default; 654 655 SyntheticChildrenFrontEnd * 656 lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEndCreator( 657 CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { 658 return (valobj_sp ? new LibcxxUniquePtrSyntheticFrontEnd(valobj_sp) 659 : nullptr); 660 } 661 662 size_t lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: 663 CalculateNumChildren() { 664 if (m_value_ptr_sp) 665 return m_deleter_sp ? 2 : 1; 666 return 0; 667 } 668 669 lldb::ValueObjectSP 670 lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd::GetChildAtIndex( 671 size_t idx) { 672 if (!m_value_ptr_sp) 673 return lldb::ValueObjectSP(); 674 675 if (idx == 0) 676 return m_value_ptr_sp; 677 678 if (idx == 1) 679 return m_deleter_sp; 680 681 if (idx == 2) { 682 Status status; 683 auto value_sp = m_value_ptr_sp->Dereference(status); 684 if (status.Success()) { 685 return value_sp; 686 } 687 } 688 689 return lldb::ValueObjectSP(); 690 } 691 692 bool lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd::Update() { 693 ValueObjectSP valobj_sp = m_backend.GetSP(); 694 if (!valobj_sp) 695 return false; 696 697 ValueObjectSP ptr_sp(valobj_sp->GetChildMemberWithName("__ptr_")); 698 if (!ptr_sp) 699 return false; 700 701 // Retrieve the actual pointer and the deleter, and clone them to give them 702 // user-friendly names. 703 ValueObjectSP value_pointer_sp = GetFirstValueOfLibCXXCompressedPair(*ptr_sp); 704 if (value_pointer_sp) 705 m_value_ptr_sp = value_pointer_sp->Clone(ConstString("pointer")); 706 707 ValueObjectSP deleter_sp = GetSecondValueOfLibCXXCompressedPair(*ptr_sp); 708 if (deleter_sp) 709 m_deleter_sp = deleter_sp->Clone(ConstString("deleter")); 710 711 return false; 712 } 713 714 bool lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: 715 MightHaveChildren() { 716 return true; 717 } 718 719 size_t lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd:: 720 GetIndexOfChildWithName(ConstString name) { 721 if (name == "pointer") 722 return 0; 723 if (name == "deleter") 724 return 1; 725 if (name == "$$dereference$$") 726 return 2; 727 return UINT32_MAX; 728 } 729 730 bool lldb_private::formatters::LibcxxContainerSummaryProvider( 731 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 732 if (valobj.IsPointerType()) { 733 uint64_t value = valobj.GetValueAsUnsigned(0); 734 if (!value) 735 return false; 736 stream.Printf("0x%016" PRIx64 " ", value); 737 } 738 return FormatEntity::FormatStringRef("size=${svar%#}", stream, nullptr, 739 nullptr, nullptr, &valobj, false, false); 740 } 741 742 /// The field layout in a libc++ string (cap, side, data or data, size, cap). 743 namespace { 744 enum class StringLayout { CSD, DSC }; 745 } 746 747 /// Determine the size in bytes of \p valobj (a libc++ std::string object) and 748 /// extract its data payload. Return the size + payload pair. 749 // TODO: Support big-endian architectures. 750 static std::optional<std::pair<uint64_t, ValueObjectSP>> 751 ExtractLibcxxStringInfo(ValueObject &valobj) { 752 ValueObjectSP valobj_r_sp = valobj.GetChildMemberWithName("__r_"); 753 if (!valobj_r_sp || !valobj_r_sp->GetError().Success()) 754 return {}; 755 756 // __r_ is a compressed_pair of the actual data and the allocator. The data we 757 // want is in the first base class. 758 ValueObjectSP valobj_r_base_sp = valobj_r_sp->GetChildAtIndex(0); 759 if (!valobj_r_base_sp) 760 return {}; 761 762 ValueObjectSP valobj_rep_sp = 763 valobj_r_base_sp->GetChildMemberWithName("__value_"); 764 if (!valobj_rep_sp) 765 return {}; 766 767 ValueObjectSP l = valobj_rep_sp->GetChildMemberWithName("__l"); 768 if (!l) 769 return {}; 770 771 StringLayout layout = l->GetIndexOfChildWithName("__data_") == 0 772 ? StringLayout::DSC 773 : StringLayout::CSD; 774 775 bool short_mode = false; // this means the string is in short-mode and the 776 // data is stored inline 777 bool using_bitmasks = true; // Whether the class uses bitmasks for the mode 778 // flag (pre-D123580). 779 uint64_t size; 780 uint64_t size_mode_value = 0; 781 782 ValueObjectSP short_sp = valobj_rep_sp->GetChildMemberWithName("__s"); 783 if (!short_sp) 784 return {}; 785 786 ValueObjectSP is_long = short_sp->GetChildMemberWithName("__is_long_"); 787 ValueObjectSP size_sp = short_sp->GetChildMemberWithName("__size_"); 788 if (!size_sp) 789 return {}; 790 791 if (is_long) { 792 using_bitmasks = false; 793 short_mode = !is_long->GetValueAsUnsigned(/*fail_value=*/0); 794 size = size_sp->GetValueAsUnsigned(/*fail_value=*/0); 795 } else { 796 // The string mode is encoded in the size field. 797 size_mode_value = size_sp->GetValueAsUnsigned(0); 798 uint8_t mode_mask = layout == StringLayout::DSC ? 0x80 : 1; 799 short_mode = (size_mode_value & mode_mask) == 0; 800 } 801 802 if (short_mode) { 803 ValueObjectSP location_sp = short_sp->GetChildMemberWithName("__data_"); 804 if (using_bitmasks) 805 size = (layout == StringLayout::DSC) ? size_mode_value 806 : ((size_mode_value >> 1) % 256); 807 808 // When the small-string optimization takes place, the data must fit in the 809 // inline string buffer (23 bytes on x86_64/Darwin). If it doesn't, it's 810 // likely that the string isn't initialized and we're reading garbage. 811 ExecutionContext exe_ctx(location_sp->GetExecutionContextRef()); 812 const std::optional<uint64_t> max_bytes = 813 location_sp->GetCompilerType().GetByteSize( 814 exe_ctx.GetBestExecutionContextScope()); 815 if (!max_bytes || size > *max_bytes || !location_sp) 816 return {}; 817 818 return std::make_pair(size, location_sp); 819 } 820 821 // we can use the layout_decider object as the data pointer 822 ValueObjectSP location_sp = l->GetChildMemberWithName("__data_"); 823 ValueObjectSP size_vo = l->GetChildMemberWithName("__size_"); 824 ValueObjectSP capacity_vo = l->GetChildMemberWithName("__cap_"); 825 if (!size_vo || !location_sp || !capacity_vo) 826 return {}; 827 size = size_vo->GetValueAsUnsigned(LLDB_INVALID_OFFSET); 828 uint64_t capacity = capacity_vo->GetValueAsUnsigned(LLDB_INVALID_OFFSET); 829 if (!using_bitmasks && layout == StringLayout::CSD) 830 capacity *= 2; 831 if (size == LLDB_INVALID_OFFSET || capacity == LLDB_INVALID_OFFSET || 832 capacity < size) 833 return {}; 834 return std::make_pair(size, location_sp); 835 } 836 837 static bool 838 LibcxxWStringSummaryProvider(ValueObject &valobj, Stream &stream, 839 const TypeSummaryOptions &summary_options, 840 ValueObjectSP location_sp, size_t size) { 841 if (size == 0) { 842 stream.Printf("L\"\""); 843 return true; 844 } 845 if (!location_sp) 846 return false; 847 848 StringPrinter::ReadBufferAndDumpToStreamOptions options(valobj); 849 if (summary_options.GetCapping() == TypeSummaryCapping::eTypeSummaryCapped) { 850 const auto max_size = valobj.GetTargetSP()->GetMaximumSizeOfStringSummary(); 851 if (size > max_size) { 852 size = max_size; 853 options.SetIsTruncated(true); 854 } 855 } 856 857 DataExtractor extractor; 858 const size_t bytes_read = location_sp->GetPointeeData(extractor, 0, size); 859 if (bytes_read < size) 860 return false; 861 862 // std::wstring::size() is measured in 'characters', not bytes 863 TypeSystemClangSP scratch_ts_sp = 864 ScratchTypeSystemClang::GetForTarget(*valobj.GetTargetSP()); 865 if (!scratch_ts_sp) 866 return false; 867 868 auto wchar_t_size = 869 scratch_ts_sp->GetBasicType(lldb::eBasicTypeWChar).GetByteSize(nullptr); 870 if (!wchar_t_size) 871 return false; 872 873 options.SetData(std::move(extractor)); 874 options.SetStream(&stream); 875 options.SetPrefixToken("L"); 876 options.SetQuote('"'); 877 options.SetSourceSize(size); 878 options.SetBinaryZeroIsTerminator(false); 879 880 switch (*wchar_t_size) { 881 case 1: 882 return StringPrinter::ReadBufferAndDumpToStream< 883 lldb_private::formatters::StringPrinter::StringElementType::UTF8>( 884 options); 885 break; 886 887 case 2: 888 return StringPrinter::ReadBufferAndDumpToStream< 889 lldb_private::formatters::StringPrinter::StringElementType::UTF16>( 890 options); 891 break; 892 893 case 4: 894 return StringPrinter::ReadBufferAndDumpToStream< 895 lldb_private::formatters::StringPrinter::StringElementType::UTF32>( 896 options); 897 } 898 return false; 899 } 900 901 bool lldb_private::formatters::LibcxxWStringSummaryProvider( 902 ValueObject &valobj, Stream &stream, 903 const TypeSummaryOptions &summary_options) { 904 auto string_info = ExtractLibcxxStringInfo(valobj); 905 if (!string_info) 906 return false; 907 uint64_t size; 908 ValueObjectSP location_sp; 909 std::tie(size, location_sp) = *string_info; 910 911 return ::LibcxxWStringSummaryProvider(valobj, stream, summary_options, 912 location_sp, size); 913 } 914 915 template <StringPrinter::StringElementType element_type> 916 static bool 917 LibcxxStringSummaryProvider(ValueObject &valobj, Stream &stream, 918 const TypeSummaryOptions &summary_options, 919 std::string prefix_token, ValueObjectSP location_sp, 920 uint64_t size) { 921 922 if (size == 0) { 923 stream.Printf("\"\""); 924 return true; 925 } 926 927 if (!location_sp) 928 return false; 929 930 StringPrinter::ReadBufferAndDumpToStreamOptions options(valobj); 931 932 if (summary_options.GetCapping() == TypeSummaryCapping::eTypeSummaryCapped) { 933 const auto max_size = valobj.GetTargetSP()->GetMaximumSizeOfStringSummary(); 934 if (size > max_size) { 935 size = max_size; 936 options.SetIsTruncated(true); 937 } 938 } 939 940 { 941 DataExtractor extractor; 942 const size_t bytes_read = location_sp->GetPointeeData(extractor, 0, size); 943 if (bytes_read < size) 944 return false; 945 946 options.SetData(std::move(extractor)); 947 } 948 options.SetStream(&stream); 949 if (prefix_token.empty()) 950 options.SetPrefixToken(nullptr); 951 else 952 options.SetPrefixToken(prefix_token); 953 options.SetQuote('"'); 954 options.SetSourceSize(size); 955 options.SetBinaryZeroIsTerminator(false); 956 return StringPrinter::ReadBufferAndDumpToStream<element_type>(options); 957 } 958 959 template <StringPrinter::StringElementType element_type> 960 static bool 961 LibcxxStringSummaryProvider(ValueObject &valobj, Stream &stream, 962 const TypeSummaryOptions &summary_options, 963 std::string prefix_token) { 964 auto string_info = ExtractLibcxxStringInfo(valobj); 965 if (!string_info) 966 return false; 967 uint64_t size; 968 ValueObjectSP location_sp; 969 std::tie(size, location_sp) = *string_info; 970 971 return LibcxxStringSummaryProvider<element_type>( 972 valobj, stream, summary_options, prefix_token, location_sp, size); 973 } 974 template <StringPrinter::StringElementType element_type> 975 static bool formatStringImpl(ValueObject &valobj, Stream &stream, 976 const TypeSummaryOptions &summary_options, 977 std::string prefix_token) { 978 StreamString scratch_stream; 979 const bool success = LibcxxStringSummaryProvider<element_type>( 980 valobj, scratch_stream, summary_options, prefix_token); 981 if (success) 982 stream << scratch_stream.GetData(); 983 else 984 stream << "Summary Unavailable"; 985 return true; 986 } 987 988 bool lldb_private::formatters::LibcxxStringSummaryProviderASCII( 989 ValueObject &valobj, Stream &stream, 990 const TypeSummaryOptions &summary_options) { 991 return formatStringImpl<StringPrinter::StringElementType::ASCII>( 992 valobj, stream, summary_options, ""); 993 } 994 995 bool lldb_private::formatters::LibcxxStringSummaryProviderUTF16( 996 ValueObject &valobj, Stream &stream, 997 const TypeSummaryOptions &summary_options) { 998 return formatStringImpl<StringPrinter::StringElementType::UTF16>( 999 valobj, stream, summary_options, "u"); 1000 } 1001 1002 bool lldb_private::formatters::LibcxxStringSummaryProviderUTF32( 1003 ValueObject &valobj, Stream &stream, 1004 const TypeSummaryOptions &summary_options) { 1005 return formatStringImpl<StringPrinter::StringElementType::UTF32>( 1006 valobj, stream, summary_options, "U"); 1007 } 1008 1009 static std::tuple<bool, ValueObjectSP, size_t> 1010 LibcxxExtractStringViewData(ValueObject& valobj) { 1011 auto dataobj = GetChildMemberWithName( 1012 valobj, {ConstString("__data_"), ConstString("__data")}); 1013 auto sizeobj = GetChildMemberWithName( 1014 valobj, {ConstString("__size_"), ConstString("__size")}); 1015 if (!dataobj || !sizeobj) 1016 return std::make_tuple<bool,ValueObjectSP,size_t>(false, {}, {}); 1017 1018 if (!dataobj->GetError().Success() || !sizeobj->GetError().Success()) 1019 return std::make_tuple<bool,ValueObjectSP,size_t>(false, {}, {}); 1020 1021 bool success{false}; 1022 uint64_t size = sizeobj->GetValueAsUnsigned(0, &success); 1023 if (!success) 1024 return std::make_tuple<bool,ValueObjectSP,size_t>(false, {}, {}); 1025 1026 return std::make_tuple(true,dataobj,size); 1027 } 1028 1029 template <StringPrinter::StringElementType element_type> 1030 static bool formatStringViewImpl(ValueObject &valobj, Stream &stream, 1031 const TypeSummaryOptions &summary_options, 1032 std::string prefix_token) { 1033 1034 bool success; 1035 ValueObjectSP dataobj; 1036 size_t size; 1037 std::tie(success, dataobj, size) = LibcxxExtractStringViewData(valobj); 1038 1039 if (!success) { 1040 stream << "Summary Unavailable"; 1041 return true; 1042 } 1043 1044 return LibcxxStringSummaryProvider<element_type>( 1045 valobj, stream, summary_options, prefix_token, dataobj, size); 1046 } 1047 1048 bool lldb_private::formatters::LibcxxStringViewSummaryProviderASCII( 1049 ValueObject &valobj, Stream &stream, 1050 const TypeSummaryOptions &summary_options) { 1051 return formatStringViewImpl<StringPrinter::StringElementType::ASCII>( 1052 valobj, stream, summary_options, ""); 1053 } 1054 1055 bool lldb_private::formatters::LibcxxStringViewSummaryProviderUTF16( 1056 ValueObject &valobj, Stream &stream, 1057 const TypeSummaryOptions &summary_options) { 1058 return formatStringViewImpl<StringPrinter::StringElementType::UTF16>( 1059 valobj, stream, summary_options, "u"); 1060 } 1061 1062 bool lldb_private::formatters::LibcxxStringViewSummaryProviderUTF32( 1063 ValueObject &valobj, Stream &stream, 1064 const TypeSummaryOptions &summary_options) { 1065 return formatStringViewImpl<StringPrinter::StringElementType::UTF32>( 1066 valobj, stream, summary_options, "U"); 1067 } 1068 1069 bool lldb_private::formatters::LibcxxWStringViewSummaryProvider( 1070 ValueObject &valobj, Stream &stream, 1071 const TypeSummaryOptions &summary_options) { 1072 1073 bool success; 1074 ValueObjectSP dataobj; 1075 size_t size; 1076 std::tie(success, dataobj, size) = LibcxxExtractStringViewData(valobj); 1077 1078 if (!success) { 1079 stream << "Summary Unavailable"; 1080 return true; 1081 } 1082 1083 return ::LibcxxWStringSummaryProvider(valobj, stream, summary_options, 1084 dataobj, size); 1085 } 1086 1087 bool lldb_private::formatters::LibcxxChronoSysSecondsSummaryProvider( 1088 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 1089 ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__d_"); 1090 if (!ptr_sp) 1091 return false; 1092 ptr_sp = ptr_sp->GetChildMemberWithName("__rep_"); 1093 if (!ptr_sp) 1094 return false; 1095 1096 // The date time in the chrono library is valid in the range 1097 // [-32767-01-01T00:00:00Z, 32767-12-31T23:59:59Z]. A 64-bit time_t has a 1098 // larger range, the function strftime is not able to format the entire range 1099 // of time_t. The exact point has not been investigated; it's limited to 1100 // chrono's range. 1101 const std::time_t chrono_timestamp_min = 1102 -1'096'193'779'200; // -32767-01-01T00:00:00Z 1103 const std::time_t chrono_timestamp_max = 1104 971'890'963'199; // 32767-12-31T23:59:59Z 1105 1106 const std::time_t seconds = ptr_sp->GetValueAsSigned(0); 1107 if (seconds < chrono_timestamp_min || seconds > chrono_timestamp_max) 1108 stream.Printf("timestamp=%ld s", seconds); 1109 else { 1110 std::array<char, 128> str; 1111 std::size_t size = 1112 std::strftime(str.data(), str.size(), "%FT%H:%M:%SZ", gmtime(&seconds)); 1113 if (size == 0) 1114 return false; 1115 1116 stream.Printf("date/time=%s timestamp=%ld s", str.data(), seconds); 1117 } 1118 1119 return true; 1120 } 1121 1122 bool lldb_private::formatters::LibcxxChronoSysDaysSummaryProvider( 1123 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 1124 ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__d_"); 1125 if (!ptr_sp) 1126 return false; 1127 ptr_sp = ptr_sp->GetChildMemberWithName("__rep_"); 1128 if (!ptr_sp) 1129 return false; 1130 1131 // The date time in the chrono library is valid in the range 1132 // [-32767-01-01Z, 32767-12-31Z]. A 32-bit time_t has a larger range, the 1133 // function strftime is not able to format the entire range of time_t. The 1134 // exact point has not been investigated; it's limited to chrono's range. 1135 const int chrono_timestamp_min = -12'687'428; // -32767-01-01Z 1136 const int chrono_timestamp_max = 11'248'737; // 32767-12-31Z 1137 1138 const int days = ptr_sp->GetValueAsSigned(0); 1139 if (days < chrono_timestamp_min || days > chrono_timestamp_max) 1140 stream.Printf("timestamp=%d days", days); 1141 1142 else { 1143 const std::time_t seconds = std::time_t(86400) * days; 1144 1145 std::array<char, 128> str; 1146 std::size_t size = 1147 std::strftime(str.data(), str.size(), "%FZ", gmtime(&seconds)); 1148 if (size == 0) 1149 return false; 1150 1151 stream.Printf("date=%s timestamp=%d days", str.data(), days); 1152 } 1153 1154 return true; 1155 } 1156 1157 bool lldb_private::formatters::LibcxxChronoMonthSummaryProvider( 1158 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 1159 // FIXME: These are the names used in the C++20 ostream operator. Since LLVM 1160 // uses C++17 it's not possible to use the ostream operator directly. 1161 static const std::array<std::string_view, 12> months = { 1162 "January", "February", "March", "April", "May", "June", 1163 "July", "August", "September", "October", "November", "December"}; 1164 1165 ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__m_"); 1166 if (!ptr_sp) 1167 return false; 1168 1169 const unsigned month = ptr_sp->GetValueAsUnsigned(0); 1170 if (month >= 1 && month <= 12) 1171 stream << "month=" << months[month - 1]; 1172 else 1173 stream.Printf("month=%u", month); 1174 1175 return true; 1176 } 1177 1178 bool lldb_private::formatters::LibcxxChronoWeekdaySummaryProvider( 1179 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 1180 // FIXME: These are the names used in the C++20 ostream operator. Since LLVM 1181 // uses C++17 it's not possible to use the ostream operator directly. 1182 static const std::array<std::string_view, 7> weekdays = { 1183 "Sunday", "Monday", "Tuesday", "Wednesday", 1184 "Thursday", "Friday", "Saturday"}; 1185 1186 ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__wd_"); 1187 if (!ptr_sp) 1188 return false; 1189 1190 const unsigned weekday = ptr_sp->GetValueAsUnsigned(0); 1191 if (weekday >= 0 && weekday < 7) 1192 stream << "weekday=" << weekdays[weekday]; 1193 else 1194 stream.Printf("weekday=%u", weekday); 1195 1196 return true; 1197 } 1198 1199 bool lldb_private::formatters::LibcxxChronoYearMonthDaySummaryProvider( 1200 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 1201 ValueObjectSP ptr_sp = valobj.GetChildMemberWithName("__y_"); 1202 if (!ptr_sp) 1203 return false; 1204 ptr_sp = ptr_sp->GetChildMemberWithName("__y_"); 1205 if (!ptr_sp) 1206 return false; 1207 int year = ptr_sp->GetValueAsSigned(0); 1208 1209 ptr_sp = valobj.GetChildMemberWithName("__m_"); 1210 if (!ptr_sp) 1211 return false; 1212 ptr_sp = ptr_sp->GetChildMemberWithName("__m_"); 1213 if (!ptr_sp) 1214 return false; 1215 const unsigned month = ptr_sp->GetValueAsUnsigned(0); 1216 1217 ptr_sp = valobj.GetChildMemberWithName("__d_"); 1218 if (!ptr_sp) 1219 return false; 1220 ptr_sp = ptr_sp->GetChildMemberWithName("__d_"); 1221 if (!ptr_sp) 1222 return false; 1223 const unsigned day = ptr_sp->GetValueAsUnsigned(0); 1224 1225 stream << "date="; 1226 if (year < 0) { 1227 stream << '-'; 1228 year = -year; 1229 } 1230 stream.Printf("%04d-%02u-%02u", year, month, day); 1231 1232 return true; 1233 } 1234