blob: d7a8f727adc88d9229fe9a118b1b388ff8efa815 [file] [log] [blame]
// 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/format.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/developer/debug/zxdb/expr/eval_context.h"
#include "src/developer/debug/zxdb/expr/expr.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/pretty_type_manager.h"
#include "src/developer/debug/zxdb/expr/resolve_array.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/expr/resolve_variant.h"
#include "src/developer/debug/zxdb/symbols/arch.h"
#include "src/developer/debug/zxdb/symbols/array_type.h"
#include "src/developer/debug/zxdb/symbols/base_type.h"
#include "src/developer/debug/zxdb/symbols/collection.h"
#include "src/developer/debug/zxdb/symbols/enumeration.h"
#include "src/developer/debug/zxdb/symbols/inherited_from.h"
#include "src/developer/debug/zxdb/symbols/member_ptr.h"
#include "src/developer/debug/zxdb/symbols/modified_type.h"
#include "src/developer/debug/zxdb/symbols/symbol_data_provider.h"
#include "src/developer/debug/zxdb/symbols/variant.h"
#include "src/developer/debug/zxdb/symbols/variant_part.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
using NumFormat = FormatOptions::NumFormat;
// Returns true if the base type is some kind of number such that the NumFormat of the format
// options should be applied.
bool IsNumericBaseType(int base_type) {
return base_type == BaseType::kBaseTypeSigned || base_type == BaseType::kBaseTypeUnsigned ||
base_type == BaseType::kBaseTypeBoolean || base_type == BaseType::kBaseTypeFloat ||
base_type == BaseType::kBaseTypeSignedChar ||
base_type == BaseType::kBaseTypeUnsignedChar || base_type == BaseType::kBaseTypeUTF;
}
// Returns true if the given type (assumed to be a pointer) is a pointer to a function (but NOT a
// member function).
bool IsPointerToFunction(const ModifiedType* pointer) {
FXL_DCHECK(pointer->tag() == DwarfTag::kPointerType);
return !!pointer->modified().Get()->AsFunctionType();
}
// Appends the given byte to the destination, escaping as per C rules.
void AppendEscapedChar(uint8_t ch, std::string* dest) {
if (ch == '\'' || ch == '\"' || ch == '\\') {
// These characters get backslash-escaped.
dest->push_back('\\');
dest->push_back(ch);
} else if (ch == '\n') {
dest->append("\\n");
} else if (ch == '\r') {
dest->append("\\r");
} else if (ch == '\t') {
dest->append("\\t");
} else if (isprint(ch)) {
dest->push_back(ch);
} else {
// Hex-encode everything else.
*dest += fxl::StringPrintf("\\x%02x", static_cast<unsigned>(ch));
}
}
void FormatBoolean(FormatNode* node) {
uint64_t int_val = 0;
Err err = node->value().PromoteTo64(&int_val);
if (err.has_error())
node->set_err(err);
else if (int_val)
node->set_description("true");
else
node->set_description("false");
}
void FormatFloat(FormatNode* node) {
const ExprValue& value = node->value();
switch (node->value().data().size()) {
case sizeof(float):
node->set_description(fxl::StringPrintf("%g", value.GetAs<float>()));
break;
case sizeof(double):
node->set_description(fxl::StringPrintf("%g", value.GetAs<double>()));
break;
default:
node->set_err(Err(
fxl::StringPrintf("Unknown float of size %d", static_cast<int>(value.data().size()))));
break;
}
}
void FormatSignedInt(FormatNode* node) {
int64_t int_val = 0;
Err err = node->value().PromoteTo64(&int_val);
if (err.has_error())
node->set_err(err);
else
node->set_description(fxl::StringPrintf("%" PRId64, int_val));
}
void FormatUnsignedInt(FormatNode* node, const FormatOptions& options) {
// This formatter handles unsigned and hex output.
uint64_t int_val = 0;
Err err = node->value().PromoteTo64(&int_val);
if (err.has_error())
node->set_err(err);
else if (options.num_format == NumFormat::kHex)
node->set_description(fxl::StringPrintf("0x%" PRIx64, int_val));
else
node->set_description(fxl::StringPrintf("%" PRIu64, int_val));
}
// Returns true if the given symbol points to a character type that would appear in a pretty-printed
// string.
bool IsCharacterType(fxl::RefPtr<EvalContext>& eval_context, const Type* type) {
if (!type)
return false;
fxl::RefPtr<Type> concrete = eval_context->GetConcreteType(type);
// Expect a 1-byte character type.
// TODO(brettw) handle Unicode.
if (concrete->byte_size() != 1)
return false;
const BaseType* base_type = concrete->AsBaseType();
if (!base_type)
return false;
return base_type->base_type() == BaseType::kBaseTypeSignedChar ||
base_type->base_type() == BaseType::kBaseTypeUnsignedChar;
}
void FormatChar(FormatNode* node) {
// Just take the first byte for all char.
// TODO(brettw) handle unicode, etc.
if (node->value().data().empty()) {
node->set_err(Err("Invalid char type"));
return;
}
std::string str;
str.push_back('\'');
AppendEscapedChar(node->value().data()[0], &str);
str.push_back('\'');
node->set_description(std::move(str));
}
// Formats a numeric-style input. This assumes the type of the value in the given node has already
// been determined to be numeric. This may also be called as a fallback for things like enums.
void FormatNumeric(FormatNode* node, const FormatOptions& options) {
node->set_description_kind(FormatNode::kBaseType);
if (options.num_format != NumFormat::kDefault) {
// Overridden format option.
switch (options.num_format) {
case NumFormat::kUnsigned:
case NumFormat::kHex:
FormatUnsignedInt(node, options);
break;
case NumFormat::kSigned:
FormatSignedInt(node);
break;
case NumFormat::kChar:
FormatChar(node);
break;
case NumFormat::kDefault:
// Prevent warning for unused enum type.
break;
}
} else {
// Default handling for base types based on the number.
switch (node->value().GetBaseType()) {
case BaseType::kBaseTypeBoolean:
FormatBoolean(node);
break;
case BaseType::kBaseTypeFloat:
FormatFloat(node);
break;
case BaseType::kBaseTypeSigned:
FormatSignedInt(node);
break;
case BaseType::kBaseTypeUnsigned:
FormatUnsignedInt(node, options);
break;
case BaseType::kBaseTypeSignedChar:
case BaseType::kBaseTypeUnsignedChar:
case BaseType::kBaseTypeUTF:
FormatChar(node);
break;
}
}
}
void FormatZxStatusT(FormatNode* node, const FormatOptions& options) {
FormatNumeric(node, options);
// Caller should have checked this is the right size.
debug_ipc::zx_status_t int_val = node->value().GetAs<debug_ipc::zx_status_t>();
node->set_description(node->description() +
fxl::StringPrintf(" (%s)", debug_ipc::ZxStatusToString(int_val)));
}
void FormatEnum(FormatNode* node, const Enumeration* enum_type, const FormatOptions& options) {
// Get the value out casted to a uint64.
Err err;
uint64_t numeric_value;
if (enum_type->is_signed()) {
int64_t signed_value;
err = node->value().PromoteTo64(&signed_value);
if (!err.has_error())
numeric_value = static_cast<uint64_t>(signed_value);
} else {
err = node->value().PromoteTo64(&numeric_value);
}
if (err.has_error()) {
node->set_err(err);
return;
}
// When the output is marked for a specific numeric type, always skip name lookup and output the
// numeric value below instead.
if (options.num_format == NumFormat::kDefault) {
const auto& map = enum_type->values();
auto found = map.find(numeric_value);
if (found != map.end()) {
// Got the enum value string.
node->set_description(found->second);
return;
}
// Not found, fall through to numeric formatting.
}
// Invalid enum values of explicitly overridden numeric formatting gets printed as a number.
// Be explicit about the number formatting since the enum won't be a BaseType.
FormatOptions modified_opts = options;
if (modified_opts.num_format == NumFormat::kDefault)
modified_opts.num_format = enum_type->is_signed() ? NumFormat::kSigned : NumFormat::kUnsigned;
FormatNumeric(node, modified_opts);
}
// Rust enums will resolve to a different type. We put the resolved type in a child of this node.
// As with references, this is not the best presentation for a GUI. See FormatReference() for
// some thoughts on how this could be improved.
//
// The active variant will have a set of data members of which only one will be used. It will refer
// to a collection which will have the set of members. This structure will vary according to the
// type of enum:
// EnumWithNoValue
// The struct will have no members.
// OneValue(u32)
// The struct will have one member named __0
// Tuple(u32, u32, etc.)
// The struct will have two values, __0 and __1, etc.
// Struct{x:u32, y:u32}
// The struct will have "x" and "y" members.
void FormatRustEnum(FormatNode* node, const Collection* coll, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context) {
node->set_description_kind(FormatNode::kRustEnum);
const VariantPart* variant_part = coll->variant_part().Get()->AsVariantPart();
if (!variant_part) {
node->set_err(Err("Missing variant part for Rust enum."));
return;
}
fxl::RefPtr<Variant> variant;
Err err = ResolveVariant(eval_context, node->value(), variant_part, &variant);
if (err.has_error()) {
node->set_err(err);
return;
}
// Add each variant data member as a child of this node. In Rust we expect exactly one but it
// can't hurt to be general.
std::string enum_name;
for (const auto& lazy_member : variant->data_members()) {
const DataMember* member = lazy_member.Get()->AsDataMember();
if (!member)
continue;
// Save the first member's name to be the name of the whole enum, even if there are no data
// members. Normally there will be exactly one.
if (enum_name.empty())
enum_name = member->GetAssignedName();
// In the error case, still append a child so that the child can have the error associated with
// it. Note that Rust enums are never static so we can use the synchronous variant.
node->children().push_back(std::make_unique<FormatNode>(
member->GetAssignedName(), ResolveNonstaticMember(eval_context, node->value(), member)));
}
// Name for the whole node.
node->set_description(enum_name);
}
// Handles both tuples and tuple structs. The "kind" differentiates these.
void FormatRustTuple(FormatNode* node, const Collection* coll, FormatNode::DescriptionKind kind,
const FormatOptions& options, fxl::RefPtr<EvalContext> eval_context) {
node->set_description_kind(kind);
// Rust tuple (and tuple struct) symbols have the tuple members encoded as "__0", "__1", etc.
for (const auto& lazy_member : coll->data_members()) {
const DataMember* member = lazy_member.Get()->AsDataMember();
if (!member)
continue;
// Convert the names to indices "__0" -> 0.
auto name = member->GetAssignedName();
if (name.size() > 2 && name[0] == '_' && name[1] == '_')
name.erase(name.begin(), name.begin() + 2);
// In the error case, still append a child so that the child can have the error associated
// with it.
node->children().push_back(
std::make_unique<FormatNode>(name, ResolveNonstaticMember(eval_context, node->value(), member)));
}
// When we have a use for the short description, this should be set to: "(<0>, <1>, ...)"
}
void FormatCollection(FormatNode* node, const Collection* coll, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context) {
if (coll->is_declaration()) {
// Sometimes a value will have a type that's a forward declaration and we couldn't resolve its
// concrete type. Print an error instead of "{}".
node->set_err(Err("No definition."));
return;
}
// Special-cases of collections.
switch (coll->GetSpecialType()) {
case Collection::kNotSpecial:
break;
case Collection::kRustEnum:
FormatRustEnum(node, coll, options, std::move(eval_context));
return;
case Collection::kRustTuple:
FormatRustTuple(node, coll, FormatNode::kRustTuple, options, std::move(eval_context));
return;
case Collection::kRustTupleStruct:
FormatRustTuple(node, coll, FormatNode::kRustTupleStruct, options, std::move(eval_context));
return;
}
// Base classes.
for (const auto& lazy_inherited : coll->inherited_from()) {
const InheritedFrom* inherited = lazy_inherited.Get()->AsInheritedFrom();
if (!inherited)
continue;
const Collection* from = inherited->from().Get()->AsCollection();
if (!from)
continue;
// Some base classes are empty. Only show if this base class or any of its base classes have
// member values.
VisitResult has_members_result = VisitClassHierarchy(from, [](const Collection* cur, uint64_t) {
if (cur->data_members().empty())
return VisitResult::kContinue;
return VisitResult::kDone;
});
if (has_members_result == VisitResult::kContinue)
continue;
// Derived class nodes are named by the type of the base class.
std::unique_ptr<FormatNode> base_class_node = std::make_unique<FormatNode>(
from->GetFullName(), ResolveInherited(eval_context, node->value(), inherited));
base_class_node->set_child_kind(FormatNode::kBaseClass);
node->children().push_back(std::move(base_class_node));
}
// Data members.
for (const auto& lazy_member : coll->data_members()) {
const DataMember* member = lazy_member.Get()->AsDataMember();
if (!member)
continue;
if (member->artificial())
continue; // Skip compiler-generated data.
// Skip static data members. This could potentially be revisited. This generally gives
// duplicated and uninteresting data in the view, and the user can still explicitly type the
// name if desired.
//
// To implement we should probably append a FormatNode with a lambda that gets the right
// value. It can be asynchronously expanded layer. That way this function doesn't need to
// handle any asynchronous state.
if (member->is_external())
continue;
node->children().push_back(std::make_unique<FormatNode>(
member->GetAssignedName(), ResolveNonstaticMember(eval_context, node->value(), member)));
}
node->set_description_kind(FormatNode::kCollection);
}
// For now a reference is formatted like a pointer where the outer node is the address, and the
// inner node is the "dereferenced" value. This is nice because it keeps the formatting code
// synchronous, while only the value resolution (in the child node) needs to be asynchronous.
//
// If this is put into a GUI, we'll want the reference value to be in the main description and not
// have any children. Visual Studio shows references the same as if it was a value which is probably
// the correct behavior.
//
// To do this we'll likely want to add another ExprValue to the FormatNode (maybe it's in a
// std::optional?) that contains the "resolved value" of the node. This would also be useful for
// Rust enums.
void FormatReference(FormatNode* node, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context) {
node->set_description_kind(FormatNode::kReference);
Err err = node->value().EnsureSizeIs(kTargetPointerSize);
if (err.has_error()) {
node->set_err(err);
return;
}
// The address goes in the description (see note above).
node->set_description(fxl::StringPrintf("0x%" PRIx64, node->value().GetAs<TargetPointer>()));
auto deref_node = std::make_unique<FormatNode>(
std::string(),
[ref = node->value()](fxl::RefPtr<EvalContext> context,
fit::callback<void(const Err& err, ExprValue value)> cb) {
EnsureResolveReference(context, ref, ErrOrValue::FromPairCallback(std::move(cb)));
});
deref_node->set_child_kind(FormatNode::kPointerExpansion);
node->children().push_back(std::move(deref_node));
}
void FormatFunctionPointer(FormatNode* node, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context) {
node->set_description_kind(FormatNode::kFunctionPointer);
Err err = node->value().EnsureSizeIs(kTargetPointerSize);
if (err.has_error()) {
node->set_err(err);
return;
}
TargetPointer address = node->value().GetAs<TargetPointer>();
if (address == 0) {
// Special-case null pointers. Don't bother trying to decode the address.
node->set_description("0x0");
return;
}
// Allow overrides for the number format. Normally one would expect to
// provide a hex override to get the address rather than the resolved
// function name.
if (options.num_format != NumFormat::kDefault) {
FormatNumeric(node, options);
return;
}
// Try to symbolize the function being pointed to.
Location loc = eval_context->GetLocationForAddress(address);
std::string function_name;
if (loc.symbol()) {
if (const Function* func = loc.symbol().Get()->AsFunction())
function_name = func->GetFullName();
}
if (function_name.empty()) {
// No function name, just print out the address.
node->set_description(fxl::StringPrintf("0x%" PRIx64, address));
} else {
node->set_description("&" + function_name);
}
}
void FormatMemberPtr(FormatNode* node, const MemberPtr* type, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context) {
const Type* container_type = type->container_type().Get()->AsType();
const Type* pointed_to_type = type->member_type().Get()->AsType();
if (!container_type || !pointed_to_type) {
node->set_err(Err("Missing symbol information."));
return;
}
if (const FunctionType* func = pointed_to_type->AsFunctionType()) {
// Pointers to member functions can be handled just like regular function pointers.
FormatFunctionPointer(node, options, eval_context);
} else {
// Pointers to data.
node->set_description_kind(FormatNode::kOther);
if (Err err = node->value().EnsureSizeIs(kTargetPointerSize); err.has_error()) {
node->set_err(err);
return;
}
// The address goes in the description.
//
// TODO(brettw) it would be nice if this interrogated the type and figured out the name of the
// member being pointed to. The address is not very helpful.
node->set_description(fxl::StringPrintf("0x%" PRIx64, node->value().GetAs<TargetPointer>()));
}
}
void FormatCharPointer(FormatNode* node, const Type* char_type, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context, fit::deferred_callback cb) {
node->set_description_kind(FormatNode::kString);
// Extracts the pointer and calls the general "char*" formatter.
if (node->value().data().size() != kTargetPointerSize) {
node->set_err(Err("Bad pointer data."));
return;
}
FormatCharPointerNode(node, node->value().GetAs<TargetPointer>(), char_type, std::nullopt,
options, eval_context, std::move(cb));
}
// Attempts to format arrays, char arrays, and char pointers. Because these are many different types
// this is handled by a separate helper function.
//
// Returns true if the node was formatted by this function. If the operation is asynchronous the
// callback will be moved from to defer it until the async operation is complete.
//
// A false return value means this was not an array or a string and other types of formatting should
// be attempted. The callback will be unmodified.
bool TryFormatArrayOrString(FormatNode* node, const Type* type, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context, fit::deferred_callback& cb) {
FXL_DCHECK(type == type->StripCVT());
if (type->tag() == DwarfTag::kPointerType) {
// Any pointer type (we only char about char*).
const ModifiedType* modified = type->AsModifiedType();
if (!modified)
return false;
const Type* char_type = modified->modified().Get()->AsType();
if (IsCharacterType(eval_context, char_type)) {
FormatCharPointer(node, char_type, options, eval_context, std::move(cb));
return true;
}
return false; // All other pointer types are unhandled.
} else if (type->tag() == DwarfTag::kArrayType) {
// Any array type with a known size (we care about both).
const ArrayType* array = type->AsArrayType();
if (!array)
return false;
if (!array->num_elts()) {
// Unknown array size, see ArrayType header for what this means. Nothing to do in this case.
node->SetDescribedError(Err("Array with unknown size."));
return true;
}
auto value_type = eval_context->GetConcreteType(array->value_type());
if (!value_type)
return false;
if (IsCharacterType(eval_context, value_type.get())) {
size_t length = *array->num_elts();
bool truncated = false;
if (length > options.max_array_size) {
length = options.max_array_size;
truncated = true;
}
FormatCharArrayNode(node, value_type, node->value().data().data(), length, true, truncated);
} else {
FormatArrayNode(node, node->value(), *array->num_elts(), options, eval_context,
std::move(cb));
}
return true;
}
return false;
}
// Unspecified types are normally nullptr_t and print as a number (probably 0x0).
void FormatUnspecified(FormatNode* node) {
node->set_description_kind(FormatNode::kOther);
uint64_t unspecified_value = 0;
if (node->value().PromoteTo64(&unspecified_value).has_error())
node->set_description("<unspecified>");
else
node->set_description(fxl::StringPrintf("0x%" PRIx64, unspecified_value));
}
// Given a node with a value already filled, fills the description.
void FillFormatNodeDescriptionFromValue(FormatNode* node, const FormatOptions& options,
fxl::RefPtr<EvalContext> context,
fit::deferred_callback cb) {
FXL_DCHECK(node->state() != FormatNode::kUnevaluated);
if (node->state() == FormatNode::kEmpty || node->err().has_error()) {
node->set_state(FormatNode::kDescribed);
return;
}
// All code paths below convert to "described" state.
node->set_state(FormatNode::kDescribed);
node->set_description(std::string());
node->set_description_kind(FormatNode::kNone);
node->children().clear();
node->set_err(Err());
// Format type name.
if (!node->value().type()) {
node->set_err(Err("No type"));
return;
}
node->set_type(node->value().type()->GetFullName());
// Check for pretty-printers. This also happens again below if the type changed.
if (options.enable_pretty_printing &&
context->GetPrettyTypeManager().Format(node, node->value().type(), options, context, cb))
return;
// Special-case zx_status_t. Long-term this should be removed and replaced with a pretty-printing
// system where this can be expressed generically.
//
// This code needs to go here because zx_status_t is a typedef that will be expanded away by the
// GetConcreteType() below.
if (node->value().type()->GetFullName() == "zx_status_t" &&
node->value().type()->byte_size() == sizeof(debug_ipc::zx_status_t)) {
FormatZxStatusT(node, options);
return;
}
// Trim "const", "volatile", etc. and follow typedef and using for the type checking below.
//
// Always use this variable below instead of value.type().
fxl::RefPtr<Type> type = node->value().GetConcreteType(context.get());
// Check for pretty-printers again now that we've resolved concrete types. Either the source or
// the destination of a typedef could have a pretty-printer.
if (options.enable_pretty_printing && type.get() != node->value().type() &&
context->GetPrettyTypeManager().Format(node, type.get(), options, context, cb))
return;
// Arrays and strings.
if (TryFormatArrayOrString(node, type.get(), options, context, cb))
return;
if (const ModifiedType* modified_type = type->AsModifiedType()) {
// Modified types (references were handled above).
switch (modified_type->tag()) {
case DwarfTag::kPointerType:
// Function pointers need special handling.
if (IsPointerToFunction(modified_type))
FormatFunctionPointer(node, options, context);
else
FormatPointerNode(node, node->value(), options);
break;
case DwarfTag::kReferenceType:
case DwarfTag::kRvalueReferenceType:
FormatReference(node, options, context);
break;
default:
node->set_err(Err("Unhandled type modifier 0x%x, please file a bug.",
static_cast<unsigned>(modified_type->tag())));
break;
}
} else if (IsNumericBaseType(node->value().GetBaseType())) {
// Numeric types.
FormatNumeric(node, options);
} else if (const MemberPtr* member_ptr = type->AsMemberPtr()) {
// Pointers to class/struct members.
FormatMemberPtr(node, member_ptr, options, context);
} else if (const FunctionType* func = type->AsFunctionType()) {
// Functions. These don't have a direct C++ equivalent without being
// modified by a "pointer". Assume these act like pointers to functions.
FormatFunctionPointer(node, options, context);
} else if (const Enumeration* enum_type = type->AsEnumeration()) {
// Enumerations.
FormatEnum(node, enum_type, options);
} else if (const Collection* coll = type->AsCollection()) {
// Collections (structs, classes, and unions).
FormatCollection(node, coll, options, context);
} else if (type->tag() == DwarfTag::kUnspecifiedType) {
// Unspecified (nullptr_t).
FormatUnspecified(node);
} else {
node->set_err(Err("Unsupported type for new formatting system."));
}
}
} // namespace
void FillFormatNodeValue(FormatNode* node, fxl::RefPtr<EvalContext> context,
fit::deferred_callback cb) {
switch (node->source()) {
case FormatNode::kValue:
// Already has the value.
return;
case FormatNode::kExpression: {
// Evaluate the expression.
// TODO(brettw) remove this make_shared when EvalExpression takes a fit::callback.
auto shared_cb = std::make_shared<fit::deferred_callback>(std::move(cb));
EvalExpression(node->expression(), context, true,
[weak_node = node->GetWeakPtr(), shared_cb](ErrOrValue value) {
if (!weak_node)
return;
if (value.has_error()) {
weak_node->set_err(value.err());
weak_node->SetValue(ExprValue());
} else {
weak_node->SetValue(value.take_value());
}
});
return;
}
case FormatNode::kProgramatic:
// Lambda provides the value.
node->FillProgramaticValue(std::move(context), std::move(cb));
return;
}
FXL_NOTREACHED();
}
void FillFormatNodeDescription(FormatNode* node, const FormatOptions& options,
fxl::RefPtr<EvalContext> context, fit::deferred_callback cb) {
if (node->state() == FormatNode::kEmpty || node->err().has_error()) {
node->set_state(FormatNode::kDescribed);
return;
}
if (node->state() == FormatNode::kUnevaluated) {
// Need to compute the value (possibly asynchronously).
FillFormatNodeValue(node, context,
fit::defer_callback([weak_node = node->GetWeakPtr(), options, context,
cb = std::move(cb)]() mutable {
if (weak_node)
FillFormatNodeDescriptionFromValue(weak_node.get(), options, context,
std::move(cb));
}));
} else {
// Value already available, can format now.
FillFormatNodeDescriptionFromValue(node, options, context, std::move(cb));
}
}
// Sometimes we know the real length of the array as in c "char[12]" type. In this case the expanded
// children should always include all elements, even if there is a null in the middle. This is what
// length_was_known means. When unset we assume a guessed length (as in "char*"), stop at the first
// null, and don't include it.
//
// TODO(brettw) currently this handles 8-bit characters only.
void FormatCharArrayNode(FormatNode* node, fxl::RefPtr<Type> char_type, const uint8_t* data,
size_t length, bool length_was_known, bool truncated) {
node->set_description_kind(FormatNode::kString);
// Expect the string to be null-terminated. If we didn't find a null before the end of the buffer,
// mark as truncated.
size_t output_len = strnlen(reinterpret_cast<const char*>(data), length);
// It's possible a null happened before the end of the buffer, in which case it's no longer
// truncated.
if (output_len < length)
truncated = false;
// Generate the string in the description. Stop at the first null (computed above) and don't
// include it.
std::string result("\"");
for (size_t i = 0; i < output_len; i++)
AppendEscapedChar(data[i], &result);
result.push_back('"');
// Add children to the first null unless the length was known in advance.
size_t child_len = length_was_known ? length : output_len;
for (size_t i = 0; i < child_len; i++) {
auto char_node = std::make_unique<FormatNode>(fxl::StringPrintf("[%zu]", i),
ExprValue(char_type, {data[i]}));
char_node->set_child_kind(FormatNode::kArrayItem);
node->children().push_back(std::move(char_node));
}
// Add an indication if the string was truncated to the max size.
if (truncated) {
result += "...";
node->children().push_back(std::make_unique<FormatNode>("..."));
}
node->set_description(result);
node->set_state(FormatNode::kDescribed);
}
void FormatCharPointerNode(FormatNode* node, uint64_t ptr, const Type* char_type,
std::optional<uint32_t> length, const FormatOptions& options,
fxl::RefPtr<EvalContext> eval_context, fit::deferred_callback cb) {
node->set_description_kind(FormatNode::kString);
if (!ptr) {
// Special-case null pointers to just print a null address.
node->set_description("0x0");
return;
}
if (length && *length == 0) {
// Empty string.
node->set_description("\"\"");
return;
}
// Speculatively request the max string size.
uint32_t bytes_to_fetch;
bool truncated = false;
if (length) {
if (*length > options.max_array_size) {
bytes_to_fetch = options.max_array_size;
truncated = true;
} else {
bytes_to_fetch = *length;
}
} else {
bytes_to_fetch = options.max_array_size;
// Report as truncated because if the string goes to the end of this array it will be.
// FormatCharArrayNode will clear this flag if it finds a null before the end of the buffer.
//
// Don't want to set truncated if the data ended before the requested size, this means it
// hit the end of valid memory, so we're not omitting data by only showing that part of it.
truncated = true;
}
if (bytes_to_fetch == 0) {
// No array data should be fetched. Indicate that the result was truncated.
node->set_description("\"\"...");
return;
}
fxl::RefPtr<SymbolDataProvider> data_provider = eval_context->GetDataProvider();
data_provider->GetMemoryAsync(ptr, bytes_to_fetch,
[ptr, bytes_to_fetch, char_type = RefPtrTo(char_type), truncated,
weak_node = node->GetWeakPtr(), cb = std::move(cb)](
const Err& err, std::vector<uint8_t> data) mutable {
if (!weak_node)
return;
if (data.empty()) {
// Should not have requested 0 size, so it if came back empty
// the pointer was invalid.
weak_node->set_err(Err("0x%" PRIx64 " invalid pointer", ptr));
return;
}
bool new_truncated = truncated && data.size() == bytes_to_fetch;
FormatCharArrayNode(weak_node.get(), char_type, &data[0],
data.size(), false, new_truncated);
});
}
void FormatArrayNode(FormatNode* node, const ExprValue& value, int elt_count,
const FormatOptions& options, fxl::RefPtr<EvalContext> eval_context,
fit::deferred_callback cb) {
node->set_description_kind(FormatNode::kArray);
if (elt_count < 0)
return node->SetDescribedError(Err("Invalid array size of %d.", elt_count));
int print_count = std::min(static_cast<int>(options.max_array_size), elt_count);
ResolveArray(eval_context, value, 0, print_count,
[weak_node = node->GetWeakPtr(), elt_count,
cb = std::move(cb)](ErrOrValueVector result) mutable {
if (!weak_node)
return;
FormatNode* node = weak_node.get();
if (result.has_error())
return node->SetDescribedError(result.err());
for (size_t i = 0; i < result.value().size(); i++) {
auto item_node = std::make_unique<FormatNode>(fxl::StringPrintf("[%zu]", i),
std::move(result.value()[i]));
item_node->set_child_kind(FormatNode::kArrayItem);
node->children().push_back(std::move(item_node));
}
if (static_cast<uint32_t>(elt_count) > result.value().size()) {
// Add "..." annotation to show some things were clipped.
//
// TODO(brettW) We may want to put a flag on the node that it was clipped,
// and also indicate the number of clipped elements.
node->children().push_back(std::make_unique<FormatNode>("..."));
}
});
}
void FormatPointerNode(FormatNode* node, const ExprValue& value, const FormatOptions& options) {
node->set_description_kind(FormatNode::kPointer);
// Note: don't make assumptions about the type of value.type() since it isn't necessarily a
// ModifiedType representing a pointer, but could be other things like a pointer to a member.
Err err = value.EnsureSizeIs(kTargetPointerSize);
if (err.has_error()) {
node->set_err(err);
return;
}
// The address goes in the description.
TargetPointer pointer_value = value.GetAs<TargetPointer>();
node->set_description(fxl::StringPrintf("0x%" PRIx64, pointer_value));
// Make a child node that's the dereferenced pointer value. If/when we support GUIs, we should
// probably remove the intermediate node and put the dereferenced struct members directly as
// children on this node. Otherwise it's an annoying extra step to expand to things.
if (pointer_value != 0) {
// Use our name but with a "*" to show it dereferenced.
auto deref_node = std::make_unique<FormatNode>(
"*" + node->name(),
[ptr_value = value](fxl::RefPtr<EvalContext> context,
fit::callback<void(const Err& err, ExprValue value)> cb) {
ResolvePointer(context, ptr_value, ErrOrValue::FromPairCallback(std::move(cb)));
});
deref_node->set_child_kind(FormatNode::kPointerExpansion);
node->children().push_back(std::move(deref_node));
}
}
void FormatWrapper(FormatNode* node, const std::string& description, const std::string& prefix,
const std::string& suffix, const std::string& contained_name,
ErrOrValue contained_value) {
// Declare it as a pointer with the value as the pointed-to thing.
node->set_description_kind(FormatNode::kWrapper);
node->set_description(description);
node->set_wrapper_prefix(prefix);
node->set_wrapper_suffix(suffix);
node->children().push_back(
std::make_unique<FormatNode>(contained_name, std::move(contained_value)));
}
} // namespace zxdb