blob: 3d81e56d2fa42049e70142cf506898f692f444ce [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/async_dwarf_expr_eval.h"
#include "src/developer/debug/zxdb/expr/bitfield.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_base.h"
#include "src/developer/debug/zxdb/expr/resolve_const_value.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/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.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();
}
// Backend for ResolveMemberByPointer variants that does the actual memory fetch. It's given a
// concrete pointer and pointed-to type, along with a specific found member inside it.
void DoResolveMemberByPointer(const fxl::RefPtr<EvalContext>& context, const ExprValue& base_ptr,
const Collection* pointed_to_type, const FoundMember& member,
EvalCallback cb) {
if (Err err = base_ptr.EnsureSizeIs(kTargetPointerSize); err.has_error())
return cb(err);
if (member.data_member()->is_bitfield()) {
// The bitfield case is complicated. Get the full pointed-to collection value and then resolve
// the member access using "." mode to re-use the non-pointer codepath. This avoids duplicating
// the bitfield logic. (This is actually valid logic for every case but fetches unnecessary
// memory which we avoid in the common case below).
ResolvePointer(context, base_ptr,
[context, member, cb = std::move(cb)](ErrOrValue value) mutable {
if (value.has_error())
return cb(std::move(value));
cb(ResolveBitfieldMember(context, value.value(), member));
});
} else {
// Common case for non-bitfield members. We can avoid fetching the entire structure (which can
// be very large in some edge cases) and just get fetch the memory for the item we need.
fxl::RefPtr<Type> member_type;
uint32_t member_size = 0;
Err 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));
}
}
// Implementation of ResolveMemberByPointer with a named member. This does everything except handle
// conversion to base classes.
void DoResolveMemberNameByPointer(const fxl::RefPtr<EvalContext>& context,
const ExprValue& base_ptr, const ParsedIdentifier& identifier,
fit::callback<void(ErrOrValue, const FoundMember&)> cb) {
fxl::RefPtr<Collection> coll;
if (Err err = GetConcretePointedToCollection(context, base_ptr.type(), &coll); err.has_error())
return cb(err, FoundMember());
ErrOr<FoundMember> found = FindMemberWithErr(coll.get(), identifier);
if (found.has_error())
return cb(found.err(), FoundMember());
// Dispatch to low-level version now that the member is found by name.
DoResolveMemberByPointer(context, base_ptr, coll.get(), found.value(),
[cb = std::move(cb), found = found.value()](ErrOrValue value) mutable {
cb(std::move(value), found);
});
}
// 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) {
// Bitfields get special handling.
if (member.data_member()->is_bitfield())
return ResolveBitfieldMember(context, base, member);
// Constant value members.
if (member.data_member()->const_value().has_value())
return ResolveConstValue(context, member.data_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. In C++ static members can't be bitfields so we don't handle
// them.
return context->GetVariableValue(RefPtrTo(member.data_member()), std::move(cb));
}
// Normal nonstatic resolution is synchronous.
cb(DoResolveNonstaticMember(context, base, member));
}
} // namespace
void ResolveMember(const fxl::RefPtr<EvalContext>& context, const ExprValue& base,
const FoundMember& member, EvalCallback cb) {
if (member.is_null())
return cb(GetErrorForInvalidMemberOf(base));
DoResolveMember(context, base, member, 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 FoundMember& member) {
if (member.is_null())
return GetErrorForInvalidMemberOf(base);
return DoResolveNonstaticMember(context, base, member);
}
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, EvalCallback 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, const FoundMember&)> cb) {
if (context->ShouldPromoteToDerived()) {
// Check to see if this is a reference to a base class that we can convert to a derived class.
PromotePtrRefToDerived(context, PromoteToDerived::kPtrOnly, std::move(base_ptr),
[context, identifier, cb = std::move(cb)](ErrOrValue result) mutable {
if (result.has_error()) {
cb(result, {});
} else {
DoResolveMemberNameByPointer(context, result.take_value(),
identifier, std::move(cb));
}
});
} else {
// No magic base-class resolution is required, just check the pointer.
DoResolveMemberNameByPointer(context, base_ptr, identifier, std::move(cb));
}
}
void ResolveInherited(const fxl::RefPtr<EvalContext>& context, const ExprValue& value,
const InheritedFrom* from, const SymbolContext& from_symbol_context,
fit::callback<void(ErrOrValue)> cb) {
const Type* from_type = from->from().Get()->AsType();
if (!from_type)
return cb(GetErrorForInvalidMemberOf(value));
if (from->kind() == InheritedFrom::kConstant) // Easy constant case.
return cb(ExtractSubType(context, value, RefPtrTo(from_type), from->offset()));
// Everything else is an expression which needs to be evaluated.
if (value.source().type() != ExprValueSource::Type::kMemory || value.source().is_bitfield())
return cb(Err("Can't evaluate virtual inheritance on an object without a memory address."));
TargetPointer object_ptr = value.source().address();
// This is normally async for virtual inheritance. Virtual inheritance is implemented as every
// derived class having a pointer to the base class rather than an offset (so multiple derived
// classes can point to the same base). Therefore, the expressions normally look like
// "take offset from object pointer and dereference."
FXL_DCHECK(from->kind() == InheritedFrom::kExpression);
auto evaluator = fxl::MakeRefCounted<AsyncDwarfExprEval>(std::move(cb), RefPtrTo(from_type));
// Inheritance expressions are evaluated by pushing the object pointer on the stack before
// execution.
evaluator->dwarf_eval().Push(object_ptr);
// This will normally have to do some memory fetches to request the vtable data that will be
// referenced by the expression. Then it will fetch the base class anew from memory. This second
// fetch is unnecessary because we know it's contained inside |value| (since it's a base class of
// our input). It could be optimized to take advantage of this, but virtual inheritance is usually
// uncommon and this implementation is simpler.
evaluator->Eval(context, from_symbol_context, from->location_expression());
}
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