| // 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. | |
| #ifndef SRC_DEVELOPER_DEBUG_ZXDB_EXPR_EXPR_NODE_H_ | |
| #define SRC_DEVELOPER_DEBUG_ZXDB_EXPR_EXPR_NODE_H_ | |
| #include <iosfwd> | |
| #include <memory> | |
| #include "lib/fit/function.h" | |
| #include "src/developer/debug/zxdb/expr/cast.h" | |
| #include "src/developer/debug/zxdb/expr/eval_callback.h" | |
| #include "src/developer/debug/zxdb/expr/expr_token.h" | |
| #include "src/developer/debug/zxdb/expr/expr_value.h" | |
| #include "src/developer/debug/zxdb/expr/parsed_identifier.h" | |
| #include "src/lib/fxl/memory/ref_counted.h" | |
| #include "src/lib/fxl/memory/ref_ptr.h" | |
| namespace zxdb { | |
| class Err; | |
| class EvalContext; | |
| class Type; | |
| // Node subclasses. | |
| class AddressOfExprNode; | |
| class ArrayAccessExprNode; | |
| class BinaryOpExprNode; | |
| class BlockExprNode; | |
| class CastExprNode; | |
| class ConditionExprNode; | |
| class DereferenceExprNode; | |
| class FunctionCallExprNode; | |
| class IdentifierExprNode; | |
| class LiteralExprNode; | |
| class MemberAccessExprNode; | |
| class SizeofExprNode; | |
| class TypeExprNode; | |
| class UnaryOpExprNode; | |
| // Represents one node in the abstract syntax tree. | |
| class ExprNode : public fxl::RefCountedThreadSafe<ExprNode> { | |
| public: | |
| virtual const AddressOfExprNode* AsAddressOf() const { return nullptr; } | |
| virtual const ArrayAccessExprNode* AsArrayAccess() const { return nullptr; } | |
| virtual const BinaryOpExprNode* AsBinaryOp() const { return nullptr; } | |
| virtual const BlockExprNode* AsBlock() const { return nullptr; } | |
| virtual const CastExprNode* AsCast() const { return nullptr; } | |
| virtual const ConditionExprNode* AsCondition() const { return nullptr; } | |
| virtual const DereferenceExprNode* AsDereference() const { return nullptr; } | |
| virtual const FunctionCallExprNode* AsFunctionCall() const { return nullptr; } | |
| virtual const IdentifierExprNode* AsIdentifier() const { return nullptr; } | |
| virtual const LiteralExprNode* AsLiteral() const { return nullptr; } | |
| virtual const MemberAccessExprNode* AsMemberAccess() const { return nullptr; } | |
| virtual const SizeofExprNode* AsSizeof() const { return nullptr; } | |
| virtual const TypeExprNode* AsType() const { return nullptr; } | |
| virtual const UnaryOpExprNode* AsUnaryOp() const { return nullptr; } | |
| // Evaluates the expression and calls the callback with the result. The callback may be called | |
| // reentrantly (meaning from within the callstack of Eval itself). | |
| // | |
| // Some eval operations are asynchronous because they require reading data from the remote system. | |
| // Many are not. Since we expect relatively few evals (from user typing) and that they are quite | |
| // simple (most are one value or a simple dereference), we opt for simplicity and make all evals | |
| // require a callback. | |
| // | |
| // For larger expressions this can be quite heavyweight because not only will the expression be | |
| // recursively executed, but returning the result will double the depth of the recursion (not to | |
| // mention a heavyweight lambda bind for each). | |
| // | |
| // One thing that might cause expression eval speed to be an issue is when they are automatically | |
| // executed as in a conditional breakpoint. If we start using expressions in conditional | |
| // breakpoints and find that performance is unacceptable, this should be optimized to support | |
| // evals that do not require callbacks unless necessary. | |
| // | |
| // The caller is responsible for ensuring the tree of nodes is in scope for the duration of this | |
| // call until the callback is executed. This would normally be done by having the tree be owned by | |
| // the callback itself. If this is causing memory lifetime problems, we should switch nodes to be | |
| // reference counted. | |
| // | |
| // See also EvalFollowReferences below. | |
| virtual void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const = 0; | |
| // Like "Eval" but expands all references to the values they point to. When evaluating a | |
| // subexpression this is the variant you want because without it the ExprValue in the callback | |
| // will be the reference, which just contains the address of the value you want. | |
| // | |
| // The time you wouldn't want this is when calling externally where the caller wants to know the | |
| // actual type the expression evaluated to. | |
| void EvalFollowReferences(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const; | |
| // Dumps the tree to a stream with the given indent. Used for unit testing and debugging. | |
| virtual void Print(std::ostream& out, int indent) const = 0; | |
| protected: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(ExprNode); | |
| ExprNode() = default; | |
| virtual ~ExprNode() = default; | |
| }; | |
| // Implements taking an address of n expression ("&" in C). | |
| class AddressOfExprNode : public ExprNode { | |
| public: | |
| const AddressOfExprNode* AsAddressOf() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(AddressOfExprNode); | |
| FRIEND_MAKE_REF_COUNTED(AddressOfExprNode); | |
| AddressOfExprNode(); | |
| AddressOfExprNode(fxl::RefPtr<ExprNode> expr) : expr_(std::move(expr)) {} | |
| ~AddressOfExprNode() override = default; | |
| fxl::RefPtr<ExprNode> expr_; | |
| }; | |
| // Implements an array access: foo[bar]. | |
| class ArrayAccessExprNode : public ExprNode { | |
| public: | |
| const ArrayAccessExprNode* AsArrayAccess() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(ArrayAccessExprNode); | |
| FRIEND_MAKE_REF_COUNTED(ArrayAccessExprNode); | |
| ArrayAccessExprNode(); | |
| ArrayAccessExprNode(fxl::RefPtr<ExprNode> left, fxl::RefPtr<ExprNode> inner) | |
| : left_(std::move(left)), inner_(std::move(inner)) {} | |
| ~ArrayAccessExprNode() override = default; | |
| // Converts the given value which is the result of executing the "inner" expression and converts | |
| // it to an integer if possible. | |
| static Err InnerValueToOffset(const fxl::RefPtr<EvalContext>& context, const ExprValue& inner, | |
| int64_t* offset); | |
| fxl::RefPtr<ExprNode> left_; | |
| fxl::RefPtr<ExprNode> inner_; | |
| }; | |
| // Implements all binary operators. | |
| class BinaryOpExprNode : public ExprNode { | |
| public: | |
| const BinaryOpExprNode* AsBinaryOp() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(BinaryOpExprNode); | |
| FRIEND_MAKE_REF_COUNTED(BinaryOpExprNode); | |
| BinaryOpExprNode(); | |
| BinaryOpExprNode(fxl::RefPtr<ExprNode> left, ExprToken op, fxl::RefPtr<ExprNode> right) | |
| : left_(std::move(left)), op_(std::move(op)), right_(std::move(right)) {} | |
| ~BinaryOpExprNode() override = default; | |
| fxl::RefPtr<ExprNode> left_; | |
| ExprToken op_; | |
| fxl::RefPtr<ExprNode> right_; | |
| }; | |
| class BlockExprNode : public ExprNode { | |
| public: | |
| const BlockExprNode* AsBlock() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| const std::vector<fxl::RefPtr<ExprNode>>& statements() const { return statements_; } | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(BlockExprNode); | |
| FRIEND_MAKE_REF_COUNTED(BlockExprNode); | |
| BlockExprNode(); | |
| BlockExprNode(std::vector<fxl::RefPtr<ExprNode>> statements) | |
| : statements_(std::move(statements)) {} | |
| ~BlockExprNode() override = default; | |
| // Evaluates the given block starging from the statement at the given index. This is used to | |
| // recursively evaluate the block statements. | |
| static void EvalBlockFrom(fxl::RefPtr<BlockExprNode> node, size_t index, | |
| const fxl::RefPtr<EvalContext>& context, EvalCallback cb); | |
| std::vector<fxl::RefPtr<ExprNode>> statements_; | |
| }; | |
| // Implements all types of casts. | |
| class CastExprNode : public ExprNode { | |
| public: | |
| const CastExprNode* AsCast() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(CastExprNode); | |
| FRIEND_MAKE_REF_COUNTED(CastExprNode); | |
| CastExprNode(); | |
| CastExprNode(CastType cast_type, fxl::RefPtr<TypeExprNode> to_type, fxl::RefPtr<ExprNode> from) | |
| : cast_type_(cast_type), to_type_(std::move(to_type)), from_(std::move(from)) {} | |
| ~CastExprNode() override = default; | |
| CastType cast_type_; | |
| fxl::RefPtr<TypeExprNode> to_type_; | |
| fxl::RefPtr<ExprNode> from_; | |
| }; | |
| // Implements all types of if and if/else | |
| class ConditionExprNode : public ExprNode { | |
| public: | |
| struct Pair { | |
| Pair(fxl::RefPtr<ExprNode> c, fxl::RefPtr<ExprNode> t) | |
| : cond(std::move(c)), then(std::move(t)) {} | |
| fxl::RefPtr<ExprNode> cond; // Conditional expression to evaluate. | |
| fxl::RefPtr<ExprNode> then; // Code to execute when condition is satisfied. Possibly null. | |
| }; | |
| const ConditionExprNode* AsCondition() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(ConditionExprNode); | |
| FRIEND_MAKE_REF_COUNTED(ConditionExprNode); | |
| ConditionExprNode(); | |
| // The conditions are evaluated in-order until one is true. The "else" can be null in which case | |
| // it will be ignored. | |
| ConditionExprNode(std::vector<Pair> conds, fxl::RefPtr<ExprNode> else_case) | |
| : conds_(std::move(conds)), else_(std::move(else_case)) {} | |
| ~ConditionExprNode() override = default; | |
| static void EvalFromCond(fxl::RefPtr<ConditionExprNode> node, size_t index, | |
| const fxl::RefPtr<EvalContext>& context, EvalCallback cb); | |
| std::vector<Pair> conds_; | |
| fxl::RefPtr<ExprNode> else_; // Possibly null. | |
| }; | |
| // Implements dereferencing a pointer ("*" in C). | |
| class DereferenceExprNode : public ExprNode { | |
| public: | |
| const DereferenceExprNode* AsDereference() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(DereferenceExprNode); | |
| FRIEND_MAKE_REF_COUNTED(DereferenceExprNode); | |
| DereferenceExprNode(); | |
| DereferenceExprNode(fxl::RefPtr<ExprNode> expr) : expr_(std::move(expr)) {} | |
| ~DereferenceExprNode() override = default; | |
| fxl::RefPtr<ExprNode> expr_; | |
| }; | |
| // Function calls include things like: "Foo()", "ns::Foo<int>(6, 5)". | |
| class FunctionCallExprNode : public ExprNode { | |
| public: | |
| const FunctionCallExprNode* AsFunctionCall() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| const fxl::RefPtr<ExprNode>& call() const { return call_; } | |
| const std::vector<fxl::RefPtr<ExprNode>>& args() const { return args_; } | |
| // Returns true if the given ExprNode is valid for the "call" of a function. | |
| static bool IsValidCall(const fxl::RefPtr<ExprNode>& call); | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(FunctionCallExprNode); | |
| FRIEND_MAKE_REF_COUNTED(FunctionCallExprNode); | |
| FunctionCallExprNode(); | |
| explicit FunctionCallExprNode(fxl::RefPtr<ExprNode> call, | |
| std::vector<fxl::RefPtr<ExprNode>> args = {}) | |
| : call_(std::move(call)), args_(std::move(args)) {} | |
| ~FunctionCallExprNode() override = default; | |
| // Backend to evaluate a member function call on the given base object. For example, | |
| // "object.fn_name()". | |
| // | |
| // This assumes no function parameters (it's currently used for the PrettyType getters only). | |
| // | |
| // The second version handle the "->" case where the object should be a pointer. | |
| static void EvalMemberCall(const fxl::RefPtr<EvalContext>& context, const ExprValue& object, | |
| const std::string& fn_name, EvalCallback cb); | |
| static void EvalMemberPtrCall(const fxl::RefPtr<EvalContext>& context, | |
| const ExprValue& object_ptr, const std::string& fn_name, | |
| EvalCallback cb); | |
| // This will either be an IdentifierExprNode which gives the function name, or a | |
| // MemberAccessExprNode which gives an object and the function name. | |
| fxl::RefPtr<ExprNode> call_; | |
| std::vector<fxl::RefPtr<ExprNode>> args_; | |
| }; | |
| // Implements a bare identifier. | |
| class IdentifierExprNode : public ExprNode { | |
| public: | |
| const IdentifierExprNode* AsIdentifier() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| ParsedIdentifier& ident() { return ident_; } | |
| const ParsedIdentifier& ident() const { return ident_; } | |
| // Destructively moves the identifier out of this class. This unusual mutating getter is | |
| // implemented because the expression parser is also used to parse identifiers, and this will hold | |
| // the result which we would prefer not to copy to get out. | |
| ParsedIdentifier TakeIdentifier() { return std::move(ident_); } | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(IdentifierExprNode); | |
| FRIEND_MAKE_REF_COUNTED(IdentifierExprNode); | |
| IdentifierExprNode() = default; | |
| // Simple one-name identifier. | |
| IdentifierExprNode(std::string name) : ident_(ParsedIdentifierComponent(std::move(name))) {} | |
| IdentifierExprNode(ParsedIdentifier id) : ident_(std::move(id)) {} | |
| ~IdentifierExprNode() override = default; | |
| ParsedIdentifier ident_; | |
| }; | |
| // Implements a literal like a number, boolean, or string. | |
| class LiteralExprNode : public ExprNode { | |
| public: | |
| const LiteralExprNode* AsLiteral() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| // The token's value won't have been checked that it's valid, only that it starts like the type of | |
| // literal it is. This checking will be done at evaluation time. | |
| const ExprToken& token() const { return token_; } | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(LiteralExprNode); | |
| FRIEND_MAKE_REF_COUNTED(LiteralExprNode); | |
| LiteralExprNode() = default; | |
| explicit LiteralExprNode(const ExprToken& token) : token_(token) {} | |
| ~LiteralExprNode() override = default; | |
| ExprToken token_; | |
| }; | |
| // Implements both "." and "->" struct/class/union data member accesses. | |
| class MemberAccessExprNode : public ExprNode { | |
| public: | |
| const MemberAccessExprNode* AsMemberAccess() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| // Expression on the left side of the "." or "->". | |
| const ExprNode* left() const { return left_.get(); } | |
| // The "." or "->" token itself. | |
| const ExprToken& accessor() const { return accessor_; } | |
| // The name of the data member. | |
| const ParsedIdentifier& member() const { return member_; } | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(MemberAccessExprNode); | |
| FRIEND_MAKE_REF_COUNTED(MemberAccessExprNode); | |
| MemberAccessExprNode() = default; | |
| MemberAccessExprNode(fxl::RefPtr<ExprNode> left, const ExprToken& access, | |
| const ParsedIdentifier& member) | |
| : left_(std::move(left)), accessor_(access), member_(member) {} | |
| ~MemberAccessExprNode() override = default; | |
| fxl::RefPtr<ExprNode> left_; | |
| ExprToken accessor_; | |
| ParsedIdentifier member_; | |
| }; | |
| class SizeofExprNode : public ExprNode { | |
| public: | |
| const SizeofExprNode* AsSizeof() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(SizeofExprNode); | |
| FRIEND_MAKE_REF_COUNTED(SizeofExprNode); | |
| SizeofExprNode(); | |
| SizeofExprNode(fxl::RefPtr<ExprNode> expr) : expr_(std::move(expr)) {} | |
| ~SizeofExprNode() override = default; | |
| static void SizeofType(const fxl::RefPtr<EvalContext>& context, const Type* type, | |
| EvalCallback cb); | |
| fxl::RefPtr<ExprNode> expr_; | |
| }; | |
| // Implements references to type names. This mostly appears in casts. | |
| class TypeExprNode : public ExprNode { | |
| public: | |
| const TypeExprNode* AsType() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| fxl::RefPtr<Type>& type() { return type_; } | |
| const fxl::RefPtr<Type>& type() const { return type_; } | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(TypeExprNode); | |
| FRIEND_MAKE_REF_COUNTED(TypeExprNode); | |
| TypeExprNode(); | |
| TypeExprNode(fxl::RefPtr<Type> type) : type_(std::move(type)) {} | |
| ~TypeExprNode() override = default; | |
| fxl::RefPtr<Type> type_; | |
| }; | |
| // Implements unary mathematical operators (the operation depends on the operator token). | |
| class UnaryOpExprNode : public ExprNode { | |
| public: | |
| const UnaryOpExprNode* AsUnaryOp() const override { return this; } | |
| void Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const override; | |
| void Print(std::ostream& out, int indent) const override; | |
| private: | |
| FRIEND_REF_COUNTED_THREAD_SAFE(UnaryOpExprNode); | |
| FRIEND_MAKE_REF_COUNTED(UnaryOpExprNode); | |
| UnaryOpExprNode(); | |
| UnaryOpExprNode(const ExprToken& op, fxl::RefPtr<ExprNode> expr) | |
| : op_(op), expr_(std::move(expr)) {} | |
| ~UnaryOpExprNode() override = default; | |
| ExprToken op_; | |
| fxl::RefPtr<ExprNode> expr_; | |
| }; | |
| } // namespace zxdb | |
| #endif // SRC_DEVELOPER_DEBUG_ZXDB_EXPR_EXPR_NODE_H_ |