| // 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 |