ValueObject.cpp revision fe424a92fc6fd92f810d243912461fe028a2b63c
1//===-- ValueObject.cpp -----------------------------------------*- C++ -*-===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9 10#include "lldb/Core/ValueObject.h" 11 12// C Includes 13#include <stdlib.h> 14 15// C++ Includes 16// Other libraries and framework includes 17#include "llvm/Support/raw_ostream.h" 18 19// Project includes 20#include "lldb/Core/DataBufferHeap.h" 21#include "lldb/Core/StreamString.h" 22#include "lldb/Core/ValueObjectChild.h" 23#include "lldb/Core/ValueObjectList.h" 24 25#include "lldb/Symbol/ClangASTType.h" 26#include "lldb/Symbol/ClangASTContext.h" 27#include "lldb/Symbol/Type.h" 28 29#include "lldb/Target/ExecutionContext.h" 30#include "lldb/Target/Process.h" 31#include "lldb/Target/RegisterContext.h" 32#include "lldb/Target/Target.h" 33#include "lldb/Target/Thread.h" 34 35using namespace lldb; 36using namespace lldb_private; 37 38static lldb::user_id_t g_value_obj_uid = 0; 39 40//---------------------------------------------------------------------- 41// ValueObject constructor 42//---------------------------------------------------------------------- 43ValueObject::ValueObject () : 44 UserID (++g_value_obj_uid), // Unique identifier for every value object 45 m_update_id (0), // Value object lists always start at 1, value objects start at zero 46 m_name (), 47 m_data (), 48 m_value (), 49 m_error (), 50 m_value_str (), 51 m_old_value_str (), 52 m_location_str (), 53 m_summary_str (), 54 m_object_desc_str (), 55 m_children (), 56 m_synthetic_children (), 57 m_value_is_valid (false), 58 m_value_did_change (false), 59 m_children_count_valid (false), 60 m_old_value_valid (false) 61{ 62} 63 64//---------------------------------------------------------------------- 65// Destructor 66//---------------------------------------------------------------------- 67ValueObject::~ValueObject () 68{ 69} 70 71user_id_t 72ValueObject::GetUpdateID() const 73{ 74 return m_update_id; 75} 76 77bool 78ValueObject::UpdateValueIfNeeded (ExecutionContextScope *exe_scope) 79{ 80 if (exe_scope) 81 { 82 Process *process = exe_scope->CalculateProcess(); 83 if (process) 84 { 85 const user_id_t stop_id = process->GetStopID(); 86 if (m_update_id != stop_id) 87 { 88 bool first_update = m_update_id == 0; 89 // Save the old value using swap to avoid a string copy which 90 // also will clear our m_value_str 91 if (m_value_str.empty()) 92 { 93 m_old_value_valid = false; 94 } 95 else 96 { 97 m_old_value_valid = true; 98 m_old_value_str.swap (m_value_str); 99 m_value_str.clear(); 100 } 101 m_location_str.clear(); 102 m_summary_str.clear(); 103 m_object_desc_str.clear(); 104 105 const bool value_was_valid = GetValueIsValid(); 106 SetValueDidChange (false); 107 108 m_error.Clear(); 109 110 // Call the pure virtual function to update the value 111 UpdateValue (exe_scope); 112 113 // Update the fact that we tried to update the value for this 114 // value object whether or not we succeed 115 m_update_id = stop_id; 116 bool success = m_error.Success(); 117 SetValueIsValid (success); 118 119 if (first_update) 120 SetValueDidChange (false); 121 else if (!m_value_did_change && success == false) 122 { 123 // The value wasn't gotten successfully, so we mark this 124 // as changed if the value used to be valid and now isn't 125 SetValueDidChange (value_was_valid); 126 } 127 } 128 } 129 } 130 return m_error.Success(); 131} 132 133const DataExtractor & 134ValueObject::GetDataExtractor () const 135{ 136 return m_data; 137} 138 139DataExtractor & 140ValueObject::GetDataExtractor () 141{ 142 return m_data; 143} 144 145const Error & 146ValueObject::GetError() const 147{ 148 return m_error; 149} 150 151const ConstString & 152ValueObject::GetName() const 153{ 154 return m_name; 155} 156 157const char * 158ValueObject::GetLocationAsCString (ExecutionContextScope *exe_scope) 159{ 160 if (UpdateValueIfNeeded(exe_scope)) 161 { 162 if (m_location_str.empty()) 163 { 164 StreamString sstr; 165 166 switch (m_value.GetValueType()) 167 { 168 default: 169 break; 170 171 case Value::eValueTypeScalar: 172 if (m_value.GetContextType() == Value::eContextTypeDCRegisterInfo) 173 { 174 RegisterInfo *reg_info = m_value.GetRegisterInfo(); 175 if (reg_info) 176 { 177 if (reg_info->name) 178 m_location_str = reg_info->name; 179 else if (reg_info->alt_name) 180 m_location_str = reg_info->alt_name; 181 break; 182 } 183 } 184 m_location_str = "scalar"; 185 break; 186 187 case Value::eValueTypeLoadAddress: 188 case Value::eValueTypeFileAddress: 189 case Value::eValueTypeHostAddress: 190 { 191 uint32_t addr_nibble_size = m_data.GetAddressByteSize() * 2; 192 sstr.Printf("0x%*.*llx", addr_nibble_size, addr_nibble_size, m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS)); 193 m_location_str.swap(sstr.GetString()); 194 } 195 break; 196 } 197 } 198 } 199 return m_location_str.c_str(); 200} 201 202Value & 203ValueObject::GetValue() 204{ 205 return m_value; 206} 207 208const Value & 209ValueObject::GetValue() const 210{ 211 return m_value; 212} 213 214bool 215ValueObject::GetValueIsValid () const 216{ 217 return m_value_is_valid; 218} 219 220 221void 222ValueObject::SetValueIsValid (bool b) 223{ 224 m_value_is_valid = b; 225} 226 227bool 228ValueObject::GetValueDidChange (ExecutionContextScope *exe_scope) 229{ 230 GetValueAsCString (exe_scope); 231 return m_value_did_change; 232} 233 234void 235ValueObject::SetValueDidChange (bool value_changed) 236{ 237 m_value_did_change = value_changed; 238} 239 240ValueObjectSP 241ValueObject::GetChildAtIndex (uint32_t idx, bool can_create) 242{ 243 ValueObjectSP child_sp; 244 if (idx < GetNumChildren()) 245 { 246 // Check if we have already made the child value object? 247 if (can_create && m_children[idx].get() == NULL) 248 { 249 // No we haven't created the child at this index, so lets have our 250 // subclass do it and cache the result for quick future access. 251 m_children[idx] = CreateChildAtIndex (idx, false, 0); 252 } 253 254 child_sp = m_children[idx]; 255 } 256 return child_sp; 257} 258 259uint32_t 260ValueObject::GetIndexOfChildWithName (const ConstString &name) 261{ 262 bool omit_empty_base_classes = true; 263 return ClangASTContext::GetIndexOfChildWithName (GetClangAST(), 264 GetOpaqueClangQualType(), 265 name.AsCString(), 266 omit_empty_base_classes); 267} 268 269ValueObjectSP 270ValueObject::GetChildMemberWithName (const ConstString &name, bool can_create) 271{ 272 // when getting a child by name, it could be burried inside some base 273 // classes (which really aren't part of the expression path), so we 274 // need a vector of indexes that can get us down to the correct child 275 std::vector<uint32_t> child_indexes; 276 clang::ASTContext *clang_ast = GetClangAST(); 277 void *clang_type = GetOpaqueClangQualType(); 278 bool omit_empty_base_classes = true; 279 const size_t num_child_indexes = ClangASTContext::GetIndexOfChildMemberWithName (clang_ast, 280 clang_type, 281 name.AsCString(), 282 omit_empty_base_classes, 283 child_indexes); 284 ValueObjectSP child_sp; 285 if (num_child_indexes > 0) 286 { 287 std::vector<uint32_t>::const_iterator pos = child_indexes.begin (); 288 std::vector<uint32_t>::const_iterator end = child_indexes.end (); 289 290 child_sp = GetChildAtIndex(*pos, can_create); 291 for (++pos; pos != end; ++pos) 292 { 293 if (child_sp) 294 { 295 ValueObjectSP new_child_sp(child_sp->GetChildAtIndex (*pos, can_create)); 296 child_sp = new_child_sp; 297 } 298 else 299 { 300 child_sp.reset(); 301 } 302 303 } 304 } 305 return child_sp; 306} 307 308 309uint32_t 310ValueObject::GetNumChildren () 311{ 312 if (!m_children_count_valid) 313 { 314 SetNumChildren (CalculateNumChildren()); 315 } 316 return m_children.size(); 317} 318void 319ValueObject::SetNumChildren (uint32_t num_children) 320{ 321 m_children_count_valid = true; 322 m_children.resize(num_children); 323} 324 325void 326ValueObject::SetName (const char *name) 327{ 328 m_name.SetCString(name); 329} 330 331void 332ValueObject::SetName (const ConstString &name) 333{ 334 m_name = name; 335} 336 337ValueObjectSP 338ValueObject::CreateChildAtIndex (uint32_t idx, bool synthetic_array_member, int32_t synthetic_index) 339{ 340 ValueObjectSP valobj_sp; 341 bool omit_empty_base_classes = true; 342 343 std::string child_name_str; 344 uint32_t child_byte_size = 0; 345 int32_t child_byte_offset = 0; 346 uint32_t child_bitfield_bit_size = 0; 347 uint32_t child_bitfield_bit_offset = 0; 348 const bool transparent_pointers = synthetic_array_member == false; 349 clang::ASTContext *clang_ast = GetClangAST(); 350 void *clang_type = GetOpaqueClangQualType(); 351 void *child_clang_type; 352 child_clang_type = ClangASTContext::GetChildClangTypeAtIndex (clang_ast, 353 GetName().AsCString(), 354 clang_type, 355 idx, 356 transparent_pointers, 357 omit_empty_base_classes, 358 child_name_str, 359 child_byte_size, 360 child_byte_offset, 361 child_bitfield_bit_size, 362 child_bitfield_bit_offset); 363 if (child_clang_type) 364 { 365 if (synthetic_index) 366 child_byte_offset += child_byte_size * synthetic_index; 367 368 ConstString child_name; 369 if (!child_name_str.empty()) 370 child_name.SetCString (child_name_str.c_str()); 371 372 valobj_sp.reset (new ValueObjectChild (this, 373 clang_ast, 374 child_clang_type, 375 child_name, 376 child_byte_size, 377 child_byte_offset, 378 child_bitfield_bit_size, 379 child_bitfield_bit_offset)); 380 } 381 return valobj_sp; 382} 383 384const char * 385ValueObject::GetSummaryAsCString (ExecutionContextScope *exe_scope) 386{ 387 if (UpdateValueIfNeeded (exe_scope)) 388 { 389 if (m_summary_str.empty()) 390 { 391 void *clang_type = GetOpaqueClangQualType(); 392 393 // See if this is a pointer to a C string? 394 uint32_t fixed_length = 0; 395 if (clang_type) 396 { 397 StreamString sstr; 398 399 if (ClangASTContext::IsCStringType (clang_type, fixed_length)) 400 { 401 Process *process = exe_scope->CalculateProcess(); 402 if (process != NULL) 403 { 404 lldb::AddressType cstr_address_type = eAddressTypeInvalid; 405 lldb::addr_t cstr_address = GetPointerValue (cstr_address_type, true); 406 407 if (cstr_address != LLDB_INVALID_ADDRESS) 408 { 409 DataExtractor data; 410 size_t bytes_read = 0; 411 std::vector<char> data_buffer; 412 std::vector<char> cstr_buffer; 413 size_t cstr_length; 414 Error error; 415 if (fixed_length > 0) 416 { 417 data_buffer.resize(fixed_length); 418 // Resize the formatted buffer in case every character 419 // uses the "\xXX" format and one extra byte for a NULL 420 cstr_buffer.resize(data_buffer.size() * 4 + 1); 421 data.SetData (&data_buffer.front(), data_buffer.size(), eByteOrderHost); 422 bytes_read = process->ReadMemory (cstr_address, &data_buffer.front(), fixed_length, error); 423 if (bytes_read > 0) 424 { 425 sstr << '"'; 426 cstr_length = data.Dump (&sstr, 427 0, // Start offset in "data" 428 eFormatChar, // Print as characters 429 1, // Size of item (1 byte for a char!) 430 bytes_read, // How many bytes to print? 431 UINT32_MAX, // num per line 432 LLDB_INVALID_ADDRESS,// base address 433 0, // bitfield bit size 434 0); // bitfield bit offset 435 sstr << '"'; 436 } 437 } 438 else 439 { 440 const size_t k_max_buf_size = 256; 441 data_buffer.resize (k_max_buf_size + 1); 442 // NULL terminate in case we don't get the entire C string 443 data_buffer.back() = '\0'; 444 // Make a formatted buffer that can contain take 4 445 // bytes per character in case each byte uses the 446 // "\xXX" format and one extra byte for a NULL 447 cstr_buffer.resize (k_max_buf_size * 4 + 1); 448 449 data.SetData (&data_buffer.front(), data_buffer.size(), eByteOrderHost); 450 size_t total_cstr_len = 0; 451 while ((bytes_read = process->ReadMemory (cstr_address, &data_buffer.front(), k_max_buf_size, error)) > 0) 452 { 453 size_t len = strlen(&data_buffer.front()); 454 if (len == 0) 455 break; 456 if (len > bytes_read) 457 len = bytes_read; 458 if (sstr.GetSize() == 0) 459 sstr << '"'; 460 461 cstr_length = data.Dump (&sstr, 462 0, // Start offset in "data" 463 eFormatChar, // Print as characters 464 1, // Size of item (1 byte for a char!) 465 len, // How many bytes to print? 466 UINT32_MAX, // num per line 467 LLDB_INVALID_ADDRESS,// base address 468 0, // bitfield bit size 469 0); // bitfield bit offset 470 471 if (len < k_max_buf_size) 472 break; 473 cstr_address += total_cstr_len; 474 } 475 if (sstr.GetSize() > 0) 476 sstr << '"'; 477 } 478 } 479 } 480 481 if (sstr.GetSize() > 0) 482 m_summary_str.assign (sstr.GetData(), sstr.GetSize()); 483 } 484 else if (ClangASTContext::IsFunctionPointerType (clang_type)) 485 { 486 lldb::AddressType func_ptr_address_type = eAddressTypeInvalid; 487 lldb::addr_t func_ptr_address = GetPointerValue (func_ptr_address_type, true); 488 489 if (func_ptr_address != 0 && func_ptr_address != LLDB_INVALID_ADDRESS) 490 { 491 switch (func_ptr_address_type) 492 { 493 case eAddressTypeInvalid: 494 case eAddressTypeFile: 495 break; 496 497 case eAddressTypeLoad: 498 { 499 Address so_addr; 500 Target *target = exe_scope->CalculateTarget(); 501 if (target && target->GetSectionLoadList().IsEmpty() == false) 502 { 503 if (target->GetSectionLoadList().ResolveLoadAddress(func_ptr_address, so_addr)) 504 { 505 so_addr.Dump (&sstr, 506 exe_scope, 507 Address::DumpStyleResolvedDescription, 508 Address::DumpStyleSectionNameOffset); 509 } 510 } 511 } 512 break; 513 514 case eAddressTypeHost: 515 break; 516 } 517 } 518 if (sstr.GetSize() > 0) 519 { 520 m_summary_str.assign (1, '('); 521 m_summary_str.append (sstr.GetData(), sstr.GetSize()); 522 m_summary_str.append (1, ')'); 523 } 524 } 525 } 526 } 527 } 528 if (m_summary_str.empty()) 529 return NULL; 530 return m_summary_str.c_str(); 531} 532 533 534const char * 535ValueObject::GetObjectDescription (ExecutionContextScope *exe_scope) 536{ 537 if (!m_object_desc_str.empty()) 538 return m_object_desc_str.c_str(); 539 540 if (!ClangASTContext::IsPointerType (GetOpaqueClangQualType())) 541 return NULL; 542 543 if (!GetValueIsValid()) 544 return NULL; 545 546 Process *process = exe_scope->CalculateProcess(); 547 548 if (!process) 549 return NULL; 550 551 Scalar scalar; 552 553 if (!ClangASTType::GetValueAsScalar (GetClangAST(), 554 GetOpaqueClangQualType(), 555 GetDataExtractor(), 556 0, 557 GetByteSize(), 558 scalar)) 559 return NULL; 560 561 ExecutionContext exe_ctx; 562 exe_scope->Calculate(exe_ctx); 563 564 Value val(scalar); 565 val.SetContext(Value::eContextTypeOpaqueClangQualType, 566 ClangASTContext::GetVoidPtrType(GetClangAST(), false)); 567 568 StreamString s; 569 // FIXME: Check the runtime this object belongs to and get the appropriate object printer for the object kind. 570 if (process->GetObjCObjectPrinter().PrintObject(s, val, exe_ctx)) 571 { 572 m_object_desc_str.append (s.GetData()); 573 } 574 return m_object_desc_str.c_str(); 575} 576 577const char * 578ValueObject::GetValueAsCString (ExecutionContextScope *exe_scope) 579{ 580 // If our byte size is zero this is an aggregate type that has children 581 if (ClangASTContext::IsAggregateType (GetOpaqueClangQualType()) == false) 582 { 583 if (UpdateValueIfNeeded(exe_scope)) 584 { 585 if (m_value_str.empty()) 586 { 587 const Value::ContextType context_type = m_value.GetContextType(); 588 589 switch (context_type) 590 { 591 case Value::eContextTypeOpaqueClangQualType: 592 case Value::eContextTypeDCType: 593 case Value::eContextTypeDCVariable: 594 { 595 void *clang_type = GetOpaqueClangQualType (); 596 if (clang_type) 597 { 598 StreamString sstr; 599 lldb::Format format = ClangASTType::GetFormat(clang_type); 600 if (ClangASTType::DumpTypeValue(GetClangAST(), // The clang AST 601 clang_type, // The clang type to display 602 &sstr, 603 format, // Format to display this type with 604 m_data, // Data to extract from 605 0, // Byte offset into "m_data" 606 GetByteSize(), // Byte size of item in "m_data" 607 GetBitfieldBitSize(), // Bitfield bit size 608 GetBitfieldBitOffset())) // Bitfield bit offset 609 m_value_str.swap(sstr.GetString()); 610 else 611 m_value_str.clear(); 612 } 613 } 614 break; 615 616 case Value::eContextTypeDCRegisterInfo: 617 { 618 const RegisterInfo *reg_info = m_value.GetRegisterInfo(); 619 if (reg_info) 620 { 621 StreamString reg_sstr; 622 m_data.Dump(®_sstr, 0, reg_info->format, reg_info->byte_size, 1, UINT32_MAX, LLDB_INVALID_ADDRESS, 0, 0); 623 m_value_str.swap(reg_sstr.GetString()); 624 } 625 } 626 break; 627 628 default: 629 break; 630 } 631 } 632 633 if (!m_value_did_change && m_old_value_valid) 634 { 635 // The value was gotten successfully, so we consider the 636 // value as changed if the value string differs 637 SetValueDidChange (m_old_value_str != m_value_str); 638 } 639 } 640 } 641 if (m_value_str.empty()) 642 return NULL; 643 return m_value_str.c_str(); 644} 645 646addr_t 647ValueObject::GetPointerValue (lldb::AddressType &address_type, bool scalar_is_load_address) 648{ 649 lldb::addr_t address = LLDB_INVALID_ADDRESS; 650 address_type = eAddressTypeInvalid; 651 switch (GetValue().GetValueType()) 652 { 653 case Value::eValueTypeScalar: 654 if (scalar_is_load_address) 655 { 656 address = m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); 657 address_type = eAddressTypeLoad; 658 } 659 break; 660 661 case Value::eValueTypeLoadAddress: 662 case Value::eValueTypeFileAddress: 663 case Value::eValueTypeHostAddress: 664 { 665 uint32_t data_offset = 0; 666 address = m_data.GetPointer(&data_offset); 667 address_type = m_value.GetValueAddressType(); 668 if (address_type == eAddressTypeInvalid) 669 address_type = eAddressTypeLoad; 670 } 671 break; 672 } 673 return address; 674} 675 676bool 677ValueObject::SetValueFromCString (ExecutionContextScope *exe_scope, const char *value_str) 678{ 679 // Make sure our value is up to date first so that our location and location 680 // type is valid. 681 if (!UpdateValueIfNeeded(exe_scope)) 682 return false; 683 684 uint32_t count = 0; 685 lldb::Encoding encoding = ClangASTType::GetEncoding (GetOpaqueClangQualType(), count); 686 687 char *end = NULL; 688 const size_t byte_size = GetByteSize(); 689 switch (encoding) 690 { 691 case eEncodingInvalid: 692 return false; 693 694 case eEncodingUint: 695 if (byte_size > sizeof(unsigned long long)) 696 { 697 return false; 698 } 699 else 700 { 701 unsigned long long ull_val = strtoull(value_str, &end, 0); 702 if (end && *end != '\0') 703 return false; 704 m_value = ull_val; 705 // Limit the bytes in our m_data appropriately. 706 m_value.GetScalar().GetData (m_data, byte_size); 707 } 708 break; 709 710 case eEncodingSint: 711 if (byte_size > sizeof(long long)) 712 { 713 return false; 714 } 715 else 716 { 717 long long sll_val = strtoll(value_str, &end, 0); 718 if (end && *end != '\0') 719 return false; 720 m_value = sll_val; 721 // Limit the bytes in our m_data appropriately. 722 m_value.GetScalar().GetData (m_data, byte_size); 723 } 724 break; 725 726 case eEncodingIEEE754: 727 { 728 const off_t byte_offset = GetByteOffset(); 729 uint8_t *dst = const_cast<uint8_t *>(m_data.PeekData(byte_offset, byte_size)); 730 if (dst != NULL) 731 { 732 // We are decoding a float into host byte order below, so make 733 // sure m_data knows what it contains. 734 m_data.SetByteOrder(eByteOrderHost); 735 const size_t converted_byte_size = ClangASTContext::ConvertStringToFloatValue ( 736 GetClangAST(), 737 GetOpaqueClangQualType(), 738 value_str, 739 dst, 740 byte_size); 741 742 if (converted_byte_size == byte_size) 743 { 744 } 745 } 746 } 747 break; 748 749 case eEncodingVector: 750 return false; 751 752 default: 753 return false; 754 } 755 756 // If we have made it here the value is in m_data and we should write it 757 // out to the target 758 return Write (); 759} 760 761bool 762ValueObject::Write () 763{ 764 // Clear the update ID so the next time we try and read the value 765 // we try and read it again. 766 m_update_id = 0; 767 768 // TODO: when Value has a method to write a value back, call it from here. 769 return false; 770 771} 772 773void 774ValueObject::AddSyntheticChild (const ConstString &key, ValueObjectSP& valobj_sp) 775{ 776 m_synthetic_children[key] = valobj_sp; 777} 778 779ValueObjectSP 780ValueObject::GetSyntheticChild (const ConstString &key) const 781{ 782 ValueObjectSP synthetic_child_sp; 783 std::map<ConstString, ValueObjectSP>::const_iterator pos = m_synthetic_children.find (key); 784 if (pos != m_synthetic_children.end()) 785 synthetic_child_sp = pos->second; 786 return synthetic_child_sp; 787} 788 789bool 790ValueObject::IsPointerType () 791{ 792 return ClangASTContext::IsPointerType (GetOpaqueClangQualType()); 793} 794 795bool 796ValueObject::IsPointerOrReferenceType () 797{ 798 return ClangASTContext::IsPointerOrReferenceType(GetOpaqueClangQualType()); 799} 800 801ValueObjectSP 802ValueObject::GetSyntheticArrayMemberFromPointer (int32_t index, bool can_create) 803{ 804 ValueObjectSP synthetic_child_sp; 805 if (IsPointerType ()) 806 { 807 char index_str[64]; 808 snprintf(index_str, sizeof(index_str), "[%i]", index); 809 ConstString index_const_str(index_str); 810 // Check if we have already created a synthetic array member in this 811 // valid object. If we have we will re-use it. 812 synthetic_child_sp = GetSyntheticChild (index_const_str); 813 if (!synthetic_child_sp) 814 { 815 // We haven't made a synthetic array member for INDEX yet, so 816 // lets make one and cache it for any future reference. 817 synthetic_child_sp = CreateChildAtIndex(0, true, index); 818 819 // Cache the value if we got one back... 820 if (synthetic_child_sp) 821 AddSyntheticChild(index_const_str, synthetic_child_sp); 822 } 823 } 824 return synthetic_child_sp; 825} 826