blob: 1225fa02ff95aab5cc4e066c39f3f2e03b8323a0 [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/zxdb/console/format_value.h"
#include <ctype.h>
#include <string.h>
#include "garnet/bin/zxdb/expr/expr_value.h"
#include "garnet/bin/zxdb/expr/resolve_array.h"
#include "garnet/bin/zxdb/expr/resolve_collection.h"
#include "garnet/bin/zxdb/expr/resolve_ptr_ref.h"
#include "garnet/bin/zxdb/expr/symbol_variable_resolver.h"
#include "garnet/bin/zxdb/symbols/array_type.h"
#include "garnet/bin/zxdb/symbols/base_type.h"
#include "garnet/bin/zxdb/symbols/collection.h"
#include "garnet/bin/zxdb/symbols/data_member.h"
#include "garnet/bin/zxdb/symbols/enumeration.h"
#include "garnet/bin/zxdb/symbols/function.h"
#include "garnet/bin/zxdb/symbols/function_type.h"
#include "garnet/bin/zxdb/symbols/inherited_from.h"
#include "garnet/bin/zxdb/symbols/location.h"
#include "garnet/bin/zxdb/symbols/member_ptr.h"
#include "garnet/bin/zxdb/symbols/modified_type.h"
#include "garnet/bin/zxdb/symbols/symbol_data_provider.h"
#include "garnet/bin/zxdb/symbols/variable.h"
#include "garnet/bin/zxdb/symbols/visit_scopes.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
using NumFormat = FormatExprValueOptions::NumFormat;
using Verbosity = FormatExprValueOptions::Verbosity;
// When there are errors during value printing we can't just print them since
// they're associated with a value. This function formats the error in a way
// appropriate for value output.
OutputBuffer ErrToOutput(const Err& err) {
FXL_DCHECK(err.has_error());
return OutputBuffer(Syntax::kComment, "<" + err.msg() + ">");
}
OutputBuffer ErrStringToOutput(const std::string& s) {
return OutputBuffer(Syntax::kComment, "<" + s + ">");
}
OutputBuffer InvalidPointerToOutput(TargetPointer address) {
OutputBuffer out;
out.Append(OutputBuffer(fxl::StringPrintf("0x%" PRIx64 " ", address)));
out.Append(ErrStringToOutput("invalid pointer"));
return out;
}
// Get a possibly-elided version of the type name for a medium verbosity level.
std::string GetElidedTypeName(const std::string& name) {
// Names shorter than this are always displayed in full.
if (name.size() <= 32)
return name;
// This value was picked to be smaller than the above value so we don't elide
// one or two characters (which looks dumb). It was selected to be long
// enough so that with the common prefix of "std::__2::" (which occurs on
// many long types), you still get enough to read approximately what the
// type is.
return name.substr(0, 20) + "…";
}
// 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 symbol points to a character type that would
// appear in a pretty-printed string.
bool IsCharacterType(const Type* type) {
if (!type)
return false;
const Type* concrete = type->GetConcreteType();
// 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;
}
bool IsCharacterType(const LazySymbol& symbol) {
return IsCharacterType(symbol.Get()->AsType());
}
// 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));
}
}
// 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() == Symbol::kTagPointerType);
return !!pointer->modified().Get()->AsFunctionType();
}
} // namespace
FormatValue::FormatValue(std::unique_ptr<ProcessContext> process_context)
: process_context_(std::move(process_context)), weak_factory_(this) {}
FormatValue::~FormatValue() = default;
void FormatValue::AppendValue(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue value,
const FormatExprValueOptions& options) {
FormatExprValue(data_provider, value, options, false,
AsyncAppend(GetRootOutputKey()));
}
void FormatValue::AppendVariable(const SymbolContext& symbol_context,
fxl::RefPtr<SymbolDataProvider> data_provider,
const Variable* var,
const FormatExprValueOptions& options) {
OutputKey output_key = AsyncAppend(
NodeType::kVariable, var->GetAssignedName(), GetRootOutputKey());
auto resolver = std::make_unique<SymbolVariableResolver>(data_provider);
// We can capture "this" here since the callback will be scoped to the
// lifetime of the resolver which this class owns.
resolver->ResolveVariable(
symbol_context, var, [this, data_provider, options, output_key](
const Err& err, ExprValue val) {
// The variable has been resolved, now we need to print it (which could
// in itself be asynchronous).
FormatExprValue(data_provider, err, val, options, false, output_key);
});
// Keep in our class scope so the callbacks will be run.
resolvers_.push_back(std::move(resolver));
}
void FormatValue::Append(OutputBuffer out) {
AppendToOutputKey(GetRootOutputKey(), std::move(out));
}
void FormatValue::Append(std::string str) {
Append(OutputBuffer(std::move(str)));
}
void FormatValue::Complete(Callback callback) {
FXL_DCHECK(!complete_callback_);
complete_callback_ = std::move(callback);
// If there are no pending formats, issue the callback right away.
CheckPendingResolution();
// WARNING: |this| may be deleted.
}
void FormatValue::FormatExprValue(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value,
const FormatExprValueOptions& options,
bool suppress_type_printing,
OutputKey output_key) {
const Type* type = value.type();
if (!type) {
OutputKeyComplete(output_key, ErrStringToOutput("no type"));
return;
}
// First output the type if required.
if (options.verbosity == Verbosity::kAllTypes && !suppress_type_printing) {
AppendToOutputKey(
output_key,
OutputBuffer(Syntax::kComment,
fxl::StringPrintf("(%s) ", type->GetFullName().c_str())));
}
// Trim "const", "volatile", etc. for the type checking below.
type = type->GetConcreteType();
// Structs and classes.
if (const Collection* coll = type->AsCollection()) {
FormatCollection(data_provider, coll, value, options, output_key);
return;
}
// Arrays and strings.
if (TryFormatArrayOrString(data_provider, type, value, options, output_key))
return;
// References (these require asynchronous calls to format so can't be in the
// "modified types" block below in the synchronous section).
if (type->tag() == Symbol::kTagReferenceType ||
type->tag() == Symbol::kTagRvalueReferenceType) {
FormatReference(data_provider, value, options, output_key);
return;
}
// Everything below here is formatted synchronously. Do not early return
// since the bottom of this function sets the output and marks the output key
// resolved.
OutputBuffer out;
if (const ModifiedType* modified_type = type->AsModifiedType()) {
// Modified types (references were handled above).
switch (modified_type->tag()) {
case Symbol::kTagPointerType:
// Function pointers need special handling.
if (IsPointerToFunction(modified_type))
FormatFunctionPointer(value, options, &out);
else
FormatPointer(value, options, &out);
break;
default:
out.Append(Syntax::kComment,
fxl::StringPrintf(
"<Unhandled type modifier 0x%x, please file a bug.>",
static_cast<unsigned>(modified_type->tag())));
break;
}
} else if (const MemberPtr* member_ptr = value.type()->AsMemberPtr()) {
// Pointers to class/struct members.
FormatMemberPtr(value, member_ptr, options, &out);
} else if (const FunctionType* func = value.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(value, options, &out);
} else if (const Enumeration* enum_type = value.type()->AsEnumeration()) {
// Enumerations.
FormatEnum(value, enum_type, options, &out);
} else if (IsNumericBaseType(value.GetBaseType())) {
// Numeric types.
FormatNumeric(value, options, &out);
} else {
// Non-numeric base types.
switch (value.GetBaseType()) {
case BaseType::kBaseTypeAddress: {
// Always print addresses as unsigned hex.
FormatExprValueOptions overridden(options);
overridden.num_format = NumFormat::kHex;
FormatUnsignedInt(value, options, &out);
break;
}
default:
if (value.data().empty()) {
out.Append(ErrStringToOutput("no data"));
} else {
// For now, print a hex dump for everything else.
std::string result;
for (size_t i = 0; i < value.data().size(); i++) {
if (i > 0)
result.push_back(' ');
result.append(fxl::StringPrintf("0x%02x", value.data()[i]));
}
out.Append(std::move(result));
}
}
}
OutputKeyComplete(output_key, std::move(out));
}
void FormatValue::FormatExprValue(fxl::RefPtr<SymbolDataProvider> data_provider,
const Err& err, const ExprValue& value,
const FormatExprValueOptions& options,
bool suppress_type_printing,
OutputKey output_key) {
if (err.has_error()) {
// If the future we probably want to rewrite "optimized out" errors to
// something shorter. The evaluator makes a longer message suitable for
// printing to the console in response to a command, but is too long
// for printing as as the value in "foo = bar". For now, though, the longer
// messages can be helpful for debugging. It would be:
// if (err.type() == ErrType::kOptimizedOut)
// out->Append(ErrStringToOutput("optimized out"));
OutputKeyComplete(output_key, ErrToOutput(err));
} else {
FormatExprValue(std::move(data_provider), value, options,
suppress_type_printing, output_key);
}
}
// GDB format:
// {<BaseClass> = { ... }, a = 1, b = 2, sub_struct = {foo = 1, bar = 2}}
//
// LLDB format:
// {
// BaseClass = { ... }
// a = 1
// b = 2
// sub_struct = {
// foo = 1
// bar = 2
// }
// }
void FormatValue::FormatCollection(
fxl::RefPtr<SymbolDataProvider> data_provider, const Collection* coll,
const ExprValue& value, const FormatExprValueOptions& options,
OutputKey output_key) {
AppendToOutputKey(output_key, OutputBuffer("{"));
// True after printing the first item.
bool needs_comma = false;
// 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.
if (!VisitClassHierarchy(from, [](const Collection* cur, uint32_t) -> bool {
return !cur->data_members().empty();
}))
continue;
if (needs_comma)
AppendToOutputKey(output_key, OutputBuffer(", "));
else
needs_comma = true;
// Print "ClassName = "
std::string base_name = from->GetFullName();
if (options.verbosity == Verbosity::kMinimal)
base_name = GetElidedTypeName(base_name);
// Pass "true" to suppress type printing since we just printed the type.
ExprValue from_value;
Err err = ResolveInherited(value, inherited, &from_value);
FormatExprValue(
data_provider, err, from_value, options, true,
AsyncAppend(NodeType::kBaseClass, std::move(base_name), output_key));
}
// Data members.
for (const auto& lazy_member : coll->data_members()) {
const DataMember* member = lazy_member.Get()->AsDataMember();
if (!member)
continue;
if (needs_comma)
AppendToOutputKey(output_key, OutputBuffer(", "));
else
needs_comma = true;
ExprValue member_value;
Err err = ResolveMember(value, member, &member_value);
// Type info if requested.
if (options.verbosity == Verbosity::kAllTypes && member_value.type()) {
AppendToOutputKey(
output_key,
OutputBuffer(
Syntax::kComment,
fxl::StringPrintf("(%s) ",
member_value.type()->GetFullName().c_str())));
}
// Force omitting the type info since we already handled that before
// showing the name. This is because:
// (int) b = 12
// looks better than:
// b = (int) 12
FormatExprValue(data_provider, err, member_value, options, true,
AsyncAppend(NodeType::kVariable, member->GetAssignedName(),
output_key));
}
AppendToOutputKey(output_key, OutputBuffer("}"));
OutputKeyComplete(output_key);
}
void FormatValue::FormatString(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value,
const Type* array_value_type,
int known_elt_count,
const FormatExprValueOptions& options,
OutputKey output_key) {}
bool FormatValue::TryFormatArrayOrString(
fxl::RefPtr<SymbolDataProvider> data_provider, const Type* type,
const ExprValue& value, const FormatExprValueOptions& options,
OutputKey output_key) {
FXL_DCHECK(type == type->GetConcreteType());
if (type->tag() == Symbol::kTagPointerType) {
// Any pointer type (we only char about char*).
const ModifiedType* modified = type->AsModifiedType();
if (!modified)
return false;
if (IsCharacterType(modified->modified())) {
FormatCharPointer(data_provider, type, value, options, output_key);
return true;
}
return false; // All other pointer types are unhandled.
} else if (type->tag() == Symbol::kTagArrayType) {
// Any array type with a known size (we care about both).
const ArrayType* array = type->AsArrayType();
if (!array)
return false;
if (IsCharacterType(array->value_type())) {
size_t length = array->num_elts();
bool truncated = false;
if (length > options.max_array_size) {
length = options.max_array_size;
truncated = true;
}
FormatCharArray(value.data().data(), length, truncated, output_key);
} else {
FormatArray(data_provider, value, array->num_elts(), options, output_key);
}
return true;
}
return false;
}
void FormatValue::FormatCharPointer(
fxl::RefPtr<SymbolDataProvider> data_provider, const Type* type,
const ExprValue& value, const FormatExprValueOptions& options,
OutputKey output_key) {
if (value.data().size() != kTargetPointerSize) {
OutputKeyComplete(output_key, ErrStringToOutput("Bad pointer data."));
return;
}
TargetPointer address = value.GetAs<TargetPointer>();
if (!address) {
// Special-case null pointers to just print a null address.
OutputKeyComplete(output_key, OutputBuffer("0x0"));
return;
}
// Speculatively request the max string size.
uint32_t bytes_to_fetch = options.max_array_size;
if (bytes_to_fetch == 0) {
// No array data should be fetched. Indicate that the result was truncated.
OutputKeyComplete(output_key, OutputBuffer("\"\"..."));
return;
}
data_provider->GetMemoryAsync(address, bytes_to_fetch, [
address, bytes_to_fetch, weak_this = weak_factory_.GetWeakPtr(), output_key
](const Err& err, std::vector<uint8_t> data) {
if (!weak_this)
return;
if (data.empty()) {
// Should not have requested 0 size, so it if came back empty the
// pointer was invalid.
weak_this->OutputKeyComplete(output_key, InvalidPointerToOutput(address));
return;
}
// Report as truncated because if the string goes to the end of this array
// it will be. FormatCharArray 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.
bool truncated = data.size() == bytes_to_fetch;
weak_this->FormatCharArray(&data[0], data.size(), truncated, output_key);
});
}
void FormatValue::FormatCharArray(const uint8_t* data, size_t length,
bool truncated, OutputKey output_key) {
// 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;
std::string result("\"");
for (size_t i = 0; i < output_len; i++)
AppendEscapedChar(data[i], &result);
result.push_back('"');
// Add an indication if the string was truncated to the max size.
if (truncated)
result += "...";
OutputKeyComplete(output_key, OutputBuffer(result));
}
void FormatValue::FormatArray(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value, int elt_count,
const FormatExprValueOptions& options,
OutputKey output_key) {
// Arrays should have known non-zero sizes.
FXL_DCHECK(elt_count >= 0);
int print_count =
std::min(static_cast<int>(options.max_array_size), elt_count);
std::vector<ExprValue> items;
Err err = ResolveArray(value, 0, print_count, &items);
if (err.has_error()) {
OutputKeyComplete(output_key, ErrToOutput(err));
return;
}
AppendToOutputKey(output_key, OutputBuffer("{"));
for (size_t i = 0; i < items.size(); i++) {
if (i > 0)
AppendToOutputKey(output_key, OutputBuffer(", "));
// Avoid forcing type info for every array value. This will be encoded in
// the main array type.
FormatExprValue(data_provider, items[i], options, true,
AsyncAppend(output_key));
}
AppendToOutputKey(
output_key,
OutputBuffer(static_cast<uint32_t>(elt_count) > items.size() ? ", ...}"
: "}"));
// Now we can mark the root output key as complete. The children added above
// may or may not have completed synchronously.
OutputKeyComplete(output_key);
}
void FormatValue::FormatNumeric(const ExprValue& value,
const FormatExprValueOptions& options,
OutputBuffer* out) {
if (options.num_format != NumFormat::kDefault) {
// Overridden format option.
switch (options.num_format) {
case NumFormat::kUnsigned:
case NumFormat::kHex:
FormatUnsignedInt(value, options, out);
break;
case NumFormat::kSigned:
FormatSignedInt(value, out);
break;
case NumFormat::kChar:
FormatChar(value, out);
break;
case NumFormat::kDefault:
// Prevent warning for unused enum type.
break;
}
} else {
// Default handling for base types based on the number.
switch (value.GetBaseType()) {
case BaseType::kBaseTypeBoolean:
FormatBoolean(value, out);
break;
case BaseType::kBaseTypeFloat:
FormatFloat(value, out);
break;
case BaseType::kBaseTypeSigned:
FormatSignedInt(value, out);
break;
case BaseType::kBaseTypeUnsigned:
FormatUnsignedInt(value, options, out);
break;
case BaseType::kBaseTypeSignedChar:
case BaseType::kBaseTypeUnsignedChar:
case BaseType::kBaseTypeUTF:
FormatChar(value, out);
break;
}
}
}
void FormatValue::FormatBoolean(const ExprValue& value, OutputBuffer* out) {
uint64_t int_val = 0;
Err err = value.PromoteTo64(&int_val);
if (err.has_error())
out->Append(ErrToOutput(err));
else if (int_val)
out->Append("true");
else
out->Append("false");
}
void FormatValue::FormatEnum(const ExprValue& value,
const Enumeration* enum_type,
const FormatExprValueOptions& options,
OutputBuffer* out) {
// Get the value out casted to a uint64.
Err err;
uint64_t numeric_value;
if (enum_type->is_signed()) {
int64_t signed_value;
err = value.PromoteTo64(&signed_value);
if (!err.has_error())
numeric_value = static_cast<uint64_t>(signed_value);
} else {
err = value.PromoteTo64(&numeric_value);
}
if (err.has_error()) {
out->Append(ErrToOutput(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.
out->Append(found->second);
return;
}
// Not found, fall through to numeric output.
}
// Invalid enum values of explicitly overridden numeric formatting gets
// printed as a number.
FormatExprValueOptions modified_opts = options;
if (modified_opts.num_format == NumFormat::kDefault) {
// Compute the formatting for invalid enum values when there is no numeric
// override.
modified_opts.num_format =
enum_type->is_signed() ? NumFormat::kSigned : NumFormat::kUnsigned;
}
FormatNumeric(value, modified_opts, out);
}
void FormatValue::FormatFloat(const ExprValue& value, OutputBuffer* out) {
switch (value.data().size()) {
case sizeof(float):
out->Append(fxl::StringPrintf("%g", value.GetAs<float>()));
break;
case sizeof(double):
out->Append(fxl::StringPrintf("%g", value.GetAs<double>()));
break;
default:
out->Append(ErrStringToOutput(fxl::StringPrintf(
"unknown float of size %d", static_cast<int>(value.data().size()))));
break;
}
}
void FormatValue::FormatSignedInt(const ExprValue& value, OutputBuffer* out) {
int64_t int_val = 0;
Err err = value.PromoteTo64(&int_val);
if (err.has_error())
out->Append(ErrToOutput(err));
else
out->Append(fxl::StringPrintf("%" PRId64, int_val));
}
void FormatValue::FormatUnsignedInt(const ExprValue& value,
const FormatExprValueOptions& options,
OutputBuffer* out) {
// This formatter handles unsigned and hex output.
uint64_t int_val = 0;
Err err = value.PromoteTo64(&int_val);
if (err.has_error())
out->Append(ErrToOutput(err));
else if (options.num_format == NumFormat::kHex)
out->Append(fxl::StringPrintf("0x%" PRIx64, int_val));
else
out->Append(fxl::StringPrintf("%" PRIu64, int_val));
}
void FormatValue::FormatChar(const ExprValue& value, OutputBuffer* out) {
// Just take the first byte for all char.
if (value.data().empty()) {
out->Append(ErrStringToOutput("invalid char type"));
return;
}
std::string str;
str.push_back('\'');
AppendEscapedChar(value.data()[0], &str);
str.push_back('\'');
out->Append(str);
}
void FormatValue::FormatPointer(const ExprValue& value,
const FormatExprValueOptions& options,
OutputBuffer* out) {
// 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.
// Type info. The caller will have handled the case when type printing was
// forced always on, so we need only ahndle the lower verbosities.
if (options.verbosity == Verbosity::kMinimal) {
out->Append(Syntax::kComment, "(*) ");
} else if (options.verbosity == Verbosity::kMedium) {
out->Append(
Syntax::kComment,
fxl::StringPrintf("(%s) ", value.type()->GetFullName().c_str()));
}
Err err = value.EnsureSizeIs(kTargetPointerSize);
if (err.has_error())
out->Append(ErrToOutput(err));
else
out->Append(fxl::StringPrintf("0x%" PRIx64, value.GetAs<TargetPointer>()));
}
void FormatValue::FormatReference(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value,
const FormatExprValueOptions& options,
OutputKey output_key) {
EnsureResolveReference(data_provider, value, [
weak_this = weak_factory_.GetWeakPtr(), data_provider,
original_value = value, options, output_key
](const Err& err, ExprValue resolved_value) {
if (!weak_this)
return;
OutputBuffer out;
// First show the type if desired. As with pointers, the calling code will
// have printed the type for the "all types" case.
if (options.verbosity == Verbosity::kMedium) {
out.Append(
Syntax::kComment,
fxl::StringPrintf(
"(%s) ",
GetElidedTypeName(original_value.type()->GetFullName()).c_str()));
}
// Followed by the address (only in non-minimal modes).
if (options.verbosity != Verbosity::kMinimal) {
TargetPointer address = 0;
Err addr_err = original_value.PromoteTo64(&address);
if (addr_err.has_error()) {
// Invalid data in the reference.
out.Append(ErrToOutput(addr_err));
weak_this->OutputKeyComplete(output_key, std::move(out));
return;
}
out.Append(Syntax::kComment,
fxl::StringPrintf("0x%" PRIx64 " = ", address));
}
// Follow with the resolved value.
if (err.has_error()) {
out.Append(ErrToOutput(err));
weak_this->OutputKeyComplete(output_key, std::move(out));
} else {
// FormatExprValue will mark the output key complete when it's done
// formatting. Pass true for suppress_type_printing since the type of
// the reference was printed above.
weak_this->AppendToOutputKey(output_key, std::move(out));
weak_this->FormatExprValue(data_provider, resolved_value, options, true,
output_key);
}
});
}
void FormatValue::FormatFunctionPointer(const ExprValue& value,
const FormatExprValueOptions& options,
OutputBuffer* out) {
// Unlike pointers, we don't print the type for function pointers. These
// are usually very long and not very informative. If explicitly requested,
// the types will be printed out by the calling function.
Err err = value.EnsureSizeIs(kTargetPointerSize);
if (err.has_error()) {
out->Append(ErrToOutput(err));
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(value, options, out);
return;
}
TargetPointer address = value.GetAs<TargetPointer>();
if (address == 0) {
// Special-case null pointers. Don't bother trying to decode the address
// or show a type.
out->Append("0x0");
return;
}
// Try to symbolize the function being pointed to.
Location loc = process_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.
out->Append(fxl::StringPrintf("0x%" PRIx64, address));
} else {
out->Append("&" + function_name);
}
}
void FormatValue::FormatMemberPtr(const ExprValue& value, const MemberPtr* type,
const FormatExprValueOptions& options,
OutputBuffer* out) {
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) {
out->Append("<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(value, options, out);
} else {
// Pointers to everything else can be handled like normal pointers.
FormatPointer(value, options, out);
}
}
FormatValue::OutputKey FormatValue::GetRootOutputKey() {
return reinterpret_cast<intptr_t>(&root_);
}
void FormatValue::AppendToOutputKey(OutputKey output_key, OutputBuffer buffer) {
// See OutputKey definition in the header for how it works.
OutputNode* parent_node = reinterpret_cast<OutputNode*>(output_key);
auto new_node = std::make_unique<OutputNode>();
new_node->buffer = std::move(buffer);
parent_node->child.push_back(std::move(new_node));
}
FormatValue::OutputKey FormatValue::AsyncAppend(OutputKey parent) {
return AsyncAppend(NodeType::kGeneric, std::string(), parent);
}
FormatValue::OutputKey FormatValue::AsyncAppend(NodeType type, std::string name,
OutputKey parent) {
OutputNode* parent_node = reinterpret_cast<OutputNode*>(parent);
auto new_node = std::make_unique<OutputNode>();
new_node->type = type;
new_node->name = std::move(name);
new_node->pending = true;
pending_resolution_++;
// The OutputKey is secretly the pointer to the node (as an intptr_t). See
// the OutputKey definition in the header for more.
OutputKey result = reinterpret_cast<OutputKey>(new_node.get());
parent_node->child.push_back(std::move(new_node));
return result;
}
void FormatValue::OutputKeyComplete(OutputKey key) {
// See OutputKey definition in the header for how it works.
OutputNode* dest = reinterpret_cast<OutputNode*>(key);
// Asnyc sets should always be pending.
FXL_DCHECK(dest->pending);
dest->pending = false;
// Decrement the pending count.
FXL_DCHECK(pending_resolution_ > 0);
pending_resolution_--;
CheckPendingResolution();
}
void FormatValue::OutputKeyComplete(OutputKey key, OutputBuffer contents) {
AppendToOutputKey(key, std::move(contents));
OutputKeyComplete(key);
}
void FormatValue::CheckPendingResolution() {
// Pending resolution could be zero before Complete() was called to set the
// callback (the format result was synchronous) in which case ignore.
if (pending_resolution_ != 0 || !complete_callback_)
return;
OutputBuffer out;
RecursiveCollectOutput(&root_, &out);
// The callback may be holding a ref to us, so we need to clear it
// explicitly. But it could indirectly cause us to be deleted so need to
// not dereference |this| after running it. This temporary will do things
// in the order we need.
auto cb = std::move(complete_callback_);
cb(std::move(out));
// WARNING: |this| may be deleted!
}
void FormatValue::RecursiveCollectOutput(OutputNode* node, OutputBuffer* out) {
// Everything should be resolved when producing output.
FXL_DCHECK(!node->pending);
if (!node->name.empty()) {
Syntax syntax;
switch (node->type) {
case NodeType::kGeneric:
syntax = Syntax::kNormal;
break;
case NodeType::kVariable:
syntax = Syntax::kVariable;
break;
case NodeType::kBaseClass:
syntax = Syntax::kComment;
break;
}
out->Append(syntax, std::move(node->name));
out->Append(" = ");
}
// Each node should either have children or a buffer, but not both.
if (node->child.empty()) {
out->Append(std::move(node->buffer));
} else {
for (auto& child : node->child)
RecursiveCollectOutput(child.get(), out);
}
}
} // namespace zxdb