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