| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/expr/pretty_type.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include "src/developer/debug/shared/zx_status.h" |
| #include "src/developer/debug/zxdb/common/leb.h" |
| #include "src/developer/debug/zxdb/expr/expr.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/expr/format.h" |
| #include "src/developer/debug/zxdb/expr/format_node.h" |
| #include "src/developer/debug/zxdb/expr/format_options.h" |
| #include "src/developer/debug/zxdb/expr/resolve_collection.h" |
| #include "src/developer/debug/zxdb/expr/resolve_ptr_ref.h" |
| #include "src/developer/debug/zxdb/symbols/function.h" |
| #include "src/developer/debug/zxdb/symbols/modified_type.h" |
| #include "src/developer/debug/zxdb/symbols/variable.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // How to handle quotes for the name in PopulateName(). |
| enum class NameQuotes { kStrip, kKeep }; |
| |
| // Populates the "key" or "name" of the given FormatNode with the given ExprValue. Since names are |
| // strings, we need a stringified version of the ExprValue. |
| // |
| // This is relatively simplistic. It just formats the value and takes the toplevel description of |
| // that node. This is normally empty for complex structs. If the key is some complicated struct, you |
| // probably can't handle that being formatted as the key in a list of array values anyway. |
| // |
| // It would be nice to give the pretty-printer more control over what the key is. Implementing |
| // something like StringPrintf might be nice, here showing a Golang-like "%v" for what our default |
| // description for any value would be: |
| // StringPrintf("[%v]", key); |
| // We could also implement to_string() and use + to concatenate string literals: |
| // "[" + $zxdb::to_string(key) + "]" |
| void PopulateName(const fxl::RefPtr<EvalContext>& eval_context, fxl::WeakPtr<FormatNode> weak_node, |
| const ExprValue& name, NameQuotes quotes, const FormatOptions& options, |
| EvalCallback cb) { |
| // Create a format node to format the key. |
| auto name_node = std::make_unique<FormatNode>(std::string(), name); |
| |
| // Asynchronously expand the key's FormatNode to get the string for it. |
| FormatNode* name_node_ptr = name_node.get(); |
| FillFormatNodeDescription( |
| name_node_ptr, options, eval_context, |
| fit::defer_callback( |
| [weak_node, quotes, name_node = std::move(name_node), cb = std::move(cb)]() mutable { |
| if (weak_node) { |
| const std::string& desc = name_node->description(); |
| if (quotes == NameQuotes::kStrip && desc.size() >= 2u && desc[0] == '"' && |
| desc.back() == '"') { |
| // Strip the quotes. |
| weak_node->set_name(desc.substr(1, desc.size() - 2)); |
| } else if (desc.empty()) { |
| // In cases where the output is empty, add some placeholder text so it doesn't |
| // look broken. |
| weak_node->set_name("{…}"); |
| } else { |
| weak_node->set_name(desc); |
| } |
| } |
| cb(ExprValue()); // Done, AppendKeyValueRow returns no value. |
| })); |
| } |
| |
| // Constructs an identifier in the $zxdb namespace with the given name. |
| ParsedIdentifier ZxdbNamespaced(const std::string& name) { |
| ParsedIdentifier result((ParsedIdentifierComponent(SpecialIdentifier::kZxdb))); |
| result.AppendComponent(ParsedIdentifierComponent(name)); |
| return result; |
| } |
| |
| // Pretty-printing built-in functions -------------------------------------------------------------- |
| |
| // Implementation of the built-in pretty-printer function $zxdb::AppendKeyValueRow() and |
| // AppendNameValueRow(). |
| // |
| // void $zxdb::AppendKeyValueRow(auto key, auto value); |
| // void $zxdb::AppendNameValueRow(auto key, auto value); |
| // |
| // Appends the given key/value to the list of children of the current FormatNode being formatted. |
| // This is used when the key comes from the program being debugged. |
| void AppendNameOrKeyValueRow(const fxl::RefPtr<EvalContext>& eval_context, |
| const std::vector<ExprValue>& params, FormatNode* node, |
| NameQuotes quotes, const FormatOptions& options, EvalCallback cb) { |
| if (params.size() != 2u) |
| return cb(Err("$zxdb::Append*ValueRow() expects two arguments.")); |
| |
| // First fill in the child with no name. |
| node->children().push_back(std::make_unique<FormatNode>(std::string(), std::move(params[1]))); |
| |
| // Asynchronously fill in the name (it may need evaluating). |
| PopulateName(eval_context, node->children().back()->GetWeakPtr(), params[0], quotes, options, |
| std::move(cb)); |
| } |
| |
| // Implementation of the built-in pretty-printer function AppendKeyRow(). |
| // |
| // void $zxdb::AppendNameRow(auto name); |
| // |
| // Appends the given name to the list of children of the current FormatNode being formatted. Unlike |
| // AppendNameValueRow(), this will have no value (which would be appear in the output differently |
| // than, for example, nullptr or empty string). This can be useful to append things like "..." to |
| // the end of truncated arrays. |
| void AppendNameRow(const fxl::RefPtr<EvalContext>& eval_context, |
| const std::vector<ExprValue>& params, FormatNode* node, |
| const FormatOptions& options, EvalCallback cb) { |
| if (params.size() != 1u) |
| return cb(Err("$zxdb::AppendNameRow() expects one argument.")); |
| |
| // First fill in the child with no name. |
| node->children().push_back(std::make_unique<FormatNode>(std::string())); |
| |
| // Asynchronously fill in the name (it may need evaluating). |
| PopulateName(eval_context, node->children().back()->GetWeakPtr(), params[0], NameQuotes::kStrip, |
| options, std::move(cb)); |
| } |
| |
| // Implementation of the built-in pretty-printer function GetMaxArraySize(). |
| // |
| // int $zxdb::GetMaxArraySize(); |
| // |
| // This function returns the maximum number of children that a pretty-printer for a container type |
| // should generate. Otherwise, things can easily get too long and slow. Using this value instead of |
| // hard-coding a limit allows the user to override the value consistently if they want more items. |
| // |
| // If a pretty-printer for a container stops populating items early because it hit the max array |
| // size, it should call: |
| // $zxdb::AppendNameRow("..."); |
| // to make clear that the output was truncated. |
| void GetMaxArraySize(const fxl::RefPtr<EvalContext>& eval_context, |
| const std::vector<ExprValue>& params, const FormatOptions& options, |
| EvalCallback cb) { |
| if (!params.empty()) |
| return cb(Err("$zxdb::GetMaxArraySize() expects no arguments.")); |
| return cb(ExprValue(options.max_array_size)); |
| } |
| |
| // An EvalContext that shadows another one and injects all members of a given value into the |
| // current namespace. This allows pretty-printers to reference variables on the object being printed |
| // as if the code was in the context of that object. |
| // |
| // So for example, when pretty-printing the type: |
| // |
| // struct Foo { |
| // int bar; |
| // char baz; |
| // }; |
| // |
| // The |value| passed in to the constructor would be the "Foo" instance. Expressions evaluated |
| // using this context can then refer to "bar" and "baz" without qualification. |
| class PrettyEvalContext : public EvalContext { |
| public: |
| // Construct with fxl::MakeRefCounted(). |
| |
| // EvalContext implementation. Everything except FindName()/GetNamedValue() passes through to the |
| // impl_. |
| ExprLanguage GetLanguage() const override { return impl_->GetLanguage(); } |
| const std::shared_ptr<Abi>& GetAbi() const override { return impl_->GetAbi(); } |
| void FindName(const FindNameOptions& options, const ParsedIdentifier& looking_for, |
| std::vector<FoundName>* results) const override; |
| FindNameContext GetFindNameContext() const override; |
| void GetNamedValue(const ParsedIdentifier& name, EvalCallback cb) const override; |
| void GetVariableValue(fxl::RefPtr<Value> variable, EvalCallback cb) const override { |
| return impl_->GetVariableValue(std::move(variable), std::move(cb)); |
| } |
| const BuiltinFuncCallback* GetBuiltinFunction(const ParsedIdentifier& name) const override; |
| const ProcessSymbols* GetProcessSymbols() const override { return impl_->GetProcessSymbols(); } |
| fxl::RefPtr<SymbolDataProvider> GetDataProvider() override { return impl_->GetDataProvider(); } |
| Location GetLocationForAddress(uint64_t address) const override { |
| return impl_->GetLocationForAddress(address); |
| } |
| const PrettyTypeManager& GetPrettyTypeManager() const override { |
| return impl_->GetPrettyTypeManager(); |
| } |
| VectorRegisterFormat GetVectorRegisterFormat() const override { |
| return VectorRegisterFormat::kDouble; |
| } |
| bool ShouldPromoteToDerived() const override { |
| // Pretty-printers should be coded such that they always handle the types given, so don't |
| // promote to derived classes for them. |
| return false; |
| } |
| |
| private: |
| FRIEND_REF_COUNTED_THREAD_SAFE(PrettyEvalContext); |
| FRIEND_MAKE_REF_COUNTED(PrettyEvalContext); |
| |
| // Use the node variant to enable use-cases where the implementation of the formatter may |
| // add children to the final ExprNode. The value to be formatted is in node->value(). |
| PrettyEvalContext(fxl::RefPtr<EvalContext> impl, FormatNode* node, |
| const FormatOptions& options = FormatOptions()) |
| : impl_(std::move(impl)), |
| weak_node_(node->GetWeakPtr()), |
| value_(node->value()), |
| format_options_(options) { |
| AddBuiltinFuncs(); |
| FillFakeMemberFn(); |
| } |
| |
| // This variant does not support any mutation of the output node. This is used for more narrowly |
| // defined cases and only take the thing to be formatted. |
| PrettyEvalContext(fxl::RefPtr<EvalContext> impl, ExprValue value, |
| const FormatOptions& options = FormatOptions()) |
| : impl_(std::move(impl)), value_(std::move(value)), format_options_(options) { |
| AddBuiltinFuncs(); |
| FillFakeMemberFn(); |
| } |
| |
| ~PrettyEvalContext() override = default; |
| |
| // Populates the builtin_funcs_ map. |
| void AddBuiltinFuncs(); |
| |
| // Populates fake_member_fn_. |
| void FillFakeMemberFn(); |
| |
| fxl::RefPtr<EvalContext> impl_; |
| |
| fxl::WeakPtr<FormatNode> weak_node_; |
| ExprValue value_; |
| FormatOptions format_options_; |
| |
| // A function symbol we've synthesized to make FindName implicitly search the object we're |
| // pretty-printing for values and types. This function is made with a "this" variable whose type |
| // refers to the type being pretty-printed. |
| fxl::RefPtr<Function> fake_member_fn_; |
| |
| std::map<ParsedIdentifier, BuiltinFuncCallback> builtin_funcs_; |
| }; |
| |
| void PrettyEvalContext::FindName(const FindNameOptions& options, |
| const ParsedIdentifier& looking_for, |
| std::vector<FoundName>* results) const { |
| return ::zxdb::FindName(GetFindNameContext(), options, looking_for, results); |
| } |
| |
| FindNameContext PrettyEvalContext::GetFindNameContext() const { |
| // The block comes from the fake member function we made. Everything else comes from the |
| // surrounding context. |
| FindNameContext context = impl_->GetFindNameContext(); |
| context.block = fake_member_fn_.get(); |
| return context; |
| } |
| |
| void PrettyEvalContext::GetNamedValue(const ParsedIdentifier& name, EvalCallback cb) const { |
| // First try to resolve all names on the object given. |
| ResolveMember(impl_, value_, name, |
| [impl = impl_, name, cb = std::move(cb)](ErrOrValue value) mutable { |
| if (value.ok()) |
| return cb(std::move(value)); |
| |
| // Fall back on regular name lookup. |
| impl->GetNamedValue(name, std::move(cb)); |
| }); |
| } |
| |
| const EvalContext::BuiltinFuncCallback* PrettyEvalContext::GetBuiltinFunction( |
| const ParsedIdentifier& name) const { |
| if (auto found = builtin_funcs_.find(name); found != builtin_funcs_.end()) |
| return &found->second; |
| return nullptr; |
| } |
| |
| void PrettyEvalContext::AddBuiltinFuncs() { |
| builtin_funcs_[ZxdbNamespaced("AppendKeyValueRow")] = |
| [weak_node = weak_node_, format_options = format_options_]( |
| const fxl::RefPtr<EvalContext>& eval_context, const std::vector<ExprValue>& params, |
| EvalCallback cb) { |
| if (weak_node) { |
| AppendNameOrKeyValueRow(eval_context, params, weak_node.get(), NameQuotes::kKeep, |
| format_options, std::move(cb)); |
| } else { |
| cb(Err("Value gone")); |
| } |
| }; |
| builtin_funcs_[ZxdbNamespaced("AppendNameValueRow")] = |
| [weak_node = weak_node_, format_options = format_options_]( |
| const fxl::RefPtr<EvalContext>& eval_context, const std::vector<ExprValue>& params, |
| EvalCallback cb) { |
| if (weak_node) { |
| AppendNameOrKeyValueRow(eval_context, params, weak_node.get(), NameQuotes::kStrip, |
| format_options, std::move(cb)); |
| } else { |
| cb(Err("Value gone")); |
| } |
| }; |
| builtin_funcs_[ZxdbNamespaced("AppendNameRow")] = |
| [weak_node = weak_node_, format_options = format_options_]( |
| const fxl::RefPtr<EvalContext>& eval_context, const std::vector<ExprValue>& params, |
| EvalCallback cb) { |
| if (weak_node) { |
| AppendNameRow(eval_context, params, weak_node.get(), format_options, std::move(cb)); |
| } else { |
| cb(Err("Value gone")); |
| } |
| }; |
| builtin_funcs_[ZxdbNamespaced("GetMaxArraySize")] = |
| [format_options = format_options_](const fxl::RefPtr<EvalContext>& eval_context, |
| const std::vector<ExprValue>& params, EvalCallback cb) { |
| GetMaxArraySize(eval_context, params, format_options, std::move(cb)); |
| }; |
| } |
| |
| void PrettyEvalContext::FillFakeMemberFn() { |
| // See the declaration of fake_member_fn_ above for more. |
| fake_member_fn_ = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| |
| // Make a DWARF expression that evaluates to the data of the variable being pretty-printed. This |
| // isn't strictly necessary as of this writing because the variable data is never used, only the |
| // type is used for FindName while values go through GetNamedValue() which doesn't use this |
| // code path. |
| // |
| // This data is being provided for completeness to avoid weird effects of a technically-invalid |
| // Variable in case something is changed in the future around member finding. This is implemented |
| // using the DW_OP_piece opcode which is followed by the byte count (ULEB-encoded) and that |
| // number of bytes of the object data. |
| const std::vector<uint8_t>& source_data = value_.data().bytes(); |
| std::vector<uint8_t> location_expr_data; |
| location_expr_data.push_back(llvm::dwarf::DW_OP_piece); |
| AppendULeb(source_data.size(), &location_expr_data); |
| location_expr_data.insert(location_expr_data.end(), source_data.begin(), source_data.end()); |
| DwarfExpr location_expr(std::move(location_expr_data)); |
| |
| // FindName expects "this" to be a pointer type on the block. |
| auto this_ptr = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, value_.type()); |
| auto this_var = fxl::MakeRefCounted<Variable>(DwarfTag::kVariable, "this", this_ptr, |
| VariableLocation(std::move(location_expr))); |
| fake_member_fn_->set_object_pointer(LazySymbol(std::move(this_var))); |
| } |
| |
| // When doing multi-evaluation, we'll have a vector of values, any of which could have generated an |
| // error. This checks for errors and returns the first one. |
| Err UnionErrors(const std::vector<ErrOrValue>& input) { |
| for (const auto& cur : input) { |
| if (cur.has_error()) |
| return cur.err(); |
| } |
| return Err(); |
| } |
| |
| } // namespace |
| |
| PrettyType::PrettyType(std::initializer_list<std::pair<std::string, std::string>> getters) { |
| for (const auto& cur : getters) |
| AddGetterExpression(cur.first, cur.second); |
| } |
| |
| void PrettyType::AddGetterExpression(const std::string& getter_name, |
| const std::string& expression) { |
| getters_[getter_name] = expression; |
| } |
| |
| PrettyType::EvalFunction PrettyType::GetGetter(const std::string& getter_name) const { |
| auto found = getters_.find(getter_name); |
| if (found == getters_.end()) |
| return EvalFunction(); |
| return [expression = found->second](const fxl::RefPtr<EvalContext>& context, |
| const ExprValue& object_value, EvalCallback cb) { |
| EvalExpressionOn(context, object_value, expression, std::move(cb)); |
| }; |
| } |
| |
| void PrettyType::EvalExpressionOn(const fxl::RefPtr<EvalContext>& context, const ExprValue& object, |
| const std::string& expression, EvalCallback cb) { |
| // Evaluates the expression in our magic wrapper context that promotes members to the active |
| // context. |
| EvalExpression(expression, fxl::MakeRefCounted<PrettyEvalContext>(context, object), true, |
| std::move(cb)); |
| } |
| |
| void PrettyArray::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { |
| // Evaluate the expressions with this context to make the members in the current scope. |
| auto pretty_context = fxl::MakeRefCounted<PrettyEvalContext>(context, node, options); |
| |
| EvalExpressions({ptr_expr_, size_expr_}, pretty_context, true, |
| [cb = std::move(cb), weak_node = node->GetWeakPtr(), options, |
| context](std::vector<ErrOrValue> results) mutable { |
| FX_DCHECK(results.size() == 2u); |
| if (!weak_node) |
| return; |
| |
| if (Err e = UnionErrors(results); e.has_error()) |
| return weak_node->SetDescribedError(e); |
| |
| uint64_t len = 0; |
| if (Err err = results[1].value().PromoteTo64(&len); err.has_error()) |
| return weak_node->SetDescribedError(err); |
| |
| FormatArrayNode(weak_node.get(), results[0].value(), len, options, context, |
| std::move(cb)); |
| }); |
| } |
| |
| PrettyArray::EvalArrayFunction PrettyArray::GetArrayAccess() const { |
| // Since the PrettyArray is accessed by its pointer, we can just use the array access operator |
| // combined with the pointer expression to produce an expression that references into the array. |
| return [expression = ptr_expr_](const fxl::RefPtr<EvalContext>& context, |
| const ExprValue& object_value, int64_t index, EvalCallback cb) { |
| EvalExpressionOn(context, object_value, |
| fxl::StringPrintf("(%s)[%" PRId64 "]", expression.c_str(), index), |
| std::move(cb)); |
| }; |
| } |
| |
| void PrettyGenericContainer::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, |
| fit::deferred_callback cb) { |
| auto pretty_context = fxl::MakeRefCounted<PrettyEvalContext>(context, node, options); |
| |
| // Format this as a collection which will just be a list of key/value pairs. This pretty-printer |
| // will be used for things like maps and sets which will have different requirements. |
| node->set_description_kind(FormatNode::kCollection); |
| |
| EvalExpression( |
| expand_expr_, pretty_context, true, |
| [weak_node = node->GetWeakPtr(), cb = std::move(cb)](ErrOrValue result) mutable { |
| // The callback will get issued automatically whwen it goes out of scope. |
| if (result.has_error()) { |
| if (weak_node) { |
| weak_node->SetDescribedError(Err("Error pretty-printing: " + result.err().msg())); |
| } |
| } |
| }); |
| } |
| |
| void PrettyHeapString::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { |
| // Evaluate the expressions with this context to make the members in the current scope. |
| auto pretty_context = fxl::MakeRefCounted<PrettyEvalContext>(context, node->value(), options); |
| |
| EvalExpressions({ptr_expr_, size_expr_}, pretty_context, true, |
| [cb = std::move(cb), weak_node = node->GetWeakPtr(), options, |
| context](std::vector<ErrOrValue> results) mutable { |
| FX_DCHECK(results.size() == 2u); |
| if (!weak_node) |
| return; |
| |
| if (Err err = UnionErrors(results); err.has_error()) |
| return weak_node->SetDescribedError(err); |
| |
| // Pointed-to address. |
| uint64_t addr = 0; |
| if (Err err = results[0].value().PromoteTo64(&addr); err.has_error()) |
| return weak_node->SetDescribedError(err); |
| |
| // Pointed-to type. |
| fxl::RefPtr<Type> char_type; |
| if (Err err = GetPointedToType(context, results[0].value().type(), &char_type); |
| err.has_error()) |
| return weak_node->SetDescribedError(err); |
| |
| // Length. |
| uint64_t len = 0; |
| if (Err err = results[1].value().PromoteTo64(&len); err.has_error()) |
| return weak_node->SetDescribedError(err); |
| |
| FormatCharPointerNode(weak_node.get(), addr, char_type.get(), len, options, |
| context, std::move(cb)); |
| }); |
| } |
| |
| PrettyHeapString::EvalArrayFunction PrettyHeapString::GetArrayAccess() const { |
| return [expression = ptr_expr_](const fxl::RefPtr<EvalContext>& context, |
| const ExprValue& object_value, int64_t index, EvalCallback cb) { |
| EvalExpressionOn(context, object_value, |
| fxl::StringPrintf("(%s)[%" PRId64 "]", expression.c_str(), index), |
| std::move(cb)); |
| }; |
| } |
| |
| void PrettyIterator::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { |
| EvalExpressionOn(context, node->value(), value_expr_, |
| [weak_node = node->GetWeakPtr(), cb = std::move(cb)](ErrOrValue value) { |
| if (!weak_node) |
| return; |
| if (value.has_error()) |
| return weak_node->SetDescribedError(value.err()); |
| |
| // Declare it as a pointer with the value as the pointed-to thing. |
| weak_node->set_description_kind(FormatNode::kPointer); |
| |
| // There isn't a good address to show since the actual pointer is to the tree |
| // node and showing the node address in the description looks misleading. But |
| // some generic text. |
| weak_node->set_description("iterator"); |
| |
| // Make the dereference child node the value. |
| auto deref_node = std::make_unique<FormatNode>("*", value.take_value()); |
| deref_node->set_child_kind(FormatNode::kPointerExpansion); |
| weak_node->children().push_back(std::move(deref_node)); |
| }); |
| } |
| |
| PrettyIterator::EvalFunction PrettyIterator::GetDereferencer() const { |
| return [expr = value_expr_](const fxl::RefPtr<EvalContext>& context, const ExprValue& iter, |
| EvalCallback cb) { |
| EvalExpressionOn(context, iter, expr, std::move(cb)); |
| }; |
| } |
| |
| void PrettyPointer::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { |
| auto pretty_context = fxl::MakeRefCounted<PrettyEvalContext>(context, node->value(), options); |
| |
| EvalExpression( |
| expr_, pretty_context, true, |
| [cb = std::move(cb), weak_node = node->GetWeakPtr(), options](ErrOrValue value) mutable { |
| if (!weak_node) |
| return; |
| |
| if (value.has_error()) |
| weak_node->SetDescribedError(value.err()); |
| else |
| FormatPointerNode(weak_node.get(), value.value(), options); |
| }); |
| } |
| |
| PrettyPointer::EvalFunction PrettyPointer::GetDereferencer() const { |
| return [expr = expr_](const fxl::RefPtr<EvalContext>& context, const ExprValue& object_value, |
| EvalCallback cb) { |
| // The value is from dereferencing the pointer value expression. |
| EvalExpressionOn(context, object_value, "*(" + expr + ")", std::move(cb)); |
| }; |
| } |
| |
| void PrettyOptional::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { |
| EvalOptional( |
| context, node->value(), is_engaged_expr_, value_expr_, |
| [simple_type_name = simple_type_name_, name_when_disengaged = name_when_disengaged_, |
| cb = std::move(cb), weak_node = node->GetWeakPtr()](ErrOrValue value, bool is_empty) { |
| if (!weak_node) |
| return; |
| |
| if (is_empty) |
| weak_node->set_description(name_when_disengaged); |
| else if (value.has_error()) |
| weak_node->SetDescribedError(value.err()); |
| else |
| FormatWrapper(weak_node.get(), simple_type_name, "(", ")", "", std::move(value)); |
| }); |
| } |
| |
| PrettyOptional::EvalFunction PrettyOptional::GetDereferencer() const { |
| return |
| [is_engaged_expr = is_engaged_expr_, value_expr = value_expr_, |
| name_when_disengaged = name_when_disengaged_]( |
| const fxl::RefPtr<EvalContext>& context, const ExprValue& object_value, EvalCallback cb) { |
| EvalOptional( |
| context, object_value, is_engaged_expr, value_expr, |
| [cb = std::move(cb), name_when_disengaged](ErrOrValue value, bool is_empty) mutable { |
| if (is_empty) |
| return cb(Err("Attempting to dereference a " + name_when_disengaged)); |
| cb(std::move(value)); |
| }); |
| }; |
| } |
| |
| // static |
| void PrettyOptional::EvalOptional(const fxl::RefPtr<EvalContext>& context, ExprValue object, |
| const std::string& is_engaged_expr, const std::string& value_expr, |
| fit::callback<void(ErrOrValue, bool is_empty)> cb) { |
| auto pretty_context = fxl::MakeRefCounted<PrettyEvalContext>(context, object); |
| EvalExpression( |
| is_engaged_expr, pretty_context, true, |
| [pretty_context, cb = std::move(cb), value_expr](ErrOrValue is_engaged_value) mutable { |
| if (is_engaged_value.has_error()) |
| return cb(is_engaged_value, false); |
| |
| uint64_t is_engaged = 0; |
| if (Err e = is_engaged_value.value().PromoteTo64(&is_engaged); e.has_error()) |
| return cb(e, false); |
| |
| if (is_engaged) { |
| // Valid, extract the value. |
| EvalExpression( |
| value_expr, pretty_context, true, |
| [cb = std::move(cb)](ErrOrValue value) mutable { cb(std::move(value), false); }); |
| } else { |
| // Not engaged, describe as "nullopt" or equivalent. |
| cb(ExprValue(), true); |
| } |
| }); |
| } |
| |
| PrettyStruct::PrettyStruct(std::initializer_list<std::pair<std::string, std::string>> members) |
| : PrettyType({}), members_(std::begin(members), std::end(members)) {} |
| |
| void PrettyStruct::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { |
| node->set_description_kind(FormatNode::kCollection); |
| |
| // Generates a node for each "member_" that evaluates to the result of the corresponding expr. |
| for (const auto& [name, expr] : members_) { |
| auto child = std::make_unique<FormatNode>( |
| name, [object = node->value(), expr = expr]( |
| fxl::RefPtr<EvalContext> context, |
| fit::callback<void(const Err& err, ExprValue value)> cb) { |
| EvalExpressionOn(context, object, expr, ErrOrValue::FromPairCallback(std::move(cb))); |
| }); |
| node->children().push_back(std::move(child)); |
| } |
| } |
| |
| void PrettyRecursiveVariant::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, |
| fit::deferred_callback cb) { |
| auto eval_index_cb = [weak_node = node->GetWeakPtr(), simple_type_name = simple_type_name_, |
| base_expr = base_expr_, next_expr = next_expr_, value_expr = value_expr_, |
| no_value_string = no_value_string_, cb = std::move(cb)](ErrOrValue index) { |
| if (!weak_node) |
| return; |
| if (index.has_error()) |
| return weak_node->SetDescribedError(index.err()); |
| |
| // Index value. |
| int64_t index_value = 0; |
| if (Err err = index.value().PromoteTo64(&index_value); err.has_error()) |
| return weak_node->SetDescribedError(err); |
| |
| if (index_value < 0) { |
| // This variant has no value. |
| weak_node->set_description_kind(FormatNode::kOther); |
| weak_node->set_description(no_value_string); |
| return; |
| } |
| |
| // Sanity check index to prevent crash on corrupt data. |
| constexpr int64_t kMaxIndex = 16; |
| if (index_value > kMaxIndex) |
| return weak_node->SetDescribedError(Err("Variant index %" PRId64 " too large.", index_value)); |
| |
| // This expression evaluates to the variant value (see header). |
| std::string expr = base_expr; |
| for (int64_t i = 0; i < index_value; i++) { |
| if (!expr.empty()) |
| expr.push_back('.'); |
| expr.append(next_expr); |
| } |
| if (!value_expr.empty()) { |
| expr.push_back('.'); |
| expr.append(value_expr); |
| } |
| |
| FormatWrapper(weak_node.get(), simple_type_name, "(", ")", "", |
| [object = weak_node->value(), expr]( |
| fxl::RefPtr<EvalContext> context, |
| fit::callback<void(const Err& err, ExprValue value)> cb) { |
| EvalExpressionOn(context, object, expr, |
| ErrOrValue::FromPairCallback(std::move(cb))); |
| }); |
| }; |
| |
| node->set_description_kind(FormatNode::kCollection); |
| EvalExpressionOn(context, node->value(), index_expr_, std::move(eval_index_cb)); |
| } |
| |
| void PrettyWrappedValue::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, |
| fit::deferred_callback cb) { |
| FormatWrapper(node, name_, open_bracket_, close_bracket_, "", |
| [object = node->value(), expr = expression_]( |
| fxl::RefPtr<EvalContext> context, |
| fit::callback<void(const Err& err, ExprValue value)> cb) { |
| EvalExpressionOn(context, object, expr, |
| ErrOrValue::FromPairCallback(std::move(cb))); |
| }); |
| } |
| |
| PrettyWrappedValue::EvalFunction PrettyWrappedValue::GetDereferencer() const { |
| return [expr = expression_](const fxl::RefPtr<EvalContext>& context, const ExprValue& val, |
| EvalCallback cb) { |
| EvalExpressionOn(context, val, expr, std::move(cb)); |
| }; |
| } |
| |
| PrettyZxStatusT::PrettyZxStatusT() : PrettyType({}) {} |
| |
| void PrettyZxStatusT::Format(FormatNode* node, const FormatOptions& options, |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { |
| FormatNumericNode(node, options); |
| |
| if (node->value().type()->byte_size() == sizeof(debug::zx_status_t)) { |
| debug::zx_status_t int_val = node->value().GetAs<debug::zx_status_t>(); |
| node->set_description(node->description() + |
| fxl::StringPrintf(" (%s)", debug::ZxStatusToString(int_val))); |
| } |
| } |
| |
| } // namespace zxdb |