| // Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/developer/debug/zxdb/expr/pretty_std_string.h" | |
| #include "src/developer/debug/zxdb/expr/format.h" | |
| #include "src/developer/debug/zxdb/expr/format_node.h" | |
| #include "src/developer/debug/zxdb/expr/format_options.h" | |
| #include "src/developer/debug/zxdb/symbols/base_type.h" | |
| #include "src/developer/debug/zxdb/symbols/modified_type.h" | |
| #include "src/developer/debug/zxdb/symbols/symbol_data_provider.h" | |
| namespace zxdb { | |
| namespace { | |
| // A hardcoded pretty-printer for our std::string implementation. | |
| // | |
| // Long-term, we'll want a better pretty-printing system that's more extensible and versionable | |
| // with our C++ library. This is a first step to designing such a system. | |
| // | |
| // In libc++ std::string is an "extern template" which means that the char specialization of | |
| // basic_string is in the shared library. Without symbols for libc++, there is no definition for | |
| // std::string. | |
| // | |
| // As of this writing our libc++ doesn't have symbols, and it's also nice to allow people to | |
| // print strings in their own program without all of the lib++ symbols (other containers don't | |
| // require this so it can be surprising). | |
| // | |
| // As a result, this pretty-printer is designed to work with no symbol information, and getting | |
| // a value with no size (the expression evaluator won't know what size to make in many cases). | |
| // This complicates it considerably, but std::string is likely the only class that will need such | |
| // handling. | |
| // | |
| // THE DEFINITION | |
| // -------------- | |
| // | |
| // Our libc++'s std::string implementation has two modes, a "short" mode where the string is stored | |
| // inline in the string object, and a "long" mode where it stores a pointer to a heap-allocated | |
| // buffer. These modes are differentiated with a bit on the last byte of the storage. | |
| // | |
| // class basic_string { | |
| // // For little-endian: | |
| // static const size_type __short_mask = 0x80; | |
| // static const size_type __long_mask = ~(size_type(~0) >> 1); // High bit set. | |
| // | |
| // bool is_long() const {return __r_.__s.__size_ & __short_mask; } | |
| // | |
| // struct __rep { | |
| // // Long is used when "__s.__size_ & __short_mask" is true. | |
| // union { | |
| // struct __long { | |
| // value_type* __data_; | |
| // size_t __size_; | |
| // size_t __cap_; // & with __long_mask to get. | |
| // } __l; | |
| // | |
| // struct __short { | |
| // char value_type[23] | |
| // // padding of sizeof(char) - 1 | |
| // struct { | |
| // unsigned char __size_; | |
| // }; | |
| // } __s; | |
| // | |
| // __raw __r; // Can ignore, used only for rapidly copying the representation. | |
| // }; | |
| // }; | |
| // | |
| // // actually "__compressed_pair<__rep, allocator> __r_" but effectively: | |
| // compressed_pair __r_; | |
| // }; | |
| constexpr uint32_t kStdStringSize = 24; | |
| // Offset from beginning of the object to "__short.__size_" (last byte). | |
| constexpr size_t kShortSizeOffset = 23; | |
| // Bit that indicates the "short" representation. | |
| constexpr uint64_t kShortMask = 0x80; | |
| // Offsets within the data for the "long" representation. | |
| constexpr uint64_t kLongPtrOffset = 0; | |
| constexpr uint64_t kLongSizeOffset = 8; | |
| constexpr uint64_t kLongCapacityOffset = 16; | |
| fxl::RefPtr<BaseType> GetStdStringCharType() { | |
| return fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSignedChar, 1, "char"); | |
| } | |
| fxl::RefPtr<BaseType> GetSizeTType() { | |
| return fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsigned, 8, "size_t"); | |
| } | |
| // Returns true if this std::string uses the inline representation. It's assumed the data has | |
| // al;ready been validated as being the correct length. | |
| bool IsInlineString(const std::vector<uint8_t>& mem) { | |
| FX_DCHECK(mem.size() == kStdStringSize); | |
| return !(mem[kShortSizeOffset] & kShortMask); | |
| } | |
| // Fills in the data pointer for the given std::string. | |
| Err GetStringPtr(const ExprValue& value, uint64_t* ptr) { | |
| if (value.data().size() != kStdStringSize) | |
| return Err("Invalid std::string data."); | |
| if (IsInlineString(value.data())) { | |
| // The address is just the beginning of the string. | |
| if (value.source().type() != ExprValueSource::Type::kMemory || value.source().address() == 0) | |
| return Err("Can't get string pointer to a temporary."); | |
| *ptr = value.source().address(); | |
| } else { | |
| memcpy(ptr, &value.data()[kLongPtrOffset], sizeof(uint64_t)); | |
| } | |
| return Err(); | |
| } | |
| Err GetStringSize(const std::vector<uint8_t>& mem, uint64_t* size) { | |
| if (mem.size() != kStdStringSize) | |
| return Err("Invalid std::string data."); | |
| if (IsInlineString(mem)) | |
| *size = mem[kShortSizeOffset]; | |
| else | |
| memcpy(size, &mem[kLongSizeOffset], sizeof(uint64_t)); | |
| return Err(); | |
| } | |
| Err GetStringCapacity(const std::vector<uint8_t>& mem, uint64_t* capacity) { | |
| if (mem.size() != kStdStringSize) | |
| return Err("Invalid std::string data."); | |
| if (IsInlineString(mem)) { | |
| *capacity = kShortSizeOffset - 1; // Inline size is stuff before the short size minus null. | |
| } else { | |
| memcpy(capacity, &mem[kLongCapacityOffset], sizeof(uint64_t)); | |
| // Mask off the high bit which is the "large" flag. | |
| *capacity &= 0x7fffffffffffffff; | |
| } | |
| return Err(); | |
| } | |
| void FormatStdStringMemory(const std::vector<uint8_t>& mem, FormatNode* node, | |
| const FormatOptions& options, const fxl::RefPtr<EvalContext>& context, | |
| fit::deferred_callback cb) { | |
| node->set_type("std::string"); | |
| if (mem.size() != kStdStringSize) | |
| return node->SetDescribedError(Err("Invalid.")); | |
| auto char_type = GetStdStringCharType(); | |
| uint64_t string_size = 0; | |
| if (Err err = GetStringSize(mem, &string_size); err.has_error()) | |
| return node->SetDescribedError(err); | |
| if (IsInlineString(mem)) { | |
| FormatCharArrayNode(node, char_type, &mem[0], string_size, true, false); | |
| } else { | |
| // Long representation (with pointer). | |
| uint64_t data_ptr; | |
| memcpy(&data_ptr, &mem[kLongPtrOffset], sizeof(uint64_t)); | |
| FormatCharPointerNode(node, data_ptr, char_type.get(), string_size, options, context, | |
| std::move(cb)); | |
| } | |
| } | |
| // Normally when we have a std::string we won't have the data because the definition is | |
| // missing. But the "source" will usually be set and we can go fetch the right amount of data. | |
| // This function calls the callback with a populated ExprValue if it can be made to have the correct | |
| // size. | |
| void EnsureStdStringMemory(const fxl::RefPtr<EvalContext>& context, const ExprValue& value, | |
| EvalCallback cb) { | |
| if (value.data().size() != 0) { | |
| if (value.data().size() == kStdStringSize) | |
| return cb(value); | |
| return cb(Err("Invalid std::string type size.")); | |
| } | |
| // Don't have the data, see if we can fetch it. | |
| if (value.source().type() != ExprValueSource::Type::kMemory || value.source().address() == 0) | |
| return cb(Err("Can't handle a temporary std::string.")); | |
| context->GetDataProvider()->GetMemoryAsync( | |
| value.source().address(), kStdStringSize, | |
| [value, cb = std::move(cb)](const Err& err, std::vector<uint8_t> data) mutable { | |
| if (err.has_error()) | |
| cb(err); | |
| else if (data.size() != kStdStringSize) | |
| cb(Err("Invalid memory.")); | |
| else | |
| cb(ExprValue(value.type_ref(), std::move(data), value.source())); | |
| }); | |
| } | |
| // Getters all need to do the same thing: ensure memory, error check, and then run on the result. | |
| // This returns a callback that does that stuff, with the given "getter" implementation taking | |
| // a complete string of a known correct size. | |
| PrettyStdString::EvalFunction MakeGetter(fit::function<void(ExprValue, EvalCallback)> getter) { | |
| return [getter = std::move(getter)](const fxl::RefPtr<EvalContext>& context, | |
| const ExprValue& object_value, EvalCallback cb) mutable { | |
| EnsureStdStringMemory( | |
| context, object_value, | |
| [context, cb = std::move(cb), getter = std::move(getter)](ErrOrValue value) mutable { | |
| if (value.has_error()) | |
| return cb(value); | |
| getter(value.value(), std::move(cb)); | |
| }); | |
| }; | |
| } | |
| } // namespace | |
| void PrettyStdString::Format(FormatNode* node, const FormatOptions& options, | |
| const fxl::RefPtr<EvalContext>& context, fit::deferred_callback cb) { | |
| EnsureStdStringMemory(context, node->value(), | |
| [weak_node = node->GetWeakPtr(), options, context, | |
| cb = std::move(cb)](ErrOrValue value) mutable { | |
| if (!weak_node) | |
| return; | |
| if (value.has_error()) { | |
| weak_node->set_err(value.err()); | |
| weak_node->set_state(FormatNode::kDescribed); | |
| } else { | |
| FormatStdStringMemory(value.value().data(), weak_node.get(), options, | |
| context, std::move(cb)); | |
| } | |
| }); | |
| } | |
| PrettyStdString::EvalFunction PrettyStdString::GetGetter(const std::string& getter_name) const { | |
| if (getter_name == "data" || getter_name == "c_str") { | |
| return MakeGetter([](ExprValue value, EvalCallback cb) { | |
| uint64_t ptr = 0; | |
| if (Err err = GetStringPtr(value, &ptr); err.has_error()) | |
| return cb(err); | |
| auto char_ptr = | |
| fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, GetStdStringCharType()); | |
| cb(ExprValue(ptr, char_ptr)); | |
| }); | |
| } | |
| if (getter_name == "size" || getter_name == "length") { | |
| return MakeGetter([](ExprValue value, EvalCallback cb) { | |
| uint64_t string_size = 0; | |
| if (Err err = GetStringSize(value.data(), &string_size); err.has_error()) | |
| cb(err); | |
| cb(ExprValue(string_size, GetSizeTType())); | |
| }); | |
| } | |
| if (getter_name == "capacity") { | |
| return MakeGetter([](ExprValue value, EvalCallback cb) { | |
| uint64_t cap = 0; | |
| if (Err err = GetStringCapacity(value.data(), &cap); err.has_error()) | |
| return cb(err); | |
| cb(ExprValue(cap, GetSizeTType())); | |
| }); | |
| } | |
| if (getter_name == "empty") { | |
| return MakeGetter([](ExprValue value, EvalCallback cb) { | |
| uint64_t string_size = 0; | |
| if (Err err = GetStringSize(value.data(), &string_size); err.has_error()) | |
| return cb(err); | |
| cb(ExprValue(string_size == 0)); | |
| }); | |
| } | |
| return EvalFunction(); | |
| } | |
| PrettyStdString::EvalArrayFunction PrettyStdString::GetArrayAccess() const { | |
| return [](const fxl::RefPtr<EvalContext>& context, const ExprValue& object_value, int64_t index, | |
| EvalCallback cb) { | |
| EnsureStdStringMemory( | |
| context, object_value, [context, cb = std::move(cb), index](ErrOrValue value) mutable { | |
| if (value.has_error()) | |
| return cb(value.err()); | |
| if (IsInlineString(value.value().data())) { | |
| // Use the inline data. Need to range check since we're indexing into our local | |
| // address space. | |
| if (index >= static_cast<int64_t>(kShortSizeOffset) || index < 0) | |
| return cb(Err("String index out of range.")); | |
| // Inline array starts from the beginning of the string. | |
| return cb(ExprValue(GetStdStringCharType(), {value.value().data()[index]}, | |
| value.value().source().GetOffsetInto(index))); | |
| } else { | |
| uint64_t ptr = 0; | |
| if (Err err = GetStringPtr(value.value(), &ptr); err.has_error()) | |
| return cb(err); | |
| context->GetDataProvider()->GetMemoryAsync( | |
| ptr, 1, | |
| [context, ptr, cb = std::move(cb)](const Err& err, | |
| std::vector<uint8_t> data) mutable { | |
| if (err.has_error()) | |
| return cb(err); | |
| if (data.size() == 0) | |
| return cb(Err("Invalid address 0x%" PRIx64, ptr)); | |
| cb(ExprValue(GetStdStringCharType(), {data[0]}, ExprValueSource(ptr))); | |
| }); | |
| } | |
| }); | |
| }; | |
| } | |
| } // namespace zxdb |