aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/lldb/source/Core/Value.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/lldb/source/Core/Value.cpp')
-rw-r--r--contrib/llvm-project/lldb/source/Core/Value.cpp693
1 files changed, 693 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Core/Value.cpp b/contrib/llvm-project/lldb/source/Core/Value.cpp
new file mode 100644
index 000000000000..995cc934c820
--- /dev/null
+++ b/contrib/llvm-project/lldb/source/Core/Value.cpp
@@ -0,0 +1,693 @@
+//===-- Value.cpp ---------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Core/Value.h"
+
+#include "lldb/Core/Address.h"
+#include "lldb/Core/Module.h"
+#include "lldb/Symbol/CompilerType.h"
+#include "lldb/Symbol/ObjectFile.h"
+#include "lldb/Symbol/SymbolContext.h"
+#include "lldb/Symbol/Type.h"
+#include "lldb/Symbol/Variable.h"
+#include "lldb/Target/ExecutionContext.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/SectionLoadList.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/DataExtractor.h"
+#include "lldb/Utility/Endian.h"
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/State.h"
+#include "lldb/Utility/Stream.h"
+#include "lldb/lldb-defines.h"
+#include "lldb/lldb-forward.h"
+#include "lldb/lldb-types.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+
+#include <cinttypes>
+
+using namespace lldb;
+using namespace lldb_private;
+
+Value::Value() : m_value(), m_compiler_type(), m_data_buffer() {}
+
+Value::Value(const Scalar &scalar)
+ : m_value(scalar), m_compiler_type(), m_data_buffer() {}
+
+Value::Value(const void *bytes, int len)
+ : m_value(), m_compiler_type(), m_value_type(ValueType::HostAddress),
+ m_data_buffer() {
+ SetBytes(bytes, len);
+}
+
+Value::Value(const Value &v)
+ : m_value(v.m_value), m_compiler_type(v.m_compiler_type),
+ m_context(v.m_context), m_value_type(v.m_value_type),
+ m_context_type(v.m_context_type), m_data_buffer() {
+ const uintptr_t rhs_value =
+ (uintptr_t)v.m_value.ULongLong(LLDB_INVALID_ADDRESS);
+ if ((rhs_value != 0) &&
+ (rhs_value == (uintptr_t)v.m_data_buffer.GetBytes())) {
+ m_data_buffer.CopyData(v.m_data_buffer.GetBytes(),
+ v.m_data_buffer.GetByteSize());
+
+ m_value = (uintptr_t)m_data_buffer.GetBytes();
+ }
+}
+
+Value &Value::operator=(const Value &rhs) {
+ if (this != &rhs) {
+ m_value = rhs.m_value;
+ m_compiler_type = rhs.m_compiler_type;
+ m_context = rhs.m_context;
+ m_value_type = rhs.m_value_type;
+ m_context_type = rhs.m_context_type;
+ const uintptr_t rhs_value =
+ (uintptr_t)rhs.m_value.ULongLong(LLDB_INVALID_ADDRESS);
+ if ((rhs_value != 0) &&
+ (rhs_value == (uintptr_t)rhs.m_data_buffer.GetBytes())) {
+ m_data_buffer.CopyData(rhs.m_data_buffer.GetBytes(),
+ rhs.m_data_buffer.GetByteSize());
+
+ m_value = (uintptr_t)m_data_buffer.GetBytes();
+ }
+ }
+ return *this;
+}
+
+void Value::SetBytes(const void *bytes, int len) {
+ m_value_type = ValueType::HostAddress;
+ m_data_buffer.CopyData(bytes, len);
+ m_value = (uintptr_t)m_data_buffer.GetBytes();
+}
+
+void Value::AppendBytes(const void *bytes, int len) {
+ m_value_type = ValueType::HostAddress;
+ m_data_buffer.AppendData(bytes, len);
+ m_value = (uintptr_t)m_data_buffer.GetBytes();
+}
+
+void Value::Dump(Stream *strm) {
+ if (!strm)
+ return;
+ m_value.GetValue(*strm, true);
+ strm->Printf(", value_type = %s, context = %p, context_type = %s",
+ Value::GetValueTypeAsCString(m_value_type), m_context,
+ Value::GetContextTypeAsCString(m_context_type));
+}
+
+Value::ValueType Value::GetValueType() const { return m_value_type; }
+
+AddressType Value::GetValueAddressType() const {
+ switch (m_value_type) {
+ case ValueType::Invalid:
+ case ValueType::Scalar:
+ break;
+ case ValueType::LoadAddress:
+ return eAddressTypeLoad;
+ case ValueType::FileAddress:
+ return eAddressTypeFile;
+ case ValueType::HostAddress:
+ return eAddressTypeHost;
+ }
+ return eAddressTypeInvalid;
+}
+
+Value::ValueType Value::GetValueTypeFromAddressType(AddressType address_type) {
+ switch (address_type) {
+ case eAddressTypeFile:
+ return Value::ValueType::FileAddress;
+ case eAddressTypeLoad:
+ return Value::ValueType::LoadAddress;
+ case eAddressTypeHost:
+ return Value::ValueType::HostAddress;
+ case eAddressTypeInvalid:
+ return Value::ValueType::Invalid;
+ }
+ llvm_unreachable("Unexpected address type!");
+}
+
+RegisterInfo *Value::GetRegisterInfo() const {
+ if (m_context_type == ContextType::RegisterInfo)
+ return static_cast<RegisterInfo *>(m_context);
+ return nullptr;
+}
+
+Type *Value::GetType() {
+ if (m_context_type == ContextType::LLDBType)
+ return static_cast<Type *>(m_context);
+ return nullptr;
+}
+
+size_t Value::AppendDataToHostBuffer(const Value &rhs) {
+ if (this == &rhs)
+ return 0;
+
+ size_t curr_size = m_data_buffer.GetByteSize();
+ Status error;
+ switch (rhs.GetValueType()) {
+ case ValueType::Invalid:
+ return 0;
+ case ValueType::Scalar: {
+ const size_t scalar_size = rhs.m_value.GetByteSize();
+ if (scalar_size > 0) {
+ const size_t new_size = curr_size + scalar_size;
+ if (ResizeData(new_size) == new_size) {
+ rhs.m_value.GetAsMemoryData(m_data_buffer.GetBytes() + curr_size,
+ scalar_size, endian::InlHostByteOrder(),
+ error);
+ return scalar_size;
+ }
+ }
+ } break;
+ case ValueType::FileAddress:
+ case ValueType::LoadAddress:
+ case ValueType::HostAddress: {
+ const uint8_t *src = rhs.GetBuffer().GetBytes();
+ const size_t src_len = rhs.GetBuffer().GetByteSize();
+ if (src && src_len > 0) {
+ const size_t new_size = curr_size + src_len;
+ if (ResizeData(new_size) == new_size) {
+ ::memcpy(m_data_buffer.GetBytes() + curr_size, src, src_len);
+ return src_len;
+ }
+ }
+ } break;
+ }
+ return 0;
+}
+
+size_t Value::ResizeData(size_t len) {
+ m_value_type = ValueType::HostAddress;
+ m_data_buffer.SetByteSize(len);
+ m_value = (uintptr_t)m_data_buffer.GetBytes();
+ return m_data_buffer.GetByteSize();
+}
+
+bool Value::ValueOf(ExecutionContext *exe_ctx) {
+ switch (m_context_type) {
+ case ContextType::Invalid:
+ case ContextType::RegisterInfo: // RegisterInfo *
+ case ContextType::LLDBType: // Type *
+ break;
+
+ case ContextType::Variable: // Variable *
+ ResolveValue(exe_ctx);
+ return true;
+ }
+ return false;
+}
+
+uint64_t Value::GetValueByteSize(Status *error_ptr, ExecutionContext *exe_ctx) {
+ switch (m_context_type) {
+ case ContextType::RegisterInfo: // RegisterInfo *
+ if (GetRegisterInfo()) {
+ if (error_ptr)
+ error_ptr->Clear();
+ return GetRegisterInfo()->byte_size;
+ }
+ break;
+
+ case ContextType::Invalid:
+ case ContextType::LLDBType: // Type *
+ case ContextType::Variable: // Variable *
+ {
+ auto *scope = exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr;
+ if (std::optional<uint64_t> size = GetCompilerType().GetByteSize(scope)) {
+ if (error_ptr)
+ error_ptr->Clear();
+ return *size;
+ }
+ break;
+ }
+ }
+ if (error_ptr && error_ptr->Success())
+ error_ptr->SetErrorString("Unable to determine byte size.");
+ return 0;
+}
+
+const CompilerType &Value::GetCompilerType() {
+ if (!m_compiler_type.IsValid()) {
+ switch (m_context_type) {
+ case ContextType::Invalid:
+ break;
+
+ case ContextType::RegisterInfo:
+ break; // TODO: Eventually convert into a compiler type?
+
+ case ContextType::LLDBType: {
+ Type *lldb_type = GetType();
+ if (lldb_type)
+ m_compiler_type = lldb_type->GetForwardCompilerType();
+ } break;
+
+ case ContextType::Variable: {
+ Variable *variable = GetVariable();
+ if (variable) {
+ Type *variable_type = variable->GetType();
+ if (variable_type)
+ m_compiler_type = variable_type->GetForwardCompilerType();
+ }
+ } break;
+ }
+ }
+
+ return m_compiler_type;
+}
+
+void Value::SetCompilerType(const CompilerType &compiler_type) {
+ m_compiler_type = compiler_type;
+}
+
+lldb::Format Value::GetValueDefaultFormat() {
+ switch (m_context_type) {
+ case ContextType::RegisterInfo:
+ if (GetRegisterInfo())
+ return GetRegisterInfo()->format;
+ break;
+
+ case ContextType::Invalid:
+ case ContextType::LLDBType:
+ case ContextType::Variable: {
+ const CompilerType &ast_type = GetCompilerType();
+ if (ast_type.IsValid())
+ return ast_type.GetFormat();
+ } break;
+ }
+
+ // Return a good default in case we can't figure anything out
+ return eFormatHex;
+}
+
+bool Value::GetData(DataExtractor &data) {
+ switch (m_value_type) {
+ case ValueType::Invalid:
+ return false;
+ case ValueType::Scalar:
+ if (m_value.GetData(data))
+ return true;
+ break;
+
+ case ValueType::LoadAddress:
+ case ValueType::FileAddress:
+ case ValueType::HostAddress:
+ if (m_data_buffer.GetByteSize()) {
+ data.SetData(m_data_buffer.GetBytes(), m_data_buffer.GetByteSize(),
+ data.GetByteOrder());
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
+ Module *module) {
+ data.Clear();
+
+ Status error;
+ lldb::addr_t address = LLDB_INVALID_ADDRESS;
+ AddressType address_type = eAddressTypeFile;
+ Address file_so_addr;
+ const CompilerType &ast_type = GetCompilerType();
+ std::optional<uint64_t> type_size = ast_type.GetByteSize(
+ exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr);
+ // Nothing to be done for a zero-sized type.
+ if (type_size && *type_size == 0)
+ return error;
+
+ switch (m_value_type) {
+ case ValueType::Invalid:
+ error.SetErrorString("invalid value");
+ break;
+ case ValueType::Scalar: {
+ data.SetByteOrder(endian::InlHostByteOrder());
+ if (ast_type.IsValid())
+ data.SetAddressByteSize(ast_type.GetPointerByteSize());
+ else
+ data.SetAddressByteSize(sizeof(void *));
+
+ uint32_t limit_byte_size = UINT32_MAX;
+
+ if (type_size)
+ limit_byte_size = *type_size;
+
+ if (limit_byte_size <= m_value.GetByteSize()) {
+ if (m_value.GetData(data, limit_byte_size))
+ return error; // Success;
+ }
+
+ error.SetErrorString("extracting data from value failed");
+ break;
+ }
+ case ValueType::LoadAddress:
+ if (exe_ctx == nullptr) {
+ error.SetErrorString("can't read load address (no execution context)");
+ } else {
+ Process *process = exe_ctx->GetProcessPtr();
+ if (process == nullptr || !process->IsAlive()) {
+ Target *target = exe_ctx->GetTargetPtr();
+ if (target) {
+ // Allow expressions to run and evaluate things when the target has
+ // memory sections loaded. This allows you to use "target modules
+ // load" to load your executable and any shared libraries, then
+ // execute commands where you can look at types in data sections.
+ const SectionLoadList &target_sections = target->GetSectionLoadList();
+ if (!target_sections.IsEmpty()) {
+ address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
+ if (target_sections.ResolveLoadAddress(address, file_so_addr)) {
+ address_type = eAddressTypeLoad;
+ data.SetByteOrder(target->GetArchitecture().GetByteOrder());
+ data.SetAddressByteSize(
+ target->GetArchitecture().GetAddressByteSize());
+ } else
+ address = LLDB_INVALID_ADDRESS;
+ }
+ } else {
+ error.SetErrorString("can't read load address (invalid process)");
+ }
+ } else {
+ address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
+ address_type = eAddressTypeLoad;
+ data.SetByteOrder(
+ process->GetTarget().GetArchitecture().GetByteOrder());
+ data.SetAddressByteSize(
+ process->GetTarget().GetArchitecture().GetAddressByteSize());
+ }
+ }
+ break;
+
+ case ValueType::FileAddress:
+ if (exe_ctx == nullptr) {
+ error.SetErrorString("can't read file address (no execution context)");
+ } else if (exe_ctx->GetTargetPtr() == nullptr) {
+ error.SetErrorString("can't read file address (invalid target)");
+ } else {
+ address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
+ if (address == LLDB_INVALID_ADDRESS) {
+ error.SetErrorString("invalid file address");
+ } else {
+ if (module == nullptr) {
+ // The only thing we can currently lock down to a module so that we
+ // can resolve a file address, is a variable.
+ Variable *variable = GetVariable();
+ if (variable) {
+ SymbolContext var_sc;
+ variable->CalculateSymbolContext(&var_sc);
+ module = var_sc.module_sp.get();
+ }
+ }
+
+ if (module) {
+ bool resolved = false;
+ ObjectFile *objfile = module->GetObjectFile();
+ if (objfile) {
+ Address so_addr(address, objfile->GetSectionList());
+ addr_t load_address =
+ so_addr.GetLoadAddress(exe_ctx->GetTargetPtr());
+ bool process_launched_and_stopped =
+ exe_ctx->GetProcessPtr()
+ ? StateIsStoppedState(exe_ctx->GetProcessPtr()->GetState(),
+ true /* must_exist */)
+ : false;
+ // Don't use the load address if the process has exited.
+ if (load_address != LLDB_INVALID_ADDRESS &&
+ process_launched_and_stopped) {
+ resolved = true;
+ address = load_address;
+ address_type = eAddressTypeLoad;
+ data.SetByteOrder(
+ exe_ctx->GetTargetRef().GetArchitecture().GetByteOrder());
+ data.SetAddressByteSize(exe_ctx->GetTargetRef()
+ .GetArchitecture()
+ .GetAddressByteSize());
+ } else {
+ if (so_addr.IsSectionOffset()) {
+ resolved = true;
+ file_so_addr = so_addr;
+ data.SetByteOrder(objfile->GetByteOrder());
+ data.SetAddressByteSize(objfile->GetAddressByteSize());
+ }
+ }
+ }
+ if (!resolved) {
+ Variable *variable = GetVariable();
+
+ if (module) {
+ if (variable)
+ error.SetErrorStringWithFormat(
+ "unable to resolve the module for file address 0x%" PRIx64
+ " for variable '%s' in %s",
+ address, variable->GetName().AsCString(""),
+ module->GetFileSpec().GetPath().c_str());
+ else
+ error.SetErrorStringWithFormat(
+ "unable to resolve the module for file address 0x%" PRIx64
+ " in %s",
+ address, module->GetFileSpec().GetPath().c_str());
+ } else {
+ if (variable)
+ error.SetErrorStringWithFormat(
+ "unable to resolve the module for file address 0x%" PRIx64
+ " for variable '%s'",
+ address, variable->GetName().AsCString(""));
+ else
+ error.SetErrorStringWithFormat(
+ "unable to resolve the module for file address 0x%" PRIx64,
+ address);
+ }
+ }
+ } else {
+ // Can't convert a file address to anything valid without more
+ // context (which Module it came from)
+ error.SetErrorString(
+ "can't read memory from file address without more context");
+ }
+ }
+ }
+ break;
+
+ case ValueType::HostAddress:
+ address = m_value.ULongLong(LLDB_INVALID_ADDRESS);
+ address_type = eAddressTypeHost;
+ if (exe_ctx) {
+ Target *target = exe_ctx->GetTargetPtr();
+ if (target) {
+ data.SetByteOrder(target->GetArchitecture().GetByteOrder());
+ data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize());
+ break;
+ }
+ }
+ // fallback to host settings
+ data.SetByteOrder(endian::InlHostByteOrder());
+ data.SetAddressByteSize(sizeof(void *));
+ break;
+ }
+
+ // Bail if we encountered any errors
+ if (error.Fail())
+ return error;
+
+ if (address == LLDB_INVALID_ADDRESS) {
+ error.SetErrorStringWithFormat("invalid %s address",
+ address_type == eAddressTypeHost ? "host"
+ : "load");
+ return error;
+ }
+
+ // If we got here, we need to read the value from memory.
+ size_t byte_size = GetValueByteSize(&error, exe_ctx);
+
+ // Bail if we encountered any errors getting the byte size.
+ if (error.Fail())
+ return error;
+
+ // No memory to read for zero-sized types.
+ if (byte_size == 0)
+ return error;
+
+ // Make sure we have enough room within "data", and if we don't make
+ // something large enough that does
+ if (!data.ValidOffsetForDataOfSize(0, byte_size)) {
+ auto data_sp = std::make_shared<DataBufferHeap>(byte_size, '\0');
+ data.SetData(data_sp);
+ }
+
+ uint8_t *dst = const_cast<uint8_t *>(data.PeekData(0, byte_size));
+ if (dst != nullptr) {
+ if (address_type == eAddressTypeHost) {
+ // The address is an address in this process, so just copy it.
+ if (address == 0) {
+ error.SetErrorString("trying to read from host address of 0.");
+ return error;
+ }
+ memcpy(dst, reinterpret_cast<uint8_t *>(address), byte_size);
+ } else if ((address_type == eAddressTypeLoad) ||
+ (address_type == eAddressTypeFile)) {
+ if (file_so_addr.IsValid()) {
+ const bool force_live_memory = true;
+ if (exe_ctx->GetTargetRef().ReadMemory(file_so_addr, dst, byte_size,
+ error, force_live_memory) !=
+ byte_size) {
+ error.SetErrorStringWithFormat(
+ "read memory from 0x%" PRIx64 " failed", (uint64_t)address);
+ }
+ } else {
+ // The execution context might have a NULL process, but it might have a
+ // valid process in the exe_ctx->target, so use the
+ // ExecutionContext::GetProcess accessor to ensure we get the process
+ // if there is one.
+ Process *process = exe_ctx->GetProcessPtr();
+
+ if (process) {
+ const size_t bytes_read =
+ process->ReadMemory(address, dst, byte_size, error);
+ if (bytes_read != byte_size)
+ error.SetErrorStringWithFormat(
+ "read memory from 0x%" PRIx64 " failed (%u of %u bytes read)",
+ (uint64_t)address, (uint32_t)bytes_read, (uint32_t)byte_size);
+ } else {
+ error.SetErrorStringWithFormat("read memory from 0x%" PRIx64
+ " failed (invalid process)",
+ (uint64_t)address);
+ }
+ }
+ } else {
+ error.SetErrorStringWithFormat("unsupported AddressType value (%i)",
+ address_type);
+ }
+ } else {
+ error.SetErrorString("out of memory");
+ }
+
+ return error;
+}
+
+Scalar &Value::ResolveValue(ExecutionContext *exe_ctx, Module *module) {
+ const CompilerType &compiler_type = GetCompilerType();
+ if (compiler_type.IsValid()) {
+ switch (m_value_type) {
+ case ValueType::Invalid:
+ case ValueType::Scalar: // raw scalar value
+ break;
+
+ case ValueType::FileAddress:
+ case ValueType::LoadAddress: // load address value
+ case ValueType::HostAddress: // host address value (for memory in the process
+ // that is using liblldb)
+ {
+ DataExtractor data;
+ lldb::addr_t addr = m_value.ULongLong(LLDB_INVALID_ADDRESS);
+ Status error(GetValueAsData(exe_ctx, data, module));
+ if (error.Success()) {
+ Scalar scalar;
+ if (compiler_type.GetValueAsScalar(
+ data, 0, data.GetByteSize(), scalar,
+ exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr)) {
+ m_value = scalar;
+ m_value_type = ValueType::Scalar;
+ } else {
+ if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) {
+ m_value.Clear();
+ m_value_type = ValueType::Scalar;
+ }
+ }
+ } else {
+ if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) {
+ m_value.Clear();
+ m_value_type = ValueType::Scalar;
+ }
+ }
+ } break;
+ }
+ }
+ return m_value;
+}
+
+Variable *Value::GetVariable() {
+ if (m_context_type == ContextType::Variable)
+ return static_cast<Variable *>(m_context);
+ return nullptr;
+}
+
+void Value::Clear() {
+ m_value.Clear();
+ m_compiler_type.Clear();
+ m_value_type = ValueType::Scalar;
+ m_context = nullptr;
+ m_context_type = ContextType::Invalid;
+ m_data_buffer.Clear();
+}
+
+const char *Value::GetValueTypeAsCString(ValueType value_type) {
+ switch (value_type) {
+ case ValueType::Invalid:
+ return "invalid";
+ case ValueType::Scalar:
+ return "scalar";
+ case ValueType::FileAddress:
+ return "file address";
+ case ValueType::LoadAddress:
+ return "load address";
+ case ValueType::HostAddress:
+ return "host address";
+ };
+ llvm_unreachable("enum cases exhausted.");
+}
+
+const char *Value::GetContextTypeAsCString(ContextType context_type) {
+ switch (context_type) {
+ case ContextType::Invalid:
+ return "invalid";
+ case ContextType::RegisterInfo:
+ return "RegisterInfo *";
+ case ContextType::LLDBType:
+ return "Type *";
+ case ContextType::Variable:
+ return "Variable *";
+ };
+ llvm_unreachable("enum cases exhausted.");
+}
+
+void Value::ConvertToLoadAddress(Module *module, Target *target) {
+ if (!module || !target || (GetValueType() != ValueType::FileAddress))
+ return;
+
+ lldb::addr_t file_addr = GetScalar().ULongLong(LLDB_INVALID_ADDRESS);
+ if (file_addr == LLDB_INVALID_ADDRESS)
+ return;
+
+ Address so_addr;
+ if (!module->ResolveFileAddress(file_addr, so_addr))
+ return;
+ lldb::addr_t load_addr = so_addr.GetLoadAddress(target);
+ if (load_addr == LLDB_INVALID_ADDRESS)
+ return;
+
+ SetValueType(Value::ValueType::LoadAddress);
+ GetScalar() = load_addr;
+}
+
+void ValueList::PushValue(const Value &value) { m_values.push_back(value); }
+
+size_t ValueList::GetSize() { return m_values.size(); }
+
+Value *ValueList::GetValueAtIndex(size_t idx) {
+ if (idx < GetSize()) {
+ return &(m_values[idx]);
+ } else
+ return nullptr;
+}
+
+void ValueList::Clear() { m_values.clear(); }