| //===-- ValueObjectPrinter.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/DataFormatters/ValueObjectPrinter.h" | |
| #include "lldb/DataFormatters/DataVisualization.h" | |
| #include "lldb/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Target/Language.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/ValueObject/ValueObject.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/MathExtras.h" | |
| #include <cinttypes> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| ValueObjectPrinter::ValueObjectPrinter(ValueObject &valobj, Stream *s) | |
| : m_orig_valobj(valobj) { | |
| DumpValueObjectOptions options(valobj); | |
| Init(valobj, s, options, m_options.m_max_ptr_depth, 0, nullptr); | |
| } | |
| ValueObjectPrinter::ValueObjectPrinter(ValueObject &valobj, Stream *s, | |
| const DumpValueObjectOptions &options) | |
| : m_orig_valobj(valobj) { | |
| Init(valobj, s, options, m_options.m_max_ptr_depth, 0, nullptr); | |
| } | |
| ValueObjectPrinter::ValueObjectPrinter( | |
| ValueObject &valobj, Stream *s, const DumpValueObjectOptions &options, | |
| const DumpValueObjectOptions::PointerDepth &ptr_depth, uint32_t curr_depth, | |
| InstancePointersSetSP printed_instance_pointers) | |
| : m_orig_valobj(valobj) { | |
| Init(valobj, s, options, ptr_depth, curr_depth, printed_instance_pointers); | |
| } | |
| void ValueObjectPrinter::Init( | |
| ValueObject &valobj, Stream *s, const DumpValueObjectOptions &options, | |
| const DumpValueObjectOptions::PointerDepth &ptr_depth, uint32_t curr_depth, | |
| InstancePointersSetSP printed_instance_pointers) { | |
| m_cached_valobj = nullptr; | |
| m_stream = s; | |
| m_options = options; | |
| m_ptr_depth = ptr_depth; | |
| m_curr_depth = curr_depth; | |
| assert(m_stream && "cannot print to a NULL Stream"); | |
| m_should_print = eLazyBoolCalculate; | |
| m_is_nil = eLazyBoolCalculate; | |
| m_is_uninit = eLazyBoolCalculate; | |
| m_is_ptr = eLazyBoolCalculate; | |
| m_is_ref = eLazyBoolCalculate; | |
| m_is_aggregate = eLazyBoolCalculate; | |
| m_is_instance_ptr = eLazyBoolCalculate; | |
| m_summary_formatter = {nullptr, false}; | |
| m_value.assign(""); | |
| m_summary.assign(""); | |
| m_error.assign(""); | |
| m_val_summary_ok = false; | |
| m_printed_instance_pointers = printed_instance_pointers | |
| ? printed_instance_pointers | |
| : std::make_shared<InstancePointersSet>(); | |
| SetupMostSpecializedValue(); | |
| } | |
| static const char *maybeNewline(const std::string &s) { | |
| // If the string already ends with a \n don't add another one. | |
| if (s.empty() || s.back() != '\n') | |
| return "\n"; | |
| return ""; | |
| } | |
| bool ValueObjectPrinter::ShouldPrintObjectDescription() { | |
| return ShouldPrintValueObject() && m_options.m_use_object_desc && !IsNil() && | |
| !IsUninitialized() && !m_options.m_pointer_as_array; | |
| } | |
| llvm::Error ValueObjectPrinter::PrintValueObject() { | |
| // If the incoming ValueObject is in an error state, the best we're going to | |
| // get out of it is its type. But if we don't even have that, just print | |
| // the error and exit early. | |
| if (m_orig_valobj.GetError().Fail() && | |
| !m_orig_valobj.GetCompilerType().IsValid()) | |
| return m_orig_valobj.GetError().ToError(); | |
| std::optional<std::string> object_desc; | |
| if (ShouldPrintObjectDescription()) { | |
| // The object description is invoked now, but not printed until after | |
| // value/summary. Calling GetObjectDescription at the outset of printing | |
| // allows for early discovery of errors. In the case of an error, the value | |
| // object is printed normally. | |
| llvm::Expected<std::string> object_desc_or_err = | |
| GetMostSpecializedValue().GetObjectDescription(); | |
| if (!object_desc_or_err) { | |
| *m_stream << "warning: `po` was unsuccessful, running `p` instead\n"; | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Expressions), | |
| object_desc_or_err.takeError(), | |
| "Object description fallback due to error: {0}"); | |
| // Print the value object directly. | |
| m_options.DisableObjectDescription(); | |
| } else { | |
| object_desc = *object_desc_or_err; | |
| } | |
| } | |
| if (ShouldPrintValueObject()) { | |
| PrintLocationIfNeeded(); | |
| m_stream->Indent(); | |
| PrintDecl(); | |
| } | |
| bool value_printed = false; | |
| bool summary_printed = false; | |
| m_val_summary_ok = | |
| PrintValueAndSummaryIfNeeded(value_printed, summary_printed); | |
| if (m_val_summary_ok) { | |
| PrintObjectDescriptionIfNeeded(object_desc); | |
| return PrintChildrenIfNeeded(value_printed, summary_printed); | |
| } | |
| m_stream->EOL(); | |
| return llvm::Error::success(); | |
| } | |
| ValueObject &ValueObjectPrinter::GetMostSpecializedValue() { | |
| assert(m_cached_valobj && "ValueObjectPrinter must have a valid ValueObject"); | |
| return *m_cached_valobj; | |
| } | |
| void ValueObjectPrinter::SetupMostSpecializedValue() { | |
| bool update_success = m_orig_valobj.UpdateValueIfNeeded(true); | |
| // If we can't find anything better, we'll fall back on the original | |
| // ValueObject. | |
| m_cached_valobj = &m_orig_valobj; | |
| if (update_success) { | |
| if (m_orig_valobj.IsDynamic()) { | |
| if (m_options.m_use_dynamic == eNoDynamicValues) { | |
| ValueObject *static_value = m_orig_valobj.GetStaticValue().get(); | |
| if (static_value) | |
| m_cached_valobj = static_value; | |
| } | |
| } else { | |
| if (m_options.m_use_dynamic != eNoDynamicValues) { | |
| ValueObject *dynamic_value = | |
| m_orig_valobj.GetDynamicValue(m_options.m_use_dynamic).get(); | |
| if (dynamic_value) | |
| m_cached_valobj = dynamic_value; | |
| } | |
| } | |
| if (m_cached_valobj->IsSynthetic()) { | |
| if (!m_options.m_use_synthetic) { | |
| ValueObject *non_synthetic = | |
| m_cached_valobj->GetNonSyntheticValue().get(); | |
| if (non_synthetic) | |
| m_cached_valobj = non_synthetic; | |
| } | |
| } else { | |
| if (m_options.m_use_synthetic) { | |
| ValueObject *synthetic = m_cached_valobj->GetSyntheticValue().get(); | |
| if (synthetic) | |
| m_cached_valobj = synthetic; | |
| } | |
| } | |
| } | |
| m_compiler_type = m_cached_valobj->GetCompilerType(); | |
| m_type_flags = m_compiler_type.GetTypeInfo(); | |
| assert(m_cached_valobj && | |
| "SetupMostSpecialized value must compute a valid ValueObject"); | |
| } | |
| const char *ValueObjectPrinter::GetRootNameForDisplay() { | |
| const char *root_valobj_name = | |
| m_options.m_root_valobj_name.empty() | |
| ? GetMostSpecializedValue().GetName().AsCString(nullptr) | |
| : m_options.m_root_valobj_name.c_str(); | |
| return root_valobj_name ? root_valobj_name : ""; | |
| } | |
| bool ValueObjectPrinter::ShouldPrintValueObject() { | |
| if (m_should_print == eLazyBoolCalculate) | |
| m_should_print = | |
| (!m_options.m_flat_output || m_type_flags.Test(eTypeHasValue)) | |
| ? eLazyBoolYes | |
| : eLazyBoolNo; | |
| return m_should_print == eLazyBoolYes; | |
| } | |
| bool ValueObjectPrinter::IsNil() { | |
| if (m_is_nil == eLazyBoolCalculate) | |
| m_is_nil = | |
| GetMostSpecializedValue().IsNilReference() ? eLazyBoolYes : eLazyBoolNo; | |
| return m_is_nil == eLazyBoolYes; | |
| } | |
| bool ValueObjectPrinter::IsUninitialized() { | |
| if (m_is_uninit == eLazyBoolCalculate) | |
| m_is_uninit = GetMostSpecializedValue().IsUninitializedReference() | |
| ? eLazyBoolYes | |
| : eLazyBoolNo; | |
| return m_is_uninit == eLazyBoolYes; | |
| } | |
| bool ValueObjectPrinter::IsPtr() { | |
| if (m_is_ptr == eLazyBoolCalculate) | |
| m_is_ptr = m_type_flags.Test(eTypeIsPointer) ? eLazyBoolYes : eLazyBoolNo; | |
| return m_is_ptr == eLazyBoolYes; | |
| } | |
| bool ValueObjectPrinter::IsRef() { | |
| if (m_is_ref == eLazyBoolCalculate) | |
| m_is_ref = m_type_flags.Test(eTypeIsReference) ? eLazyBoolYes : eLazyBoolNo; | |
| return m_is_ref == eLazyBoolYes; | |
| } | |
| bool ValueObjectPrinter::IsAggregate() { | |
| if (m_is_aggregate == eLazyBoolCalculate) | |
| m_is_aggregate = | |
| m_type_flags.Test(eTypeHasChildren) ? eLazyBoolYes : eLazyBoolNo; | |
| return m_is_aggregate == eLazyBoolYes; | |
| } | |
| bool ValueObjectPrinter::IsInstancePointer() { | |
| // you need to do this check on the value's clang type | |
| ValueObject &valobj = GetMostSpecializedValue(); | |
| if (m_is_instance_ptr == eLazyBoolCalculate) | |
| m_is_instance_ptr = (valobj.GetValue().GetCompilerType().GetTypeInfo() & | |
| eTypeInstanceIsPointer) != 0 | |
| ? eLazyBoolYes | |
| : eLazyBoolNo; | |
| if ((eLazyBoolYes == m_is_instance_ptr) && valobj.IsBaseClass()) | |
| m_is_instance_ptr = eLazyBoolNo; | |
| return m_is_instance_ptr == eLazyBoolYes; | |
| } | |
| bool ValueObjectPrinter::PrintLocationIfNeeded() { | |
| if (m_options.m_show_location) { | |
| m_stream->Printf("%s: ", GetMostSpecializedValue().GetLocationAsCString()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| void ValueObjectPrinter::PrintDecl() { | |
| bool show_type = true; | |
| // if we are at the root-level and been asked to hide the root's type, then | |
| // hide it | |
| if (m_curr_depth == 0 && m_options.m_hide_root_type) | |
| show_type = false; | |
| else | |
| // otherwise decide according to the usual rules (asked to show types - | |
| // always at the root level) | |
| show_type = m_options.m_show_types || | |
| (m_curr_depth == 0 && !m_options.m_flat_output); | |
| StreamString typeName; | |
| // Figure out which ValueObject we're acting on | |
| ValueObject &valobj = GetMostSpecializedValue(); | |
| // always show the type at the root level if it is invalid | |
| if (show_type) { | |
| // Some ValueObjects don't have types (like registers sets). Only print the | |
| // type if there is one to print | |
| ConstString type_name; | |
| if (m_compiler_type.IsValid()) { | |
| type_name = m_options.m_use_type_display_name | |
| ? valobj.GetDisplayTypeName() | |
| : valobj.GetQualifiedTypeName(); | |
| } else { | |
| // only show an invalid type name if the user explicitly triggered | |
| // show_type | |
| if (m_options.m_show_types) | |
| type_name = ConstString("<invalid type>"); | |
| } | |
| if (type_name) { | |
| std::string type_name_str(type_name.GetCString()); | |
| if (m_options.m_hide_pointer_value) { | |
| for (auto iter = type_name_str.find(" *"); iter != std::string::npos; | |
| iter = type_name_str.find(" *")) { | |
| type_name_str.erase(iter, 2); | |
| } | |
| } | |
| typeName << type_name_str.c_str(); | |
| } | |
| } | |
| StreamString varName; | |
| if (ShouldShowName()) { | |
| if (m_options.m_flat_output) | |
| valobj.GetExpressionPath(varName); | |
| else | |
| varName << GetRootNameForDisplay(); | |
| } | |
| bool decl_printed = false; | |
| if (!m_options.m_decl_printing_helper) { | |
| // if the user didn't give us a custom helper, pick one based upon the | |
| // language, either the one that this printer is bound to, or the preferred | |
| // one for the ValueObject | |
| lldb::LanguageType lang_type = | |
| (m_options.m_varformat_language == lldb::eLanguageTypeUnknown) | |
| ? valobj.GetPreferredDisplayLanguage() | |
| : m_options.m_varformat_language; | |
| if (Language *lang_plugin = Language::FindPlugin(lang_type)) { | |
| m_options.m_decl_printing_helper = lang_plugin->GetDeclPrintingHelper(); | |
| } | |
| } | |
| if (m_options.m_decl_printing_helper) { | |
| ConstString type_name_cstr(typeName.GetString()); | |
| ConstString var_name_cstr(varName.GetString()); | |
| DumpValueObjectOptions decl_print_options = m_options; | |
| // Pass printing helpers an option object that indicates whether the name | |
| // should be shown or hidden. | |
| decl_print_options.SetHideName(!ShouldShowName()); | |
| StreamString dest_stream; | |
| if (m_options.m_decl_printing_helper(type_name_cstr, var_name_cstr, | |
| decl_print_options, dest_stream)) { | |
| decl_printed = true; | |
| m_stream->PutCString(dest_stream.GetString()); | |
| } | |
| } | |
| // if the helper failed, or there is none, do a default thing | |
| if (!decl_printed) { | |
| if (!typeName.Empty()) | |
| m_stream->Printf("(%s) ", typeName.GetData()); | |
| if (!varName.Empty()) | |
| m_stream->Printf("%s =", varName.GetData()); | |
| else if (ShouldShowName()) | |
| m_stream->PutCString(" ="); | |
| } | |
| } | |
| bool ValueObjectPrinter::CheckScopeIfNeeded() { | |
| if (m_options.m_scope_already_checked) | |
| return true; | |
| return GetMostSpecializedValue().IsInScope(); | |
| } | |
| TypeSummaryImpl *ValueObjectPrinter::GetSummaryFormatter(bool null_if_omitted) { | |
| if (!m_summary_formatter.second) { | |
| TypeSummaryImpl *entry = | |
| m_options.m_summary_sp | |
| ? m_options.m_summary_sp.get() | |
| : GetMostSpecializedValue().GetSummaryFormat().get(); | |
| if (m_options.m_omit_summary_depth > 0) | |
| entry = nullptr; | |
| m_summary_formatter.first = entry; | |
| m_summary_formatter.second = true; | |
| } | |
| if (m_options.m_omit_summary_depth > 0 && null_if_omitted) | |
| return nullptr; | |
| return m_summary_formatter.first; | |
| } | |
| static bool IsPointerValue(const CompilerType &type) { | |
| Flags type_flags(type.GetTypeInfo()); | |
| if (type_flags.AnySet(eTypeInstanceIsPointer | eTypeIsPointer)) | |
| return type_flags.AllClear(eTypeIsBuiltIn); | |
| return false; | |
| } | |
| void ValueObjectPrinter::GetValueSummaryError(std::string &value, | |
| std::string &summary, | |
| std::string &error) { | |
| lldb::Format format = m_options.m_format; | |
| ValueObject &valobj = GetMostSpecializedValue(); | |
| // if I am printing synthetized elements, apply the format to those elements | |
| // only | |
| if (m_options.m_pointer_as_array) | |
| valobj.GetValueAsCString(lldb::eFormatDefault, value); | |
| else if (format != eFormatDefault && format != valobj.GetFormat()) | |
| valobj.GetValueAsCString(format, value); | |
| else { | |
| const char *val_cstr = valobj.GetValueAsCString(); | |
| if (val_cstr) | |
| value.assign(val_cstr); | |
| } | |
| const char *err_cstr = valobj.GetError().AsCString(); | |
| if (err_cstr) | |
| error.assign(err_cstr); | |
| if (!ShouldPrintValueObject()) | |
| return; | |
| if (IsNil()) { | |
| lldb::LanguageType lang_type = | |
| (m_options.m_varformat_language == lldb::eLanguageTypeUnknown) | |
| ? valobj.GetPreferredDisplayLanguage() | |
| : m_options.m_varformat_language; | |
| if (Language *lang_plugin = Language::FindPlugin(lang_type)) { | |
| summary.assign(lang_plugin->GetNilReferenceSummaryString().str()); | |
| } else { | |
| // We treat C as the fallback language rather than as a separate Language | |
| // plugin. | |
| summary.assign("NULL"); | |
| } | |
| } else if (IsUninitialized()) { | |
| summary.assign("<uninitialized>"); | |
| } else if (m_options.m_omit_summary_depth == 0) { | |
| TypeSummaryImpl *entry = GetSummaryFormatter(); | |
| if (entry) { | |
| valobj.GetSummaryAsCString(entry, summary, | |
| m_options.m_varformat_language); | |
| } else { | |
| const char *sum_cstr = | |
| valobj.GetSummaryAsCString(m_options.m_varformat_language); | |
| if (sum_cstr) | |
| summary.assign(sum_cstr); | |
| } | |
| } | |
| } | |
| bool ValueObjectPrinter::PrintValueAndSummaryIfNeeded(bool &value_printed, | |
| bool &summary_printed) { | |
| bool error_printed = false; | |
| if (ShouldPrintValueObject()) { | |
| if (!CheckScopeIfNeeded()) | |
| m_error.assign("out of scope"); | |
| if (m_error.empty()) { | |
| GetValueSummaryError(m_value, m_summary, m_error); | |
| } | |
| if (m_error.size()) { | |
| // we need to support scenarios in which it is actually fine for a value | |
| // to have no type but - on the other hand - if we get an error *AND* | |
| // have no type, we try to get out gracefully, since most often that | |
| // combination means "could not resolve a type" and the default failure | |
| // mode is quite ugly | |
| if (!m_compiler_type.IsValid()) { | |
| m_stream->PutCString(" <could not resolve type>"); | |
| return false; | |
| } | |
| error_printed = true; | |
| m_stream->Printf(" <%s>", m_error.c_str()); | |
| } else { | |
| // Make sure we have a value and make sure the summary didn't specify | |
| // that the value should not be printed - and do not print the value if | |
| // this thing is nil (but show the value if the user passes a format | |
| // explicitly) | |
| TypeSummaryImpl *entry = GetSummaryFormatter(); | |
| ValueObject &valobj = GetMostSpecializedValue(); | |
| const bool has_nil_or_uninitialized_summary = | |
| (IsNil() || IsUninitialized()) && !m_summary.empty(); | |
| if (!has_nil_or_uninitialized_summary && !m_value.empty() && | |
| (entry == nullptr || | |
| (entry->DoesPrintValue(&valobj) || | |
| m_options.m_format != eFormatDefault) || | |
| m_summary.empty()) && | |
| !m_options.m_hide_value) { | |
| if (m_options.m_hide_pointer_value && | |
| IsPointerValue(valobj.GetCompilerType())) { | |
| } else { | |
| if (auto stripped = valobj.GetStrippedPointerValue( | |
| valobj.GetPointerValue().address)) | |
| m_stream->Printf(" (actual=0x%" PRIx64 ")", *stripped); | |
| if (ShouldShowName()) | |
| m_stream->PutChar(' '); | |
| m_stream->PutCString(m_value); | |
| value_printed = true; | |
| } | |
| } | |
| if (m_summary.size()) { | |
| if (ShouldShowName() || value_printed) | |
| m_stream->PutChar(' '); | |
| m_stream->PutCString(m_summary); | |
| summary_printed = true; | |
| } | |
| } | |
| } | |
| return !error_printed; | |
| } | |
| void ValueObjectPrinter::PrintObjectDescriptionIfNeeded( | |
| std::optional<std::string> object_desc) { | |
| if (!object_desc) | |
| return; | |
| if (!m_options.m_hide_value || ShouldShowName()) | |
| *m_stream << ' '; | |
| *m_stream << *object_desc << maybeNewline(*object_desc); | |
| } | |
| bool DumpValueObjectOptions::PointerDepth::CanAllowExpansion() const { | |
| return m_count > 0; | |
| } | |
| bool ValueObjectPrinter::ShouldPrintChildren( | |
| DumpValueObjectOptions::PointerDepth &curr_ptr_depth) { | |
| const bool is_ref = IsRef(); | |
| const bool is_ptr = IsPtr(); | |
| const bool is_uninit = IsUninitialized(); | |
| if (is_uninit) | |
| return false; | |
| // If we have reached the maximum depth we shouldn't print any more children. | |
| if (HasReachedMaximumDepth()) | |
| return false; | |
| // if the user has specified an element count, always print children as it is | |
| // explicit user demand being honored | |
| if (m_options.m_pointer_as_array) | |
| return true; | |
| if (m_options.m_use_object_desc) | |
| return false; | |
| bool print_children = true; | |
| ValueObject &valobj = GetMostSpecializedValue(); | |
| if (TypeSummaryImpl *type_summary = GetSummaryFormatter()) | |
| print_children = type_summary->DoesPrintChildren(&valobj); | |
| // We will show children for all concrete types. We won't show pointer | |
| // contents unless a pointer depth has been specified. We won't reference | |
| // contents unless the reference is the root object (depth of zero). | |
| // Use a new temporary pointer depth in case we override the current | |
| // pointer depth below... | |
| if (is_ptr || is_ref) { | |
| // We have a pointer or reference whose value is an address. Make sure | |
| // that address is not NULL | |
| if (valobj.GetPointerValue().address == 0) | |
| return false; | |
| const bool is_root_level = m_curr_depth == 0; | |
| const bool is_expanded_ptr = | |
| is_ptr && m_type_flags.Test(m_options.m_expand_ptr_type_flags); | |
| if ((is_ref || is_expanded_ptr) && is_root_level && print_children) { | |
| // If this is the root object (depth is zero) that we are showing and it | |
| // is either a reference or a preferred type of pointer, then print it. | |
| // Don't do this at deeper depths otherwise we can end up with infinite | |
| // recursion... | |
| return true; | |
| } | |
| return curr_ptr_depth.CanAllowExpansion(); | |
| } | |
| return print_children || m_summary.empty(); | |
| } | |
| bool ValueObjectPrinter::ShouldExpandEmptyAggregates() { | |
| TypeSummaryImpl *entry = GetSummaryFormatter(); | |
| if (!entry) | |
| return true; | |
| return entry->DoesPrintEmptyAggregates(); | |
| } | |
| ValueObject &ValueObjectPrinter::GetValueObjectForChildrenGeneration() { | |
| return GetMostSpecializedValue(); | |
| } | |
| void ValueObjectPrinter::PrintChildrenPreamble(bool value_printed, | |
| bool summary_printed) { | |
| if (m_options.m_flat_output) { | |
| if (ShouldPrintValueObject()) | |
| m_stream->EOL(); | |
| } else { | |
| if (ShouldPrintValueObject()) { | |
| if (IsRef()) { | |
| m_stream->PutCString(": "); | |
| } else if (value_printed || summary_printed || ShouldShowName()) { | |
| m_stream->PutChar(' '); | |
| } | |
| m_stream->PutCString("{\n"); | |
| } | |
| m_stream->IndentMore(); | |
| } | |
| } | |
| void ValueObjectPrinter::PrintChild( | |
| ValueObjectSP child_sp, | |
| const DumpValueObjectOptions::PointerDepth &curr_ptr_depth) { | |
| const uint32_t consumed_summary_depth = m_options.m_pointer_as_array ? 0 : 1; | |
| const bool does_consume_ptr_depth = | |
| ((IsPtr() && !m_options.m_pointer_as_array) || IsRef()); | |
| DumpValueObjectOptions child_options(m_options); | |
| child_options.SetFormat(m_options.m_format) | |
| .SetSummary() | |
| .SetRootValueObjectName(); | |
| child_options.SetScopeChecked(true) | |
| .SetHideName(m_options.m_hide_name) | |
| .SetHideValue(m_options.m_hide_value) | |
| .SetOmitSummaryDepth(child_options.m_omit_summary_depth > 1 | |
| ? child_options.m_omit_summary_depth - | |
| consumed_summary_depth | |
| : 0) | |
| .SetElementCount(0); | |
| if (child_sp.get()) { | |
| auto ptr_depth = curr_ptr_depth; | |
| if (does_consume_ptr_depth) | |
| ptr_depth = curr_ptr_depth.Decremented(); | |
| ValueObjectPrinter child_printer(*(child_sp.get()), m_stream, child_options, | |
| ptr_depth, m_curr_depth + 1, | |
| m_printed_instance_pointers); | |
| llvm::Error error = child_printer.PrintValueObject(); | |
| if (error) { | |
| if (m_stream) | |
| *m_stream << "error: " << toString(std::move(error)); | |
| else | |
| llvm::consumeError(std::move(error)); | |
| } | |
| } | |
| } | |
| llvm::Expected<uint32_t> | |
| ValueObjectPrinter::GetMaxNumChildrenToPrint(bool &print_dotdotdot) { | |
| ValueObject &synth_valobj = GetValueObjectForChildrenGeneration(); | |
| if (m_options.m_pointer_as_array) | |
| return m_options.m_pointer_as_array.m_element_count; | |
| const uint32_t max_num_children = | |
| m_options.m_ignore_cap ? UINT32_MAX | |
| : GetMostSpecializedValue() | |
| .GetTargetSP() | |
| ->GetMaximumNumberOfChildrenToDisplay(); | |
| // Ask for one more child than the maximum to see if we should print "...". | |
| auto num_children_or_err = synth_valobj.GetNumChildren( | |
| llvm::SaturatingAdd(max_num_children, uint32_t(1))); | |
| if (!num_children_or_err) | |
| return num_children_or_err; | |
| if (*num_children_or_err > max_num_children) { | |
| print_dotdotdot = true; | |
| return max_num_children; | |
| } | |
| return num_children_or_err; | |
| } | |
| void ValueObjectPrinter::PrintChildrenPostamble(bool print_dotdotdot) { | |
| if (!m_options.m_flat_output) { | |
| if (print_dotdotdot) { | |
| GetMostSpecializedValue() | |
| .GetTargetSP() | |
| ->GetDebugger() | |
| .GetCommandInterpreter() | |
| .ChildrenTruncated(); | |
| m_stream->Indent("...\n"); | |
| } | |
| m_stream->IndentLess(); | |
| m_stream->Indent("}\n"); | |
| } | |
| } | |
| bool ValueObjectPrinter::ShouldPrintEmptyBrackets(bool value_printed, | |
| bool summary_printed) { | |
| ValueObject &synth_valobj = GetValueObjectForChildrenGeneration(); | |
| if (!IsAggregate()) | |
| return false; | |
| if (!m_options.m_reveal_empty_aggregates) { | |
| if (value_printed || summary_printed) | |
| return false; | |
| } | |
| if (synth_valobj.MightHaveChildren()) | |
| return true; | |
| if (m_val_summary_ok) | |
| return false; | |
| return true; | |
| } | |
| static constexpr size_t PhysicalIndexForLogicalIndex(size_t base, size_t stride, | |
| size_t logical) { | |
| return base + logical * stride; | |
| } | |
| ValueObjectSP ValueObjectPrinter::GenerateChild(ValueObject &synth_valobj, | |
| size_t idx) { | |
| if (m_options.m_pointer_as_array) { | |
| // if generating pointer-as-array children, use GetSyntheticArrayMember | |
| return synth_valobj.GetSyntheticArrayMember( | |
| PhysicalIndexForLogicalIndex( | |
| m_options.m_pointer_as_array.m_base_element, | |
| m_options.m_pointer_as_array.m_stride, idx), | |
| true); | |
| } else { | |
| // otherwise, do the usual thing | |
| return synth_valobj.GetChildAtIndex(idx); | |
| } | |
| } | |
| void ValueObjectPrinter::PrintChildren( | |
| bool value_printed, bool summary_printed, | |
| const DumpValueObjectOptions::PointerDepth &curr_ptr_depth) { | |
| ValueObject &synth_valobj = GetValueObjectForChildrenGeneration(); | |
| bool print_dotdotdot = false; | |
| auto num_children_or_err = GetMaxNumChildrenToPrint(print_dotdotdot); | |
| if (!num_children_or_err) { | |
| *m_stream << " <" << llvm::toString(num_children_or_err.takeError()) << '>'; | |
| return; | |
| } | |
| uint32_t num_children = *num_children_or_err; | |
| if (num_children) { | |
| bool any_children_printed = false; | |
| for (size_t idx = 0; idx < num_children; ++idx) { | |
| if (ValueObjectSP child_sp = GenerateChild(synth_valobj, idx)) { | |
| if (m_options.m_child_printing_decider && | |
| !m_options.m_child_printing_decider(child_sp->GetName())) | |
| continue; | |
| if (!any_children_printed) { | |
| PrintChildrenPreamble(value_printed, summary_printed); | |
| any_children_printed = true; | |
| } | |
| PrintChild(child_sp, curr_ptr_depth); | |
| } | |
| } | |
| if (any_children_printed) | |
| PrintChildrenPostamble(print_dotdotdot); | |
| else { | |
| if (ShouldPrintEmptyBrackets(value_printed, summary_printed)) { | |
| if (ShouldPrintValueObject()) | |
| m_stream->PutCString(" {}\n"); | |
| else | |
| m_stream->EOL(); | |
| } else | |
| m_stream->EOL(); | |
| } | |
| } else if (ShouldPrintEmptyBrackets(value_printed, summary_printed)) { | |
| // Aggregate, no children... | |
| if (ShouldPrintValueObject()) { | |
| // if it has a synthetic value, then don't print {}, the synthetic | |
| // children are probably only being used to vend a value | |
| if (GetMostSpecializedValue().DoesProvideSyntheticValue() || | |
| !ShouldExpandEmptyAggregates()) | |
| m_stream->PutCString("\n"); | |
| else | |
| m_stream->PutCString(" {}\n"); | |
| } | |
| } else { | |
| if (ShouldPrintValueObject()) | |
| m_stream->EOL(); | |
| } | |
| } | |
| bool ValueObjectPrinter::PrintChildrenOneLiner(bool hide_names) { | |
| ValueObject &synth_valobj = GetValueObjectForChildrenGeneration(); | |
| bool print_dotdotdot = false; | |
| auto num_children_or_err = GetMaxNumChildrenToPrint(print_dotdotdot); | |
| if (!num_children_or_err) { | |
| *m_stream << '<' << llvm::toString(num_children_or_err.takeError()) << '>'; | |
| return true; | |
| } | |
| uint32_t num_children = *num_children_or_err; | |
| if (num_children) { | |
| m_stream->PutChar('('); | |
| bool did_print_children = false; | |
| for (uint32_t idx = 0; idx < num_children; ++idx) { | |
| lldb::ValueObjectSP child_sp(synth_valobj.GetChildAtIndex(idx)); | |
| if (child_sp) | |
| child_sp = child_sp->GetQualifiedRepresentationIfAvailable( | |
| m_options.m_use_dynamic, m_options.m_use_synthetic); | |
| if (child_sp) { | |
| if (m_options.m_child_printing_decider && | |
| !m_options.m_child_printing_decider(child_sp->GetName())) | |
| continue; | |
| if (idx && did_print_children) | |
| m_stream->PutCString(", "); | |
| did_print_children = true; | |
| if (!hide_names) { | |
| llvm::StringRef name = child_sp.get()->GetName().GetStringRef(); | |
| if (!name.empty()) { | |
| m_stream->PutCString(name); | |
| m_stream->PutCString(" = "); | |
| } | |
| } | |
| child_sp->DumpPrintableRepresentation( | |
| *m_stream, ValueObject::eValueObjectRepresentationStyleSummary, | |
| m_options.m_format, | |
| ValueObject::PrintableRepresentationSpecialCases::eDisable); | |
| } | |
| } | |
| if (print_dotdotdot) | |
| m_stream->PutCString(", ...)"); | |
| else | |
| m_stream->PutChar(')'); | |
| } | |
| return true; | |
| } | |
| llvm::Error ValueObjectPrinter::PrintChildrenIfNeeded(bool value_printed, | |
| bool summary_printed) { | |
| ValueObject &valobj = GetMostSpecializedValue(); | |
| DumpValueObjectOptions::PointerDepth curr_ptr_depth = m_ptr_depth; | |
| const bool print_children = ShouldPrintChildren(curr_ptr_depth); | |
| const bool print_oneline = | |
| (curr_ptr_depth.CanAllowExpansion() || m_options.m_show_types || | |
| !m_options.m_allow_oneliner_mode || m_options.m_flat_output || | |
| (m_options.m_pointer_as_array) || m_options.m_show_location) | |
| ? false | |
| : DataVisualization::ShouldPrintAsOneLiner(valobj); | |
| if (print_children && IsInstancePointer()) { | |
| uint64_t instance_ptr_value = valobj.GetValueAsUnsigned(0); | |
| if (m_printed_instance_pointers->count(instance_ptr_value)) { | |
| // We already printed this instance-is-pointer thing, so don't expand it. | |
| m_stream->PutCString(" {...}\n"); | |
| return llvm::Error::success(); | |
| } else { | |
| // Remember this guy for future reference. | |
| m_printed_instance_pointers->emplace(instance_ptr_value); | |
| } | |
| } | |
| if (print_children) { | |
| if (print_oneline) { | |
| m_stream->PutChar(' '); | |
| PrintChildrenOneLiner(false); | |
| m_stream->EOL(); | |
| } else | |
| PrintChildren(value_printed, summary_printed, curr_ptr_depth); | |
| } else if (HasReachedMaximumDepth() && IsAggregate() && | |
| ShouldPrintValueObject()) { | |
| m_stream->PutCString(" {...}\n"); | |
| // The maximum child depth has been reached. If `m_max_depth` is the default | |
| // (i.e. the user has _not_ customized it), then lldb presents a warning to | |
| // the user. The warning tells the user that the limit has been reached, but | |
| // more importantly tells them how to expand the limit if desired. | |
| if (m_options.m_max_depth_is_default) | |
| valobj.GetTargetSP() | |
| ->GetDebugger() | |
| .GetCommandInterpreter() | |
| .SetReachedMaximumDepth(); | |
| } else | |
| m_stream->EOL(); | |
| return llvm::Error::success(); | |
| } | |
| bool ValueObjectPrinter::HasReachedMaximumDepth() { | |
| return m_curr_depth >= m_options.m_max_depth; | |
| } | |
| bool ValueObjectPrinter::ShouldShowName() const { | |
| if (m_curr_depth == 0) | |
| return !m_options.m_hide_root_name && !m_options.m_hide_name; | |
| return !m_options.m_hide_name; | |
| } |