| // 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 |