blob: bf54334a88660d66fef6eeef49b2645f52c84e8c [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/resolve_collection.h"
#include "src/developer/debug/zxdb/expr/eval_context.h"
#include "src/developer/debug/zxdb/expr/expr_parser.h"
#include "src/developer/debug/zxdb/expr/expr_value.h"
#include "src/developer/debug/zxdb/expr/find_name.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/collection.h"
#include "src/developer/debug/zxdb/symbols/data_member.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/identifier.h"
#include "src/developer/debug/zxdb/symbols/inherited_from.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/variable.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
// A wrapper around FindMember that issues errors rather than returning an optional. The base can be
// null for the convenience of the caller. On error, the output FoundMember will be untouched.
ErrOr<FoundMember> FindMemberWithErr(const Collection* base, const ParsedIdentifier& identifier) {
if (!base) {
return Err("Can't resolve '%s' on non-struct/class/union value.",
identifier.GetFullName().c_str());
}
FindNameOptions options(FindNameOptions::kNoKinds);
options.find_vars = true;
std::vector<FoundName> found;
FindMember(FindNameContext(), options, base, identifier, nullptr, &found);
if (!found.empty()) {
FXL_DCHECK(found[0].kind() == FoundName::kMemberVariable);
return found[0].member();
}
return Err("No member '%s' in %s '%s'.", identifier.GetFullName().c_str(), base->GetKindString(),
base->GetFullName().c_str());
}
// Variant of the above that extracts the collection type from the given base value.
ErrOr<FoundMember> FindMemberWithErr(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const ParsedIdentifier& identifier) {
fxl::RefPtr<Type> concrete_base = base.GetConcreteType(context.get());
if (!concrete_base)
return Err("No type information for collection.");
return FindMemberWithErr(concrete_base->AsCollection(), identifier);
}
Err GetErrorForInvalidMemberOf(const Collection* coll) {
return Err("Invalid data member for %s '%s'.", coll->GetKindString(),
coll->GetFullName().c_str());
}
// Tries to describe the type of the value as best as possible when a member access is invalid.
Err GetErrorForInvalidMemberOf(const ExprValue& value) {
if (!value.type())
return Err("No type information.");
if (const Collection* coll = value.type()->AsCollection())
return GetErrorForInvalidMemberOf(coll);
// Something other than a collection is the base.
return Err("Accessing a member of non-struct/class/union '%s'.",
value.type()->GetFullName().c_str());
}
// Validates the input member (it will null check) and extracts the type and size for the member.
// The size is returned separately because the member_type might be a forward declaration and
// we can't return a concrete type without breaking CV qualifiers.
Err GetMemberType(const fxl::RefPtr<EvalContext>& context, const Collection* coll,
const DataMember* member, fxl::RefPtr<Type>* member_type, uint32_t* member_size) {
if (!member)
return GetErrorForInvalidMemberOf(coll);
*member_type = RefPtrTo(member->type().Get()->AsType());
if (!*member_type) {
return Err("Bad type information for '%s.%s'.", coll->GetFullName().c_str(),
member->GetAssignedName().c_str());
}
return Err();
}
void DoResolveMemberByPointer(const fxl::RefPtr<EvalContext>& context, const ExprValue& base_ptr,
const Collection* pointed_to_type, const FoundMember& member,
fit::callback<void(ErrOrValue)> cb) {
Err err = base_ptr.EnsureSizeIs(kTargetPointerSize);
if (err.has_error())
return cb(err);
fxl::RefPtr<Type> member_type;
uint32_t member_size = 0;
err = GetMemberType(context, pointed_to_type, member.data_member(), &member_type, &member_size);
if (err.has_error())
return cb(err);
TargetPointer base_address = base_ptr.GetAs<TargetPointer>();
ResolvePointer(context, base_address + member.data_member_offset(), std::move(member_type),
std::move(cb));
}
// Extracts an embedded type inside of a base. This can be used for finding collection data members
// and inherited classes, both of which consist of a type and an offset.
ErrOrValue ExtractSubType(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
fxl::RefPtr<Type> sub_type, uint32_t offset) {
// Need a valid size for the inside type so it has to be concrete.
auto concrete = context->GetConcreteType(sub_type.get());
uint32_t size = concrete->byte_size();
if (offset + size > base.data().size()) {
return Err("Invalid data offset %" PRIu32 " in object of size %zu.", offset,
base.data().size());
}
std::vector<uint8_t> member_data(base.data().begin() + offset,
base.data().begin() + (offset + size));
return ExprValue(std::move(sub_type), std::move(member_data),
base.source().GetOffsetInto(offset));
}
// This variant takes a precomputed offset of the data member in the base class. This is to support
// the case where the data member is in a derived class (the derived class will have its own
// offset).
ErrOrValue DoResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const FoundMember& member) {
fxl::RefPtr<Type> concrete_type = base.GetConcreteType(context.get());
const Collection* coll = nullptr;
if (!base.type() || !(coll = concrete_type->AsCollection()))
return Err("Can't resolve data member on non-struct/class value.");
fxl::RefPtr<Type> member_type;
uint32_t member_size = 0;
Err err = GetMemberType(context, coll, member.data_member(), &member_type, &member_size);
if (err.has_error())
return err;
return ExtractSubType(context, base, std::move(member_type), member.data_member_offset());
}
// As with DoResolveNonstaticMember, this takes a precomputed offset. It is asynchronous to handle
// static data members that may require a memory fetch.
void DoResolveMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const FoundMember& member, EvalCallback cb) {
FXL_DCHECK(member.data_member());
if (member.data_member()->is_external()) {
// A forward-declared static member.
return context->GetVariableValue(
RefPtrTo(member.data_member()),
[cb = std::move(cb)](ErrOrValue value, fxl::RefPtr<Symbol>) mutable {
// Discard the symbol since it's not needed in this path.
cb(std::move(value));
});
}
// Normal nonstatic resolution is synchronous.
cb(DoResolveNonstaticMember(context, base, member));
}
} // namespace
void ResolveMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const DataMember* member, EvalCallback cb) {
if (!member)
return cb(GetErrorForInvalidMemberOf(base));
DoResolveMember(context, base, FoundMember(member, member->member_location()), std::move(cb));
}
void ResolveMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const ParsedIdentifier& identifier, EvalCallback cb) {
ErrOr<FoundMember> found = FindMemberWithErr(context, base, identifier);
if (found.has_error())
return cb(found.err());
DoResolveMember(context, base, found.value(), std::move(cb));
}
ErrOrValue ResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const DataMember* member) {
if (!member)
return GetErrorForInvalidMemberOf(base);
return DoResolveNonstaticMember(context, base, FoundMember(member, member->member_location()));
}
ErrOrValue ResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const ParsedIdentifier& identifier) {
ErrOr<FoundMember> found = FindMemberWithErr(context, base, identifier);
if (found.has_error())
return found.err();
return DoResolveNonstaticMember(context, base, found.value());
}
ErrOrValue ResolveNonstaticMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
std::initializer_list<std::string> names) {
ExprValue cur = base;
for (const std::string& name : names) {
ParsedIdentifier id;
if (Err err = ExprParser::ParseIdentifier(name, &id); err.has_error())
return err;
ErrOrValue result = ResolveNonstaticMember(context, cur, id);
if (result.has_error())
return result.err();
cur = std::move(result.take_value());
}
return cur;
}
void ResolveMemberByPointer(const fxl::RefPtr<EvalContext>& context, const ExprValue& base_ptr,
const FoundMember& found_member, fit::callback<void(ErrOrValue)> cb) {
fxl::RefPtr<Collection> pointed_to;
Err err = GetConcretePointedToCollection(context, base_ptr.type(), &pointed_to);
if (err.has_error())
return cb(err);
DoResolveMemberByPointer(context, base_ptr, pointed_to.get(), found_member, std::move(cb));
}
void ResolveMemberByPointer(const fxl::RefPtr<EvalContext>& context, const ExprValue& base_ptr,
const ParsedIdentifier& identifier,
fit::callback<void(ErrOrValue, fxl::RefPtr<DataMember>)> cb) {
fxl::RefPtr<Collection> coll;
if (Err err = GetConcretePointedToCollection(context, base_ptr.type(), &coll); err.has_error())
return cb(err, nullptr);
ErrOr<FoundMember> found = FindMemberWithErr(coll.get(), identifier);
if (found.has_error())
return cb(found.err(), nullptr);
DoResolveMemberByPointer(
context, base_ptr, coll.get(), found.value(),
[cb = std::move(cb), member_ref = RefPtrTo(found.value().data_member())](
ErrOrValue value) mutable { cb(std::move(value), std::move(member_ref)); });
}
ErrOrValue ResolveInherited(const fxl::RefPtr<EvalContext>& context, const ExprValue& value,
const InheritedFrom* from) {
const Type* from_type = from->from().Get()->AsType();
if (!from_type)
return GetErrorForInvalidMemberOf(value);
return ExtractSubType(context, value, RefPtrTo(from_type), from->offset());
}
ErrOrValue ResolveInherited(const fxl::RefPtr<EvalContext>& context, const ExprValue& value,
fxl::RefPtr<Type> base_type, uint64_t offset) {
return ExtractSubType(context, value, std::move(base_type), offset);
}
Err GetConcretePointedToCollection(const fxl::RefPtr<EvalContext>& eval_context, const Type* input,
fxl::RefPtr<Collection>* pointed_to) {
fxl::RefPtr<Type> to_type;
if (Err err = GetPointedToType(eval_context, input, &to_type); err.has_error())
return err;
to_type = eval_context->GetConcreteType(to_type.get());
if (const Collection* collection = to_type->AsCollection()) {
*pointed_to = fxl::RefPtr<Collection>(const_cast<Collection*>(collection));
return Err();
}
return Err("Attempting to dereference a pointer to '%s' which is not a class, struct, or union.",
to_type->GetFullName().c_str());
}
} // namespace zxdb