blob: b92816d7b360e4443ae07f8179fd291f6a97e7b2 [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 "garnet/bin/zxdb/expr/expr_node.h"
#include <stdlib.h>
#include <ostream>
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/expr/eval_operators.h"
#include "garnet/bin/zxdb/expr/expr_eval_context.h"
#include "garnet/bin/zxdb/expr/expr_value.h"
#include "garnet/bin/zxdb/expr/resolve_array.h"
#include "garnet/bin/zxdb/expr/resolve_collection.h"
#include "garnet/bin/zxdb/expr/resolve_ptr_ref.h"
#include "garnet/bin/zxdb/expr/symbol_variable_resolver.h"
#include "garnet/bin/zxdb/symbols/arch.h"
#include "garnet/bin/zxdb/symbols/array_type.h"
#include "garnet/bin/zxdb/symbols/base_type.h"
#include "garnet/bin/zxdb/symbols/data_member.h"
#include "garnet/bin/zxdb/symbols/modified_type.h"
#include "garnet/bin/zxdb/symbols/symbol_data_provider.h"
#include "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 EvalUnaryOperator(const ExprToken& op_token, const ExprValue& value,
ExprNode::EvalCallback cb) {
// This manually extracts the value rather than calling PromoteTo64() so
// that the result type is exactly the same as the input type.
//
// TODO(brettw) when we add more mathematical operations we'll want a
// more flexible system for getting the results out.
if (op_token.type() == ExprToken::kMinus) {
// Currently "-" is the only unary operator. Since this is a debugger
// primarily for C-like languages, use the C rules for negating values: the
// result type is the same as the input, and negating an unsigned value
// gives the two's compliment (C++11 standard section 5.3.1).
switch (value.GetBaseType()) {
case BaseType::kBaseTypeSigned:
switch (value.data().size()) {
case sizeof(int8_t):
cb(Err(), ExprValue(-value.GetAs<int8_t>()));
return;
case sizeof(int16_t):
cb(Err(), ExprValue(-value.GetAs<int16_t>()));
return;
case sizeof(int32_t):
cb(Err(), ExprValue(-value.GetAs<int32_t>()));
return;
case sizeof(int64_t):
cb(Err(), ExprValue(-value.GetAs<int64_t>()));
return;
}
break;
case BaseType::kBaseTypeUnsigned:
switch (value.data().size()) {
case sizeof(uint8_t):
cb(Err(), ExprValue(-value.GetAs<uint8_t>()));
return;
case sizeof(uint16_t):
cb(Err(), ExprValue(-value.GetAs<uint16_t>()));
return;
case sizeof(uint32_t):
cb(Err(), ExprValue(-value.GetAs<uint32_t>()));
return;
case sizeof(uint64_t):
cb(Err(), ExprValue(-value.GetAs<uint64_t>()));
return;
}
break;
default:
FXL_NOTREACHED();
}
cb(Err("Negation for this value is not supported."), ExprValue());
return;
}
FXL_NOTREACHED();
cb(Err("Internal error evaluating unary operator."), ExprValue());
}
} // namespace
void ExprNode::EvalFollowReferences(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
Eval(context,
[ context, cb = std::move(cb) ](const Err& err, ExprValue value) {
if (err.has_error()) {
cb(err, ExprValue());
} else {
EnsureResolveReference(context->GetDataProvider(), std::move(value),
std::move(cb));
}
});
}
void AddressOfExprNode::Eval(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
expr_->EvalFollowReferences(
context, [cb = std::move(cb)](const Err& err, ExprValue value) {
if (value.source().type() != ExprValueSource::Type::kMemory) {
cb(Err("Can't take the address of a temporary."), ExprValue());
} else {
// Construct a pointer type to the variable.
auto ptr_type = fxl::MakeRefCounted<ModifiedType>(
Symbol::kTagPointerType, LazySymbol(value.type_ref()));
std::vector<uint8_t> contents;
contents.resize(kTargetPointerSize);
TargetPointer address = value.source().address();
memcpy(&contents[0], &address, sizeof(kTargetPointerSize));
cb(Err(), 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(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
left_->EvalFollowReferences(
context, [ inner = inner_, context, cb = std::move(cb) ](
const Err& err, ExprValue left_value) {
if (err.has_error()) {
cb(err, ExprValue());
} else {
// "left" has been evaluated, now do "inner".
inner->EvalFollowReferences(context, [
context, left_value = std::move(left_value), cb = std::move(cb)
](const Err& err, ExprValue inner_value) {
if (err.has_error()) {
cb(err, ExprValue());
} else {
// Both "left" and "inner" has been evaluated.
int64_t offset = 0;
Err offset_err = InnerValueToOffset(inner_value, &offset);
if (offset_err.has_error()) {
cb(offset_err, ExprValue());
} else {
DoAccess(std::move(context), std::move(left_value), offset,
std::move(cb));
}
}
});
}
});
}
// static
Err ArrayAccessExprNode::InnerValueToOffset(const ExprValue& inner,
int64_t* offset) {
// Type should be some kind of number.
const Type* type = inner.type();
if (!type)
return Err("Bad type, please file a bug with a repro.");
type = type->GetConcreteType(); // Skip "const", etc.
const BaseType* base_type = 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();
}
// static
void ArrayAccessExprNode::DoAccess(fxl::RefPtr<ExprEvalContext> context,
ExprValue left, int64_t offset,
EvalCallback cb) {
ResolveArray(
context->GetDataProvider(), left, static_cast<size_t>(offset),
static_cast<size_t>(offset) + 1,
[cb = std::move(cb)](const Err& err, std::vector<ExprValue> result) {
if (err.has_error()) {
cb(err, ExprValue());
return;
}
if (result.size() == 0) {
// Short read, array not big enough.
cb(Err("Array index out of range."), ExprValue());
return;
}
FXL_DCHECK(result.size() == 1);
cb(Err(), result[0]);
});
}
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(fxl::RefPtr<ExprEvalContext> 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 DereferenceExprNode::Eval(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
expr_->EvalFollowReferences(context, [ context, cb = std::move(cb) ](
const Err& err, ExprValue value) {
ResolvePointer(context->GetDataProvider(), value, std::move(cb));
});
}
void DereferenceExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "DEREFERENCE\n";
expr_->Print(out, indent + 1);
}
void IdentifierExprNode::Eval(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
// For now, pass the stringified identifier. In the future we'll want to pass
// the Identifier itself so the namespaces can be resolved.
context->GetNamedValue(
ident_, [cb = std::move(cb)](const Err& err, fxl::RefPtr<Symbol>,
ExprValue value) {
// Discard resolved symbol, we only need the value.
cb(err, std::move(value));
});
}
void IdentifierExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "IDENTIFIER(" << ident_.GetDebugName() << ")\n";
}
void LiteralExprNode::Eval(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
switch (token_.type()) {
case ExprToken::kInteger:
// The tokenizer will have already validated the integer format.
cb(Err(), ExprValue(static_cast<int64_t>(atoll(token_.value().c_str()))));
break;
case ExprToken::kTrue:
cb(Err(), ExprValue(true));
break;
case ExprToken::kFalse:
cb(Err(), ExprValue(false));
break;
default:
FXL_NOTREACHED();
}
}
void LiteralExprNode::Print(std::ostream& out, int indent) const {
out << IndentFor(indent) << "LITERAL(" << token_.value() << ")\n";
}
void MemberAccessExprNode::Eval(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
bool is_arrow = accessor_.type() == ExprToken::kArrow;
left_->EvalFollowReferences(
context, [ context, is_arrow, member = member_, cb = std::move(cb) ](
const Err& err, ExprValue base) {
if (!is_arrow) {
// "." operator.
ExprValue result;
Err err = ResolveMember(base, member, &result);
cb(err, std::move(result));
return;
}
// Everything else should be a -> operator.
ResolveMemberByPointer(
context, base, member,
[cb = std::move(cb)](const Err& err, fxl::RefPtr<Symbol>,
ExprValue result) {
// Discard resolved symbol, we only need the value.
cb(err, 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 UnaryOpExprNode::Eval(fxl::RefPtr<ExprEvalContext> context,
EvalCallback cb) const {
expr_->EvalFollowReferences(context, [ cb = std::move(cb), op = op_ ](
const Err& err, ExprValue value) {
if (err.has_error())
cb(err, std::move(value));
else
EvalUnaryOperator(op, 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