| // 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. |
| |
| #ifndef SRC_DEVELOPER_DEBUG_ZXDB_EXPR_PRETTY_TYPE_H_ |
| #define SRC_DEVELOPER_DEBUG_ZXDB_EXPR_PRETTY_TYPE_H_ |
| |
| #include <initializer_list> |
| #include <map> |
| |
| #include "lib/fit/defer.h" |
| #include "lib/fit/function.h" |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/expr/eval_callback.h" |
| #include "src/developer/debug/zxdb/expr/eval_context.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| |
| namespace zxdb { |
| |
| class FormatNode; |
| struct FormatOptions; |
| |
| // Base class for a type we can do "pretty" things with. |
| // |
| // At the most basic level, a PrettyType provides alternate formatting which can properly |
| // encapsulate more complex data structures like vectors and arrays. |
| // |
| // We also provide expression evaluation support for these types, allowing them to implement |
| // getters, pointer derefercing, and array access. This allows the debugger to emulate common |
| // queries on types that may not have an otherwise easy access point. For example, users will often |
| // want to query the size, capacity, or a single indexed element of a vector. This is difficult to |
| // do using just the struct information, and we do not allow actually executing code to run the real |
| // implementations of these functions. |
| class PrettyType { |
| public: |
| using EvalFunction = |
| fit::function<void(fxl::RefPtr<EvalContext>, const ExprValue& object_value, EvalCallback)>; |
| using EvalArrayFunction = fit::function<void( |
| fxl::RefPtr<EvalContext>, const ExprValue& object_value, int64_t index, EvalCallback)>; |
| |
| PrettyType() = default; |
| |
| // This variant takes a list of getter names and expressions for this type. For example, a vector |
| // might pass something like |
| // { |
| // {"size", "end_ - begin_"}, |
| // {"capacity", "capacity_" } |
| // } |
| // to provide size() and capacity() getters. |
| explicit PrettyType(std::initializer_list<std::pair<std::string, std::string>> getters); |
| |
| virtual ~PrettyType() = default; |
| |
| // Adds a getter expression to the lookup table returned by GetGetter(). |
| void AddGetterExpression(const std::string& getter_name, const std::string& expression); |
| |
| // Fills the given FormatNode. Upon completion, issues the given deferred_callback. If the format |
| // node is filled asynchronously the implementation should take a weak pointer to it since the |
| // lifetime is not guaranteed. |
| virtual void Format(FormatNode* node, const FormatOptions& options, |
| fxl::RefPtr<EvalContext> context, fit::deferred_callback cb) = 0; |
| |
| // Returns a function which can be evaluated to execute a getter on an object of this type. |
| // If there is no matching getter, a null fit::function is returned. |
| // |
| // (Implementation note: this design is so the caller can check if there is a getter and then |
| // execute it with a callback, which is how most nodes want to run.) |
| virtual EvalFunction GetGetter(const std::string& getter_name) const; |
| |
| // Returns a function which can be evaluated to execute a unary "*" dereference operator on an |
| // object of the given type. |
| // |
| // This will also be used for "operator->" which is implemented as a dereference followed by |
| // a ".". |
| // |
| // This is used for smart-pointer-like classes without forcing the user to look into the guts of a |
| // smart pointer. Returns an empty function if there is no member access operator. |
| virtual EvalFunction GetDereferencer() const { return EvalFunction(); } |
| |
| // Returns a function which can be executed to perform an array access. This allows the pretty |
| // printer to implement "operator[]" on a type. This is important for implementing wrappers around |
| // vector types. |
| virtual EvalArrayFunction GetArrayAccess() const { return EvalArrayFunction(); } |
| |
| protected: |
| // Evaluates the given expression in the context of the given object. The object's members will |
| // be injected into the active scope. |
| static void EvalExpressionOn(const fxl::RefPtr<EvalContext>& context, const ExprValue& object, |
| const std::string& expression, EvalCallback cb); |
| |
| private: |
| // Registered getter functions and the expressions they map to. |
| std::map<std::string, std::string> getters_; |
| }; |
| |
| class PrettyArray : public PrettyType { |
| public: |
| PrettyArray(const std::string& ptr_expr, const std::string& size_expr, |
| std::initializer_list<std::pair<std::string, std::string>> getters = {}) |
| : PrettyType(getters), ptr_expr_(ptr_expr), size_expr_(size_expr) {} |
| |
| void Format(FormatNode* node, const FormatOptions& options, fxl::RefPtr<EvalContext> context, |
| fit::deferred_callback cb) override; |
| EvalArrayFunction GetArrayAccess() const override; |
| |
| private: |
| const std::string ptr_expr_; // Expression to compute array start pointer. |
| const std::string size_expr_; // Expression to compute array size. |
| }; |
| |
| // For pretty-printing character strings that live on the heap. |
| // |
| // This gets a little more complicated for strings that live in an array inline in some type |
| // because theoretically it could (but normally can't) be in a temporary we can't take the address |
| // of. Even if we could do that, it would require a fetch of memory from the target that we already |
| // have locally. So this class is limited to the fetching from the heap case. |
| class PrettyHeapString : public PrettyType { |
| public: |
| PrettyHeapString(const std::string& ptr_expr, const std::string& size_expr, |
| std::initializer_list<std::pair<std::string, std::string>> getters = {}) |
| : PrettyType(getters), ptr_expr_(ptr_expr), size_expr_(size_expr) {} |
| |
| void Format(FormatNode* node, const FormatOptions& options, fxl::RefPtr<EvalContext> context, |
| fit::deferred_callback cb) override; |
| EvalArrayFunction GetArrayAccess() const override; |
| |
| private: |
| const std::string ptr_expr_; |
| const std::string size_expr_; |
| }; |
| |
| // For pretty-printing smart pointers. |
| // |
| // This has an expression that evaluates to a single pointer. This pointer is the result of the |
| // operation and the object will be formatted like a bare pointer using that value. |
| class PrettyPointer : public PrettyType { |
| public: |
| PrettyPointer(const std::string& expr, |
| std::initializer_list<std::pair<std::string, std::string>> getters = {}) |
| : PrettyType(std::move(getters)), expr_(expr) {} |
| |
| void Format(FormatNode* node, const FormatOptions& options, fxl::RefPtr<EvalContext> context, |
| fit::deferred_callback cb) override; |
| EvalFunction GetDereferencer() const override; |
| |
| private: |
| const std::string expr_; |
| }; |
| |
| // Implements pretty-printing for "std::optional" and similar classes that can have a value or not. |
| class PrettyOptional : public PrettyType { |
| public: |
| // Evaluates the |is_engaged_expr|. If engaged (nonempty), show the result of evaluating the |
| // |value_expr| which retrieves the value. If invalid the description of this item will be |
| // |name_when_disengaged|. |
| // |
| // The is_engaged_expr should evaluate to a boolean or integer that's either zero or nonzero. |
| PrettyOptional(const std::string& simple_type_name, const std::string& is_engaged_expr, |
| const std::string& value_expr, const std::string& name_when_disengaged, |
| std::initializer_list<std::pair<std::string, std::string>> getters) |
| : PrettyType(std::move(getters)), |
| simple_type_name_(simple_type_name), |
| is_engaged_expr_(is_engaged_expr), |
| value_expr_(value_expr), |
| name_when_disengaged_(name_when_disengaged) {} |
| |
| void Format(FormatNode* node, const FormatOptions& options, fxl::RefPtr<EvalContext> context, |
| fit::deferred_callback cb) override; |
| EvalFunction GetDereferencer() const override; |
| |
| private: |
| // Executes the callback for the given optional struct. This takes the expression and executes the |
| // callback which can be an error, is_disengaged, or have a value. |
| static void EvalOptional(fxl::RefPtr<EvalContext>& context, ExprValue object, |
| const std::string& is_engaged_expr, const std::string& value_expr, |
| fit::callback<void(ErrOrValue, bool is_disengaged)> cb); |
| |
| const std::string simple_type_name_; |
| const std::string is_engaged_expr_; |
| const std::string value_expr_; |
| const std::string name_when_disengaged_; |
| }; |
| |
| // Represents a simplified structure with a list of members. This is used to map a complicated |
| // struct (perhaps with non-normally-relevant members or inheritance) to a simpler presentation. |
| class PrettyStruct : public PrettyType { |
| public: |
| // Takes a list of struct member names and the expressions that evaluate them. |
| PrettyStruct(std::initializer_list<std::pair<std::string, std::string>> members); |
| |
| void Format(FormatNode* node, const FormatOptions& options, fxl::RefPtr<EvalContext> context, |
| fit::deferred_callback cb) override; |
| |
| private: |
| std::vector<std::pair<std::string, std::string>> members_; |
| }; |
| |
| } // namespace zxdb |
| |
| #endif // SRC_DEVELOPER_DEBUG_ZXDB_EXPR_PRETTY_TYPE_H_ |