mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-23 05:40:09 +00:00
[lldb] Improve maintainability and readability for ValueObject methods (#75865)
As I worked through changes to another PR (https://github.com/llvm/llvm-project/pull/74912), I couldn't help but rewrite a few methods for readability, maintainability, and possibly some behavior correctness too. 1. Exiting early instead of nested `if`-statements, which: - Reduces indentation levels for all subsequent lines - Treats missing pre-conditions similar to an error - Clearly indicates that the full length of the method is the "happy path". 2. Explicitly return empty Value Object shared pointers for those error (like) situations, which - Reduces the time it takes a maintainer to figure out what the method actually returns based on those conditions. 3. Converting a mix of `if` and `if`-`else`-statements around an enum into one `switch` statement, which: - Consolidates the former branching logic - Lets the compiler warn you of a (future) missing enum case - This one may actually change behavior slightly, because what was an early test for one enum case, now happens later on in the `switch`. 4. Consolidating near-identical, "copy-pasta" logic into one place, which: - Separates the common code to the diverging paths. - Highlights the differences between the code paths. rdar://119833526
This commit is contained in:
parent
25e1916d88
commit
d657519838
@ -1582,62 +1582,64 @@ bool ValueObject::IsUninitializedReference() {
|
|||||||
|
|
||||||
ValueObjectSP ValueObject::GetSyntheticArrayMember(size_t index,
|
ValueObjectSP ValueObject::GetSyntheticArrayMember(size_t index,
|
||||||
bool can_create) {
|
bool can_create) {
|
||||||
ValueObjectSP synthetic_child_sp;
|
if (!IsPointerType() && !IsArrayType())
|
||||||
if (IsPointerType() || IsArrayType()) {
|
return ValueObjectSP();
|
||||||
std::string index_str = llvm::formatv("[{0}]", index);
|
|
||||||
ConstString index_const_str(index_str);
|
|
||||||
// Check if we have already created a synthetic array member in this valid
|
|
||||||
// object. If we have we will re-use it.
|
|
||||||
synthetic_child_sp = GetSyntheticChild(index_const_str);
|
|
||||||
if (!synthetic_child_sp) {
|
|
||||||
ValueObject *synthetic_child;
|
|
||||||
// We haven't made a synthetic array member for INDEX yet, so lets make
|
|
||||||
// one and cache it for any future reference.
|
|
||||||
synthetic_child = CreateChildAtIndex(0, true, index);
|
|
||||||
|
|
||||||
// Cache the value if we got one back...
|
std::string index_str = llvm::formatv("[{0}]", index);
|
||||||
if (synthetic_child) {
|
ConstString index_const_str(index_str);
|
||||||
AddSyntheticChild(index_const_str, synthetic_child);
|
// Check if we have already created a synthetic array member in this valid
|
||||||
synthetic_child_sp = synthetic_child->GetSP();
|
// object. If we have we will re-use it.
|
||||||
synthetic_child_sp->SetName(ConstString(index_str));
|
if (auto existing_synthetic_child = GetSyntheticChild(index_const_str))
|
||||||
synthetic_child_sp->m_flags.m_is_array_item_for_pointer = true;
|
return existing_synthetic_child;
|
||||||
}
|
|
||||||
}
|
// We haven't made a synthetic array member for INDEX yet, so lets make
|
||||||
}
|
// one and cache it for any future reference.
|
||||||
|
ValueObject *synthetic_child = CreateChildAtIndex(0, true, index);
|
||||||
|
|
||||||
|
if (!synthetic_child)
|
||||||
|
return ValueObjectSP();
|
||||||
|
|
||||||
|
// Cache the synthetic child's value because it's valid.
|
||||||
|
AddSyntheticChild(index_const_str, synthetic_child);
|
||||||
|
auto synthetic_child_sp = synthetic_child->GetSP();
|
||||||
|
synthetic_child_sp->SetName(ConstString(index_str));
|
||||||
|
synthetic_child_sp->m_flags.m_is_array_item_for_pointer = true;
|
||||||
return synthetic_child_sp;
|
return synthetic_child_sp;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueObjectSP ValueObject::GetSyntheticBitFieldChild(uint32_t from, uint32_t to,
|
ValueObjectSP ValueObject::GetSyntheticBitFieldChild(uint32_t from, uint32_t to,
|
||||||
bool can_create) {
|
bool can_create) {
|
||||||
ValueObjectSP synthetic_child_sp;
|
if (!IsScalarType())
|
||||||
if (IsScalarType()) {
|
return ValueObjectSP();
|
||||||
std::string index_str = llvm::formatv("[{0}-{1}]", from, to);
|
|
||||||
ConstString index_const_str(index_str);
|
|
||||||
// Check if we have already created a synthetic array member in this valid
|
|
||||||
// object. If we have we will re-use it.
|
|
||||||
synthetic_child_sp = GetSyntheticChild(index_const_str);
|
|
||||||
if (!synthetic_child_sp) {
|
|
||||||
uint32_t bit_field_size = to - from + 1;
|
|
||||||
uint32_t bit_field_offset = from;
|
|
||||||
if (GetDataExtractor().GetByteOrder() == eByteOrderBig)
|
|
||||||
bit_field_offset =
|
|
||||||
GetByteSize().value_or(0) * 8 - bit_field_size - bit_field_offset;
|
|
||||||
// We haven't made a synthetic array member for INDEX yet, so lets make
|
|
||||||
// one and cache it for any future reference.
|
|
||||||
ValueObjectChild *synthetic_child = new ValueObjectChild(
|
|
||||||
*this, GetCompilerType(), index_const_str, GetByteSize().value_or(0),
|
|
||||||
0, bit_field_size, bit_field_offset, false, false,
|
|
||||||
eAddressTypeInvalid, 0);
|
|
||||||
|
|
||||||
// Cache the value if we got one back...
|
std::string index_str = llvm::formatv("[{0}-{1}]", from, to);
|
||||||
if (synthetic_child) {
|
ConstString index_const_str(index_str);
|
||||||
AddSyntheticChild(index_const_str, synthetic_child);
|
|
||||||
synthetic_child_sp = synthetic_child->GetSP();
|
// Check if we have already created a synthetic array member in this valid
|
||||||
synthetic_child_sp->SetName(ConstString(index_str));
|
// object. If we have we will re-use it.
|
||||||
synthetic_child_sp->m_flags.m_is_bitfield_for_scalar = true;
|
if (auto existing_synthetic_child = GetSyntheticChild(index_const_str))
|
||||||
}
|
return existing_synthetic_child;
|
||||||
}
|
|
||||||
}
|
uint32_t bit_field_size = to - from + 1;
|
||||||
|
uint32_t bit_field_offset = from;
|
||||||
|
if (GetDataExtractor().GetByteOrder() == eByteOrderBig)
|
||||||
|
bit_field_offset =
|
||||||
|
GetByteSize().value_or(0) * 8 - bit_field_size - bit_field_offset;
|
||||||
|
|
||||||
|
// We haven't made a synthetic array member for INDEX yet, so lets make
|
||||||
|
// one and cache it for any future reference.
|
||||||
|
ValueObjectChild *synthetic_child = new ValueObjectChild(
|
||||||
|
*this, GetCompilerType(), index_const_str, GetByteSize().value_or(0), 0,
|
||||||
|
bit_field_size, bit_field_offset, false, false, eAddressTypeInvalid, 0);
|
||||||
|
|
||||||
|
if (!synthetic_child)
|
||||||
|
return ValueObjectSP();
|
||||||
|
|
||||||
|
// Cache the synthetic child's value because it's valid.
|
||||||
|
AddSyntheticChild(index_const_str, synthetic_child);
|
||||||
|
auto synthetic_child_sp = synthetic_child->GetSP();
|
||||||
|
synthetic_child_sp->SetName(ConstString(index_str));
|
||||||
|
synthetic_child_sp->m_flags.m_is_bitfield_for_scalar = true;
|
||||||
return synthetic_child_sp;
|
return synthetic_child_sp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1647,9 +1649,8 @@ ValueObjectSP ValueObject::GetSyntheticChildAtOffset(
|
|||||||
|
|
||||||
ValueObjectSP synthetic_child_sp;
|
ValueObjectSP synthetic_child_sp;
|
||||||
|
|
||||||
if (name_const_str.IsEmpty()) {
|
if (name_const_str.IsEmpty())
|
||||||
name_const_str.SetString("@" + std::to_string(offset));
|
name_const_str.SetString("@" + std::to_string(offset));
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have already created a synthetic array member in this valid
|
// Check if we have already created a synthetic array member in this valid
|
||||||
// object. If we have we will re-use it.
|
// object. If we have we will re-use it.
|
||||||
@ -1659,13 +1660,13 @@ ValueObjectSP ValueObject::GetSyntheticChildAtOffset(
|
|||||||
return synthetic_child_sp;
|
return synthetic_child_sp;
|
||||||
|
|
||||||
if (!can_create)
|
if (!can_create)
|
||||||
return {};
|
return ValueObjectSP();
|
||||||
|
|
||||||
ExecutionContext exe_ctx(GetExecutionContextRef());
|
ExecutionContext exe_ctx(GetExecutionContextRef());
|
||||||
std::optional<uint64_t> size =
|
std::optional<uint64_t> size =
|
||||||
type.GetByteSize(exe_ctx.GetBestExecutionContextScope());
|
type.GetByteSize(exe_ctx.GetBestExecutionContextScope());
|
||||||
if (!size)
|
if (!size)
|
||||||
return {};
|
return ValueObjectSP();
|
||||||
ValueObjectChild *synthetic_child =
|
ValueObjectChild *synthetic_child =
|
||||||
new ValueObjectChild(*this, type, name_const_str, *size, offset, 0, 0,
|
new ValueObjectChild(*this, type, name_const_str, *size, offset, 0, 0,
|
||||||
false, false, eAddressTypeInvalid, 0);
|
false, false, eAddressTypeInvalid, 0);
|
||||||
@ -1699,7 +1700,7 @@ ValueObjectSP ValueObject::GetSyntheticBase(uint32_t offset,
|
|||||||
return synthetic_child_sp;
|
return synthetic_child_sp;
|
||||||
|
|
||||||
if (!can_create)
|
if (!can_create)
|
||||||
return {};
|
return ValueObjectSP();
|
||||||
|
|
||||||
const bool is_base_class = true;
|
const bool is_base_class = true;
|
||||||
|
|
||||||
@ -1707,7 +1708,7 @@ ValueObjectSP ValueObject::GetSyntheticBase(uint32_t offset,
|
|||||||
std::optional<uint64_t> size =
|
std::optional<uint64_t> size =
|
||||||
type.GetByteSize(exe_ctx.GetBestExecutionContextScope());
|
type.GetByteSize(exe_ctx.GetBestExecutionContextScope());
|
||||||
if (!size)
|
if (!size)
|
||||||
return {};
|
return ValueObjectSP();
|
||||||
ValueObjectChild *synthetic_child =
|
ValueObjectChild *synthetic_child =
|
||||||
new ValueObjectChild(*this, type, name_const_str, *size, offset, 0, 0,
|
new ValueObjectChild(*this, type, name_const_str, *size, offset, 0, 0,
|
||||||
is_base_class, false, eAddressTypeInvalid, 0);
|
is_base_class, false, eAddressTypeInvalid, 0);
|
||||||
@ -1736,30 +1737,30 @@ static const char *SkipLeadingExpressionPathSeparators(const char *expression) {
|
|||||||
ValueObjectSP
|
ValueObjectSP
|
||||||
ValueObject::GetSyntheticExpressionPathChild(const char *expression,
|
ValueObject::GetSyntheticExpressionPathChild(const char *expression,
|
||||||
bool can_create) {
|
bool can_create) {
|
||||||
ValueObjectSP synthetic_child_sp;
|
|
||||||
ConstString name_const_string(expression);
|
ConstString name_const_string(expression);
|
||||||
// Check if we have already created a synthetic array member in this valid
|
// Check if we have already created a synthetic array member in this valid
|
||||||
// object. If we have we will re-use it.
|
// object. If we have we will re-use it.
|
||||||
synthetic_child_sp = GetSyntheticChild(name_const_string);
|
if (auto existing_synthetic_child = GetSyntheticChild(name_const_string))
|
||||||
if (!synthetic_child_sp) {
|
return existing_synthetic_child;
|
||||||
// We haven't made a synthetic array member for expression yet, so lets
|
|
||||||
// make one and cache it for any future reference.
|
|
||||||
synthetic_child_sp = GetValueForExpressionPath(
|
|
||||||
expression, nullptr, nullptr,
|
|
||||||
GetValueForExpressionPathOptions().SetSyntheticChildrenTraversal(
|
|
||||||
GetValueForExpressionPathOptions::SyntheticChildrenTraversal::
|
|
||||||
None));
|
|
||||||
|
|
||||||
// Cache the value if we got one back...
|
// We haven't made a synthetic array member for expression yet, so lets
|
||||||
if (synthetic_child_sp.get()) {
|
// make one and cache it for any future reference.
|
||||||
// FIXME: this causes a "real" child to end up with its name changed to
|
auto path_options = GetValueForExpressionPathOptions();
|
||||||
// the contents of expression
|
path_options.SetSyntheticChildrenTraversal(
|
||||||
AddSyntheticChild(name_const_string, synthetic_child_sp.get());
|
GetValueForExpressionPathOptions::SyntheticChildrenTraversal::None);
|
||||||
synthetic_child_sp->SetName(
|
auto synthetic_child =
|
||||||
ConstString(SkipLeadingExpressionPathSeparators(expression)));
|
GetValueForExpressionPath(expression, nullptr, nullptr, path_options);
|
||||||
}
|
|
||||||
}
|
if (!synthetic_child)
|
||||||
return synthetic_child_sp;
|
return ValueObjectSP();
|
||||||
|
|
||||||
|
// Cache the synthetic child's value because it's valid.
|
||||||
|
// FIXME: this causes a "real" child to end up with its name changed to
|
||||||
|
// the contents of expression
|
||||||
|
AddSyntheticChild(name_const_string, synthetic_child.get());
|
||||||
|
synthetic_child->SetName(
|
||||||
|
ConstString(SkipLeadingExpressionPathSeparators(expression)));
|
||||||
|
return synthetic_child;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ValueObject::CalculateSyntheticValue() {
|
void ValueObject::CalculateSyntheticValue() {
|
||||||
@ -1956,66 +1957,55 @@ ValueObjectSP ValueObject::GetValueForExpressionPath(
|
|||||||
const GetValueForExpressionPathOptions &options,
|
const GetValueForExpressionPathOptions &options,
|
||||||
ExpressionPathAftermath *final_task_on_target) {
|
ExpressionPathAftermath *final_task_on_target) {
|
||||||
|
|
||||||
ExpressionPathScanEndReason dummy_reason_to_stop =
|
auto dummy_stop_reason = eExpressionPathScanEndReasonUnknown;
|
||||||
ValueObject::eExpressionPathScanEndReasonUnknown;
|
auto dummy_value_type = eExpressionPathEndResultTypeInvalid;
|
||||||
ExpressionPathEndResultType dummy_final_value_type =
|
auto dummy_final_task = eExpressionPathAftermathNothing;
|
||||||
ValueObject::eExpressionPathEndResultTypeInvalid;
|
|
||||||
ExpressionPathAftermath dummy_final_task_on_target =
|
|
||||||
ValueObject::eExpressionPathAftermathNothing;
|
|
||||||
|
|
||||||
ValueObjectSP ret_val = GetValueForExpressionPath_Impl(
|
auto proxy_stop_reason = reason_to_stop ? reason_to_stop : &dummy_stop_reason;
|
||||||
expression, reason_to_stop ? reason_to_stop : &dummy_reason_to_stop,
|
auto proxy_value_type =
|
||||||
final_value_type ? final_value_type : &dummy_final_value_type, options,
|
final_value_type ? final_value_type : &dummy_value_type;
|
||||||
final_task_on_target ? final_task_on_target
|
auto proxy_final_task =
|
||||||
: &dummy_final_task_on_target);
|
final_task_on_target ? final_task_on_target : &dummy_final_task;
|
||||||
|
|
||||||
if (!final_task_on_target ||
|
auto ret_value = GetValueForExpressionPath_Impl(expression, proxy_stop_reason,
|
||||||
*final_task_on_target == ValueObject::eExpressionPathAftermathNothing)
|
proxy_value_type, options,
|
||||||
return ret_val;
|
proxy_final_task);
|
||||||
|
|
||||||
if (ret_val.get() &&
|
// The caller knows nothing happened if `final_task_on_target` doesn't change.
|
||||||
((final_value_type ? *final_value_type : dummy_final_value_type) ==
|
if (!ret_value || (*proxy_value_type) != eExpressionPathEndResultTypePlain ||
|
||||||
eExpressionPathEndResultTypePlain)) // I can only deref and takeaddress
|
!final_task_on_target)
|
||||||
// of plain objects
|
return ValueObjectSP();
|
||||||
{
|
|
||||||
if ((final_task_on_target ? *final_task_on_target
|
ExpressionPathAftermath &final_task_on_target_ref = (*final_task_on_target);
|
||||||
: dummy_final_task_on_target) ==
|
ExpressionPathScanEndReason stop_reason_for_error;
|
||||||
ValueObject::eExpressionPathAftermathDereference) {
|
Status error;
|
||||||
Status error;
|
// The method can only dereference and take the address of plain objects.
|
||||||
ValueObjectSP final_value = ret_val->Dereference(error);
|
switch (final_task_on_target_ref) {
|
||||||
if (error.Fail() || !final_value.get()) {
|
case eExpressionPathAftermathNothing:
|
||||||
if (reason_to_stop)
|
return ret_value;
|
||||||
*reason_to_stop =
|
|
||||||
ValueObject::eExpressionPathScanEndReasonDereferencingFailed;
|
case eExpressionPathAftermathDereference:
|
||||||
if (final_value_type)
|
ret_value = ret_value->Dereference(error);
|
||||||
*final_value_type = ValueObject::eExpressionPathEndResultTypeInvalid;
|
stop_reason_for_error = eExpressionPathScanEndReasonDereferencingFailed;
|
||||||
return ValueObjectSP();
|
break;
|
||||||
} else {
|
|
||||||
if (final_task_on_target)
|
case eExpressionPathAftermathTakeAddress:
|
||||||
*final_task_on_target = ValueObject::eExpressionPathAftermathNothing;
|
ret_value = ret_value->AddressOf(error);
|
||||||
return final_value;
|
stop_reason_for_error = eExpressionPathScanEndReasonTakingAddressFailed;
|
||||||
}
|
break;
|
||||||
}
|
|
||||||
if (*final_task_on_target ==
|
|
||||||
ValueObject::eExpressionPathAftermathTakeAddress) {
|
|
||||||
Status error;
|
|
||||||
ValueObjectSP final_value = ret_val->AddressOf(error);
|
|
||||||
if (error.Fail() || !final_value.get()) {
|
|
||||||
if (reason_to_stop)
|
|
||||||
*reason_to_stop =
|
|
||||||
ValueObject::eExpressionPathScanEndReasonTakingAddressFailed;
|
|
||||||
if (final_value_type)
|
|
||||||
*final_value_type = ValueObject::eExpressionPathEndResultTypeInvalid;
|
|
||||||
return ValueObjectSP();
|
|
||||||
} else {
|
|
||||||
if (final_task_on_target)
|
|
||||||
*final_task_on_target = ValueObject::eExpressionPathAftermathNothing;
|
|
||||||
return final_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ret_val; // final_task_on_target will still have its original value, so
|
|
||||||
// you know I did not do it
|
if (ret_value && error.Success()) {
|
||||||
|
final_task_on_target_ref = eExpressionPathAftermathNothing;
|
||||||
|
return ret_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reason_to_stop)
|
||||||
|
*reason_to_stop = stop_reason_for_error;
|
||||||
|
|
||||||
|
if (final_value_type)
|
||||||
|
*final_value_type = eExpressionPathEndResultTypeInvalid;
|
||||||
|
return ValueObjectSP();
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
|
ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
|
||||||
@ -2686,39 +2676,47 @@ ValueObjectSP ValueObject::AddressOf(Status &error) {
|
|||||||
const bool scalar_is_load_address = false;
|
const bool scalar_is_load_address = false;
|
||||||
addr_t addr = GetAddressOf(scalar_is_load_address, &address_type);
|
addr_t addr = GetAddressOf(scalar_is_load_address, &address_type);
|
||||||
error.Clear();
|
error.Clear();
|
||||||
if (addr != LLDB_INVALID_ADDRESS && address_type != eAddressTypeHost) {
|
|
||||||
switch (address_type) {
|
|
||||||
case eAddressTypeInvalid: {
|
|
||||||
StreamString expr_path_strm;
|
|
||||||
GetExpressionPath(expr_path_strm);
|
|
||||||
error.SetErrorStringWithFormat("'%s' is not in memory",
|
|
||||||
expr_path_strm.GetData());
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case eAddressTypeFile:
|
StreamString expr_path_strm;
|
||||||
case eAddressTypeLoad: {
|
GetExpressionPath(expr_path_strm);
|
||||||
CompilerType compiler_type = GetCompilerType();
|
const char *expr_path_str = expr_path_strm.GetData();
|
||||||
if (compiler_type) {
|
|
||||||
std::string name(1, '&');
|
ExecutionContext exe_ctx(GetExecutionContextRef());
|
||||||
name.append(m_name.AsCString(""));
|
auto scope = exe_ctx.GetBestExecutionContextScope();
|
||||||
ExecutionContext exe_ctx(GetExecutionContextRef());
|
|
||||||
m_addr_of_valobj_sp = ValueObjectConstResult::Create(
|
if (addr == LLDB_INVALID_ADDRESS) {
|
||||||
exe_ctx.GetBestExecutionContextScope(),
|
|
||||||
compiler_type.GetPointerType(), ConstString(name.c_str()), addr,
|
|
||||||
eAddressTypeInvalid, m_data.GetAddressByteSize());
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
StreamString expr_path_strm;
|
|
||||||
GetExpressionPath(expr_path_strm);
|
|
||||||
error.SetErrorStringWithFormat("'%s' doesn't have a valid address",
|
error.SetErrorStringWithFormat("'%s' doesn't have a valid address",
|
||||||
expr_path_strm.GetData());
|
expr_path_str);
|
||||||
|
return ValueObjectSP();
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_addr_of_valobj_sp;
|
switch (address_type) {
|
||||||
|
case eAddressTypeInvalid:
|
||||||
|
error.SetErrorStringWithFormat("'%s' is not in memory", expr_path_str);
|
||||||
|
return ValueObjectSP();
|
||||||
|
|
||||||
|
case eAddressTypeHost:
|
||||||
|
error.SetErrorStringWithFormat("'%s' is in host process (LLDB) memory",
|
||||||
|
expr_path_str);
|
||||||
|
return ValueObjectSP();
|
||||||
|
|
||||||
|
case eAddressTypeFile:
|
||||||
|
case eAddressTypeLoad: {
|
||||||
|
CompilerType compiler_type = GetCompilerType();
|
||||||
|
if (!compiler_type) {
|
||||||
|
error.SetErrorStringWithFormat("'%s' doesn't have a compiler type",
|
||||||
|
expr_path_str);
|
||||||
|
return ValueObjectSP();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name(1, '&');
|
||||||
|
name.append(m_name.AsCString(""));
|
||||||
|
m_addr_of_valobj_sp = ValueObjectConstResult::Create(
|
||||||
|
scope, compiler_type.GetPointerType(), ConstString(name.c_str()), addr,
|
||||||
|
eAddressTypeInvalid, m_data.GetAddressByteSize());
|
||||||
|
return m_addr_of_valobj_sp;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueObjectSP ValueObject::DoCast(const CompilerType &compiler_type) {
|
ValueObjectSP ValueObject::DoCast(const CompilerType &compiler_type) {
|
||||||
|
Loading…
Reference in New Issue
Block a user