blob: 104513be27ef8aa0a036a2a929499311d52dc2f1 [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/console/input_location_parser.h"
#include <inttypes.h>
#include <algorithm>
#include <limits>
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/target.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/command_utils.h"
#include "src/developer/debug/zxdb/console/format_location.h"
#include "src/developer/debug/zxdb/console/string_util.h"
#include "src/developer/debug/zxdb/expr/expr.h"
#include "src/developer/debug/zxdb/expr/expr_parser.h"
#include "src/developer/debug/zxdb/expr/find_name.h"
#include "src/developer/debug/zxdb/expr/permissive_input_location.h"
#include "src/developer/debug/zxdb/symbols/identifier.h"
#include "src/developer/debug/zxdb/symbols/index.h"
#include "src/developer/debug/zxdb/symbols/location.h"
#include "src/developer/debug/zxdb/symbols/module_symbols.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/target_symbols.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
// Searches the current object ("this") in the frame for local matches of the given identifier.
// This will not return anything that exactly matches the input because it's assumed that value
// is always handled by the "global" case.
//
// For input locations it is not necessary to do a full lexical search beyond the local class
// because unqualified names will match any namespace in ResolveInputLocations(). That will catch
// all other instances of the symbol.
//
// If there is no current object or there are no matches, returns an empty vector. Otherwise returns
// tall matches with fully-qualified names.
std::vector<InputLocation> GetIdentifierMatchesOnThis(const ProcessSymbols* process_symbols,
const Location& loc,
const Identifier& input) {
if (!loc.symbol())
return {};
const CodeBlock* code_block = loc.symbol().Get()->As<CodeBlock>();
if (!code_block)
return {};
FindNameContext find_context(process_symbols, loc.symbol_context(), code_block);
// Currently location matching matches only functions. We may need to broaden this in the
// future as the needs of callers require.
FindNameOptions find_options(FindNameOptions::kNoKinds);
find_options.find_functions = true;
find_options.max_results = std::numeric_limits<size_t>::max(); // Want everything
std::vector<FoundName> found_local;
FindMemberOnThis(find_context, find_options, ToParsedIdentifier(input), &found_local);
std::vector<InputLocation> result;
for (const auto& found : found_local) {
Identifier found_ident = ToIdentifier(found.GetName());
// The empty name check is paranoid in case the symbols are declaring weird things. Don't
// duplicate the input which will be appended separately if needed.
if (!found_ident.empty() && !found_ident.EqualsIgnoringQualification(input))
result.emplace_back(found_ident);
}
return result;
}
} // namespace
Err ParseGlobalInputLocation(const Location& location, const std::string& input,
InputLocation* output) {
if (input.empty())
return Err("Passed empty location.");
// Check for one colon. Two colons is a C++ member function.
size_t colon = input.find(':');
const char kMissingFileError[] =
"There is no current file name to use, you'll have to specify a file.";
if (colon != std::string::npos && colon < input.size() - 1 && input[colon + 1] != ':') {
// <file>:<line> format.
std::string file = input.substr(0, colon);
if (file.empty()) {
// Empty file names take the current file name just like bare numbers.
if (location.file_line().file().empty())
return Err(kMissingFileError);
file = location.file_line().file();
}
uint64_t line = 0;
if (Err err = StringToUint64(input.substr(colon + 1), &line); err.has_error())
return err;
if (line == 0)
return Err("Can't have a 0 line number.");
*output = InputLocation(FileLine(std::move(file), static_cast<int>(line)));
return Err();
}
// Hex numbers are addresses.
if (CheckHexPrefix(input)) {
uint64_t address = 0;
if (Err err = StringToUint64(input, &address); err.has_error())
return err;
*output = InputLocation(address);
return Err();
}
// Standalone non-hex numbers are line numbers, assume the current file name.
uint64_t line = 0;
Err err = StringToUint64(input, &line);
if (!err.has_error()) {
if (location.file_line().file().empty())
return Err(kMissingFileError);
*output = InputLocation(FileLine(location.file_line().file(), static_cast<int>(line)));
return Err();
}
// Anything else, assume its an identifier.
Identifier ident;
if (err = ExprParser::ParseIdentifier(input, &ident); err.has_error())
return err;
*output = InputLocation(ident);
return Err();
}
void EvalGlobalInputLocation(
const fxl::RefPtr<EvalContext> eval_context, const Location& location, const std::string& input,
fit::callback<void(ErrOr<InputLocation>, std::optional<uint32_t> size)> cb) {
if (input.empty() || input[0] != '*') {
// Not an expression, forward to the synchronous parser.
InputLocation sync_result;
if (Err err = ParseGlobalInputLocation(location, input, &sync_result); err.has_error())
cb(err, std::nullopt);
else
cb(sync_result, std::nullopt);
return;
}
// Everything following the * is the expression.
std::string expr = input.substr(1);
EvalExpression(
expr, eval_context, true, [eval_context, cb = std::move(cb)](ErrOrValue result) mutable {
if (result.has_error())
return cb(RewriteCommandExpressionError(std::string(), result.err()), std::nullopt);
uint64_t address = 0;
std::optional<uint32_t> size;
if (Err err = ValueToAddressAndSize(eval_context, result.value(), &address, &size);
err.has_error()) {
return cb(err, std::nullopt);
}
cb(InputLocation(address), size);
});
}
Err ParseLocalInputLocation(const ProcessSymbols* optional_process_symbols,
const Location& location, const std::string& input,
std::vector<InputLocation>* output) {
output->clear();
InputLocation global;
if (Err err = ParseGlobalInputLocation(location, input, &global); err.has_error())
return err;
if (optional_process_symbols && global.type == InputLocation::Type::kName)
*output = GetIdentifierMatchesOnThis(optional_process_symbols, location, global.name);
// The global one always goes last so the most specific ones come first.
output->push_back(std::move(global));
return Err();
}
Err ParseLocalInputLocation(const Frame* optional_frame, const std::string& input,
std::vector<InputLocation>* output) {
const ProcessSymbols* process_symbols = nullptr;
Location location;
if (optional_frame) {
process_symbols = optional_frame->GetThread()->GetProcess()->GetSymbols();
location = optional_frame->GetLocation();
}
return ParseLocalInputLocation(process_symbols, location, input, output);
}
void EvalLocalInputLocation(
const fxl::RefPtr<EvalContext>& eval_context, const Frame* optional_frame,
const std::string& input,
fit::callback<void(ErrOr<std::vector<InputLocation>>, std::optional<uint32_t> size)> cb) {
Location cur_location;
if (optional_frame)
cur_location = optional_frame->GetLocation();
return EvalLocalInputLocation(eval_context, cur_location, input, std::move(cb));
}
void EvalLocalInputLocation(
const fxl::RefPtr<EvalContext>& eval_context, const Location& location,
const std::string& input,
fit::callback<void(ErrOr<std::vector<InputLocation>>, std::optional<uint32_t> size)> cb) {
EvalGlobalInputLocation(
eval_context, location, input,
[eval_context, location, cb = std::move(cb)](ErrOr<InputLocation> global_location,
std::optional<uint32_t> size) mutable {
if (global_location.has_error())
return cb(global_location.err(), std::nullopt);
// Possibly null.
const ProcessSymbols* process_symbols = eval_context->GetProcessSymbols();
std::vector<InputLocation> result;
if (process_symbols && global_location.value().type == InputLocation::Type::kName) {
result =
GetIdentifierMatchesOnThis(process_symbols, location, global_location.value().name);
}
// The global one always goes last so the most specific ones come first.
result.push_back(global_location.take_value());
cb(std::move(result), size);
});
}
Err ResolveInputLocations(const ProcessSymbols* process_symbols, const Location& location,
const std::string& input, bool symbolize, std::vector<Location>* output) {
std::vector<InputLocation> input_locations;
if (Err err = ParseLocalInputLocation(process_symbols, location, input, &input_locations);
err.has_error()) {
return err;
}
return ResolveInputLocations(process_symbols, input_locations, symbolize, output);
}
Err ResolveInputLocations(const Frame* optional_frame, const std::string& input, bool symbolize,
std::vector<Location>* output) {
const ProcessSymbols* process_symbols = nullptr;
Location location;
if (optional_frame) {
process_symbols = optional_frame->GetThread()->GetProcess()->GetSymbols();
location = optional_frame->GetLocation();
}
return ResolveInputLocations(process_symbols, location, input, symbolize, output);
}
Err ResolveInputLocations(const ProcessSymbols* process_symbols,
const std::vector<InputLocation>& input_locations, bool symbolize,
std::vector<Location>* locations) {
ResolveOptions options;
options.symbolize = symbolize;
*locations = ResolvePermissiveInputLocations(process_symbols, options,
FindNameContext(process_symbols), input_locations);
if (locations->empty()) {
if (input_locations.size() == 1) {
return Err("Nothing matching this %s was found.",
InputLocation::TypeToString(input_locations[0].type));
}
return Err("Nothing matching this location was found.");
}
return Err();
}
Err ResolveUniqueInputLocation(const ProcessSymbols* process_symbols,
const InputLocation& input_location, bool symbolize,
Location* location) {
return ResolveUniqueInputLocation(process_symbols, std::vector<InputLocation>{input_location},
symbolize, location);
}
// This implementation isn't great, it doesn't always show the best disambiguations for the given
// input.
//
// Also it misses a file name edge case: If there is one file whose full path in the symbols is a
// right-side subset of another (say "foo/bar.cc" and "something/foo/bar.cc"), then "foo/bar.cc" is
// the most unique name of the first file. But if the user types that, they'll get both matches and
// this function will report an ambiguous location.
//
// Instead, if the input is a file name and there is only one result where the file name matches
// exactly, we should pick it.
Err ResolveUniqueInputLocation(const ProcessSymbols* process_symbols,
const std::vector<InputLocation>& input_location, bool symbolize,
Location* location) {
std::vector<Location> locations;
Err err = ResolveInputLocations(process_symbols, input_location, symbolize, &locations);
if (err.has_error())
return err;
FX_DCHECK(!locations.empty()); // Non-empty on success should be guaranteed.
if (locations.size() == 1u) {
// Success, got a unique location.
*location = locations[0];
return Err();
}
// When there is more than one, generate an error that lists the possibilities for disambiguation.
std::string err_str = "This resolves to more than one location. Could be:\n";
constexpr size_t kMaxSuggestions = 10u;
if (!symbolize) {
// The original call did not request symbolization which will produce very
// non-helpful suggestions. We're not concerned about performance in this error case so re-query
// to get the full symbols.
locations.clear();
ResolveInputLocations(process_symbols, input_location, true, &locations);
}
for (size_t i = 0; i < locations.size() && i < kMaxSuggestions; i++) {
// Always show the full path (omit TargetSymbols) since we're doing disambiguation and the
// problem could have been two files with the same name but different paths.
err_str += fxl::StringPrintf(" %s ", GetBullet().c_str());
if (locations[i].file_line().is_valid()) {
err_str += FormatFileLine(locations[i].file_line()).AsString();
err_str += fxl::StringPrintf(" = 0x%" PRIx64, locations[i].address());
} else {
FormatLocationOptions opts;
opts.always_show_addresses = true;
err_str += FormatLocation(locations[i], opts).AsString();
}
err_str += "\n";
}
if (locations.size() > kMaxSuggestions) {
err_str += fxl::StringPrintf("...%zu more omitted...\n", locations.size() - kMaxSuggestions);
}
return Err(err_str);
}
Err ResolveUniqueInputLocation(const ProcessSymbols* process_symbols, const Location& location,
const std::string& input, bool symbolize, Location* output) {
std::vector<InputLocation> input_locations;
if (Err err = ParseLocalInputLocation(process_symbols, location, input, &input_locations);
err.has_error()) {
return err;
}
return ResolveUniqueInputLocation(process_symbols, input_locations, symbolize, output);
}
Err ResolveUniqueInputLocation(const Frame* optional_frame, const std::string& input,
bool symbolize, Location* output) {
const ProcessSymbols* process_symbols = nullptr;
Location location;
if (optional_frame) {
process_symbols = optional_frame->GetThread()->GetProcess()->GetSymbols();
location = optional_frame->GetLocation();
}
return ResolveUniqueInputLocation(process_symbols, location, input, symbolize, output);
}
void CompleteInputLocation(const Command& command, const std::string& prefix,
std::vector<std::string>* completions) {
if (!command.target())
return;
// Number of items of each category that can be added to the completions.
constexpr size_t kMaxFileNames = 32;
constexpr size_t kMaxNamespaces = 8;
constexpr size_t kMaxClasses = 32;
constexpr size_t kMaxFunctions = 32;
// Extract the current code block if possible. This will be used to find local variables and to
// prioritize symbols from the current module.
const CodeBlock* code_block = nullptr;
SymbolContext symbol_context = SymbolContext::ForRelativeAddresses();
if (const Frame* frame = command.frame()) {
const Location& location = frame->GetLocation();
if (const CodeBlock* fn_block = location.symbol().Get()->As<CodeBlock>()) {
symbol_context = location.symbol_context();
code_block = fn_block->GetMostSpecificChild(symbol_context, location.address());
}
}
// TODO(brettw) prioritize the current module when it's known (when there is a current frame with
// symbol information). Factor prioritization code from find_name.cc
for (const ModuleSymbols* mod : command.target()->GetSymbols()->GetModuleSymbols()) {
const Index& index = mod->GetIndex();
auto files = index.FindFilePrefixes(prefix);
// Files get colons at the end for the user to type a line number next.
for (auto& file : files)
file.push_back(':');
completions->insert(completions->end(), files.begin(), files.end());
}
std::sort(completions->begin(), completions->end());
if (completions->size() > kMaxFileNames)
completions->resize(kMaxFileNames);
// Now search for functions matching the given input.
FindNameOptions options(FindNameOptions::kNoKinds);
options.how = FindNameOptions::kPrefix;
ParsedIdentifier prefix_identifier;
Err err = ExprParser::ParseIdentifier(prefix, &prefix_identifier);
if (err.has_error())
return; // Can't match identifier names.
// When there's a live process there is more context to find stuff.
std::unique_ptr<FindNameContext> find_context;
if (Process* process = command.target()->GetProcess()) {
find_context =
std::make_unique<FindNameContext>(process->GetSymbols(), symbol_context, code_block);
} else {
find_context = std::make_unique<FindNameContext>(command.target()->GetSymbols());
}
// First start with namespaces.
options.find_namespaces = true;
options.max_results = kMaxNamespaces;
std::vector<FoundName> found_names;
FindName(*find_context, options, prefix_identifier, &found_names);
for (const FoundName& found : found_names)
completions->push_back(found.GetName().GetFullName() + "::");
options.find_namespaces = false;
// Follow with types. Only do structure and class types since we're really looking for function
// names. In the future it might be nice to check if there are any member functions in the types
// before adding them.
options.find_types = true;
options.max_results = kMaxClasses;
found_names.clear();
FindName(*find_context, options, prefix_identifier, &found_names);
for (const FoundName& found : found_names) {
FX_DCHECK(found.kind() == zxdb::FoundName::kType);
if (const Collection* collection = found.type()->As<Collection>())
completions->push_back(found.GetName().GetFullName() + "::");
}
options.find_types = false;
// Finish with functions.
options.find_functions = true;
options.max_results = kMaxFunctions;
found_names.clear();
FindName(*find_context, options, prefix_identifier, &found_names);
for (const FoundName& found : found_names) {
// When completing names, globally qualify the names to prevent ambiguity.
completions->push_back(found.function()->GetIdentifier().GetFullName());
}
options.find_functions = false;
}
} // namespace zxdb