| // 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 <functional> |
| #include <iosfwd> |
| #include <memory> |
| |
| #include "src/developer/debug/zxdb/expr/cast.h" |
| #include "src/developer/debug/zxdb/expr/expr_token.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/symbols/identifier.h" |
| #include "src/lib/fxl/memory/ref_counted.h" |
| #include "src/lib/fxl/memory/ref_ptr.h" |
| |
| namespace zxdb { |
| |
| class Err; |
| class ExprEvalContext; |
| class Type; |
| |
| // Node subclasses. |
| class AddressOfExprNode; |
| class ArrayAccessExprNode; |
| class BinaryOpExprNode; |
| class CastExprNode; |
| 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: |
| using EvalCallback = std::function<void(const Err& err, ExprValue value)>; |
| |
| ExprNode() = default; |
| virtual ~ExprNode() = default; |
| |
| virtual const AddressOfExprNode* AsAddressOf() const { return nullptr; } |
| virtual const ArrayAccessExprNode* AsArrayAccess() const { return nullptr; } |
| virtual const BinaryOpExprNode* AsBinaryOp() const { return nullptr; } |
| virtual const CastExprNode* AsCast() 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(fxl::RefPtr<ExprEvalContext> 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(fxl::RefPtr<ExprEvalContext> 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; |
| }; |
| |
| // Implements taking an address of n expression ("&" in C). |
| class AddressOfExprNode : public ExprNode { |
| public: |
| const AddressOfExprNode* AsAddressOf() const override { return this; } |
| void Eval(fxl::RefPtr<ExprEvalContext> 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(fxl::RefPtr<ExprEvalContext> 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 ExprValue& inner, int64_t* offset); |
| |
| static void DoAccess(fxl::RefPtr<ExprEvalContext> context, ExprValue left, |
| int64_t offset, EvalCallback cb); |
| |
| 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(fxl::RefPtr<ExprEvalContext> 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_; |
| }; |
| |
| // Implements all types of casts. |
| class CastExprNode : public ExprNode { |
| public: |
| const CastExprNode* AsCast() const override { return this; } |
| void Eval(fxl::RefPtr<ExprEvalContext> 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 dereferencing a pointer ("*" in C). |
| class DereferenceExprNode : public ExprNode { |
| public: |
| const DereferenceExprNode* AsDereference() const override { return this; } |
| void Eval(fxl::RefPtr<ExprEvalContext> 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(fxl::RefPtr<ExprEvalContext> context, |
| EvalCallback cb) const override; |
| void Print(std::ostream& out, int indent) const override; |
| |
| const Identifier& name() const { return name_; } |
| const std::vector<fxl::RefPtr<ExprNode>>& args() const { return args_; } |
| |
| private: |
| FRIEND_REF_COUNTED_THREAD_SAFE(FunctionCallExprNode); |
| FRIEND_MAKE_REF_COUNTED(FunctionCallExprNode); |
| |
| FunctionCallExprNode(); |
| FunctionCallExprNode(Identifier name, std::vector<fxl::RefPtr<ExprNode>> args) |
| : name_(std::move(name)), args_(std::move(args)) {} |
| ~FunctionCallExprNode() override = default; |
| |
| Identifier name_; |
| std::vector<fxl::RefPtr<ExprNode>> args_; |
| }; |
| |
| // Implements a bare identifier. |
| class IdentifierExprNode : public ExprNode { |
| public: |
| const IdentifierExprNode* AsIdentifier() const override { return this; } |
| void Eval(fxl::RefPtr<ExprEvalContext> context, |
| EvalCallback cb) const override; |
| void Print(std::ostream& out, int indent) const override; |
| |
| Identifier& ident() { return ident_; } |
| const Identifier& 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. |
| Identifier 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_(IdentifierComponent(std::move(name))) {} |
| |
| IdentifierExprNode(Identifier id) : ident_(std::move(id)) {} |
| ~IdentifierExprNode() override = default; |
| |
| Identifier ident_; |
| }; |
| |
| // Implements a literal like a number or a boolean. |
| class LiteralExprNode : public ExprNode { |
| public: |
| const LiteralExprNode* AsLiteral() const override { return this; } |
| void Eval(fxl::RefPtr<ExprEvalContext> 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(fxl::RefPtr<ExprEvalContext> 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 Identifier& 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 Identifier& member) |
| : left_(std::move(left)), accessor_(access), member_(member) {} |
| ~MemberAccessExprNode() override = default; |
| |
| fxl::RefPtr<ExprNode> left_; |
| ExprToken accessor_; |
| Identifier member_; |
| }; |
| |
| class SizeofExprNode : public ExprNode { |
| public: |
| const SizeofExprNode* AsSizeof() const override { return this; } |
| void Eval(fxl::RefPtr<ExprEvalContext> 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 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(fxl::RefPtr<ExprEvalContext> 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(fxl::RefPtr<ExprEvalContext> 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_ |