| // 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()->AsCodeBlock(); | |
| 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(':'); | |
| if (colon != std::string::npos && colon < input.size() - 1 && input[colon + 1] != ':') { | |
| // <file>:<line> format. | |
| std::string file = input.substr(0, colon); | |
| uint64_t line = 0; | |
| if (Err err = StringToUint64(input.substr(colon + 1), &line); err.has_error()) | |
| return err; | |
| *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("There is no current file name to use, you'll have to specify a file."); | |
| *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 += DescribeFileLine(nullptr, locations[i].file_line()); | |
| 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()->AsCodeBlock()) { | |
| 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()->AsCollection()) | |
| 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 |