blob: 72962395aa7438c77a3d55da6128512b31de403c [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/find_name.h"
#include <lib/syslog/cpp/macros.h>
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/expr/expr_parser.h"
#include "src/developer/debug/zxdb/expr/found_name.h"
#include "src/developer/debug/zxdb/expr/index_walker.h"
#include "src/developer/debug/zxdb/expr/resolve_collection.h"
#include "src/developer/debug/zxdb/expr/resolve_type.h"
#include "src/developer/debug/zxdb/symbols/code_block.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/index.h"
#include "src/developer/debug/zxdb/symbols/index_node.h"
#include "src/developer/debug/zxdb/symbols/loaded_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/modified_type.h"
#include "src/developer/debug/zxdb/symbols/module_symbols.h"
#include "src/developer/debug/zxdb/symbols/namespace.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/target_symbols.h"
namespace zxdb {
namespace {
// FindName doesn't support every type of input name.
enum class FindNameSupported {
// Normal symbol completely supported by FindName.
kFully,
// Query the module symbols for this name. Used for names not in the index like ELF symbols.
kModuleSymbolsOnly,
// This name can't use FindName. For example, registers can't be looked up.
kNo,
};
FindNameSupported GetSupported(const ParsedIdentifier& identifier) {
for (const auto& comp : identifier.components()) {
switch (comp.special()) {
case SpecialIdentifier::kNone:
case SpecialIdentifier::kAnon:
break; // Normal boring component.
case SpecialIdentifier::kEscaped:
case SpecialIdentifier::kLast:
FX_NOTREACHED(); // These annotations shouldn't appear in identifiers.
break;
case SpecialIdentifier::kMain:
case SpecialIdentifier::kElf:
case SpecialIdentifier::kPlt:
// These symbols are queried directly from ModuleSymbols.
return FindNameSupported::kModuleSymbolsOnly;
case SpecialIdentifier::kRegister:
return FindNameSupported::kNo; // Can't look up registers in the symbols.
}
}
return FindNameSupported::kFully;
}
// Returns true if an index search is required for the options. Everything but local variables
// requires the index.
bool OptionsRequiresIndex(const FindNameOptions& options) {
return options.find_types || options.find_type_defs || options.find_functions ||
options.find_templates || options.find_namespaces;
}
// Returns true if the |name| of an object matches what we're |looking_for| given the current
// options.
bool NameMatches(const FindNameOptions& options, const std::string& name,
const std::string& looking_for) {
if (options.how == FindNameOptions::kPrefix)
return StringStartsWith(name, looking_for);
return name == looking_for;
}
// Iterates over the variables in the given vector, calling the visit callback for each as long as
// the visitor says to continue. Searches the given vector of values for one with the given name. If
// found, returns it, otherwise returns null.
VisitResult VisitVariableVector(const std::vector<LazySymbol>& vect,
fit::function<VisitResult(const Variable*)>& visitor) {
for (const auto& cur : vect) {
const Variable* var = cur.Get()->As<Variable>();
if (!var)
continue; // Symbols are corrupt.
VisitResult vr = visitor(var);
if (vr != VisitResult::kContinue)
return vr;
}
return VisitResult::kContinue;
}
FoundName FoundNameFromSymbolRef(const ModuleSymbols* module_symbols,
const FindNameOptions& options, const IndexNode::SymbolRef& ref) {
LazySymbol lazy_symbol = module_symbols->IndexSymbolRefToSymbol(ref);
if (!lazy_symbol)
return FoundName();
const Symbol* symbol = lazy_symbol.Get();
if (const Function* func = symbol->As<Function>()) {
if (options.find_functions)
return FoundName(func);
return FoundName();
}
if (const Variable* var = symbol->As<Variable>()) {
if (options.find_vars)
return FoundName(var);
return FoundName();
}
if (const DataMember* dm = symbol->As<DataMember>()) {
FX_DCHECK(dm->is_external()); // Only static ("external") members should be in the index.
if (options.find_vars)
return FoundName(nullptr, FoundMember(nullptr, dm));
return FoundName();
}
if (const Namespace* ns = symbol->As<Namespace>()) {
if (options.find_namespaces)
return FoundName(FoundName::kNamespace, ns->GetFullName());
return FoundName();
}
if (const Type* type = symbol->As<Type>()) {
if (options.find_types) // All types.
return FoundName(RefPtrTo(type));
if (options.find_type_defs && !type->is_declaration()) // Type definitions only.
return FoundName(RefPtrTo(type));
return FoundName();
}
return FoundName();
}
VisitResult GetNamesFromDieList(const ModuleSymbols* module_symbols, const FindNameOptions& options,
const std::vector<IndexNode::SymbolRef>& dies,
std::vector<FoundName>* results) {
for (const IndexNode::SymbolRef& cur : dies) {
if (FoundName found = FoundNameFromSymbolRef(module_symbols, options, cur))
results->push_back(std::move(found));
if (results->size() >= options.max_results)
return VisitResult::kDone;
}
return VisitResult::kContinue;
}
// Finds the things matching the given prefix in the map of the index node. This map will correspond
// to indexed symbols of a given kind (functions, types, namespaces, etc.).
VisitResult AddPrefixesFromMap(const FindNameOptions& options, const ModuleSymbols* module_symbols,
const IndexNode::Map& map, const std::string& prefix,
std::vector<FoundName>* results) {
auto cur = map.lower_bound(prefix);
while (cur != map.end() && NameMatches(options, cur->first, prefix)) {
VisitResult vr = GetNamesFromDieList(module_symbols, options, cur->second.dies(), results);
if (vr != VisitResult::kContinue)
return vr;
++cur;
}
return VisitResult::kContinue;
}
// Adds the matches from the given node. The walker's current position should already match the name
// of the thing we're looking for.
VisitResult AddMatches(const FindNameOptions& options, const ModuleSymbols* module_symbols,
const IndexWalker& walker, const ParsedIdentifier& looking_for,
std::vector<FoundName>* results) {
// Namespaces are special because they don't store any DIEs. If we're looking for a namespace
// need to add the current node name.
if (options.find_namespaces) {
for (const auto& current_node : walker.current()) {
// Got a namespace with the name.
if (current_node->kind() == IndexNode::Kind::kNamespace) {
results->emplace_back(FoundName::kNamespace, looking_for);
if (results->size() >= options.max_results)
return VisitResult::kDone;
break;
}
}
}
// Check for things that have DIEs. Note that "templates" isn't included in this list because
// those are treated separately (they're a prefix search on a type).
if (options.find_types || options.find_type_defs || options.find_functions || options.find_vars) {
for (const auto& current_node : walker.current()) {
VisitResult vr = GetNamesFromDieList(module_symbols, options, current_node->dies(), results);
if (vr != VisitResult::kContinue)
return vr;
}
}
return VisitResult::kContinue;
}
// Given a scope, finds all things inside of it that match the prefix (the last component of
// "looking_for") and adds them to the results.
VisitResult AddPrefixes(const FindNameOptions& options, const ModuleSymbols* module_symbols,
const IndexWalker& scope, const ParsedIdentifier& looking_for,
std::vector<FoundName>* results) {
std::string prefix = looking_for.components().back().GetName(false);
// Check all nodes representing this scope (there could be multiple paths in the index
// corresponding to symbols of different kinds).
for (const auto& current_node : scope.current()) {
// Depending on the kind of thing the caller is interested in, we only need to look at
// certain parts of each node.
if (options.find_types || options.find_templates || options.find_type_defs) {
VisitResult vr =
AddPrefixesFromMap(options, module_symbols, current_node->types(), prefix, results);
if (vr != VisitResult::kContinue)
return vr;
}
if (options.find_functions) {
VisitResult vr =
AddPrefixesFromMap(options, module_symbols, current_node->functions(), prefix, results);
if (vr != VisitResult::kContinue)
return vr;
}
if (options.find_vars) {
VisitResult vr =
AddPrefixesFromMap(options, module_symbols, current_node->vars(), prefix, results);
if (vr != VisitResult::kContinue)
return vr;
}
if (options.find_namespaces) {
// Namespaces get special handling because DIEs are not actually stored for them, just
// a "namespace" IndexNode.
auto cur = current_node->namespaces().lower_bound(prefix);
while (cur != current_node->namespaces().end() && NameMatches(options, cur->first, prefix)) {
// Compute the full name of this namespace.
ParsedIdentifier full_name = looking_for.GetScope();
full_name.AppendComponent(ParsedIdentifierComponent(cur->first));
results->emplace_back(FoundName::kNamespace, std::move(full_name));
if (results->size() >= options.max_results)
return VisitResult::kDone;
++cur;
}
}
}
return VisitResult::kContinue;
}
VisitResult VisitPerModule(const FindNameContext& context,
fit::function<VisitResult(const ModuleSymbols*)> visitor) {
if (context.module_symbols) {
// Search in the current module.
VisitResult vr = visitor(context.module_symbols);
if (vr != VisitResult::kContinue)
return vr;
}
// Search in all other modules as a fallback, if any.
if (context.target_symbols) {
for (const ModuleSymbols* m : context.target_symbols->GetModuleSymbols()) {
if (m != context.module_symbols) { // Don't re-search current one.
VisitResult vr = visitor(m);
if (vr != VisitResult::kContinue)
return vr;
}
}
}
return VisitResult::kContinue;
}
// Searches for |looking_for| at a given level of the index, as stored in the given IndexWalker.
VisitResult FindInIndexLevel(const FindNameOptions& options, const ModuleSymbols* module_symbols,
const IndexWalker& walker, const ParsedIdentifier& looking_for,
std::vector<FoundName>* results) {
if (looking_for.empty())
return VisitResult::kDone;
ParsedIdentifier looking_for_scope = looking_for.GetScope();
// Walk into all but the last node of the identifier (the last one is the part that needs
// completion).
IndexWalker scope_walker(walker);
if (!scope_walker.WalkInto(looking_for_scope))
return VisitResult::kContinue;
// Need to separate out prefix so we can take advantage of the template canonicalization of the
// IndexWalker in the exact match case. This means that we can't currently do prefix matches of
// templates that are canonicalized differently than DWARF represents them.
if (options.how == FindNameOptions::kPrefix) {
VisitResult vr = AddPrefixes(options, module_symbols, scope_walker, looking_for, results);
if (vr != VisitResult::kContinue)
return vr;
} else {
// Exact match case.
//
// TODO(brettw) in cases where we know the exact type of the thing we're looking for (e.g.
// "namespaces") we could optimize by adding a way for the walker to only go into that kind of
// child IndexNode.
if (scope_walker.WalkInto(looking_for.components().back())) {
VisitResult vr = AddMatches(options, module_symbols, scope_walker, looking_for, results);
if (vr != VisitResult::kContinue)
return vr;
// Undo the walk we just made so we can search for templates below using the same scope.
scope_walker.WalkUp();
}
}
// We also want to know if there are any templates with that name which will look like
// "foo::bar<...". In that case, do a prefix search with an appended "<" and see if there are any
// results. Don't bother if the input already has a template.
//
// General prefix matches and non-template queries (if also included) will already have been
// caught above so don't handle here.
if (options.how == FindNameOptions::kExact && options.find_templates &&
!looking_for.components().back().has_template()) {
// This is the prefix for the type we look for to find the template.
std::string prefix = looking_for.components().back().GetName(false);
prefix.push_back('<');
// Check for types in each node at this scope for prefix matches. If any of them match, return
// one. We don't need to return all of them since a template query just returns whether a
// template of that name exists (each specialization is a "type" instead).
for (const auto& current_node : scope_walker.current()) {
auto found = current_node->types().lower_bound(prefix);
// Note: need StringBeginsWith rather than NameMatches since we always want to do a prefix
// search rather than applying the current prefix/exact mode from the options.
if (found != current_node->types().end() && StringStartsWith(found->first, prefix)) {
results->emplace_back(FoundName::kTemplate, looking_for.GetFullName());
if (results->size() >= options.max_results)
return VisitResult::kDone; // Don't need to look for anything else.
// Don't need to look for more template matches but may need to continue the search
// for other stuff.
break;
}
}
}
return VisitResult::kContinue;
}
// Searches the given index node and recursively, all child namespaces. This is used to implement
// the "all namespaces" search mode.
VisitResult FindInIndexLevelRecursiveNs(const FindNameOptions& options,
const ModuleSymbols* module_symbols,
const IndexWalker& walker,
const ParsedIdentifier& looking_for,
std::vector<FoundName>* results) {
// Search in this node first.
VisitResult vr = FindInIndexLevel(options, module_symbols, walker, looking_for, results);
if (vr != VisitResult::kContinue)
return vr;
// Recursively search in child namespaces.
IndexWalker ns_walker(walker); // Stores the namespace we're checking.
for (const auto& current_node : walker.current()) {
for (const auto& [ns_name, ns_node] : current_node->namespaces()) {
// Check one specific child namespace index node.
ns_walker.WalkIntoSpecific(&ns_node);
vr = FindInIndexLevelRecursiveNs(options, module_symbols, ns_walker, looking_for, results);
if (vr != VisitResult::kContinue)
return vr;
ns_walker.WalkUp();
}
}
return VisitResult::kContinue;
}
// Searches a specific collection for a data member with the given |looking_for| name. This is
// a helper for FindMember that searches one level.
//
// This takes one additional parameter over FindMember: the |cur_offset| which is the offset of
// the current collection being iterated over in whatever contains it.
VisitResult FindMemberOn(const FindNameContext& context, const FindNameOptions& options,
const InheritancePath& path, const ParsedIdentifier& looking_for,
const Variable* optional_object_ptr, std::vector<FoundName>* result) {
auto base_coll = GetConcreteTypeAs<Collection>(context, path.base());
if (!base_coll)
return VisitResult::kContinue; // Nothing to do at this level.
// Data member iteration.
if (const std::string* looking_for_name = GetSingleComponentIdentifierName(looking_for);
looking_for_name && options.find_vars) {
for (const auto& lazy : base_coll->data_members()) {
if (const DataMember* data = lazy.Get()->As<DataMember>()) {
// TODO(brettw) allow "BaseClass::foo" syntax for specifically naming a member of a base
// class. Watch out: the base class could be qualified (or not) in various ways:
// ns::BaseClass::foo, BaseClass::foo, etc.
if (NameMatches(options, data->GetAssignedName(), *looking_for_name)) {
result->emplace_back(optional_object_ptr, path, data);
if (result->size() >= options.max_results)
return VisitResult::kDone;
}
// Check for anonymous unions.
if (data->GetAssignedName().empty()) {
// Recursively search into anonymous unions. We assume this is C++ and anonymous
// collections can't have base classes so we don't need to VisitClassHierarchy().
if (const Collection* member_coll = data->type().Get()->As<Collection>()) {
// Construct a new inheritance path with a synthetic InheritedFrom member to represent
// the offset of the anonymous collection within the containing one.
InheritancePath synthetic_path(path);
synthetic_path.path().emplace_back(
fxl::MakeRefCounted<InheritedFrom>(RefPtrTo(member_coll), data->member_location()),
RefPtrTo(member_coll));
VisitResult visit_result = FindMemberOn(context, options, synthetic_path, looking_for,
optional_object_ptr, result);
if (visit_result != VisitResult::kContinue)
return visit_result;
}
}
}
} // for (data_members)
}
// Index node iteration for this class' scope.
if (OptionsRequiresIndex(options)) {
ParsedIdentifier container_name = ToParsedIdentifier(base_coll->GetIdentifier());
// Don't search previous scopes (pass |search_containing| = false). If a class derives from a
// class in another namespace, that doesn't bring the other namespace in the current scope.
VisitResult vr = FindIndexedName(context, options, container_name, looking_for, false, result);
if (vr != VisitResult::kContinue)
return vr;
}
return VisitResult::kContinue;
}
} // namespace
FindNameContext::FindNameContext(const ProcessSymbols* ps, const SymbolContext& symbol_context,
const CodeBlock* b)
: block(b) {
if (ps) {
target_symbols = ps->target_symbols();
if (!symbol_context.is_relative()) {
// Valid symbol context was given, try to find the corresponding module.
uint64_t module_load_address = symbol_context.RelativeToAbsolute(0);
for (const LoadedModuleSymbols* mod : ps->GetLoadedModuleSymbols()) {
if (mod->load_address() == module_load_address) {
module_symbols = mod->module_symbols();
break;
}
}
}
}
}
FindNameContext::FindNameContext(const TargetSymbols* ts) : target_symbols(ts) {}
FoundName FindName(const FindNameContext& context, const FindNameOptions& options,
const ParsedIdentifier& identifier) {
FindNameOptions new_opts(options);
new_opts.max_results = 1;
std::vector<FoundName> results;
FindName(context, new_opts, identifier, &results);
if (!results.empty())
return std::move(results[0]);
return FoundName();
}
void FindName(const FindNameContext& context, const FindNameOptions& options,
const ParsedIdentifier& looking_for, std::vector<FoundName>* results) {
FindNameSupported supported = GetSupported(looking_for);
if (supported == FindNameSupported::kNo)
return; // Nothing to do for these symbols.
// This only works for fully-supported identifier types. Some work only with the module-specific
// symbol query we do below.
if (supported == FindNameSupported::kFully && options.search_mode == FindNameOptions::kLexical &&
options.find_vars && context.block &&
looking_for.qualification() == IdentifierQualification::kRelative) {
// Search for local variables and function parameters.
FindLocalVariable(options, context.block, looking_for, results);
if (results->size() >= options.max_results)
return;
// Search the "this" object.
FindMemberOnThis(context, options, looking_for, results);
if (results->size() >= options.max_results)
return;
}
// Fall back to searching global vars.
if (context.module_symbols || context.target_symbols) {
// Get the scope for the current function. This may fail in which case we'll be left with an
// empty current scope. This is non-fatal: it just means we won't implicitly search the current
// namespace and will search only the global one.
ParsedIdentifier current_scope;
if (context.block) {
if (auto function = context.block->GetContainingFunction()) {
current_scope = ToParsedIdentifier(function->GetIdentifier()).GetScope();
}
}
FindIndexedName(context, options, current_scope, looking_for, true, results);
}
}
VisitResult VisitLocalVariables(const CodeBlock* block,
fit::function<VisitResult(const Variable*)> visitor) {
return VisitLocalBlocks(block, [&visitor](const CodeBlock* cur_block) {
// Local variables in this block.
VisitResult vr = VisitVariableVector(cur_block->variables(), visitor);
if (vr != VisitResult::kContinue)
return vr;
// Function parameters.
if (const Function* function = cur_block->As<Function>()) {
// Found a function, check for a match in its parameters.
vr = VisitVariableVector(function->parameters(), visitor);
if (vr != VisitResult::kContinue)
return vr;
}
return VisitResult::kContinue;
});
}
void FindLocalVariable(const FindNameOptions& options, const CodeBlock* block,
const ParsedIdentifier& looking_for, std::vector<FoundName>* results) {
FX_DCHECK(options.search_mode == FindNameOptions::kLexical);
// TODO(fxbug.dev/6038) lookup type names defined locally in this function.
// Local variables can only be simple names.
const std::string* name = GetSingleComponentIdentifierName(looking_for);
if (!name)
return;
VisitLocalVariables(block, [&options, name, &results](const Variable* var) {
if (NameMatches(options, var->GetAssignedName(), *name)) {
results->emplace_back(var);
if (results->size() >= options.max_results)
return VisitResult::kDone;
}
return VisitResult::kContinue;
});
}
void FindMember(const FindNameContext& context, const FindNameOptions& options,
const Collection* object, const ParsedIdentifier& looking_for,
const Variable* optional_object_ptr, std::vector<FoundName>* result) {
FX_DCHECK(options.search_mode == FindNameOptions::kLexical);
VisitClassHierarchy(object, [&context, &options, &looking_for, optional_object_ptr,
result](const InheritancePath& path) {
// Called for each collection in the class hierarchy.
return FindMemberOn(context, options, path, looking_for, optional_object_ptr, result);
});
}
void FindMemberOnThis(const FindNameContext& context, const FindNameOptions& options,
const ParsedIdentifier& looking_for, std::vector<FoundName>* result) {
FX_DCHECK(options.search_mode == FindNameOptions::kLexical);
if (!context.block)
return; // No current code.
auto function = context.block->GetContainingFunction();
if (!function)
return;
const Variable* this_var = function->GetObjectPointerVariable();
if (!this_var)
return; // No "this" pointer.
// Type for "this" (expect it to be a pointer).
fxl::RefPtr<ModifiedType> modified = GetConcreteTypeAs<ModifiedType>(context, this_var->type());
if (!modified || modified->tag() != DwarfTag::kPointerType)
return; // Bad type or not a pointer.
// Extract the pointed-to collection.
fxl::RefPtr<Collection> this_coll = GetConcreteTypeAs<Collection>(context, modified->modified());
if (!this_coll)
return; // "this" is not a collection, probably corrupt.
FindMember(context, options, this_coll.get(), looking_for, this_var, result);
}
VisitResult FindIndexedName(const FindNameContext& context, const FindNameOptions& options,
const ParsedIdentifier& current_scope,
const ParsedIdentifier& looking_for, bool search_containing,
std::vector<FoundName>* results) {
return VisitPerModule(context, [&options, &current_scope, &looking_for, search_containing,
results](const ModuleSymbols* ms) {
FindIndexedNameInModule(options, ms, current_scope, looking_for, search_containing, results);
return results->size() >= options.max_results ? VisitResult::kDone : VisitResult::kContinue;
});
}
void FindIndexedNameInModule(const FindNameOptions& options, const ModuleSymbols* module_symbols,
const ParsedIdentifier& current_scope,
const ParsedIdentifier& looking_for, bool search_containing,
std::vector<FoundName>* results) {
if (GetSupported(looking_for) == FindNameSupported::kModuleSymbolsOnly) {
// These symbols can only be looked up by ModuleSymbols and aren't in the normal index. Defer
// to the symbol system for these lookups.
for (const Location& loc : module_symbols->ResolveInputLocation(
SymbolContext::ForRelativeAddresses(), InputLocation(ToIdentifier(looking_for)))) {
results->emplace_back(loc.symbol().Get());
}
return;
}
IndexWalker walker(&module_symbols->GetIndex());
if (options.search_mode == FindNameOptions::kLexical && !current_scope.empty() &&
looking_for.qualification() == IdentifierQualification::kRelative) {
// Unless the input identifier is fully qualified, start the search in the current context.
walker.WalkIntoClosest(current_scope);
}
// Search from the current namespace going up.
do {
// Do recursive searching when requested. The name must also be relative. Global qualifications
// on the input override implicit namespace searching.
VisitResult vr;
if (options.search_mode == FindNameOptions::kAllNamespaces &&
looking_for.qualification() == IdentifierQualification::kRelative) {
vr = FindInIndexLevelRecursiveNs(options, module_symbols, walker, looking_for, results);
} else {
vr = FindInIndexLevel(options, module_symbols, walker, looking_for, results);
}
if (vr != VisitResult::kContinue)
return;
if (!search_containing)
break;
// Keep looking up one more level in the containing namespace.
} while (walker.WalkUp());
}
const std::string* GetSingleComponentIdentifierName(const ParsedIdentifier& ident) {
if (ident.components().size() != 1 || ident.components()[0].has_template() ||
ident.components()[0].special() != SpecialIdentifier::kNone)
return nullptr;
return &ident.components()[0].name();
}
} // namespace zxdb