1 //===-- HashedNameToDIE.cpp -------------------------------------*- C++ -*-===// 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 "HashedNameToDIE.h" 10 #include "llvm/ADT/StringRef.h" 11 12 void DWARFMappedHash::ExtractDIEArray(const DIEInfoArray &die_info_array, 13 DIEArray &die_offsets) { 14 const size_t count = die_info_array.size(); 15 for (size_t i = 0; i < count; ++i) 16 die_offsets.emplace_back(die_info_array[i].cu_offset, 17 die_info_array[i].offset); 18 } 19 20 void DWARFMappedHash::ExtractDIEArray(const DIEInfoArray &die_info_array, 21 const dw_tag_t tag, 22 DIEArray &die_offsets) { 23 if (tag == 0) { 24 ExtractDIEArray(die_info_array, die_offsets); 25 } else { 26 const size_t count = die_info_array.size(); 27 for (size_t i = 0; i < count; ++i) { 28 const dw_tag_t die_tag = die_info_array[i].tag; 29 bool tag_matches = die_tag == 0 || tag == die_tag; 30 if (!tag_matches) { 31 if (die_tag == DW_TAG_class_type || die_tag == DW_TAG_structure_type) 32 tag_matches = 33 tag == DW_TAG_structure_type || tag == DW_TAG_class_type; 34 } 35 if (tag_matches) 36 die_offsets.emplace_back(die_info_array[i].cu_offset, 37 die_info_array[i].offset); 38 } 39 } 40 } 41 42 void DWARFMappedHash::ExtractDIEArray(const DIEInfoArray &die_info_array, 43 const dw_tag_t tag, 44 const uint32_t qualified_name_hash, 45 DIEArray &die_offsets) { 46 if (tag == 0) { 47 ExtractDIEArray(die_info_array, die_offsets); 48 } else { 49 const size_t count = die_info_array.size(); 50 for (size_t i = 0; i < count; ++i) { 51 if (qualified_name_hash != die_info_array[i].qualified_name_hash) 52 continue; 53 const dw_tag_t die_tag = die_info_array[i].tag; 54 bool tag_matches = die_tag == 0 || tag == die_tag; 55 if (!tag_matches) { 56 if (die_tag == DW_TAG_class_type || die_tag == DW_TAG_structure_type) 57 tag_matches = 58 tag == DW_TAG_structure_type || tag == DW_TAG_class_type; 59 } 60 if (tag_matches) 61 die_offsets.emplace_back(die_info_array[i].cu_offset, 62 die_info_array[i].offset); 63 } 64 } 65 } 66 67 void DWARFMappedHash::ExtractClassOrStructDIEArray( 68 const DIEInfoArray &die_info_array, 69 bool return_implementation_only_if_available, DIEArray &die_offsets) { 70 const size_t count = die_info_array.size(); 71 for (size_t i = 0; i < count; ++i) { 72 const dw_tag_t die_tag = die_info_array[i].tag; 73 if (die_tag == 0 || die_tag == DW_TAG_class_type || 74 die_tag == DW_TAG_structure_type) { 75 if (die_info_array[i].type_flags & eTypeFlagClassIsImplementation) { 76 if (return_implementation_only_if_available) { 77 // We found the one true definition for this class, so only return 78 // that 79 die_offsets.clear(); 80 die_offsets.emplace_back(die_info_array[i].cu_offset, 81 die_info_array[i].offset); 82 return; 83 } else { 84 // Put the one true definition as the first entry so it matches first 85 die_offsets.emplace(die_offsets.begin(), die_info_array[i].cu_offset, 86 die_info_array[i].offset); 87 } 88 } else { 89 die_offsets.emplace_back(die_info_array[i].cu_offset, 90 die_info_array[i].offset); 91 } 92 } 93 } 94 } 95 96 void DWARFMappedHash::ExtractTypesFromDIEArray( 97 const DIEInfoArray &die_info_array, uint32_t type_flag_mask, 98 uint32_t type_flag_value, DIEArray &die_offsets) { 99 const size_t count = die_info_array.size(); 100 for (size_t i = 0; i < count; ++i) { 101 if ((die_info_array[i].type_flags & type_flag_mask) == type_flag_value) 102 die_offsets.emplace_back(die_info_array[i].cu_offset, 103 die_info_array[i].offset); 104 } 105 } 106 107 const char *DWARFMappedHash::GetAtomTypeName(uint16_t atom) { 108 switch (atom) { 109 case eAtomTypeNULL: 110 return "NULL"; 111 case eAtomTypeDIEOffset: 112 return "die-offset"; 113 case eAtomTypeCUOffset: 114 return "cu-offset"; 115 case eAtomTypeTag: 116 return "die-tag"; 117 case eAtomTypeNameFlags: 118 return "name-flags"; 119 case eAtomTypeTypeFlags: 120 return "type-flags"; 121 case eAtomTypeQualNameHash: 122 return "qualified-name-hash"; 123 } 124 return "<invalid>"; 125 } 126 127 DWARFMappedHash::DIEInfo::DIEInfo() 128 : cu_offset(DW_INVALID_OFFSET), offset(DW_INVALID_OFFSET), tag(0), 129 type_flags(0), qualified_name_hash(0) {} 130 131 DWARFMappedHash::DIEInfo::DIEInfo(dw_offset_t c, dw_offset_t o, dw_tag_t t, 132 uint32_t f, uint32_t h) 133 : cu_offset(c), offset(o), tag(t), type_flags(f), qualified_name_hash(h) {} 134 135 DWARFMappedHash::Prologue::Prologue(dw_offset_t _die_base_offset) 136 : die_base_offset(_die_base_offset), atoms(), atom_mask(0), 137 min_hash_data_byte_size(0), hash_data_has_fixed_byte_size(true) { 138 // Define an array of DIE offsets by first defining an array, and then define 139 // the atom type for the array, in this case we have an array of DIE offsets 140 AppendAtom(eAtomTypeDIEOffset, DW_FORM_data4); 141 } 142 143 void DWARFMappedHash::Prologue::ClearAtoms() { 144 hash_data_has_fixed_byte_size = true; 145 min_hash_data_byte_size = 0; 146 atom_mask = 0; 147 atoms.clear(); 148 } 149 150 bool DWARFMappedHash::Prologue::ContainsAtom(AtomType atom_type) const { 151 return (atom_mask & (1u << atom_type)) != 0; 152 } 153 154 void DWARFMappedHash::Prologue::Clear() { 155 die_base_offset = 0; 156 ClearAtoms(); 157 } 158 159 void DWARFMappedHash::Prologue::AppendAtom(AtomType type, dw_form_t form) { 160 atoms.push_back({type, form}); 161 atom_mask |= 1u << type; 162 switch (form) { 163 case DW_FORM_indirect: 164 case DW_FORM_exprloc: 165 case DW_FORM_flag_present: 166 case DW_FORM_ref_sig8: 167 llvm_unreachable("Unhandled atom form"); 168 169 case DW_FORM_string: 170 case DW_FORM_block: 171 case DW_FORM_block1: 172 case DW_FORM_sdata: 173 case DW_FORM_udata: 174 case DW_FORM_ref_udata: 175 case DW_FORM_GNU_addr_index: 176 case DW_FORM_GNU_str_index: 177 hash_data_has_fixed_byte_size = false; 178 LLVM_FALLTHROUGH; 179 case DW_FORM_flag: 180 case DW_FORM_data1: 181 case DW_FORM_ref1: 182 case DW_FORM_sec_offset: 183 min_hash_data_byte_size += 1; 184 break; 185 186 case DW_FORM_block2: 187 hash_data_has_fixed_byte_size = false; 188 LLVM_FALLTHROUGH; 189 case DW_FORM_data2: 190 case DW_FORM_ref2: 191 min_hash_data_byte_size += 2; 192 break; 193 194 case DW_FORM_block4: 195 hash_data_has_fixed_byte_size = false; 196 LLVM_FALLTHROUGH; 197 case DW_FORM_data4: 198 case DW_FORM_ref4: 199 case DW_FORM_addr: 200 case DW_FORM_ref_addr: 201 case DW_FORM_strp: 202 min_hash_data_byte_size += 4; 203 break; 204 205 case DW_FORM_data8: 206 case DW_FORM_ref8: 207 min_hash_data_byte_size += 8; 208 break; 209 } 210 } 211 212 lldb::offset_t 213 DWARFMappedHash::Prologue::Read(const lldb_private::DataExtractor &data, 214 lldb::offset_t offset) { 215 ClearAtoms(); 216 217 die_base_offset = data.GetU32(&offset); 218 219 const uint32_t atom_count = data.GetU32(&offset); 220 if (atom_count == 0x00060003u) { 221 // Old format, deal with contents of old pre-release format 222 while (data.GetU32(&offset)) 223 /* do nothing */; 224 225 // Hardcode to the only known value for now. 226 AppendAtom(eAtomTypeDIEOffset, DW_FORM_data4); 227 } else { 228 for (uint32_t i = 0; i < atom_count; ++i) { 229 AtomType type = (AtomType)data.GetU16(&offset); 230 dw_form_t form = (dw_form_t)data.GetU16(&offset); 231 AppendAtom(type, form); 232 } 233 } 234 return offset; 235 } 236 237 size_t DWARFMappedHash::Prologue::GetByteSize() const { 238 // Add an extra count to the atoms size for the zero termination Atom that 239 // gets written to disk 240 return sizeof(die_base_offset) + sizeof(uint32_t) + 241 atoms.size() * sizeof(Atom); 242 } 243 244 size_t DWARFMappedHash::Prologue::GetMinimumHashDataByteSize() const { 245 return min_hash_data_byte_size; 246 } 247 248 bool DWARFMappedHash::Prologue::HashDataHasFixedByteSize() const { 249 return hash_data_has_fixed_byte_size; 250 } 251 252 size_t DWARFMappedHash::Header::GetByteSize(const HeaderData &header_data) { 253 return header_data.GetByteSize(); 254 } 255 256 lldb::offset_t DWARFMappedHash::Header::Read(lldb_private::DataExtractor &data, 257 lldb::offset_t offset) { 258 offset = MappedHash::Header<Prologue>::Read(data, offset); 259 if (offset != UINT32_MAX) { 260 offset = header_data.Read(data, offset); 261 } 262 return offset; 263 } 264 265 bool DWARFMappedHash::Header::Read(const lldb_private::DWARFDataExtractor &data, 266 lldb::offset_t *offset_ptr, 267 DIEInfo &hash_data) const { 268 const size_t num_atoms = header_data.atoms.size(); 269 if (num_atoms == 0) 270 return false; 271 272 for (size_t i = 0; i < num_atoms; ++i) { 273 DWARFFormValue form_value(NULL, header_data.atoms[i].form); 274 275 if (!form_value.ExtractValue(data, offset_ptr)) 276 return false; 277 278 switch (header_data.atoms[i].type) { 279 case eAtomTypeDIEOffset: // DIE offset, check form for encoding 280 hash_data.offset = 281 DWARFFormValue::IsDataForm(form_value.Form()) 282 ? form_value.Unsigned() 283 : form_value.Reference(header_data.die_base_offset); 284 break; 285 286 case eAtomTypeTag: // DW_TAG value for the DIE 287 hash_data.tag = (dw_tag_t)form_value.Unsigned(); 288 break; 289 290 case eAtomTypeTypeFlags: // Flags from enum TypeFlags 291 hash_data.type_flags = (uint32_t)form_value.Unsigned(); 292 break; 293 294 case eAtomTypeQualNameHash: // Flags from enum TypeFlags 295 hash_data.qualified_name_hash = form_value.Unsigned(); 296 break; 297 298 default: 299 // We can always skip atoms we don't know about 300 break; 301 } 302 } 303 return true; 304 } 305 306 void DWARFMappedHash::Header::Dump(lldb_private::Stream &strm, 307 const DIEInfo &hash_data) const { 308 const size_t num_atoms = header_data.atoms.size(); 309 for (size_t i = 0; i < num_atoms; ++i) { 310 if (i > 0) 311 strm.PutCString(", "); 312 313 DWARFFormValue form_value(NULL, header_data.atoms[i].form); 314 switch (header_data.atoms[i].type) { 315 case eAtomTypeDIEOffset: // DIE offset, check form for encoding 316 strm.Printf("{0x%8.8x}", hash_data.offset); 317 break; 318 319 case eAtomTypeTag: // DW_TAG value for the DIE 320 { 321 const char *tag_cstr = lldb_private::DW_TAG_value_to_name(hash_data.tag); 322 if (tag_cstr) 323 strm.PutCString(tag_cstr); 324 else 325 strm.Printf("DW_TAG_(0x%4.4x)", hash_data.tag); 326 } break; 327 328 case eAtomTypeTypeFlags: // Flags from enum TypeFlags 329 strm.Printf("0x%2.2x", hash_data.type_flags); 330 if (hash_data.type_flags) { 331 strm.PutCString(" ("); 332 if (hash_data.type_flags & eTypeFlagClassIsImplementation) 333 strm.PutCString(" implementation"); 334 strm.PutCString(" )"); 335 } 336 break; 337 338 case eAtomTypeQualNameHash: // Flags from enum TypeFlags 339 strm.Printf("0x%8.8x", hash_data.qualified_name_hash); 340 break; 341 342 default: 343 strm.Printf("AtomType(0x%x)", header_data.atoms[i].type); 344 break; 345 } 346 } 347 } 348 349 DWARFMappedHash::MemoryTable::MemoryTable( 350 lldb_private::DWARFDataExtractor &table_data, 351 const lldb_private::DWARFDataExtractor &string_table, const char *name) 352 : MappedHash::MemoryTable<uint32_t, Header, DIEInfoArray>(table_data), 353 m_data(table_data), m_string_table(string_table), m_name(name) {} 354 355 const char * 356 DWARFMappedHash::MemoryTable::GetStringForKeyType(KeyType key) const { 357 // The key in the DWARF table is the .debug_str offset for the string 358 return m_string_table.PeekCStr(key); 359 } 360 361 bool DWARFMappedHash::MemoryTable::ReadHashData(uint32_t hash_data_offset, 362 HashData &hash_data) const { 363 lldb::offset_t offset = hash_data_offset; 364 offset += 4; // Skip string table offset that contains offset of hash name in 365 // .debug_str 366 const uint32_t count = m_data.GetU32(&offset); 367 if (count > 0) { 368 hash_data.resize(count); 369 for (uint32_t i = 0; i < count; ++i) { 370 if (!m_header.Read(m_data, &offset, hash_data[i])) 371 return false; 372 } 373 } else 374 hash_data.clear(); 375 return true; 376 } 377 378 DWARFMappedHash::MemoryTable::Result 379 DWARFMappedHash::MemoryTable::GetHashDataForName( 380 llvm::StringRef name, lldb::offset_t *hash_data_offset_ptr, 381 Pair &pair) const { 382 pair.key = m_data.GetU32(hash_data_offset_ptr); 383 pair.value.clear(); 384 385 // If the key is zero, this terminates our chain of HashData objects for this 386 // hash value. 387 if (pair.key == 0) 388 return eResultEndOfHashData; 389 390 // There definitely should be a string for this string offset, if there 391 // isn't, there is something wrong, return and error 392 const char *strp_cstr = m_string_table.PeekCStr(pair.key); 393 if (strp_cstr == NULL) { 394 *hash_data_offset_ptr = UINT32_MAX; 395 return eResultError; 396 } 397 398 const uint32_t count = m_data.GetU32(hash_data_offset_ptr); 399 const size_t min_total_hash_data_size = 400 count * m_header.header_data.GetMinimumHashDataByteSize(); 401 if (count > 0 && 402 m_data.ValidOffsetForDataOfSize(*hash_data_offset_ptr, 403 min_total_hash_data_size)) { 404 // We have at least one HashData entry, and we have enough data to parse at 405 // least "count" HashData entries. 406 407 // First make sure the entire C string matches... 408 const bool match = name == strp_cstr; 409 410 if (!match && m_header.header_data.HashDataHasFixedByteSize()) { 411 // If the string doesn't match and we have fixed size data, we can just 412 // add the total byte size of all HashData objects to the hash data 413 // offset and be done... 414 *hash_data_offset_ptr += min_total_hash_data_size; 415 } else { 416 // If the string does match, or we don't have fixed size data then we 417 // need to read the hash data as a stream. If the string matches we also 418 // append all HashData objects to the value array. 419 for (uint32_t i = 0; i < count; ++i) { 420 DIEInfo die_info; 421 if (m_header.Read(m_data, hash_data_offset_ptr, die_info)) { 422 // Only happened if the HashData of the string matched... 423 if (match) 424 pair.value.push_back(die_info); 425 } else { 426 // Something went wrong while reading the data 427 *hash_data_offset_ptr = UINT32_MAX; 428 return eResultError; 429 } 430 } 431 } 432 // Return the correct response depending on if the string matched or not... 433 if (match) 434 return eResultKeyMatch; // The key (cstring) matches and we have lookup 435 // results! 436 else 437 return eResultKeyMismatch; // The key doesn't match, this function will 438 // get called 439 // again for the next key/value or the key terminator which in our case is 440 // a zero .debug_str offset. 441 } else { 442 *hash_data_offset_ptr = UINT32_MAX; 443 return eResultError; 444 } 445 } 446 447 DWARFMappedHash::MemoryTable::Result 448 DWARFMappedHash::MemoryTable::AppendHashDataForRegularExpression( 449 const lldb_private::RegularExpression ®ex, 450 lldb::offset_t *hash_data_offset_ptr, Pair &pair) const { 451 pair.key = m_data.GetU32(hash_data_offset_ptr); 452 // If the key is zero, this terminates our chain of HashData objects for this 453 // hash value. 454 if (pair.key == 0) 455 return eResultEndOfHashData; 456 457 // There definitely should be a string for this string offset, if there 458 // isn't, there is something wrong, return and error 459 const char *strp_cstr = m_string_table.PeekCStr(pair.key); 460 if (strp_cstr == NULL) 461 return eResultError; 462 463 const uint32_t count = m_data.GetU32(hash_data_offset_ptr); 464 const size_t min_total_hash_data_size = 465 count * m_header.header_data.GetMinimumHashDataByteSize(); 466 if (count > 0 && 467 m_data.ValidOffsetForDataOfSize(*hash_data_offset_ptr, 468 min_total_hash_data_size)) { 469 const bool match = regex.Execute(llvm::StringRef(strp_cstr)); 470 471 if (!match && m_header.header_data.HashDataHasFixedByteSize()) { 472 // If the regex doesn't match and we have fixed size data, we can just 473 // add the total byte size of all HashData objects to the hash data 474 // offset and be done... 475 *hash_data_offset_ptr += min_total_hash_data_size; 476 } else { 477 // If the string does match, or we don't have fixed size data then we 478 // need to read the hash data as a stream. If the string matches we also 479 // append all HashData objects to the value array. 480 for (uint32_t i = 0; i < count; ++i) { 481 DIEInfo die_info; 482 if (m_header.Read(m_data, hash_data_offset_ptr, die_info)) { 483 // Only happened if the HashData of the string matched... 484 if (match) 485 pair.value.push_back(die_info); 486 } else { 487 // Something went wrong while reading the data 488 *hash_data_offset_ptr = UINT32_MAX; 489 return eResultError; 490 } 491 } 492 } 493 // Return the correct response depending on if the string matched or not... 494 if (match) 495 return eResultKeyMatch; // The key (cstring) matches and we have lookup 496 // results! 497 else 498 return eResultKeyMismatch; // The key doesn't match, this function will 499 // get called 500 // again for the next key/value or the key terminator which in our case is 501 // a zero .debug_str offset. 502 } else { 503 *hash_data_offset_ptr = UINT32_MAX; 504 return eResultError; 505 } 506 } 507 508 size_t DWARFMappedHash::MemoryTable::AppendAllDIEsThatMatchingRegex( 509 const lldb_private::RegularExpression ®ex, 510 DIEInfoArray &die_info_array) const { 511 const uint32_t hash_count = m_header.hashes_count; 512 Pair pair; 513 for (uint32_t offset_idx = 0; offset_idx < hash_count; ++offset_idx) { 514 lldb::offset_t hash_data_offset = GetHashDataOffset(offset_idx); 515 while (hash_data_offset != UINT32_MAX) { 516 const lldb::offset_t prev_hash_data_offset = hash_data_offset; 517 Result hash_result = 518 AppendHashDataForRegularExpression(regex, &hash_data_offset, pair); 519 if (prev_hash_data_offset == hash_data_offset) 520 break; 521 522 // Check the result of getting our hash data 523 switch (hash_result) { 524 case eResultKeyMatch: 525 case eResultKeyMismatch: 526 // Whether we matches or not, it doesn't matter, we keep looking. 527 break; 528 529 case eResultEndOfHashData: 530 case eResultError: 531 hash_data_offset = UINT32_MAX; 532 break; 533 } 534 } 535 } 536 die_info_array.swap(pair.value); 537 return die_info_array.size(); 538 } 539 540 size_t DWARFMappedHash::MemoryTable::AppendAllDIEsInRange( 541 const uint32_t die_offset_start, const uint32_t die_offset_end, 542 DIEInfoArray &die_info_array) const { 543 const uint32_t hash_count = m_header.hashes_count; 544 for (uint32_t offset_idx = 0; offset_idx < hash_count; ++offset_idx) { 545 bool done = false; 546 lldb::offset_t hash_data_offset = GetHashDataOffset(offset_idx); 547 while (!done && hash_data_offset != UINT32_MAX) { 548 KeyType key = m_data.GetU32(&hash_data_offset); 549 // If the key is zero, this terminates our chain of HashData objects for 550 // this hash value. 551 if (key == 0) 552 break; 553 554 const uint32_t count = m_data.GetU32(&hash_data_offset); 555 for (uint32_t i = 0; i < count; ++i) { 556 DIEInfo die_info; 557 if (m_header.Read(m_data, &hash_data_offset, die_info)) { 558 if (die_info.offset == 0) 559 done = true; 560 if (die_offset_start <= die_info.offset && 561 die_info.offset < die_offset_end) 562 die_info_array.push_back(die_info); 563 } 564 } 565 } 566 } 567 return die_info_array.size(); 568 } 569 570 size_t DWARFMappedHash::MemoryTable::FindByName(llvm::StringRef name, 571 DIEArray &die_offsets) { 572 if (name.empty()) 573 return 0; 574 575 DIEInfoArray die_info_array; 576 if (FindByName(name, die_info_array)) 577 DWARFMappedHash::ExtractDIEArray(die_info_array, die_offsets); 578 return die_info_array.size(); 579 } 580 581 size_t DWARFMappedHash::MemoryTable::FindByNameAndTag(llvm::StringRef name, 582 const dw_tag_t tag, 583 DIEArray &die_offsets) { 584 DIEInfoArray die_info_array; 585 if (FindByName(name, die_info_array)) 586 DWARFMappedHash::ExtractDIEArray(die_info_array, tag, die_offsets); 587 return die_info_array.size(); 588 } 589 590 size_t DWARFMappedHash::MemoryTable::FindByNameAndTagAndQualifiedNameHash( 591 llvm::StringRef name, const dw_tag_t tag, 592 const uint32_t qualified_name_hash, DIEArray &die_offsets) { 593 DIEInfoArray die_info_array; 594 if (FindByName(name, die_info_array)) 595 DWARFMappedHash::ExtractDIEArray(die_info_array, tag, qualified_name_hash, 596 die_offsets); 597 return die_info_array.size(); 598 } 599 600 size_t DWARFMappedHash::MemoryTable::FindCompleteObjCClassByName( 601 llvm::StringRef name, DIEArray &die_offsets, bool must_be_implementation) { 602 DIEInfoArray die_info_array; 603 if (FindByName(name, die_info_array)) { 604 if (must_be_implementation && 605 GetHeader().header_data.ContainsAtom(eAtomTypeTypeFlags)) { 606 // If we have two atoms, then we have the DIE offset and the type flags 607 // so we can find the objective C class efficiently. 608 DWARFMappedHash::ExtractTypesFromDIEArray(die_info_array, UINT32_MAX, 609 eTypeFlagClassIsImplementation, 610 die_offsets); 611 } else { 612 // We don't only want the one true definition, so try and see what we can 613 // find, and only return class or struct DIEs. If we do have the full 614 // implementation, then return it alone, else return all possible 615 // matches. 616 const bool return_implementation_only_if_available = true; 617 DWARFMappedHash::ExtractClassOrStructDIEArray( 618 die_info_array, return_implementation_only_if_available, die_offsets); 619 } 620 } 621 return die_offsets.size(); 622 } 623 624 size_t DWARFMappedHash::MemoryTable::FindByName(llvm::StringRef name, 625 DIEInfoArray &die_info_array) { 626 if (name.empty()) 627 return 0; 628 629 Pair kv_pair; 630 size_t old_size = die_info_array.size(); 631 if (Find(name, kv_pair)) { 632 die_info_array.swap(kv_pair.value); 633 return die_info_array.size() - old_size; 634 } 635 return 0; 636 } 637