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