| //===-- ValueObjectSynthetic.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/ValueObject/ValueObjectSynthetic.h" | |
| #include "lldb/Core/Value.h" | |
| #include "lldb/DataFormatters/FormattersHelpers.h" | |
| #include "lldb/DataFormatters/TypeSynthetic.h" | |
| #include "lldb/Target/ExecutionContext.h" | |
| #include "lldb/Utility/ConstString.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/ValueObject/ValueObject.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/ErrorExtras.h" | |
| #include <optional> | |
| namespace lldb_private { | |
| class Declaration; | |
| } | |
| using namespace lldb_private; | |
| class DummySyntheticFrontEnd : public SyntheticChildrenFrontEnd { | |
| public: | |
| DummySyntheticFrontEnd(ValueObject &backend) | |
| : SyntheticChildrenFrontEnd(backend) {} | |
| llvm::Expected<uint32_t> CalculateNumChildren() override { | |
| return m_backend.GetNumChildren(); | |
| } | |
| lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { | |
| return m_backend.GetChildAtIndex(idx); | |
| } | |
| llvm::Expected<size_t> | |
| GetIndexOfChildWithName(llvm::StringRef name) override { | |
| return m_backend.GetIndexOfChildWithName(name); | |
| } | |
| bool MightHaveChildren() override { return m_backend.MightHaveChildren(); } | |
| lldb::ChildCacheState Update() override { | |
| return lldb::ChildCacheState::eRefetch; | |
| } | |
| }; | |
| ValueObjectSynthetic::ValueObjectSynthetic(ValueObject &parent, | |
| lldb::SyntheticChildrenSP filter) | |
| : ValueObject(parent), m_synth_sp(std::move(filter)), m_children_byindex(), | |
| m_name_toindex(), m_synthetic_children_cache(), | |
| m_synthetic_children_count(UINT32_MAX), | |
| m_parent_type_name(parent.GetTypeName()), | |
| m_might_have_children(eLazyBoolCalculate), | |
| m_provides_value(eLazyBoolCalculate) { | |
| SetName(parent.GetName()); | |
| // Copying the data of an incomplete type won't work as it has no byte size. | |
| if (m_parent->GetCompilerType().IsCompleteType()) | |
| CopyValueData(m_parent); | |
| CreateSynthFilter(); | |
| } | |
| ValueObjectSynthetic::~ValueObjectSynthetic() = default; | |
| CompilerType ValueObjectSynthetic::GetCompilerTypeImpl() { | |
| return m_parent->GetCompilerType(); | |
| } | |
| ConstString ValueObjectSynthetic::GetTypeName() { | |
| return m_parent->GetTypeName(); | |
| } | |
| ConstString ValueObjectSynthetic::GetQualifiedTypeName() { | |
| return m_parent->GetQualifiedTypeName(); | |
| } | |
| ConstString ValueObjectSynthetic::GetDisplayTypeName() { | |
| if (ConstString synth_name = m_synth_filter_up->GetSyntheticTypeName()) | |
| return synth_name; | |
| return m_parent->GetDisplayTypeName(); | |
| } | |
| llvm::Expected<uint32_t> | |
| ValueObjectSynthetic::CalculateNumChildren(uint32_t max) { | |
| Log *log = GetLog(LLDBLog::DataFormatters); | |
| UpdateValueIfNeeded(); | |
| if (m_synthetic_children_count < UINT32_MAX) | |
| return m_synthetic_children_count <= max ? m_synthetic_children_count : max; | |
| if (max < UINT32_MAX) { | |
| auto num_children = m_synth_filter_up->CalculateNumChildren(max); | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::CalculateNumChildren] for VO of name " | |
| "{0} and type {1}, the filter returned {2} child values", | |
| GetName(), GetTypeName(), num_children ? *num_children : 0); | |
| return num_children; | |
| } else { | |
| auto num_children_or_err = m_synth_filter_up->CalculateNumChildren(max); | |
| if (!num_children_or_err) { | |
| m_synthetic_children_count = 0; | |
| return num_children_or_err; | |
| } | |
| auto num_children = (m_synthetic_children_count = *num_children_or_err); | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::CalculateNumChildren] for VO of name " | |
| "{0} and type {1}, the filter returned {2} child values", | |
| GetName(), GetTypeName(), num_children); | |
| return num_children; | |
| } | |
| } | |
| lldb::ValueObjectSP | |
| ValueObjectSynthetic::GetDynamicValue(lldb::DynamicValueType valueType) { | |
| if (!m_parent) | |
| return lldb::ValueObjectSP(); | |
| if (IsDynamic() && GetDynamicValueType() == valueType) | |
| return GetSP(); | |
| return m_parent->GetDynamicValue(valueType); | |
| } | |
| bool ValueObjectSynthetic::MightHaveChildren() { | |
| if (m_might_have_children == eLazyBoolCalculate) | |
| m_might_have_children = | |
| (m_synth_filter_up->MightHaveChildren() ? eLazyBoolYes : eLazyBoolNo); | |
| return (m_might_have_children != eLazyBoolNo); | |
| } | |
| llvm::Expected<uint64_t> ValueObjectSynthetic::GetByteSize() { | |
| return m_parent->GetByteSize(); | |
| } | |
| lldb::ValueType ValueObjectSynthetic::GetValueType() const { | |
| return m_parent->GetValueType(); | |
| } | |
| void ValueObjectSynthetic::CreateSynthFilter() { | |
| ValueObject *valobj_for_frontend = m_parent; | |
| if (m_synth_sp->WantsDereference()) { | |
| CompilerType type = m_parent->GetCompilerType(); | |
| if (type.IsValid() && type.IsPointerOrReferenceType()) { | |
| Status error; | |
| lldb::ValueObjectSP deref_sp = m_parent->Dereference(error); | |
| if (error.Success()) | |
| valobj_for_frontend = deref_sp.get(); | |
| } | |
| } | |
| m_synth_filter_up = (m_synth_sp->GetFrontEnd(*valobj_for_frontend)); | |
| if (!m_synth_filter_up) | |
| m_synth_filter_up = std::make_unique<DummySyntheticFrontEnd>(*m_parent); | |
| } | |
| bool ValueObjectSynthetic::UpdateValue() { | |
| Log *log = GetLog(LLDBLog::DataFormatters); | |
| SetValueIsValid(false); | |
| m_error.Clear(); | |
| if (!m_parent->UpdateValueIfNeeded(false)) { | |
| // our parent could not update.. as we are meaningless without a parent, | |
| // just stop | |
| if (m_parent->GetError().Fail()) | |
| m_error = m_parent->GetError().Clone(); | |
| return false; | |
| } | |
| // Regenerate the synthetic filter if our typename changes. When the (dynamic) | |
| // type of an object changes, so does their synthetic filter of choice. | |
| ConstString new_parent_type_name = m_parent->GetTypeName(); | |
| if (new_parent_type_name != m_parent_type_name) { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::UpdateValue] name={0}, type changed " | |
| "from {1} to {2}, recomputing synthetic filter", | |
| GetName(), m_parent_type_name, new_parent_type_name); | |
| m_parent_type_name = new_parent_type_name; | |
| CreateSynthFilter(); | |
| } | |
| // let our backend do its update | |
| if (m_synth_filter_up->Update() == lldb::ChildCacheState::eRefetch) { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::UpdateValue] name={0}, synthetic " | |
| "filter said caches are stale - clearing", | |
| GetName()); | |
| // filter said that cached values are stale | |
| { | |
| std::lock_guard<std::mutex> guard(m_child_mutex); | |
| m_children_byindex.clear(); | |
| m_name_toindex.clear(); | |
| } | |
| // usually, an object's value can change but this does not alter its | |
| // children count for a synthetic VO that might indeed happen, so we need | |
| // to tell the upper echelons that they need to come back to us asking for | |
| // children | |
| m_flags.m_children_count_valid = false; | |
| { | |
| std::lock_guard<std::mutex> guard(m_child_mutex); | |
| m_synthetic_children_cache.clear(); | |
| } | |
| m_synthetic_children_count = UINT32_MAX; | |
| m_might_have_children = eLazyBoolCalculate; | |
| } else { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::UpdateValue] name={0}, synthetic " | |
| "filter said caches are still valid", | |
| GetName()); | |
| } | |
| m_provides_value = eLazyBoolCalculate; | |
| lldb::ValueObjectSP synth_val(m_synth_filter_up->GetSyntheticValue()); | |
| if (synth_val && synth_val->CanProvideValue()) { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::UpdateValue] name={0}, synthetic " | |
| "filter said it can provide a value", | |
| GetName()); | |
| m_provides_value = eLazyBoolYes; | |
| CopyValueData(synth_val.get()); | |
| } else { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::UpdateValue] name={0}, synthetic " | |
| "filter said it will not provide a value", | |
| GetName()); | |
| m_provides_value = eLazyBoolNo; | |
| // Copying the data of an incomplete type won't work as it has no byte size. | |
| if (m_parent->GetCompilerType().IsCompleteType()) | |
| CopyValueData(m_parent); | |
| } | |
| SetValueIsValid(true); | |
| return true; | |
| } | |
| lldb::ValueObjectSP ValueObjectSynthetic::GetChildAtIndex(uint32_t idx, | |
| bool can_create) { | |
| Log *log = GetLog(LLDBLog::DataFormatters); | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::GetChildAtIndex] name={0}, retrieving " | |
| "child at index {1}", | |
| GetName(), idx); | |
| UpdateValueIfNeeded(); | |
| ValueObject *valobj; | |
| bool child_is_cached; | |
| { | |
| std::lock_guard<std::mutex> guard(m_child_mutex); | |
| auto cached_child_it = m_children_byindex.find(idx); | |
| child_is_cached = cached_child_it != m_children_byindex.end(); | |
| if (child_is_cached) | |
| valobj = cached_child_it->second; | |
| } | |
| if (!child_is_cached) { | |
| if (can_create && m_synth_filter_up != nullptr) { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::GetChildAtIndex] name={0}, child at " | |
| "index {1} not cached and will be created", | |
| GetName(), idx); | |
| lldb::ValueObjectSP synth_guy = m_synth_filter_up->GetChildAtIndex(idx); | |
| LLDB_LOG( | |
| log, | |
| "[ValueObjectSynthetic::GetChildAtIndex] name={0}, child at index " | |
| "{1} created as {2} (is " | |
| "synthetic: {3})", | |
| GetName(), idx, static_cast<void *>(synth_guy.get()), | |
| synth_guy.get() | |
| ? (synth_guy->IsSyntheticChildrenGenerated() ? "yes" : "no") | |
| : "no"); | |
| if (!synth_guy) | |
| return synth_guy; | |
| if (synth_guy->IsSyntheticChildrenGenerated()) | |
| synth_guy->SetLogicalParent(this); | |
| { | |
| std::lock_guard<std::mutex> guard(m_child_mutex); | |
| if (synth_guy->IsSyntheticChildrenGenerated()) | |
| m_synthetic_children_cache.push_back(synth_guy); | |
| m_children_byindex[idx] = synth_guy.get(); | |
| } | |
| synth_guy->SetPreferredDisplayLanguageIfNeeded( | |
| GetPreferredDisplayLanguage()); | |
| if (lldb::ValueObjectSP check_sp = | |
| CheckValueObjectOwnership(synth_guy.get())) | |
| return check_sp; | |
| return synth_guy; | |
| } else { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::GetChildAtIndex] name={0}, child at " | |
| "index {1} not cached and cannot " | |
| "be created (can_create = {2}, synth_filter = {3})", | |
| GetName(), idx, can_create ? "yes" : "no", | |
| static_cast<void *>(m_synth_filter_up.get())); | |
| return lldb::ValueObjectSP(); | |
| } | |
| } else { | |
| LLDB_LOG(log, | |
| "[ValueObjectSynthetic::GetChildAtIndex] name={0}, child at " | |
| "index {1} cached as {2}", | |
| GetName(), idx, static_cast<void *>(valobj)); | |
| lldb::ValueObjectSP check_sp = CheckValueObjectOwnership(valobj); | |
| if (check_sp) | |
| return check_sp; | |
| return valobj->GetSP(); | |
| } | |
| } | |
| lldb::ValueObjectSP | |
| ValueObjectSynthetic::GetChildMemberWithName(llvm::StringRef name, | |
| bool can_create) { | |
| UpdateValueIfNeeded(); | |
| auto index_or_err = GetIndexOfChildWithName(name); | |
| if (!index_or_err) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), index_or_err.takeError(), | |
| "{0}"); | |
| return lldb::ValueObjectSP(); | |
| } | |
| return GetChildAtIndex(*index_or_err, can_create); | |
| } | |
| llvm::Expected<size_t> | |
| ValueObjectSynthetic::GetIndexOfChildWithName(llvm::StringRef name_ref) { | |
| UpdateValueIfNeeded(); | |
| ConstString name(name_ref); | |
| { | |
| std::lock_guard<std::mutex> guard(m_child_mutex); | |
| auto name_to_index = m_name_toindex.find(name.GetCString()); | |
| if (name_to_index != m_name_toindex.end()) | |
| return name_to_index->second; | |
| } | |
| if (!m_synth_filter_up) | |
| return llvm::createStringErrorV("type has no child named '{0}'", name); | |
| size_t index; | |
| auto index_or_err = m_synth_filter_up->GetIndexOfChildWithName(name); | |
| if (index_or_err) { | |
| index = *index_or_err; | |
| } else { | |
| // The front-end does its own subscripting, so its answer is final. | |
| if (m_synth_sp->CustomSubscripting()) | |
| return index_or_err.takeError(); | |
| // Provide automatic support for subscript child names ("[N]"). | |
| auto maybe_index = formatters::ExtractIndexFromString(name.GetCString()); | |
| if (!maybe_index) | |
| // The child name was not of the form "[N]", return the original error. | |
| return index_or_err.takeError(); | |
| // Subscripting succeeded, ignore the original error. | |
| llvm::consumeError(index_or_err.takeError()); | |
| index = *maybe_index; | |
| // Prevent unnecessary work by limiting max to one past the index. | |
| uint32_t max = index + 1; | |
| auto num_children = GetNumChildren(max); | |
| if (!num_children) | |
| return num_children.takeError(); | |
| if (index >= *num_children) | |
| return llvm::createStringErrorV("subscript index out of range: {0}", | |
| index); | |
| } | |
| std::lock_guard<std::mutex> guard(m_child_mutex); | |
| m_name_toindex[name.GetCString()] = index; | |
| return index; | |
| } | |
| bool ValueObjectSynthetic::IsInScope() { return m_parent->IsInScope(); } | |
| lldb::ValueObjectSP ValueObjectSynthetic::GetNonSyntheticValue() { | |
| return m_parent->GetSP(); | |
| } | |
| void ValueObjectSynthetic::CopyValueData(ValueObject *source) { | |
| if (!source->UpdateValueIfNeeded()) | |
| return; | |
| m_value = source->GetValue(); | |
| ExecutionContext exe_ctx(GetExecutionContextRef()); | |
| m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); | |
| } | |
| bool ValueObjectSynthetic::CanProvideValue() { | |
| if (!UpdateValueIfNeeded()) | |
| return false; | |
| if (m_provides_value == eLazyBoolYes) | |
| return true; | |
| return m_parent->CanProvideValue(); | |
| } | |
| bool ValueObjectSynthetic::SetValueFromCString(const char *value_str, | |
| Status &error) { | |
| return m_parent->SetValueFromCString(value_str, error); | |
| } | |
| void ValueObjectSynthetic::SetFormat(lldb::Format format) { | |
| if (m_parent) { | |
| m_parent->ClearUserVisibleData(eClearUserVisibleDataItemsAll); | |
| m_parent->SetFormat(format); | |
| } | |
| this->ValueObject::SetFormat(format); | |
| this->ClearUserVisibleData(eClearUserVisibleDataItemsAll); | |
| } | |
| void ValueObjectSynthetic::SetPreferredDisplayLanguage( | |
| lldb::LanguageType lang) { | |
| this->ValueObject::SetPreferredDisplayLanguage(lang); | |
| if (m_parent) | |
| m_parent->SetPreferredDisplayLanguage(lang); | |
| } | |
| lldb::LanguageType ValueObjectSynthetic::GetPreferredDisplayLanguage() { | |
| if (m_preferred_display_language == lldb::eLanguageTypeUnknown) { | |
| if (m_parent) | |
| return m_parent->GetPreferredDisplayLanguage(); | |
| return lldb::eLanguageTypeUnknown; | |
| } else | |
| return m_preferred_display_language; | |
| } | |
| bool ValueObjectSynthetic::IsSyntheticChildrenGenerated() { | |
| if (m_parent) | |
| return m_parent->IsSyntheticChildrenGenerated(); | |
| return false; | |
| } | |
| void ValueObjectSynthetic::SetSyntheticChildrenGenerated(bool b) { | |
| if (m_parent) | |
| m_parent->SetSyntheticChildrenGenerated(b); | |
| this->ValueObject::SetSyntheticChildrenGenerated(b); | |
| } | |
| bool ValueObjectSynthetic::GetDeclaration(Declaration &decl) { | |
| if (m_parent) | |
| return m_parent->GetDeclaration(decl); | |
| return ValueObject::GetDeclaration(decl); | |
| } | |
| uint64_t ValueObjectSynthetic::GetLanguageFlags() { | |
| if (m_parent) | |
| return m_parent->GetLanguageFlags(); | |
| return this->ValueObject::GetLanguageFlags(); | |
| } | |
| void ValueObjectSynthetic::SetLanguageFlags(uint64_t flags) { | |
| if (m_parent) | |
| m_parent->SetLanguageFlags(flags); | |
| else | |
| this->ValueObject::SetLanguageFlags(flags); | |
| } | |
| void ValueObjectSynthetic::GetExpressionPath(Stream &stream, | |
| GetExpressionPathFormat epformat) { | |
| // A synthetic ValueObject may wrap an underlying Register or RegisterSet | |
| // ValueObject, which requires a different approach to generating the | |
| // expression path. In such cases, delegate to the non-synthetic value object. | |
| if (const lldb::ValueType obj_value_type = GetValueType(); | |
| IsSynthetic() && (obj_value_type == lldb::eValueTypeRegister || | |
| obj_value_type == lldb::eValueTypeRegisterSet)) { | |
| if (const lldb::ValueObjectSP raw_value = GetNonSyntheticValue()) | |
| return raw_value->GetExpressionPath(stream, epformat); | |
| } | |
| return ValueObject::GetExpressionPath(stream, epformat); | |
| } | |
| SyntheticChildrenFrontEnd * | |
| ValueObjectSynthetic::GetSyntheticChildrenFrontEnd() { | |
| return m_synth_filter_up.get(); | |
| } |