blob: 2dee926a986116b2ef0a23c6e55742d82496b35e [file] [log] [blame]
// 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/shared/message_loop.h"
#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_ptr_ref.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/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));
}
} // namespace
void ExprNode::EvalFollowReferences(const fxl::RefPtr<EvalContext>& context,
EvalCallback cb) const {
Eval(context, [context, cb = std::move(cb)](ErrOrValue value) mutable {
if (value.has_error())
return cb(value);
EnsureResolveReference(context, std::move(value.value()), std::move(cb));
});
}
void AddressOfExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
expr_->EvalFollowReferences(context, [cb = std::move(cb)](ErrOrValue value) mutable {
if (value.has_error()) {
cb(value);
} else if (value.value().source().type() != ExprValueSource::Type::kMemory) {
cb(Err("Can't take the address of a temporary."));
} else if (value.value().source().bit_size() != 0) {
cb(Err("Can't take the address of a bitfield."));
} else {
// Construct a pointer type to the variable.
auto ptr_type =
fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType, value.value().type_ref());
std::vector<uint8_t> contents;
contents.resize(kTargetPointerSize);
TargetPointer address = value.value().source().address();
memcpy(&contents[0], &address, sizeof(kTargetPointerSize));
cb(ExprValue(std::move(ptr_type), std::move(contents)));
}
});
}
void AddressOfExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "ADDRESS_OF\n";
expr_->Print(out, indent + 1);
}
void ArrayAccessExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
left_->EvalFollowReferences(context, [inner = inner_, context,
cb = std::move(cb)](ErrOrValue left_value) mutable {
if (left_value.has_error()) {
cb(left_value);
} else {
// "left" has been evaluated, now do "inner".
inner->EvalFollowReferences(context, [context, left_value = left_value.take_value(),
cb = std::move(cb)](ErrOrValue inner_value) mutable {
if (inner_value.has_error()) {
cb(inner_value);
} else {
// Both "left" and "inner" has been evaluated.
int64_t offset = 0;
if (Err err = InnerValueToOffset(context, inner_value.value(), &offset); err.has_error())
cb(err);
else
ResolveArrayItem(std::move(context), std::move(left_value), offset, std::move(cb));
}
});
}
});
}
// static
Err ArrayAccessExprNode::InnerValueToOffset(const fxl::RefPtr<EvalContext>& context,
const ExprValue& inner, int64_t* offset) {
// Type should be some kind of number.
const Type* abstract_type = inner.type();
if (!abstract_type)
return Err("Bad type, please file a bug with a repro.");
// Skip "const", etc.
fxl::RefPtr<Type> concrete_type = context->GetConcreteType(abstract_type);
const BaseType* base_type = concrete_type->AsBaseType();
if (!base_type || !BaseTypeCanBeArrayIndex(base_type))
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::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
EvalBinaryOperator(std::move(context), left_, op_, right_, std::move(cb));
}
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::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
EvalBlockFrom(RefPtrTo(this), 0, context, std::move(cb));
}
void BlockExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "BLOCK\n";
for (const auto& stmt : statements_)
stmt->Print(out, indent + 1);
}
// static
void BlockExprNode::EvalBlockFrom(fxl::RefPtr<BlockExprNode> node, size_t index,
const fxl::RefPtr<EvalContext>& context, EvalCallback cb) {
if (index >= node->statements_.size())
return cb(ExprValue());
if (index + 1 == node->statements_.size()) {
// The last statement in a block.
switch (context->GetLanguage()) {
case ExprLanguage::kC:
// Discard the result since blocks in C aren't expressions.
node->statements_[index]->Eval(context, [cb = std::move(cb)](ErrOrValue result) mutable {
cb(result.value_or_empty());
});
break;
case ExprLanguage::kRust:
// The result of a block expression is the result of the last statement inside it.
node->statements_[index]->Eval(context, std::move(cb));
break;
}
} else {
// Need to evaluate a sequence of operations following this.
node->statements_[index]->Eval(context, [node = std::move(node), context, index,
cb = std::move(cb)](ErrOrValue result) mutable {
if (result.has_error())
return cb(std::move(result));
// If we called EvalBlock() directly here, block evaluation would be recursive. For blocks
// with several lines this will be fine, but this will fall down in the general case because
// there can be many statements in a block and we can overflow the stack. Instead, resume
// evaluation of the next statement back from the message loop. This will be slower but more
// predictable.
debug_ipc::MessageLoop::Current()->PostTask(
FROM_HERE, [node = std::move(node), context, index, cb = std::move(cb)]() mutable {
EvalBlockFrom(std::move(node), index + 1, context, std::move(cb));
});
});
}
}
void CastExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
from_->Eval(context, [context, cast_type = cast_type_, to_type = to_type_->type(),
cb = std::move(cb)](ErrOrValue value) mutable {
if (value.has_error())
cb(value);
else
CastExprValue(context, cast_type, value.value(), 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::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
EvalFromCond(RefPtrTo(this), 0, context, std::move(cb));
}
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\n";
else
out << IndentFor(indent + 1) << "ELSEIF\n";
conds_[i].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 ConditionExprNode::EvalFromCond(fxl::RefPtr<ConditionExprNode> node, size_t index,
const fxl::RefPtr<EvalContext>& context, EvalCallback cb) {
if (index >= node->conds_.size()) {
// Evaluate "else" block.
if (node->else_) {
node->else_->Eval(std::move(context), std::move(cb));
} else {
// No "else" block given, the result is empty.
cb(ExprValue());
}
return;
}
node->conds_[index].cond->EvalFollowReferences(
context,
[node = std::move(node), index, context, cb = std::move(cb)](ErrOrValue cond_result) mutable {
if (cond_result.has_error()) {
cb(cond_result);
return;
}
ErrOr<bool> bool_result = CastNumericExprValueToBool(context, cond_result.value());
if (bool_result.has_error()) {
cb(bool_result.err());
return;
}
if (bool_result.value()) {
// Condition succeeded, evaluate the current block.
node->conds_[index].then->Eval(context, std::move(cb));
} else {
// Condition failed, go to next one or else block.
EvalFromCond(node, index + 1, context, std::move(cb));
}
});
}
void DereferenceExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
expr_->EvalFollowReferences(context, [context, cb = std::move(cb)](ErrOrValue value) mutable {
if (value.has_error())
return cb(std::move(value));
// First check for pretty-printers for this type.
if (PrettyType* pretty = context->GetPrettyTypeManager().GetForType(value.value().type())) {
if (auto derefer = pretty->GetDereferencer()) {
// The pretty type supplies dereference function.
return derefer(context, value.value(), std::move(cb));
}
}
// Normal dereferencing operation.
ResolvePointer(context, value.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::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
// Actually calling functions in the target is not supported.
const char kNotSupportedMsg[] =
"Arbitrary function calls are not supported. Only certain built-in getters will work.";
if (!args_.empty())
return cb(Err(kNotSupportedMsg));
if (const MemberAccessExprNode* access = call_->AsMemberAccess()) {
// Object member calls, check for getters provided by pretty-printers.
std::string fn_name = access->member().GetFullName();
access->left()->EvalFollowReferences(
context,
[context, cb = std::move(cb), op = access->accessor(), fn_name](ErrOrValue value) mutable {
if (value.has_error())
return cb(value);
if (op.type() == ExprTokenType::kArrow)
EvalMemberPtrCall(context, value.value(), fn_name, std::move(cb));
else // Assume ".".
EvalMemberCall(context, value.value(), fn_name, std::move(cb));
});
return;
}
cb(Err(kNotSupportedMsg));
}
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::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
context->GetNamedValue(ident_, std::move(cb));
}
void IdentifierExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "IDENTIFIER(" << ident_.GetDebugName() << ")\n";
}
void LiteralExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
switch (token_.type()) {
case ExprTokenType::kInteger: {
cb(StringToNumber(token_.value()));
break;
}
case ExprTokenType::kFloat: {
cb(ValueForFloatToken(context->GetLanguage(), 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);
cb(ExprValue(MakeStringLiteralType(token_.value().size() + 1), std::move(string_as_array)));
break;
}
case ExprTokenType::kTrue: {
cb(ExprValue(true));
break;
}
case ExprTokenType::kFalse: {
cb(ExprValue(false));
break;
}
default:
FX_NOTREACHED();
}
}
void LiteralExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "LITERAL(" << token_.value() << ")\n";
}
void MemberAccessExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
bool by_pointer = accessor_.type() == ExprTokenType::kArrow;
left_->EvalFollowReferences(context, [context, by_pointer, member = member_,
cb = std::move(cb)](ErrOrValue base) mutable {
if (base.has_error())
return cb(base);
auto base_value = base.value();
// Rust references can be accessed with '.'
if (!by_pointer) {
fxl::RefPtr<Type> concrete_base = base_value.GetConcreteType(context.get());
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::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
if (const TypeExprNode* type_node = const_cast<ExprNode*>(expr_.get())->AsType()) {
// Types just get used directly.
SizeofType(context, type_node->type().get(), std::move(cb));
} 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 the
expr_->Eval(context, [context, cb = std::move(cb)](ErrOrValue value) mutable {
if (value.has_error())
return cb(value);
SizeofType(context, value.value().type(), std::move(cb));
});
}
}
void SizeofExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "SIZEOF\n";
expr_->Print(out, indent + 1);
}
// static
void SizeofExprNode::SizeofType(const fxl::RefPtr<EvalContext>& context, const Type* in_type,
EvalCallback cb) {
// References should get stripped (sizeof(char&) = 1).
if (!in_type)
return cb(Err("Can't do sizeof on a null type."));
fxl::RefPtr<Type> type = context->GetConcreteType(in_type);
if (type->is_declaration())
return cb(Err("Can't resolve forward declaration for '%s'.", in_type->GetFullName().c_str()));
if (DwarfTagIsEitherReference(type->tag()))
type = RefPtrTo(type->AsModifiedType()->modified().Get()->AsType());
if (!type)
return cb(Err("Symbol error for '%s'.", in_type->GetFullName().c_str()));
cb(ExprValue(type->byte_size()));
}
void TypeExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
// Doesn't make sense to evaluate a type, callers like casts that expect a type name will look
// into the node themselves.
cb(Err("Can not evaluate a type name."));
}
void TypeExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "TYPE(";
if (type_)
out << type_->GetFullName();
out << ")\n";
}
void UnaryOpExprNode::Eval(const fxl::RefPtr<EvalContext>& context, EvalCallback cb) const {
expr_->EvalFollowReferences(context,
[context, cb = std::move(cb), op = op_](ErrOrValue value) mutable {
if (value.has_error())
cb(value);
else
EvalUnaryOperator(context, op, value.value(), std::move(cb));
});
}
void UnaryOpExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "UNARY(" << op_.value() << ")\n";
expr_->Print(out, indent + 1);
}
} // namespace zxdb