| // 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 "src/developer/debug/zxdb/expr/expr_node.h" |
| |
| #include <stdlib.h> |
| |
| #include <ostream> |
| |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/expr/cast.h" |
| #include "src/developer/debug/zxdb/expr/eval_context.h" |
| #include "src/developer/debug/zxdb/expr/eval_operators.h" |
| #include "src/developer/debug/zxdb/expr/expr_value.h" |
| #include "src/developer/debug/zxdb/expr/number_parser.h" |
| #include "src/developer/debug/zxdb/expr/pretty_type.h" |
| #include "src/developer/debug/zxdb/expr/pretty_type_manager.h" |
| #include "src/developer/debug/zxdb/expr/resolve_array.h" |
| #include "src/developer/debug/zxdb/expr/resolve_collection.h" |
| #include "src/developer/debug/zxdb/expr/resolve_function.h" |
| #include "src/developer/debug/zxdb/expr/resolve_ptr_ref.h" |
| #include "src/developer/debug/zxdb/expr/resolve_variant.h" |
| #include "src/developer/debug/zxdb/expr/return_value.h" |
| #include "src/developer/debug/zxdb/expr/vm_stream.h" |
| #include "src/developer/debug/zxdb/symbols/arch.h" |
| #include "src/developer/debug/zxdb/symbols/array_type.h" |
| #include "src/developer/debug/zxdb/symbols/base_type.h" |
| #include "src/developer/debug/zxdb/symbols/collection.h" |
| #include "src/developer/debug/zxdb/symbols/data_member.h" |
| #include "src/developer/debug/zxdb/symbols/function_call_info.h" |
| #include "src/developer/debug/zxdb/symbols/modified_type.h" |
| #include "src/developer/debug/zxdb/symbols/symbol_data_provider.h" |
| #include "src/developer/debug/zxdb/symbols/symbol_utils.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| std::string IndentFor(int value) { return std::string(value, ' '); } |
| |
| bool BaseTypeCanBeArrayIndex(const BaseType* type) { |
| int bt = type->base_type(); |
| return bt == BaseType::kBaseTypeBoolean || bt == BaseType::kBaseTypeSigned || |
| bt == BaseType::kBaseTypeSignedChar || bt == BaseType::kBaseTypeUnsigned || |
| bt == BaseType::kBaseTypeUnsignedChar; |
| } |
| |
| void DoResolveConcreteMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& value, |
| const ParsedIdentifier& member, EvalCallback cb) { |
| if (PrettyType* pretty = context->GetPrettyTypeManager().GetForType(value.type())) { |
| if (auto getter = pretty->GetMember(member.GetFullName())) { |
| return getter(context, value, std::move(cb)); |
| } |
| } |
| |
| return ResolveMember(context, value, member, std::move(cb)); |
| } |
| |
| // Prints the expression, or if null, ";". |
| void PrintExprOrSemicolon(std::ostream& out, int indent, const fxl::RefPtr<ExprNode>& expr) { |
| if (expr) { |
| expr->Print(out, indent); |
| } else { |
| out << IndentFor(indent) << ";\n"; |
| } |
| } |
| |
| ErrOrValue RustPatternMatches(const fxl::RefPtr<EvalContext>& eval_context, |
| const ParsedIdentifier& enum_name, ExprValue value) { |
| ErrOr<std::string> cur_name = GetActiveRustVariantName(eval_context, value); |
| if (cur_name.has_error()) |
| return cur_name.err(); |
| |
| // Currently this only allows "short" one-word enum names, not fully qualified ones. |
| if (enum_name.components().size() != 1) { |
| return Err("Only unqualified (single-word) patterns are supported. I saw " + |
| enum_name.GetFullName()); |
| } |
| |
| bool result = enum_name.components()[0].special() == SpecialIdentifier::kNone && |
| enum_name.components()[0].name() == cur_name.value(); |
| return ExprValue(result); |
| } |
| |
| } // namespace |
| |
| void ExprNode::EmitBytecodeExpandRef(VmStream& stream) const { |
| EmitBytecode(stream); |
| stream.push_back(VmOp::MakeExpandRef()); |
| } |
| |
| void AddressOfExprNode::EmitBytecode(VmStream& stream) const { |
| expr_->EmitBytecodeExpandRef(stream); |
| stream.push_back(VmOp::MakeCallback1( |
| [](const fxl::RefPtr<EvalContext>& eval_context, ExprValue value) -> ErrOrValue { |
| if (value.source().type() != ExprValueSource::Type::kMemory) |
| return Err("Can't take the address of a temporary."); |
| if (value.source().bit_size() != 0) |
| return Err("Can't take the address of a bitfield."); |
| |
| // Construct a pointer type to the variable. |
| auto ptr_type = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, value.type_ref()); |
| TargetPointer address = value.source().address(); |
| return ExprValue(address, std::move(ptr_type)); |
| })); |
| } |
| |
| void AddressOfExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "ADDRESS_OF\n"; |
| expr_->Print(out, indent + 1); |
| } |
| |
| void ArrayAccessExprNode::EmitBytecode(VmStream& stream) const { |
| left_->EmitBytecodeExpandRef(stream); |
| inner_->EmitBytecodeExpandRef(stream); |
| |
| stream.push_back( |
| VmOp::MakeAsyncCallback2([](const fxl::RefPtr<EvalContext>& context, ExprValue left, |
| ExprValue inner, EvalCallback cb) mutable { |
| // Both "left" and "inner" has been evaluated. |
| int64_t offset = 0; |
| if (Err err = InnerValueToOffset(context, inner, &offset); err.has_error()) { |
| cb(err); |
| } else { |
| ResolveArrayItem(context, left, offset, std::move(cb)); |
| } |
| })); |
| } |
| |
| // static |
| Err ArrayAccessExprNode::InnerValueToOffset(const fxl::RefPtr<EvalContext>& context, |
| const ExprValue& inner, int64_t* offset) { |
| // Skip "const", etc. |
| fxl::RefPtr<BaseType> base_type = context->GetConcreteTypeAs<BaseType>(inner.type()); |
| if (!base_type || !BaseTypeCanBeArrayIndex(base_type.get())) |
| return Err("Bad type for array index."); |
| |
| // This uses signed integers to explicitly allow negative indexing which the user may want to do |
| // for some reason. |
| Err promote_err = inner.PromoteTo64(offset); |
| if (promote_err.has_error()) |
| return promote_err; |
| return Err(); |
| } |
| |
| void ArrayAccessExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "ARRAY_ACCESS\n"; |
| left_->Print(out, indent + 1); |
| inner_->Print(out, indent + 1); |
| } |
| |
| void BinaryOpExprNode::EmitBytecode(VmStream& stream) const { |
| // Need to implement short-circuiting evaluation for || and && to avoid unwanted side-effects. |
| left_->EmitBytecodeExpandRef(stream); |
| if (op_.type() == ExprTokenType::kLogicalOr) { |
| // Emit the equivalent of: "left ? true : (right ? true : false)". |
| VmBytecodeForwardJumpIfFalse jump_to_right(&stream); // -> RIGHT |
| |
| // Left is true, emit a "true" value and jump to the end. |
| stream.push_back(VmOp::MakeLiteral(ExprValue(true))); |
| VmBytecodeForwardJump left_jump_out(&stream); // -> END |
| |
| // RIGHT: Evaluate right side. The first condition jump goes here. |
| jump_to_right.JumpToHere(); |
| right_->EmitBytecodeExpandRef(stream); |
| |
| // On false, jump to the end (after the "then"). |
| VmBytecodeForwardJumpIfFalse final_cond_jump(&stream); // -> FALSE |
| |
| // Right is true, emit a "true" value and jump to the end. |
| stream.push_back(VmOp::MakeLiteral(ExprValue(true))); |
| VmBytecodeForwardJump right_jump_out(&stream); // -> END |
| |
| // FALSE: Condition is false. |
| final_cond_jump.JumpToHere(); |
| stream.push_back(VmOp::MakeLiteral(ExprValue(false))); |
| |
| // END: End of condition, all the done jumps end up here. |
| left_jump_out.JumpToHere(); |
| right_jump_out.JumpToHere(); |
| } else if (op_.type() == ExprTokenType::kDoubleAnd) { |
| VmBytecodeForwardJumpIfFalse left_jump_to_false(&stream); // -> FALSE |
| |
| // Left was true, now evaluate right. |
| right_->EmitBytecodeExpandRef(stream); |
| VmBytecodeForwardJumpIfFalse right_jump_to_false(&stream); // -> FALSE |
| |
| // True case. |
| stream.push_back(VmOp::MakeLiteral(ExprValue(true))); |
| VmBytecodeForwardJump jump_to_end(&stream); // -> END |
| |
| // FALSE: The failure cases end up here. |
| left_jump_to_false.JumpToHere(); |
| right_jump_to_false.JumpToHere(); |
| stream.push_back(VmOp::MakeLiteral(ExprValue(false))); |
| |
| // END: |
| jump_to_end.JumpToHere(); |
| } else { |
| // All other binary operators can be evaluated directly. |
| right_->EmitBytecode(stream); |
| stream.push_back(VmOp::MakeBinary(op_)); |
| } |
| } |
| |
| void BinaryOpExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "BINARY_OP(" + op_.value() + ")\n"; |
| left_->Print(out, indent + 1); |
| right_->Print(out, indent + 1); |
| } |
| |
| void BlockExprNode::EmitBytecode(VmStream& stream) const { |
| // All nodes must evaluate to some value. We define the block's value as being that of the |
| // last expression (like Rust with no semicolon), and an empty ExprValue if there is nothing in |
| // the block. |
| if (statements_.empty()) { |
| stream.push_back(VmOp::MakeLiteral(ExprValue())); |
| return; |
| } |
| |
| for (size_t i = 0; i < statements_.size() - 1; i++) { |
| statements_[i]->EmitBytecode(stream); |
| stream.push_back(VmOp::MakeDrop()); // Discard intermediate statement results. |
| } |
| statements_.back()->EmitBytecode(stream); |
| |
| // Clean up any locals. This removes any variables beyond what were in scope when the block |
| // entered. See "Local variables" in vm_op.h for more info. |
| if (entry_local_var_count_) |
| stream.push_back(VmOp::MakePopLocals(*entry_local_var_count_)); |
| } |
| |
| void BlockExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "BLOCK\n"; |
| for (const auto& stmt : statements_) |
| stmt->Print(out, indent + 1); |
| } |
| |
| void BreakExprNode::EmitBytecode(VmStream& stream) const { |
| stream.push_back(VmOp::MakeBreak(token_)); |
| } |
| |
| void BreakExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "BREAK\n"; |
| } |
| |
| void CastExprNode::EmitBytecode(VmStream& stream) const { |
| from_->EmitBytecode(stream); |
| // Uses the non-concrete type for the "to" type because the cast will internally get the |
| // concrete type, but will preserve the original type in the output so that the result will have |
| // the same type the user typed (like a typedef name). |
| stream.push_back(VmOp::MakeAsyncCallback1( |
| [cast_type = cast_type_, to_type = to_type_->type()]( |
| const fxl::RefPtr<EvalContext>& eval_context, ExprValue from, EvalCallback cb) mutable { |
| CastExprValue(eval_context, cast_type, from, to_type, ExprValueSource(), std::move(cb)); |
| })); |
| } |
| |
| void CastExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "CAST(" << CastTypeToString(cast_type_) << ")\n"; |
| to_type_->Print(out, indent + 1); |
| from_->Print(out, indent + 1); |
| } |
| |
| void ConditionExprNode::EmitBytecode(VmStream& stream) const { |
| // Instruction indices of all jumps that need to go to the end of the if statement. |
| std::vector<size_t> done_jumps; |
| |
| for (const Pair& pair : conds_) { |
| pair.cond.cond->EmitBytecodeExpandRef(stream); |
| |
| VmBytecodeForwardJumpIfFalse jump_to_next; |
| |
| bool needs_if_let_pop = false; |
| if (pair.cond.IsRustIfLet()) { |
| // Rust "if let" support. |
| // |
| // Since a VM callback can't return two values (ideally it would return both whether the match |
| // succeeded and the contained value), the match is done in two phases: first the enum type is |
| // checked to see if the match succeeds, and then if it did, another callback is issued to |
| // extract the contained value of the enum. For this we need two copies of the expression |
| // result, if the match fails, the duplicate copy will be dropped before executing the next |
| // "if/else" according to needs_if_let_pop. |
| needs_if_let_pop = true; |
| stream.push_back(VmOp::MakeDup()); |
| stream.push_back( |
| VmOp::MakeCallback1([name = pair.cond.rust_pattern_name]( |
| const fxl::RefPtr<EvalContext>& eval_context, ExprValue value) { |
| return RustPatternMatches(eval_context, name, std::move(value)); |
| })); |
| jump_to_next.SetSource(&stream); // Jumps to next if the match fails. |
| |
| // Match succeeded: Extract the enum value. This can be nothing, a tuple, or a struct. |
| // Currently we don't support structs. This consumes the duplicated value we made from the |
| // expression above. |
| stream.push_back( |
| VmOp::MakeCallback1([](const fxl::RefPtr<EvalContext>& context, const ExprValue& value) { |
| return ResolveSingleVariantValue(context, value); |
| })); |
| |
| // Validate the number of tuple elements matches the number of arguments. |
| stream.push_back(VmOp::MakeDup()); |
| stream.push_back(VmOp::MakeCallback1( |
| [expected_count = pair.cond.rust_pattern_local_slots.size()]( |
| const fxl::RefPtr<EvalContext>& eval_context, ExprValue value) -> ErrOrValue { |
| ErrOr<size_t> actual_count = GetRustTupleMemberCount(eval_context, value); |
| if (actual_count.has_error()) |
| return actual_count.err(); |
| if (expected_count != actual_count.value()) { |
| return Err("Tuple pattern contains %zu members but the matched tuple has %zu.", |
| expected_count, actual_count.value()); |
| } |
| return ExprValue(); |
| })); |
| stream.push_back(VmOp::MakeDrop()); // The callback's return value is not used. |
| |
| for (size_t match_i = 0; match_i < pair.cond.rust_pattern_local_slots.size(); match_i++) { |
| // The callback consumes the stack entry so we need to save a copy for the next iteration. |
| stream.push_back(VmOp::MakeDup()); |
| |
| // Actually extract the Nth tuple member and store it in the corresponding local var. |
| stream.push_back(VmOp::MakeCallback1( |
| [match_i](const fxl::RefPtr<EvalContext>& eval_context, ExprValue value) { |
| return ExtractRustTuple(eval_context, value, match_i); |
| })); |
| stream.push_back(VmOp::MakeSetLocal(pair.cond.rust_pattern_local_slots[match_i])); |
| } |
| |
| // Drop the saved copy of the extracted tuple. |
| stream.push_back(VmOp::MakeDrop()); |
| } else { |
| // Normal "if": jump over the "then" case if false. |
| jump_to_next.SetSource(&stream); |
| } |
| |
| pair.then->EmitBytecode(stream); |
| |
| // Jump to the end of the entire if/else block (we don't know the dest until the bottom). |
| done_jumps.push_back(stream.size()); |
| stream.push_back(VmOp::MakeJump()); |
| |
| // Fixup the "else" case to go to here. |
| jump_to_next.JumpToHere(); |
| if (needs_if_let_pop) { |
| // When a Rust "if let" the condition fails, we need to drop the duplicate value we saved |
| // for extracting the enum contents. |
| stream.push_back(VmOp::MakeDrop()); |
| } |
| } |
| |
| if (else_) { |
| else_->EmitBytecode(stream); |
| } else { |
| // When there is no explicit "else" case, the if expression still needs a result. |
| stream.push_back(VmOp::MakeLiteral(ExprValue())); |
| } |
| |
| // Fixup all previous jumps to the end of the blocks. |
| for (size_t jump_source : done_jumps) { |
| stream[jump_source].SetJumpDest(static_cast<uint32_t>(stream.size())); |
| } |
| |
| // Clean up any local variables introduced in the conditions. |
| if (entry_local_var_count_) |
| stream.push_back(VmOp::MakePopLocals(*entry_local_var_count_)); |
| } |
| |
| void ConditionExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "CONDITION\n"; |
| for (size_t i = 0; i < conds_.size(); i++) { |
| if (i == 0) { |
| out << IndentFor(indent + 1) << "IF"; |
| } else { |
| out << IndentFor(indent + 1) << "ELSEIF"; |
| } |
| if (conds_[i].cond.IsRustIfLet()) { |
| out << "_LET("; |
| for (size_t s = 0; s < conds_[i].cond.rust_pattern_local_slots.size(); s++) { |
| if (s != 0) |
| out << ", "; |
| out << conds_[i].cond.rust_pattern_local_slots[s]; |
| } |
| out << ")\n"; |
| out << IndentFor(indent + 2) << conds_[i].cond.rust_pattern_name.GetDebugName() << "\n"; |
| } else { |
| out << "\n"; |
| } |
| conds_[i].cond.cond->Print(out, indent + 2); |
| |
| if (conds_[i].then) { |
| out << IndentFor(indent + 1) << "THEN\n"; |
| conds_[i].then->Print(out, indent + 2); |
| } |
| } |
| if (else_) { |
| out << IndentFor(indent + 1) << "ELSE\n"; |
| else_->Print(out, indent + 2); |
| } |
| } |
| |
| void DereferenceExprNode::EmitBytecode(VmStream& stream) const { |
| expr_->EmitBytecodeExpandRef(stream); |
| |
| stream.push_back(VmOp::MakeAsyncCallback1( |
| [](const fxl::RefPtr<EvalContext>& eval_context, ExprValue value, EvalCallback cb) mutable { |
| // First check for pretty-printers for this type. |
| if (PrettyType* pretty = eval_context->GetPrettyTypeManager().GetForType(value.type())) { |
| if (auto derefer = pretty->GetDereferencer()) { |
| // The pretty type supplies dereference function. |
| return derefer(eval_context, value, std::move(cb)); |
| } |
| } |
| |
| // Normal dereferencing operation. |
| ResolvePointer(eval_context, value, std::move(cb)); |
| })); |
| } |
| |
| void DereferenceExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "DEREFERENCE\n"; |
| expr_->Print(out, indent + 1); |
| } |
| |
| void FunctionCallExprNode::EmitBytecode(VmStream& stream) const { |
| // Start with all parameters on the stack. |
| for (const auto& arg : args_) { |
| arg->EmitBytecode(stream); |
| } |
| |
| if (const MemberAccessExprNode* access = call_->AsMemberAccess()) { |
| // For member calls, we also need to evaluate the object. That will appear as the last |
| // "parameter". |
| access->left()->EmitBytecodeExpandRef(stream); |
| stream.push_back(VmOp::MakeAsyncCallbackN( |
| static_cast<int>(args_.size()) + 1, |
| [fn_name = access->member().GetFullName(), op = access->accessor()]( |
| const fxl::RefPtr<EvalContext>& eval_context, std::vector<ExprValue> params_and_object, |
| EvalCallback cb) { |
| // The last parameter is the object, extract it. |
| ExprValue object = std::move(params_and_object.back()); |
| params_and_object.pop_back(); |
| |
| if (params_and_object.size() != 0) { |
| // TODO(https://fxbug.dev/42132103): Member functions require a |this| pointer in C++ and a |
| // (typically) |self| reference in Rust. |
| |
| // Currently we do not support any parameters. This can be handled in the future if |
| // needed. |
| return cb( |
| Err("Arbitrary function calls are not supported. Only certain built-in getters " |
| "will work.")); |
| } |
| |
| if (op.type() == ExprTokenType::kArrow) { |
| EvalMemberPtrCall(eval_context, object, fn_name, std::move(cb)); |
| } else { // Assume ".". |
| EvalMemberCall(eval_context, object, fn_name, std::move(cb)); |
| } |
| })); |
| } else if (const IdentifierExprNode* ident = call_->AsIdentifier()) { |
| // Simple standalone function call. |
| stream.push_back(VmOp::MakeAsyncCallbackN( |
| static_cast<int>(args_.size()), |
| [fn_name = ident->ident()](const fxl::RefPtr<EvalContext>& eval_context, |
| const std::vector<ExprValue>& params, EvalCallback cb) { |
| // Check for builtins first. If no builtin exists, try to call the function in the target. |
| if (const EvalContext::BuiltinFuncCallback* impl = |
| eval_context->GetBuiltinFunction(fn_name)) { |
| (*impl)(eval_context, params, std::move(cb)); |
| return; |
| } |
| |
| ResolveFunction( |
| eval_context, fn_name, params, |
| [eval_context, cb = std::move(cb)](ErrOr<FunctionCallInfo> fn_call_info_or) mutable { |
| if (fn_call_info_or.has_error()) { |
| return cb(fn_call_info_or.err()); |
| } |
| |
| auto call_info = fn_call_info_or.take_value(); |
| eval_context->GetDataProvider()->MakeFunctionCall( |
| call_info, [cb = std::move(cb)](const Err& err, fxl::RefPtr<Type> type, |
| std::vector<uint8_t> data) mutable { |
| if (err.has_error()) |
| return cb(err); |
| |
| cb(ExprValue(std::move(type), std::move(data))); |
| }); |
| }); |
| })); |
| } else { |
| stream.push_back(VmOp::MakeError(Err("Unknown function call type."))); |
| } |
| } |
| |
| void FunctionCallExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "FUNCTIONCALL\n"; |
| call_->Print(out, indent + 1); |
| for (const auto& arg : args_) |
| arg->Print(out, indent + 1); |
| } |
| |
| // static |
| bool FunctionCallExprNode::IsValidCall(const fxl::RefPtr<ExprNode>& call) { |
| return call && (call->AsIdentifier() || call->AsMemberAccess()); |
| } |
| |
| // static |
| void FunctionCallExprNode::EvalMemberCall(const fxl::RefPtr<EvalContext>& context, |
| const ExprValue& object, const std::string& fn_name, |
| EvalCallback cb) { |
| if (!object.type()) |
| return cb(Err("No type information.")); |
| |
| if (PrettyType* pretty = context->GetPrettyTypeManager().GetForType(object.type())) { |
| // Have a PrettyType for the object type. |
| if (auto getter = pretty->GetGetter(fn_name)) { |
| return getter(context, object, |
| [type_name = object.type()->GetFullName(), fn_name, |
| cb = std::move(cb)](ErrOrValue value) mutable { |
| // This lambda exists just to rewrite the error message so it's clear the |
| // error is coming from the PrettyType and not the users's input. Otherwise |
| // it can look quite confusing. |
| if (value.has_error()) { |
| cb( |
| Err("When evaluating the internal pretty getter '%s()' on the type:\n " |
| "%s\nGot the error:\n %s\nPlease file a bug.", |
| fn_name.c_str(), type_name.c_str(), value.err().msg().c_str())); |
| } else { |
| cb(std::move(value)); |
| } |
| }); |
| } |
| } |
| |
| cb(Err("No built-in getter '%s()' for the type\n %s", fn_name.c_str(), |
| object.type()->GetFullName().c_str())); |
| } |
| |
| // static |
| void FunctionCallExprNode::EvalMemberPtrCall(const fxl::RefPtr<EvalContext>& context, |
| const ExprValue& object_ptr, |
| const std::string& fn_name, EvalCallback cb) { |
| // Callback executed on the object once the pointer has been dereferenced. |
| auto on_pointer_resolved = [context, fn_name, cb = std::move(cb)](ErrOrValue value) mutable { |
| if (value.has_error()) |
| cb(value); |
| else |
| EvalMemberCall(std::move(context), value.value(), fn_name, std::move(cb)); |
| }; |
| |
| // The base object could itself have a dereference operator. For example, if you have a: |
| // std::unique_ptr<std::vector<int>> foo; |
| // and do: |
| // foo->size() |
| // It needs to use the pretty dereferencer on foo before trying to access the size() function |
| // on the resulting object. |
| if (PrettyType* pretty = context->GetPrettyTypeManager().GetForType(object_ptr.type())) { |
| if (auto derefer = pretty->GetDereferencer()) { |
| // The pretty type supplies dereference function. |
| return derefer(context, object_ptr, std::move(on_pointer_resolved)); |
| } |
| } |
| |
| // Regular, assume the base is a pointer. |
| ResolvePointer(context, object_ptr, std::move(on_pointer_resolved)); |
| } |
| |
| void IdentifierExprNode::EmitBytecode(VmStream& stream) const { |
| stream.push_back(VmOp::MakeAsyncCallback0( |
| [ident = ident_](const fxl::RefPtr<EvalContext>& exec_context, EvalCallback cb) mutable { |
| exec_context->GetNamedValue(ident, std::move(cb)); |
| })); |
| } |
| |
| void IdentifierExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "IDENTIFIER(" << ident_.GetDebugName() << ")\n"; |
| } |
| |
| void LiteralExprNode::EmitBytecode(VmStream& stream) const { |
| ErrOrValue literal((ExprValue())); |
| switch (token_.type()) { |
| case ExprTokenType::kInteger: { |
| literal = StringToNumber(language_, token_.value()); |
| break; |
| } |
| case ExprTokenType::kFloat: { |
| literal = ValueForFloatToken(language_, token_); |
| break; |
| } |
| case ExprTokenType::kStringLiteral: { |
| // Include the null terminator in the string array as C would. |
| std::vector<uint8_t> string_as_array; |
| string_as_array.reserve(token_.value().size() + 1); |
| string_as_array.assign(token_.value().begin(), token_.value().end()); |
| string_as_array.push_back(0); |
| literal = |
| ExprValue(MakeStringLiteralType(token_.value().size() + 1), std::move(string_as_array)); |
| break; |
| } |
| case ExprTokenType::kCharLiteral: { |
| FX_DCHECK(token_.value().size() == 1); |
| switch (language_) { |
| case ExprLanguage::kC: { |
| int8_t value8 = token_.value()[0]; |
| literal = ExprValue( |
| value8, fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSignedChar, 1, "char")); |
| break; |
| } |
| case ExprLanguage::kRust: { |
| // Rust character literals are 32-bit unsigned words even though we only support 8-bit for |
| // now. Promote to 32-bits. |
| uint32_t value32 = token_.value()[0]; |
| literal = ExprValue( |
| value32, fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsignedChar, 4, "char")); |
| break; |
| } |
| } |
| break; |
| } |
| case ExprTokenType::kTrue: { |
| literal = ExprValue(true); |
| break; |
| } |
| case ExprTokenType::kFalse: { |
| literal = ExprValue(false); |
| break; |
| } |
| default: |
| FX_NOTREACHED(); |
| } |
| if (literal.has_error()) { |
| stream.push_back(VmOp::MakeError(literal.err(), token_)); |
| } else { |
| stream.push_back(VmOp::MakeLiteral(literal.value(), token_)); |
| } |
| } |
| |
| void LiteralExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "LITERAL(" << token_.value() << ")\n"; |
| } |
| |
| void LocalVarExprNode::EmitBytecode(VmStream& stream) const { |
| stream.push_back(VmOp::MakeGetLocal(slot_)); |
| } |
| |
| void LocalVarExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "LOCAL_VAR(" << slot_ << ")\n"; |
| } |
| |
| void LoopExprNode::EmitBytecode(VmStream& stream) const { |
| VmBytecodePushBreak break_jumper(&stream); |
| |
| if (init_) { |
| init_->EmitBytecode(stream); |
| stream.push_back(VmOp::MakeDrop()); // The result of the initialization expression is ignored. |
| } |
| |
| // Top of actual loop contents. |
| uint32_t loop_top = stream.size(); |
| |
| VmBytecodeForwardJumpIfFalse precondition_jumper; |
| if (precondition_) { |
| precondition_->EmitBytecode(stream); |
| precondition_jumper.SetSource(&stream); // Jump out of loop if precondition is false. |
| } |
| |
| if (contents_) { |
| contents_->EmitBytecode(stream); |
| stream.push_back(VmOp::MakeDrop()); // The result of the contents is ignored. |
| } |
| |
| VmBytecodeForwardJumpIfFalse postcondition_jumper; |
| if (postcondition_) { |
| postcondition_->EmitBytecode(stream); |
| postcondition_jumper.SetSource(&stream); // Jump out of loop if postcondition is false. |
| } |
| |
| if (incr_) { |
| incr_->EmitBytecode(stream); |
| stream.push_back(VmOp::MakeDrop()); // The result of the increment expression is ignored. |
| } |
| |
| // Jump back to the top of the loop. |
| stream.push_back(VmOp::MakeJump(loop_top)); |
| |
| // The end of the loop (these will do nothing if we never called SetSource() on them). |
| precondition_jumper.JumpToHere(); |
| postcondition_jumper.JumpToHere(); |
| |
| // Clean up any locals. This removes any variables beyond what were in scope when the init |
| // expression started. See "Local variables" in vm_op.h for more info. |
| if (init_local_var_count_) |
| stream.push_back(VmOp::MakePopLocals(*init_local_var_count_)); |
| |
| // Break commands will implicitly clean up the local variables (see vm_op.h). So the destination |
| // of any break commands should be here after restoring the local var count in the normal path. |
| break_jumper.JumpToHere(); |
| stream.push_back(VmOp::MakePopBreak()); |
| |
| // Push the result of the loop expression (no value). |
| stream.push_back(VmOp::MakeLiteral(ExprValue())); |
| } |
| |
| void LoopExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "LOOP(" << token_.value() << ")\n"; |
| PrintExprOrSemicolon(out, indent + 1, init_); |
| PrintExprOrSemicolon(out, indent + 1, precondition_); |
| PrintExprOrSemicolon(out, indent + 1, postcondition_); |
| PrintExprOrSemicolon(out, indent + 1, incr_); |
| PrintExprOrSemicolon(out, indent + 1, contents_); |
| } |
| |
| void MemberAccessExprNode::EmitBytecode(VmStream& stream) const { |
| left_->EmitBytecodeExpandRef(stream); |
| |
| bool by_pointer = accessor_.type() == ExprTokenType::kArrow; |
| stream.push_back(VmOp::MakeAsyncCallback1( |
| [by_pointer, member = member_](const fxl::RefPtr<EvalContext>& context, ExprValue base_value, |
| EvalCallback cb) mutable { |
| // Rust references can be accessed with '.' |
| if (!by_pointer) { |
| fxl::RefPtr<Type> concrete_base = context->GetConcreteType(base_value.type()); |
| |
| if (!concrete_base || concrete_base->tag() != DwarfTag::kPointerType || |
| concrete_base->GetLanguage() != DwarfLang::kRust || |
| concrete_base->GetAssignedName().substr(0, 1) != "&") { |
| return DoResolveConcreteMember(context, base_value, member, std::move(cb)); |
| } |
| } |
| |
| PrettyType::EvalFunction getter = [member](const fxl::RefPtr<EvalContext>& context, |
| const ExprValue& value, EvalCallback cb) { |
| DoResolveConcreteMember(context, value, member, std::move(cb)); |
| }; |
| PrettyType::EvalFunction derefer = ResolvePointer; |
| |
| if (PrettyType* pretty = context->GetPrettyTypeManager().GetForType(base_value.type())) { |
| derefer = pretty->GetDereferencer(); |
| } else { |
| fxl::RefPtr<Collection> coll; |
| if (Err err = GetConcretePointedToCollection(context, base_value.type(), &coll); |
| err.has_error()) { |
| return cb(err); |
| } |
| |
| if (PrettyType* pretty = context->GetPrettyTypeManager().GetForType(coll.get())) { |
| getter = pretty->GetMember(member.GetFullName()); |
| } else { |
| getter = nullptr; |
| } |
| } |
| |
| if (getter && derefer) { |
| return derefer(context, base_value, |
| [context, member, getter = std::move(getter), |
| cb = std::move(cb)](ErrOrValue non_ptr_base) mutable { |
| if (non_ptr_base.has_error()) |
| return cb(non_ptr_base); |
| getter(context, non_ptr_base.value(), std::move(cb)); |
| }); |
| } |
| |
| // Normal collection resolution. |
| ResolveMemberByPointer(context, base_value, member, |
| [cb = std::move(cb)](ErrOrValue result, const FoundMember&) mutable { |
| // Discard resolved symbol, we only need the value. |
| cb(std::move(result)); |
| }); |
| })); |
| } |
| |
| void MemberAccessExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "ACCESSOR(" << accessor_.value() << ")\n"; |
| left_->Print(out, indent + 1); |
| out << IndentFor(indent + 1) << member_.GetFullName() << "\n"; |
| } |
| |
| void SizeofExprNode::EmitBytecode(VmStream& stream) const { |
| if (const TypeExprNode* type_node = const_cast<ExprNode*>(expr_.get())->AsType()) { |
| // Ask for the size of the type at execution time (it needs the EvalContext for everything). |
| stream.push_back(VmOp::MakeCallback0( |
| [type = type_node->type()](const fxl::RefPtr<EvalContext>& eval_context) -> ErrOrValue { |
| return SizeofType(eval_context, type.get()); |
| })); |
| } else { |
| // Everything else gets evaluated. Strictly C++ won't do this because it's statically typed, but |
| // our expression system is not. This doesn't need to follow references because we only need the |
| // type and SizeofType() follows them as needed. |
| expr_->EmitBytecodeExpandRef(stream); |
| stream.push_back(VmOp::MakeCallback1( |
| [](const fxl::RefPtr<EvalContext>& eval_context, ExprValue param) -> ErrOrValue { |
| return SizeofType(eval_context, param.type()); |
| })); |
| } |
| } |
| |
| void SizeofExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "SIZEOF\n"; |
| expr_->Print(out, indent + 1); |
| } |
| |
| // static |
| ErrOrValue SizeofExprNode::SizeofType(const fxl::RefPtr<EvalContext>& context, |
| const Type* in_type) { |
| // References should get stripped (sizeof(char&) = 1). |
| if (!in_type) |
| return Err("Can't do sizeof on a null type."); |
| |
| fxl::RefPtr<Type> type = context->GetConcreteType(in_type); |
| if (type->is_declaration()) |
| return Err("Can't resolve forward declaration for '%s'.", in_type->GetFullName().c_str()); |
| |
| if (DwarfTagIsEitherReference(type->tag())) |
| type = RefPtrTo(type->As<ModifiedType>()->modified().Get()->As<Type>()); |
| if (!type) |
| return Err("Symbol error for '%s'.", in_type->GetFullName().c_str()); |
| |
| return ExprValue(type->byte_size()); |
| } |
| |
| void TypeExprNode::EmitBytecode(VmStream& stream) const { |
| // This is invalid. EmitBytecode can't report errors so generate some code to set the error at |
| // runtime. |
| stream.push_back(VmOp::MakeError( |
| Err("Attempting to execute a type '" + type_->GetFullName() + "' as an expression."))); |
| } |
| |
| void TypeExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "TYPE("; |
| if (type_) |
| out << type_->GetFullName(); |
| out << ")\n"; |
| } |
| |
| void UnaryOpExprNode::EmitBytecode(VmStream& stream) const { |
| expr_->EmitBytecodeExpandRef(stream); |
| stream.push_back(VmOp::MakeUnary(op_)); |
| } |
| |
| void UnaryOpExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "UNARY(" << op_.value() << ")\n"; |
| expr_->Print(out, indent + 1); |
| } |
| |
| void VariableDeclExprNode::EmitBytecode(VmStream& stream) const { |
| EmitVariableInitializerOps(decl_info_, local_slot_, init_expr_, stream); |
| } |
| |
| void VariableDeclExprNode::Print(std::ostream& out, int indent) const { |
| out << IndentFor(indent) << "LOCAL_VAR_DECL(" << name_.value() << ", " << local_slot_ << ")\n"; |
| out << IndentFor(indent + 1) << decl_info_.ToString() << "\n"; |
| PrintExprOrSemicolon(out, indent + 1, init_expr_); |
| } |
| |
| } // namespace zxdb |