1 //===-- Value.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 "lldb/Core/Value.h" 10 11 #include "lldb/Core/Address.h" 12 #include "lldb/Core/Module.h" 13 #include "lldb/Symbol/CompilerType.h" 14 #include "lldb/Symbol/ObjectFile.h" 15 #include "lldb/Symbol/SymbolContext.h" 16 #include "lldb/Symbol/Type.h" 17 #include "lldb/Symbol/Variable.h" 18 #include "lldb/Target/ExecutionContext.h" 19 #include "lldb/Target/Process.h" 20 #include "lldb/Target/SectionLoadList.h" 21 #include "lldb/Target/Target.h" 22 #include "lldb/Utility/ConstString.h" 23 #include "lldb/Utility/DataBufferHeap.h" 24 #include "lldb/Utility/DataExtractor.h" 25 #include "lldb/Utility/Endian.h" 26 #include "lldb/Utility/FileSpec.h" 27 #include "lldb/Utility/State.h" 28 #include "lldb/Utility/Stream.h" 29 #include "lldb/lldb-defines.h" 30 #include "lldb/lldb-forward.h" 31 #include "lldb/lldb-types.h" 32 33 #include <memory> 34 #include <string> 35 36 #include <inttypes.h> 37 38 using namespace lldb; 39 using namespace lldb_private; 40 41 Value::Value() 42 : m_value(), m_compiler_type(), m_context(nullptr), 43 m_value_type(ValueType::Scalar), m_context_type(ContextType::Invalid), 44 m_data_buffer() {} 45 46 Value::Value(const Scalar &scalar) 47 : m_value(scalar), m_compiler_type(), m_context(nullptr), 48 m_value_type(ValueType::Scalar), m_context_type(ContextType::Invalid), 49 m_data_buffer() {} 50 51 Value::Value(const void *bytes, int len) 52 : m_value(), m_compiler_type(), m_context(nullptr), 53 m_value_type(ValueType::HostAddress), m_context_type(ContextType::Invalid), 54 m_data_buffer() { 55 SetBytes(bytes, len); 56 } 57 58 Value::Value(const Value &v) 59 : m_value(v.m_value), m_compiler_type(v.m_compiler_type), 60 m_context(v.m_context), m_value_type(v.m_value_type), 61 m_context_type(v.m_context_type), m_data_buffer() { 62 const uintptr_t rhs_value = 63 (uintptr_t)v.m_value.ULongLong(LLDB_INVALID_ADDRESS); 64 if ((rhs_value != 0) && 65 (rhs_value == (uintptr_t)v.m_data_buffer.GetBytes())) { 66 m_data_buffer.CopyData(v.m_data_buffer.GetBytes(), 67 v.m_data_buffer.GetByteSize()); 68 69 m_value = (uintptr_t)m_data_buffer.GetBytes(); 70 } 71 } 72 73 Value &Value::operator=(const Value &rhs) { 74 if (this != &rhs) { 75 m_value = rhs.m_value; 76 m_compiler_type = rhs.m_compiler_type; 77 m_context = rhs.m_context; 78 m_value_type = rhs.m_value_type; 79 m_context_type = rhs.m_context_type; 80 const uintptr_t rhs_value = 81 (uintptr_t)rhs.m_value.ULongLong(LLDB_INVALID_ADDRESS); 82 if ((rhs_value != 0) && 83 (rhs_value == (uintptr_t)rhs.m_data_buffer.GetBytes())) { 84 m_data_buffer.CopyData(rhs.m_data_buffer.GetBytes(), 85 rhs.m_data_buffer.GetByteSize()); 86 87 m_value = (uintptr_t)m_data_buffer.GetBytes(); 88 } 89 } 90 return *this; 91 } 92 93 void Value::SetBytes(const void *bytes, int len) { 94 m_value_type = ValueType::HostAddress; 95 m_data_buffer.CopyData(bytes, len); 96 m_value = (uintptr_t)m_data_buffer.GetBytes(); 97 } 98 99 void Value::AppendBytes(const void *bytes, int len) { 100 m_value_type = ValueType::HostAddress; 101 m_data_buffer.AppendData(bytes, len); 102 m_value = (uintptr_t)m_data_buffer.GetBytes(); 103 } 104 105 void Value::Dump(Stream *strm) { 106 m_value.GetValue(strm, true); 107 strm->Printf(", value_type = %s, context = %p, context_type = %s", 108 Value::GetValueTypeAsCString(m_value_type), m_context, 109 Value::GetContextTypeAsCString(m_context_type)); 110 } 111 112 Value::ValueType Value::GetValueType() const { return m_value_type; } 113 114 AddressType Value::GetValueAddressType() const { 115 switch (m_value_type) { 116 case ValueType::Invalid: 117 case ValueType::Scalar: 118 break; 119 case ValueType::LoadAddress: 120 return eAddressTypeLoad; 121 case ValueType::FileAddress: 122 return eAddressTypeFile; 123 case ValueType::HostAddress: 124 return eAddressTypeHost; 125 } 126 return eAddressTypeInvalid; 127 } 128 129 RegisterInfo *Value::GetRegisterInfo() const { 130 if (m_context_type == ContextType::RegisterInfo) 131 return static_cast<RegisterInfo *>(m_context); 132 return nullptr; 133 } 134 135 Type *Value::GetType() { 136 if (m_context_type == ContextType::LLDBType) 137 return static_cast<Type *>(m_context); 138 return nullptr; 139 } 140 141 size_t Value::AppendDataToHostBuffer(const Value &rhs) { 142 if (this == &rhs) 143 return 0; 144 145 size_t curr_size = m_data_buffer.GetByteSize(); 146 Status error; 147 switch (rhs.GetValueType()) { 148 case ValueType::Invalid: 149 return 0; 150 case ValueType::Scalar: { 151 const size_t scalar_size = rhs.m_value.GetByteSize(); 152 if (scalar_size > 0) { 153 const size_t new_size = curr_size + scalar_size; 154 if (ResizeData(new_size) == new_size) { 155 rhs.m_value.GetAsMemoryData(m_data_buffer.GetBytes() + curr_size, 156 scalar_size, endian::InlHostByteOrder(), 157 error); 158 return scalar_size; 159 } 160 } 161 } break; 162 case ValueType::FileAddress: 163 case ValueType::LoadAddress: 164 case ValueType::HostAddress: { 165 const uint8_t *src = rhs.GetBuffer().GetBytes(); 166 const size_t src_len = rhs.GetBuffer().GetByteSize(); 167 if (src && src_len > 0) { 168 const size_t new_size = curr_size + src_len; 169 if (ResizeData(new_size) == new_size) { 170 ::memcpy(m_data_buffer.GetBytes() + curr_size, src, src_len); 171 return src_len; 172 } 173 } 174 } break; 175 } 176 return 0; 177 } 178 179 size_t Value::ResizeData(size_t len) { 180 m_value_type = ValueType::HostAddress; 181 m_data_buffer.SetByteSize(len); 182 m_value = (uintptr_t)m_data_buffer.GetBytes(); 183 return m_data_buffer.GetByteSize(); 184 } 185 186 bool Value::ValueOf(ExecutionContext *exe_ctx) { 187 switch (m_context_type) { 188 case ContextType::Invalid: 189 case ContextType::RegisterInfo: // RegisterInfo * 190 case ContextType::LLDBType: // Type * 191 break; 192 193 case ContextType::Variable: // Variable * 194 ResolveValue(exe_ctx); 195 return true; 196 } 197 return false; 198 } 199 200 uint64_t Value::GetValueByteSize(Status *error_ptr, ExecutionContext *exe_ctx) { 201 switch (m_context_type) { 202 case ContextType::RegisterInfo: // RegisterInfo * 203 if (GetRegisterInfo()) { 204 if (error_ptr) 205 error_ptr->Clear(); 206 return GetRegisterInfo()->byte_size; 207 } 208 break; 209 210 case ContextType::Invalid: 211 case ContextType::LLDBType: // Type * 212 case ContextType::Variable: // Variable * 213 { 214 auto *scope = exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr; 215 if (llvm::Optional<uint64_t> size = GetCompilerType().GetByteSize(scope)) { 216 if (error_ptr) 217 error_ptr->Clear(); 218 return *size; 219 } 220 break; 221 } 222 } 223 if (error_ptr && error_ptr->Success()) 224 error_ptr->SetErrorString("Unable to determine byte size."); 225 return 0; 226 } 227 228 const CompilerType &Value::GetCompilerType() { 229 if (!m_compiler_type.IsValid()) { 230 switch (m_context_type) { 231 case ContextType::Invalid: 232 break; 233 234 case ContextType::RegisterInfo: 235 break; // TODO: Eventually convert into a compiler type? 236 237 case ContextType::LLDBType: { 238 Type *lldb_type = GetType(); 239 if (lldb_type) 240 m_compiler_type = lldb_type->GetForwardCompilerType(); 241 } break; 242 243 case ContextType::Variable: { 244 Variable *variable = GetVariable(); 245 if (variable) { 246 Type *variable_type = variable->GetType(); 247 if (variable_type) 248 m_compiler_type = variable_type->GetForwardCompilerType(); 249 } 250 } break; 251 } 252 } 253 254 return m_compiler_type; 255 } 256 257 void Value::SetCompilerType(const CompilerType &compiler_type) { 258 m_compiler_type = compiler_type; 259 } 260 261 lldb::Format Value::GetValueDefaultFormat() { 262 switch (m_context_type) { 263 case ContextType::RegisterInfo: 264 if (GetRegisterInfo()) 265 return GetRegisterInfo()->format; 266 break; 267 268 case ContextType::Invalid: 269 case ContextType::LLDBType: 270 case ContextType::Variable: { 271 const CompilerType &ast_type = GetCompilerType(); 272 if (ast_type.IsValid()) 273 return ast_type.GetFormat(); 274 } break; 275 } 276 277 // Return a good default in case we can't figure anything out 278 return eFormatHex; 279 } 280 281 bool Value::GetData(DataExtractor &data) { 282 switch (m_value_type) { 283 case ValueType::Invalid: 284 return false; 285 case ValueType::Scalar: 286 if (m_value.GetData(data)) 287 return true; 288 break; 289 290 case ValueType::LoadAddress: 291 case ValueType::FileAddress: 292 case ValueType::HostAddress: 293 if (m_data_buffer.GetByteSize()) { 294 data.SetData(m_data_buffer.GetBytes(), m_data_buffer.GetByteSize(), 295 data.GetByteOrder()); 296 return true; 297 } 298 break; 299 } 300 301 return false; 302 } 303 304 Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data, 305 Module *module) { 306 data.Clear(); 307 308 Status error; 309 lldb::addr_t address = LLDB_INVALID_ADDRESS; 310 AddressType address_type = eAddressTypeFile; 311 Address file_so_addr; 312 const CompilerType &ast_type = GetCompilerType(); 313 llvm::Optional<uint64_t> type_size = ast_type.GetByteSize( 314 exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr); 315 // Nothing to be done for a zero-sized type. 316 if (type_size && *type_size == 0) 317 return error; 318 319 switch (m_value_type) { 320 case ValueType::Invalid: 321 error.SetErrorString("invalid value"); 322 break; 323 case ValueType::Scalar: { 324 data.SetByteOrder(endian::InlHostByteOrder()); 325 if (ast_type.IsValid()) 326 data.SetAddressByteSize(ast_type.GetPointerByteSize()); 327 else 328 data.SetAddressByteSize(sizeof(void *)); 329 330 uint32_t limit_byte_size = UINT32_MAX; 331 332 if (type_size) 333 limit_byte_size = *type_size; 334 335 if (limit_byte_size <= m_value.GetByteSize()) { 336 if (m_value.GetData(data, limit_byte_size)) 337 return error; // Success; 338 } 339 340 error.SetErrorString("extracting data from value failed"); 341 break; 342 } 343 case ValueType::LoadAddress: 344 if (exe_ctx == nullptr) { 345 error.SetErrorString("can't read load address (no execution context)"); 346 } else { 347 Process *process = exe_ctx->GetProcessPtr(); 348 if (process == nullptr || !process->IsAlive()) { 349 Target *target = exe_ctx->GetTargetPtr(); 350 if (target) { 351 // Allow expressions to run and evaluate things when the target has 352 // memory sections loaded. This allows you to use "target modules 353 // load" to load your executable and any shared libraries, then 354 // execute commands where you can look at types in data sections. 355 const SectionLoadList &target_sections = target->GetSectionLoadList(); 356 if (!target_sections.IsEmpty()) { 357 address = m_value.ULongLong(LLDB_INVALID_ADDRESS); 358 if (target_sections.ResolveLoadAddress(address, file_so_addr)) { 359 address_type = eAddressTypeLoad; 360 data.SetByteOrder(target->GetArchitecture().GetByteOrder()); 361 data.SetAddressByteSize( 362 target->GetArchitecture().GetAddressByteSize()); 363 } else 364 address = LLDB_INVALID_ADDRESS; 365 } 366 } else { 367 error.SetErrorString("can't read load address (invalid process)"); 368 } 369 } else { 370 address = m_value.ULongLong(LLDB_INVALID_ADDRESS); 371 address_type = eAddressTypeLoad; 372 data.SetByteOrder( 373 process->GetTarget().GetArchitecture().GetByteOrder()); 374 data.SetAddressByteSize( 375 process->GetTarget().GetArchitecture().GetAddressByteSize()); 376 } 377 } 378 break; 379 380 case ValueType::FileAddress: 381 if (exe_ctx == nullptr) { 382 error.SetErrorString("can't read file address (no execution context)"); 383 } else if (exe_ctx->GetTargetPtr() == nullptr) { 384 error.SetErrorString("can't read file address (invalid target)"); 385 } else { 386 address = m_value.ULongLong(LLDB_INVALID_ADDRESS); 387 if (address == LLDB_INVALID_ADDRESS) { 388 error.SetErrorString("invalid file address"); 389 } else { 390 if (module == nullptr) { 391 // The only thing we can currently lock down to a module so that we 392 // can resolve a file address, is a variable. 393 Variable *variable = GetVariable(); 394 if (variable) { 395 SymbolContext var_sc; 396 variable->CalculateSymbolContext(&var_sc); 397 module = var_sc.module_sp.get(); 398 } 399 } 400 401 if (module) { 402 bool resolved = false; 403 ObjectFile *objfile = module->GetObjectFile(); 404 if (objfile) { 405 Address so_addr(address, objfile->GetSectionList()); 406 addr_t load_address = 407 so_addr.GetLoadAddress(exe_ctx->GetTargetPtr()); 408 bool process_launched_and_stopped = 409 exe_ctx->GetProcessPtr() 410 ? StateIsStoppedState(exe_ctx->GetProcessPtr()->GetState(), 411 true /* must_exist */) 412 : false; 413 // Don't use the load address if the process has exited. 414 if (load_address != LLDB_INVALID_ADDRESS && 415 process_launched_and_stopped) { 416 resolved = true; 417 address = load_address; 418 address_type = eAddressTypeLoad; 419 data.SetByteOrder( 420 exe_ctx->GetTargetRef().GetArchitecture().GetByteOrder()); 421 data.SetAddressByteSize(exe_ctx->GetTargetRef() 422 .GetArchitecture() 423 .GetAddressByteSize()); 424 } else { 425 if (so_addr.IsSectionOffset()) { 426 resolved = true; 427 file_so_addr = so_addr; 428 data.SetByteOrder(objfile->GetByteOrder()); 429 data.SetAddressByteSize(objfile->GetAddressByteSize()); 430 } 431 } 432 } 433 if (!resolved) { 434 Variable *variable = GetVariable(); 435 436 if (module) { 437 if (variable) 438 error.SetErrorStringWithFormat( 439 "unable to resolve the module for file address 0x%" PRIx64 440 " for variable '%s' in %s", 441 address, variable->GetName().AsCString(""), 442 module->GetFileSpec().GetPath().c_str()); 443 else 444 error.SetErrorStringWithFormat( 445 "unable to resolve the module for file address 0x%" PRIx64 446 " in %s", 447 address, module->GetFileSpec().GetPath().c_str()); 448 } else { 449 if (variable) 450 error.SetErrorStringWithFormat( 451 "unable to resolve the module for file address 0x%" PRIx64 452 " for variable '%s'", 453 address, variable->GetName().AsCString("")); 454 else 455 error.SetErrorStringWithFormat( 456 "unable to resolve the module for file address 0x%" PRIx64, 457 address); 458 } 459 } 460 } else { 461 // Can't convert a file address to anything valid without more 462 // context (which Module it came from) 463 error.SetErrorString( 464 "can't read memory from file address without more context"); 465 } 466 } 467 } 468 break; 469 470 case ValueType::HostAddress: 471 address = m_value.ULongLong(LLDB_INVALID_ADDRESS); 472 address_type = eAddressTypeHost; 473 if (exe_ctx) { 474 Target *target = exe_ctx->GetTargetPtr(); 475 if (target) { 476 data.SetByteOrder(target->GetArchitecture().GetByteOrder()); 477 data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize()); 478 break; 479 } 480 } 481 // fallback to host settings 482 data.SetByteOrder(endian::InlHostByteOrder()); 483 data.SetAddressByteSize(sizeof(void *)); 484 break; 485 } 486 487 // Bail if we encountered any errors 488 if (error.Fail()) 489 return error; 490 491 if (address == LLDB_INVALID_ADDRESS) { 492 error.SetErrorStringWithFormat("invalid %s address", 493 address_type == eAddressTypeHost ? "host" 494 : "load"); 495 return error; 496 } 497 498 // If we got here, we need to read the value from memory. 499 size_t byte_size = GetValueByteSize(&error, exe_ctx); 500 501 // Bail if we encountered any errors getting the byte size. 502 if (error.Fail()) 503 return error; 504 505 // No memory to read for zero-sized types. 506 if (byte_size == 0) 507 return error; 508 509 // Make sure we have enough room within "data", and if we don't make 510 // something large enough that does 511 if (!data.ValidOffsetForDataOfSize(0, byte_size)) { 512 auto data_sp = std::make_shared<DataBufferHeap>(byte_size, '\0'); 513 data.SetData(data_sp); 514 } 515 516 uint8_t *dst = const_cast<uint8_t *>(data.PeekData(0, byte_size)); 517 if (dst != nullptr) { 518 if (address_type == eAddressTypeHost) { 519 // The address is an address in this process, so just copy it. 520 if (address == 0) { 521 error.SetErrorString("trying to read from host address of 0."); 522 return error; 523 } 524 memcpy(dst, reinterpret_cast<uint8_t *>(address), byte_size); 525 } else if ((address_type == eAddressTypeLoad) || 526 (address_type == eAddressTypeFile)) { 527 if (file_so_addr.IsValid()) { 528 // We have a file address that we were able to translate into a section 529 // offset address so we might be able to read this from the object 530 // files if we don't have a live process. Lets always try and read from 531 // the process if we have one though since we want to read the actual 532 // value by setting "prefer_file_cache" to false. 533 const bool prefer_file_cache = false; 534 if (exe_ctx->GetTargetRef().ReadMemory(file_so_addr, prefer_file_cache, 535 dst, byte_size, 536 error) != byte_size) { 537 error.SetErrorStringWithFormat( 538 "read memory from 0x%" PRIx64 " failed", (uint64_t)address); 539 } 540 } else { 541 // The execution context might have a NULL process, but it might have a 542 // valid process in the exe_ctx->target, so use the 543 // ExecutionContext::GetProcess accessor to ensure we get the process 544 // if there is one. 545 Process *process = exe_ctx->GetProcessPtr(); 546 547 if (process) { 548 const size_t bytes_read = 549 process->ReadMemory(address, dst, byte_size, error); 550 if (bytes_read != byte_size) 551 error.SetErrorStringWithFormat( 552 "read memory from 0x%" PRIx64 " failed (%u of %u bytes read)", 553 (uint64_t)address, (uint32_t)bytes_read, (uint32_t)byte_size); 554 } else { 555 error.SetErrorStringWithFormat("read memory from 0x%" PRIx64 556 " failed (invalid process)", 557 (uint64_t)address); 558 } 559 } 560 } else { 561 error.SetErrorStringWithFormat("unsupported AddressType value (%i)", 562 address_type); 563 } 564 } else { 565 error.SetErrorString("out of memory"); 566 } 567 568 return error; 569 } 570 571 Scalar &Value::ResolveValue(ExecutionContext *exe_ctx) { 572 const CompilerType &compiler_type = GetCompilerType(); 573 if (compiler_type.IsValid()) { 574 switch (m_value_type) { 575 case ValueType::Invalid: 576 case ValueType::Scalar: // raw scalar value 577 break; 578 579 case ValueType::FileAddress: 580 case ValueType::LoadAddress: // load address value 581 case ValueType::HostAddress: // host address value (for memory in the process 582 // that is using liblldb) 583 { 584 DataExtractor data; 585 lldb::addr_t addr = m_value.ULongLong(LLDB_INVALID_ADDRESS); 586 Status error(GetValueAsData(exe_ctx, data, nullptr)); 587 if (error.Success()) { 588 Scalar scalar; 589 if (compiler_type.GetValueAsScalar( 590 data, 0, data.GetByteSize(), scalar, 591 exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr)) { 592 m_value = scalar; 593 m_value_type = ValueType::Scalar; 594 } else { 595 if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) { 596 m_value.Clear(); 597 m_value_type = ValueType::Scalar; 598 } 599 } 600 } else { 601 if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) { 602 m_value.Clear(); 603 m_value_type = ValueType::Scalar; 604 } 605 } 606 } break; 607 } 608 } 609 return m_value; 610 } 611 612 Variable *Value::GetVariable() { 613 if (m_context_type == ContextType::Variable) 614 return static_cast<Variable *>(m_context); 615 return nullptr; 616 } 617 618 void Value::Clear() { 619 m_value.Clear(); 620 m_compiler_type.Clear(); 621 m_value_type = ValueType::Scalar; 622 m_context = nullptr; 623 m_context_type = ContextType::Invalid; 624 m_data_buffer.Clear(); 625 } 626 627 const char *Value::GetValueTypeAsCString(ValueType value_type) { 628 switch (value_type) { 629 case ValueType::Invalid: 630 return "invalid"; 631 case ValueType::Scalar: 632 return "scalar"; 633 case ValueType::FileAddress: 634 return "file address"; 635 case ValueType::LoadAddress: 636 return "load address"; 637 case ValueType::HostAddress: 638 return "host address"; 639 }; 640 llvm_unreachable("enum cases exhausted."); 641 } 642 643 const char *Value::GetContextTypeAsCString(ContextType context_type) { 644 switch (context_type) { 645 case ContextType::Invalid: 646 return "invalid"; 647 case ContextType::RegisterInfo: 648 return "RegisterInfo *"; 649 case ContextType::LLDBType: 650 return "Type *"; 651 case ContextType::Variable: 652 return "Variable *"; 653 }; 654 llvm_unreachable("enum cases exhausted."); 655 } 656 657 void Value::ConvertToLoadAddress(Module *module, Target *target) { 658 if (!module || !target || (GetValueType() != ValueType::FileAddress)) 659 return; 660 661 lldb::addr_t file_addr = GetScalar().ULongLong(LLDB_INVALID_ADDRESS); 662 if (file_addr == LLDB_INVALID_ADDRESS) 663 return; 664 665 Address so_addr; 666 if (!module->ResolveFileAddress(file_addr, so_addr)) 667 return; 668 lldb::addr_t load_addr = so_addr.GetLoadAddress(target); 669 if (load_addr == LLDB_INVALID_ADDRESS) 670 return; 671 672 SetValueType(Value::ValueType::LoadAddress); 673 GetScalar() = load_addr; 674 } 675 676 ValueList::ValueList(const ValueList &rhs) { m_values = rhs.m_values; } 677 678 const ValueList &ValueList::operator=(const ValueList &rhs) { 679 m_values = rhs.m_values; 680 return *this; 681 } 682 683 void ValueList::PushValue(const Value &value) { m_values.push_back(value); } 684 685 size_t ValueList::GetSize() { return m_values.size(); } 686 687 Value *ValueList::GetValueAtIndex(size_t idx) { 688 if (idx < GetSize()) { 689 return &(m_values[idx]); 690 } else 691 return nullptr; 692 } 693 694 void ValueList::Clear() { m_values.clear(); } 695