mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 19:08:21 +08:00
Introduce the concept of internal stop hooks. These are similar to LLDB's internal breakpoints: LLDB itself will add them and users of LLDB will not be able to add or remove them. This change adds the following 3 independently-useful concepts: * Maintain a list of internal stop hooks that will be populated by LLDB and cannot be added to or removed from by users. They are managed in a separate list in `Target::m_internal_stop_hooks`. * `StopHookKind:CodeBased` and `StopHookCoded` represent a stop hook defined by a C++ code callback (instead of command line expressions or a Python class). * Stop hooks that do not print any output can now also suppress the printing of their header and description when they are hit via `StopHook::GetSuppressOutput`. Combining these 3 concepts we can model "internal stop hooks" which serve the same function as LLDB's internal breakpoints: executing built-in, LLDB-defined behavior, leveraging the existing mechanism of stop hooks. This change also simplifies `Target::RunStopHooks`. We already have to materialize a new list for combining internal and user stop hooks. Filter and only add active hooks to this list to avoid the need for "isActive?" checks later on.
915 lines
35 KiB
C++
915 lines
35 KiB
C++
//===-- CommandCompletions.cpp --------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
|
|
#include "lldb/Breakpoint/Watchpoint.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/DataFormatters/DataVisualization.h"
|
|
#include "lldb/Host/FileSystem.h"
|
|
#include "lldb/Interpreter/CommandCompletions.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/CommandObject.h"
|
|
#include "lldb/Interpreter/CommandObjectMultiword.h"
|
|
#include "lldb/Interpreter/OptionValueProperties.h"
|
|
#include "lldb/Symbol/CompileUnit.h"
|
|
#include "lldb/Symbol/Variable.h"
|
|
#include "lldb/Target/Language.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/RegisterContext.h"
|
|
#include "lldb/Target/Thread.h"
|
|
#include "lldb/Utility/FileSpec.h"
|
|
#include "lldb/Utility/FileSpecList.h"
|
|
#include "lldb/Utility/StreamString.h"
|
|
#include "lldb/Utility/TildeExpressionResolver.h"
|
|
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Path.h"
|
|
|
|
using namespace lldb_private;
|
|
|
|
// This is the command completion callback that is used to complete the
|
|
// argument of the option it is bound to (in the OptionDefinition table
|
|
// below).
|
|
typedef void (*CompletionCallback)(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
// A search filter to limit the search...
|
|
lldb_private::SearchFilter *searcher);
|
|
|
|
struct CommonCompletionElement {
|
|
uint64_t type;
|
|
CompletionCallback callback;
|
|
};
|
|
|
|
bool CommandCompletions::InvokeCommonCompletionCallbacks(
|
|
CommandInterpreter &interpreter, uint32_t completion_mask,
|
|
CompletionRequest &request, SearchFilter *searcher) {
|
|
bool handled = false;
|
|
|
|
const CommonCompletionElement common_completions[] = {
|
|
{lldb::eNoCompletion, nullptr},
|
|
{lldb::eSourceFileCompletion, CommandCompletions::SourceFiles},
|
|
{lldb::eDiskFileCompletion, CommandCompletions::DiskFiles},
|
|
{lldb::eDiskDirectoryCompletion, CommandCompletions::DiskDirectories},
|
|
{lldb::eSymbolCompletion, CommandCompletions::Symbols},
|
|
{lldb::eModuleCompletion, CommandCompletions::Modules},
|
|
{lldb::eModuleUUIDCompletion, CommandCompletions::ModuleUUIDs},
|
|
{lldb::eSettingsNameCompletion, CommandCompletions::SettingsNames},
|
|
{lldb::ePlatformPluginCompletion,
|
|
CommandCompletions::PlatformPluginNames},
|
|
{lldb::eArchitectureCompletion, CommandCompletions::ArchitectureNames},
|
|
{lldb::eVariablePathCompletion, CommandCompletions::VariablePath},
|
|
{lldb::eRegisterCompletion, CommandCompletions::Registers},
|
|
{lldb::eBreakpointCompletion, CommandCompletions::Breakpoints},
|
|
{lldb::eProcessPluginCompletion, CommandCompletions::ProcessPluginNames},
|
|
{lldb::eDisassemblyFlavorCompletion,
|
|
CommandCompletions::DisassemblyFlavors},
|
|
{lldb::eTypeLanguageCompletion, CommandCompletions::TypeLanguages},
|
|
{lldb::eFrameIndexCompletion, CommandCompletions::FrameIndexes},
|
|
{lldb::eStopHookIDCompletion, CommandCompletions::StopHookIDs},
|
|
{lldb::eThreadIndexCompletion, CommandCompletions::ThreadIndexes},
|
|
{lldb::eWatchpointIDCompletion, CommandCompletions::WatchPointIDs},
|
|
{lldb::eBreakpointNameCompletion, CommandCompletions::BreakpointNames},
|
|
{lldb::eProcessIDCompletion, CommandCompletions::ProcessIDs},
|
|
{lldb::eProcessNameCompletion, CommandCompletions::ProcessNames},
|
|
{lldb::eRemoteDiskFileCompletion, CommandCompletions::RemoteDiskFiles},
|
|
{lldb::eRemoteDiskDirectoryCompletion,
|
|
CommandCompletions::RemoteDiskDirectories},
|
|
{lldb::eTypeCategoryNameCompletion,
|
|
CommandCompletions::TypeCategoryNames},
|
|
{lldb::eThreadIDCompletion, CommandCompletions::ThreadIDs},
|
|
{lldb::eManagedPluginCompletion, CommandCompletions::ManagedPlugins},
|
|
{lldb::eTerminatorCompletion,
|
|
nullptr} // This one has to be last in the list.
|
|
};
|
|
|
|
for (int i = 0; request.ShouldAddCompletions(); i++) {
|
|
if (common_completions[i].type == lldb::eTerminatorCompletion)
|
|
break;
|
|
else if ((common_completions[i].type & completion_mask) ==
|
|
common_completions[i].type &&
|
|
common_completions[i].callback != nullptr) {
|
|
handled = true;
|
|
common_completions[i].callback(interpreter, request, searcher);
|
|
}
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
namespace {
|
|
// The Completer class is a convenient base class for building searchers that
|
|
// go along with the SearchFilter passed to the standard Completer functions.
|
|
class Completer : public Searcher {
|
|
public:
|
|
Completer(CommandInterpreter &interpreter, CompletionRequest &request)
|
|
: m_interpreter(interpreter), m_request(request) {}
|
|
|
|
~Completer() override = default;
|
|
|
|
CallbackReturn SearchCallback(SearchFilter &filter, SymbolContext &context,
|
|
Address *addr) override = 0;
|
|
|
|
lldb::SearchDepth GetDepth() override = 0;
|
|
|
|
virtual void DoCompletion(SearchFilter *filter) = 0;
|
|
|
|
protected:
|
|
CommandInterpreter &m_interpreter;
|
|
CompletionRequest &m_request;
|
|
|
|
private:
|
|
Completer(const Completer &) = delete;
|
|
const Completer &operator=(const Completer &) = delete;
|
|
};
|
|
} // namespace
|
|
|
|
// SourceFileCompleter implements the source file completer
|
|
namespace {
|
|
class SourceFileCompleter : public Completer {
|
|
public:
|
|
SourceFileCompleter(CommandInterpreter &interpreter,
|
|
CompletionRequest &request)
|
|
: Completer(interpreter, request) {
|
|
FileSpec partial_spec(m_request.GetCursorArgumentPrefix());
|
|
m_file_name = partial_spec.GetFilename().GetCString();
|
|
m_dir_name = partial_spec.GetDirectory().GetCString();
|
|
}
|
|
|
|
lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthCompUnit; }
|
|
|
|
Searcher::CallbackReturn SearchCallback(SearchFilter &filter,
|
|
SymbolContext &context,
|
|
Address *addr) override {
|
|
if (context.comp_unit != nullptr) {
|
|
const char *cur_file_name =
|
|
context.comp_unit->GetPrimaryFile().GetFilename().GetCString();
|
|
const char *cur_dir_name =
|
|
context.comp_unit->GetPrimaryFile().GetDirectory().GetCString();
|
|
|
|
bool match = false;
|
|
if (m_file_name && cur_file_name &&
|
|
strstr(cur_file_name, m_file_name) == cur_file_name)
|
|
match = true;
|
|
|
|
if (match && m_dir_name && cur_dir_name &&
|
|
strstr(cur_dir_name, m_dir_name) != cur_dir_name)
|
|
match = false;
|
|
|
|
if (match) {
|
|
m_matching_files.AppendIfUnique(context.comp_unit->GetPrimaryFile());
|
|
}
|
|
}
|
|
return m_matching_files.GetSize() >= m_request.GetMaxNumberOfCompletionsToAdd()
|
|
? Searcher::eCallbackReturnStop
|
|
: Searcher::eCallbackReturnContinue;
|
|
}
|
|
|
|
void DoCompletion(SearchFilter *filter) override {
|
|
filter->Search(*this);
|
|
// Now convert the filelist to completions:
|
|
for (size_t i = 0; i < m_matching_files.GetSize(); i++) {
|
|
m_request.AddCompletion(
|
|
m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString());
|
|
}
|
|
}
|
|
|
|
private:
|
|
FileSpecList m_matching_files;
|
|
const char *m_file_name;
|
|
const char *m_dir_name;
|
|
|
|
SourceFileCompleter(const SourceFileCompleter &) = delete;
|
|
const SourceFileCompleter &operator=(const SourceFileCompleter &) = delete;
|
|
};
|
|
} // namespace
|
|
|
|
static bool regex_chars(const char comp) {
|
|
return llvm::StringRef("[](){}+.*|^$\\?").contains(comp);
|
|
}
|
|
|
|
namespace {
|
|
class SymbolCompleter : public Completer {
|
|
|
|
public:
|
|
SymbolCompleter(CommandInterpreter &interpreter, CompletionRequest &request)
|
|
: Completer(interpreter, request) {
|
|
std::string regex_str;
|
|
if (!m_request.GetCursorArgumentPrefix().empty()) {
|
|
regex_str.append("^");
|
|
regex_str.append(std::string(m_request.GetCursorArgumentPrefix()));
|
|
} else {
|
|
// Match anything since the completion string is empty
|
|
regex_str.append(".");
|
|
}
|
|
std::string::iterator pos =
|
|
find_if(regex_str.begin() + 1, regex_str.end(), regex_chars);
|
|
while (pos < regex_str.end()) {
|
|
pos = regex_str.insert(pos, '\\');
|
|
pos = find_if(pos + 2, regex_str.end(), regex_chars);
|
|
}
|
|
m_regex = RegularExpression(regex_str);
|
|
}
|
|
|
|
lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; }
|
|
|
|
Searcher::CallbackReturn SearchCallback(SearchFilter &filter,
|
|
SymbolContext &context,
|
|
Address *addr) override {
|
|
if (context.module_sp) {
|
|
SymbolContextList sc_list;
|
|
ModuleFunctionSearchOptions function_options;
|
|
function_options.include_symbols = true;
|
|
function_options.include_inlines = true;
|
|
context.module_sp->FindFunctions(m_regex, function_options, sc_list);
|
|
|
|
// Now add the functions & symbols to the list - only add if unique:
|
|
for (const SymbolContext &sc : sc_list) {
|
|
if (m_match_set.size() >= m_request.GetMaxNumberOfCompletionsToAdd())
|
|
break;
|
|
|
|
ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled);
|
|
// Ensure that the function name matches the regex. This is more than
|
|
// a sanity check. It is possible that the demangled function name
|
|
// does not start with the prefix, for example when it's in an
|
|
// anonymous namespace.
|
|
if (!func_name.IsEmpty() && m_regex.Execute(func_name.GetStringRef()))
|
|
m_match_set.insert(func_name);
|
|
}
|
|
}
|
|
return m_match_set.size() >= m_request.GetMaxNumberOfCompletionsToAdd()
|
|
? Searcher::eCallbackReturnStop
|
|
: Searcher::eCallbackReturnContinue;
|
|
}
|
|
|
|
void DoCompletion(SearchFilter *filter) override {
|
|
filter->Search(*this);
|
|
collection::iterator pos = m_match_set.begin(), end = m_match_set.end();
|
|
for (pos = m_match_set.begin(); pos != end; pos++)
|
|
m_request.AddCompletion((*pos).GetCString());
|
|
}
|
|
|
|
private:
|
|
RegularExpression m_regex;
|
|
typedef std::set<ConstString> collection;
|
|
collection m_match_set;
|
|
|
|
SymbolCompleter(const SymbolCompleter &) = delete;
|
|
const SymbolCompleter &operator=(const SymbolCompleter &) = delete;
|
|
};
|
|
} // namespace
|
|
|
|
namespace {
|
|
class ModuleCompleter : public Completer {
|
|
public:
|
|
ModuleCompleter(CommandInterpreter &interpreter, CompletionRequest &request)
|
|
: Completer(interpreter, request) {
|
|
llvm::StringRef request_str = m_request.GetCursorArgumentPrefix();
|
|
// We can match the full path, or the file name only. The full match will be
|
|
// attempted always, the file name match only if the request does not
|
|
// contain a path separator.
|
|
|
|
// Preserve both the path as spelled by the user (used for completion) and
|
|
// the canonical version (used for matching).
|
|
m_spelled_path = request_str;
|
|
m_canonical_path = FileSpec(m_spelled_path).GetPath();
|
|
if (!m_spelled_path.empty() &&
|
|
llvm::sys::path::is_separator(m_spelled_path.back()) &&
|
|
!llvm::StringRef(m_canonical_path).ends_with(m_spelled_path.back())) {
|
|
m_canonical_path += m_spelled_path.back();
|
|
}
|
|
|
|
if (llvm::find_if(request_str, [](char c) {
|
|
return llvm::sys::path::is_separator(c);
|
|
}) == request_str.end())
|
|
m_file_name = request_str;
|
|
}
|
|
|
|
lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; }
|
|
|
|
Searcher::CallbackReturn SearchCallback(SearchFilter &filter,
|
|
SymbolContext &context,
|
|
Address *addr) override {
|
|
if (context.module_sp) {
|
|
// Attempt a full path match.
|
|
std::string cur_path = context.module_sp->GetFileSpec().GetPath();
|
|
llvm::StringRef cur_path_view = cur_path;
|
|
if (cur_path_view.consume_front(m_canonical_path))
|
|
m_request.AddCompletion((m_spelled_path + cur_path_view).str());
|
|
|
|
// And a file name match.
|
|
if (m_file_name) {
|
|
llvm::StringRef cur_file_name =
|
|
context.module_sp->GetFileSpec().GetFilename().GetStringRef();
|
|
if (cur_file_name.starts_with(*m_file_name))
|
|
m_request.AddCompletion(cur_file_name);
|
|
}
|
|
}
|
|
return m_request.ShouldAddCompletions() ? Searcher::eCallbackReturnContinue
|
|
: Searcher::eCallbackReturnStop;
|
|
}
|
|
|
|
void DoCompletion(SearchFilter *filter) override { filter->Search(*this); }
|
|
|
|
private:
|
|
std::optional<llvm::StringRef> m_file_name;
|
|
llvm::StringRef m_spelled_path;
|
|
std::string m_canonical_path;
|
|
|
|
ModuleCompleter(const ModuleCompleter &) = delete;
|
|
const ModuleCompleter &operator=(const ModuleCompleter &) = delete;
|
|
};
|
|
} // namespace
|
|
|
|
void CommandCompletions::SourceFiles(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
SourceFileCompleter completer(interpreter, request);
|
|
|
|
if (searcher == nullptr) {
|
|
lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
|
|
SearchFilterForUnconstrainedSearches null_searcher(target_sp);
|
|
completer.DoCompletion(&null_searcher);
|
|
} else {
|
|
completer.DoCompletion(searcher);
|
|
}
|
|
}
|
|
|
|
static void DiskFilesOrDirectories(const llvm::Twine &partial_name,
|
|
bool only_directories,
|
|
CompletionRequest &request,
|
|
TildeExpressionResolver &Resolver) {
|
|
llvm::SmallString<256> CompletionBuffer;
|
|
llvm::SmallString<256> Storage;
|
|
partial_name.toVector(CompletionBuffer);
|
|
|
|
if (CompletionBuffer.size() >= PATH_MAX)
|
|
return;
|
|
|
|
namespace path = llvm::sys::path;
|
|
|
|
llvm::StringRef SearchDir;
|
|
llvm::StringRef PartialItem;
|
|
|
|
if (CompletionBuffer.starts_with("~")) {
|
|
llvm::StringRef Buffer = CompletionBuffer;
|
|
size_t FirstSep =
|
|
Buffer.find_if([](char c) { return path::is_separator(c); });
|
|
|
|
llvm::StringRef Username = Buffer.take_front(FirstSep);
|
|
llvm::StringRef Remainder;
|
|
if (FirstSep != llvm::StringRef::npos)
|
|
Remainder = Buffer.drop_front(FirstSep + 1);
|
|
|
|
llvm::SmallString<256> Resolved;
|
|
if (!Resolver.ResolveExact(Username, Resolved)) {
|
|
// We couldn't resolve it as a full username. If there were no slashes
|
|
// then this might be a partial username. We try to resolve it as such
|
|
// but after that, we're done regardless of any matches.
|
|
if (FirstSep == llvm::StringRef::npos) {
|
|
llvm::StringSet<> MatchSet;
|
|
Resolver.ResolvePartial(Username, MatchSet);
|
|
for (const auto &S : MatchSet) {
|
|
Resolved = S.getKey();
|
|
path::append(Resolved, path::get_separator());
|
|
request.AddCompletion(Resolved, "", CompletionMode::Partial);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If there was no trailing slash, then we're done as soon as we resolve
|
|
// the expression to the correct directory. Otherwise we need to continue
|
|
// looking for matches within that directory.
|
|
if (FirstSep == llvm::StringRef::npos) {
|
|
// Make sure it ends with a separator.
|
|
path::append(CompletionBuffer, path::get_separator());
|
|
request.AddCompletion(CompletionBuffer, "", CompletionMode::Partial);
|
|
return;
|
|
}
|
|
|
|
// We want to keep the form the user typed, so we special case this to
|
|
// search in the fully resolved directory, but CompletionBuffer keeps the
|
|
// unmodified form that the user typed.
|
|
Storage = Resolved;
|
|
llvm::StringRef RemainderDir = path::parent_path(Remainder);
|
|
if (!RemainderDir.empty()) {
|
|
// Append the remaining path to the resolved directory.
|
|
Storage.append(path::get_separator());
|
|
Storage.append(RemainderDir);
|
|
}
|
|
SearchDir = Storage;
|
|
} else if (CompletionBuffer == path::root_directory(CompletionBuffer)) {
|
|
SearchDir = CompletionBuffer;
|
|
} else {
|
|
SearchDir = path::parent_path(CompletionBuffer);
|
|
}
|
|
|
|
size_t FullPrefixLen = CompletionBuffer.size();
|
|
|
|
PartialItem = path::filename(CompletionBuffer);
|
|
|
|
// path::filename() will return "." when the passed path ends with a
|
|
// directory separator or the separator when passed the disk root directory.
|
|
// We have to filter those out, but only when the "." doesn't come from the
|
|
// completion request itself.
|
|
if ((PartialItem == "." || PartialItem == path::get_separator()) &&
|
|
path::is_separator(CompletionBuffer.back()))
|
|
PartialItem = llvm::StringRef();
|
|
|
|
if (SearchDir.empty()) {
|
|
llvm::sys::fs::current_path(Storage);
|
|
SearchDir = Storage;
|
|
}
|
|
assert(!PartialItem.contains(path::get_separator()));
|
|
|
|
// SearchDir now contains the directory to search in, and Prefix contains the
|
|
// text we want to match against items in that directory.
|
|
|
|
FileSystem &fs = FileSystem::Instance();
|
|
std::error_code EC;
|
|
llvm::vfs::directory_iterator Iter = fs.DirBegin(SearchDir, EC);
|
|
llvm::vfs::directory_iterator End;
|
|
for (; Iter != End && !EC && request.ShouldAddCompletions();
|
|
Iter.increment(EC)) {
|
|
auto &Entry = *Iter;
|
|
llvm::ErrorOr<llvm::vfs::Status> Status = fs.GetStatus(Entry.path());
|
|
|
|
if (!Status)
|
|
continue;
|
|
|
|
auto Name = path::filename(Entry.path());
|
|
|
|
// Omit ".", ".."
|
|
if (Name == "." || Name == ".." || !Name.starts_with(PartialItem))
|
|
continue;
|
|
|
|
bool is_dir = Status->isDirectory();
|
|
|
|
// If it's a symlink, then we treat it as a directory as long as the target
|
|
// is a directory.
|
|
if (Status->isSymlink()) {
|
|
FileSpec symlink_filespec(Entry.path());
|
|
FileSpec resolved_filespec;
|
|
auto error = fs.ResolveSymbolicLink(symlink_filespec, resolved_filespec);
|
|
if (error.Success())
|
|
is_dir = fs.IsDirectory(symlink_filespec);
|
|
}
|
|
|
|
if (only_directories && !is_dir)
|
|
continue;
|
|
|
|
// Shrink it back down so that it just has the original prefix the user
|
|
// typed and remove the part of the name which is common to the located
|
|
// item and what the user typed.
|
|
CompletionBuffer.resize(FullPrefixLen);
|
|
Name = Name.drop_front(PartialItem.size());
|
|
CompletionBuffer.append(Name);
|
|
|
|
if (is_dir) {
|
|
path::append(CompletionBuffer, path::get_separator());
|
|
}
|
|
|
|
CompletionMode mode =
|
|
is_dir ? CompletionMode::Partial : CompletionMode::Normal;
|
|
request.AddCompletion(CompletionBuffer, "", mode);
|
|
}
|
|
}
|
|
|
|
static void DiskFilesOrDirectories(const llvm::Twine &partial_name,
|
|
bool only_directories, StringList &matches,
|
|
TildeExpressionResolver &Resolver) {
|
|
CompletionResult result;
|
|
std::string partial_name_str = partial_name.str();
|
|
CompletionRequest request(partial_name_str, partial_name_str.size(), result);
|
|
DiskFilesOrDirectories(partial_name, only_directories, request, Resolver);
|
|
result.GetMatches(matches);
|
|
}
|
|
|
|
static void DiskFilesOrDirectories(CompletionRequest &request,
|
|
bool only_directories) {
|
|
StandardTildeExpressionResolver resolver;
|
|
DiskFilesOrDirectories(request.GetCursorArgumentPrefix(), only_directories,
|
|
request, resolver);
|
|
}
|
|
|
|
void CommandCompletions::DiskFiles(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
DiskFilesOrDirectories(request, /*only_dirs*/ false);
|
|
}
|
|
|
|
void CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name,
|
|
StringList &matches,
|
|
TildeExpressionResolver &Resolver) {
|
|
DiskFilesOrDirectories(partial_file_name, false, matches, Resolver);
|
|
}
|
|
|
|
void CommandCompletions::DiskDirectories(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
DiskFilesOrDirectories(request, /*only_dirs*/ true);
|
|
}
|
|
|
|
void CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name,
|
|
StringList &matches,
|
|
TildeExpressionResolver &Resolver) {
|
|
DiskFilesOrDirectories(partial_file_name, true, matches, Resolver);
|
|
}
|
|
|
|
void CommandCompletions::RemoteDiskFiles(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
lldb::PlatformSP platform_sp =
|
|
interpreter.GetDebugger().GetPlatformList().GetSelectedPlatform();
|
|
if (platform_sp)
|
|
platform_sp->AutoCompleteDiskFileOrDirectory(request, false);
|
|
}
|
|
|
|
void CommandCompletions::RemoteDiskDirectories(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
lldb::PlatformSP platform_sp =
|
|
interpreter.GetDebugger().GetPlatformList().GetSelectedPlatform();
|
|
if (platform_sp)
|
|
platform_sp->AutoCompleteDiskFileOrDirectory(request, true);
|
|
}
|
|
|
|
void CommandCompletions::Modules(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
ModuleCompleter completer(interpreter, request);
|
|
|
|
if (searcher == nullptr) {
|
|
lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
|
|
SearchFilterForUnconstrainedSearches null_searcher(target_sp);
|
|
completer.DoCompletion(&null_searcher);
|
|
} else {
|
|
completer.DoCompletion(searcher);
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::ModuleUUIDs(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
const ExecutionContext &exe_ctx = interpreter.GetExecutionContext();
|
|
if (!exe_ctx.HasTargetScope())
|
|
return;
|
|
|
|
exe_ctx.GetTargetPtr()->GetImages().ForEach(
|
|
[&request](const lldb::ModuleSP &module) {
|
|
StreamString strm;
|
|
module->GetDescription(strm.AsRawOstream(),
|
|
lldb::eDescriptionLevelInitial);
|
|
request.TryCompleteCurrentArg(module->GetUUID().GetAsString(),
|
|
strm.GetString());
|
|
return IterationAction::Continue;
|
|
});
|
|
}
|
|
|
|
void CommandCompletions::Symbols(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
SymbolCompleter completer(interpreter, request);
|
|
|
|
if (searcher == nullptr) {
|
|
lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
|
|
SearchFilterForUnconstrainedSearches null_searcher(target_sp);
|
|
completer.DoCompletion(&null_searcher);
|
|
} else {
|
|
completer.DoCompletion(searcher);
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::SettingsNames(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
// Cache the full setting name list
|
|
static StringList g_property_names;
|
|
if (g_property_names.GetSize() == 0) {
|
|
// Generate the full setting name list on demand
|
|
lldb::OptionValuePropertiesSP properties_sp(
|
|
interpreter.GetDebugger().GetValueProperties());
|
|
if (properties_sp) {
|
|
StreamString strm;
|
|
properties_sp->DumpValue(nullptr, strm, OptionValue::eDumpOptionName);
|
|
const std::string &str = std::string(strm.GetString());
|
|
g_property_names.SplitIntoLines(str.c_str(), str.size());
|
|
}
|
|
}
|
|
|
|
for (const std::string &s : g_property_names)
|
|
request.TryCompleteCurrentArg(s);
|
|
}
|
|
|
|
void CommandCompletions::PlatformPluginNames(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
PluginManager::AutoCompletePlatformName(request.GetCursorArgumentPrefix(),
|
|
request);
|
|
}
|
|
|
|
void CommandCompletions::ArchitectureNames(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
ArchSpec::AutoComplete(request);
|
|
}
|
|
|
|
void CommandCompletions::VariablePath(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
Variable::AutoComplete(interpreter.GetExecutionContext(), request);
|
|
}
|
|
|
|
void CommandCompletions::Registers(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
std::string reg_prefix;
|
|
if (request.GetCursorArgumentPrefix().starts_with("$"))
|
|
reg_prefix = "$";
|
|
|
|
RegisterContext *reg_ctx =
|
|
interpreter.GetExecutionContext().GetRegisterContext();
|
|
if (!reg_ctx)
|
|
return;
|
|
|
|
const size_t reg_num = reg_ctx->GetRegisterCount();
|
|
for (size_t reg_idx = 0; reg_idx < reg_num; ++reg_idx) {
|
|
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg_idx);
|
|
request.TryCompleteCurrentArg(reg_prefix + reg_info->name,
|
|
reg_info->alt_name);
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::Breakpoints(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
lldb::TargetSP target = interpreter.GetDebugger().GetSelectedTarget();
|
|
if (!target)
|
|
return;
|
|
|
|
const BreakpointList &breakpoints = target->GetBreakpointList();
|
|
|
|
std::unique_lock<std::recursive_mutex> lock;
|
|
target->GetBreakpointList().GetListMutex(lock);
|
|
|
|
size_t num_breakpoints = breakpoints.GetSize();
|
|
if (num_breakpoints == 0)
|
|
return;
|
|
|
|
for (size_t i = 0; i < num_breakpoints; ++i) {
|
|
lldb::BreakpointSP bp = breakpoints.GetBreakpointAtIndex(i);
|
|
|
|
StreamString s;
|
|
bp->GetDescription(&s, lldb::eDescriptionLevelBrief);
|
|
llvm::StringRef bp_info = s.GetString();
|
|
|
|
const size_t colon_pos = bp_info.find_first_of(':');
|
|
if (colon_pos != llvm::StringRef::npos)
|
|
bp_info = bp_info.drop_front(colon_pos + 2);
|
|
|
|
request.TryCompleteCurrentArg(std::to_string(bp->GetID()), bp_info);
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::BreakpointNames(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
lldb::TargetSP target = interpreter.GetDebugger().GetSelectedTarget();
|
|
if (!target)
|
|
return;
|
|
|
|
std::vector<std::string> name_list;
|
|
target->GetBreakpointNames(name_list);
|
|
|
|
for (const std::string &name : name_list)
|
|
request.TryCompleteCurrentArg(name);
|
|
}
|
|
|
|
void CommandCompletions::ProcessPluginNames(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
PluginManager::AutoCompleteProcessName(request.GetCursorArgumentPrefix(),
|
|
request);
|
|
}
|
|
void CommandCompletions::DisassemblyFlavors(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
// Currently the only valid options for disassemble -F are default, and for
|
|
// Intel architectures, att and intel.
|
|
static const char *flavors[] = {"default", "att", "intel"};
|
|
for (const char *flavor : flavors) {
|
|
request.TryCompleteCurrentArg(flavor);
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::ProcessIDs(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
lldb::PlatformSP platform_sp(interpreter.GetPlatform(true));
|
|
if (!platform_sp)
|
|
return;
|
|
ProcessInstanceInfoList process_infos;
|
|
ProcessInstanceInfoMatch match_info;
|
|
platform_sp->FindProcesses(match_info, process_infos);
|
|
for (const ProcessInstanceInfo &info : process_infos)
|
|
request.TryCompleteCurrentArg(std::to_string(info.GetProcessID()),
|
|
info.GetNameAsStringRef());
|
|
}
|
|
|
|
void CommandCompletions::ProcessNames(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
lldb::PlatformSP platform_sp(interpreter.GetPlatform(true));
|
|
if (!platform_sp)
|
|
return;
|
|
ProcessInstanceInfoList process_infos;
|
|
ProcessInstanceInfoMatch match_info;
|
|
platform_sp->FindProcesses(match_info, process_infos);
|
|
for (const ProcessInstanceInfo &info : process_infos)
|
|
request.TryCompleteCurrentArg(info.GetNameAsStringRef());
|
|
}
|
|
|
|
void CommandCompletions::TypeLanguages(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
for (int bit :
|
|
Language::GetLanguagesSupportingTypeSystems().bitvector.set_bits()) {
|
|
request.TryCompleteCurrentArg(
|
|
Language::GetNameForLanguageType(static_cast<lldb::LanguageType>(bit)));
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::FrameIndexes(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
const ExecutionContext &exe_ctx = interpreter.GetExecutionContext();
|
|
if (!exe_ctx.HasProcessScope())
|
|
return;
|
|
|
|
lldb::ThreadSP thread_sp = exe_ctx.GetThreadSP();
|
|
Debugger &dbg = interpreter.GetDebugger();
|
|
const uint32_t frame_num = thread_sp->GetStackFrameCount();
|
|
for (uint32_t i = 0; i < frame_num; ++i) {
|
|
lldb::StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(i);
|
|
StreamString strm;
|
|
// Dumping frames can be slow, allow interruption.
|
|
if (INTERRUPT_REQUESTED(dbg, "Interrupted in frame completion"))
|
|
break;
|
|
frame_sp->Dump(&strm, false, true);
|
|
request.TryCompleteCurrentArg(std::to_string(i), strm.GetString());
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::StopHookIDs(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
const lldb::TargetSP target_sp =
|
|
interpreter.GetExecutionContext().GetTargetSP();
|
|
if (!target_sp)
|
|
return;
|
|
|
|
for (auto &stophook_sp : target_sp->GetStopHooks()) {
|
|
StreamString strm;
|
|
// The value 11 is an offset to make the completion description looks
|
|
// neater.
|
|
strm.SetIndentLevel(11);
|
|
stophook_sp->GetDescription(strm, lldb::eDescriptionLevelInitial);
|
|
request.TryCompleteCurrentArg(std::to_string(stophook_sp->GetID()),
|
|
strm.GetString());
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::ThreadIndexes(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
const ExecutionContext &exe_ctx = interpreter.GetExecutionContext();
|
|
if (!exe_ctx.HasProcessScope())
|
|
return;
|
|
|
|
ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList();
|
|
lldb::ThreadSP thread_sp;
|
|
for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
|
|
StreamString strm;
|
|
thread_sp->GetStatus(strm, 0, 1, 1, true, /*show_hidden*/ true);
|
|
request.TryCompleteCurrentArg(std::to_string(thread_sp->GetIndexID()),
|
|
strm.GetString());
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::WatchPointIDs(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
const ExecutionContext &exe_ctx = interpreter.GetExecutionContext();
|
|
if (!exe_ctx.HasTargetScope())
|
|
return;
|
|
|
|
const WatchpointList &wp_list = exe_ctx.GetTargetPtr()->GetWatchpointList();
|
|
for (lldb::WatchpointSP wp_sp : wp_list.Watchpoints()) {
|
|
StreamString strm;
|
|
wp_sp->Dump(&strm);
|
|
request.TryCompleteCurrentArg(std::to_string(wp_sp->GetID()),
|
|
strm.GetString());
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::TypeCategoryNames(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
DataVisualization::Categories::ForEach(
|
|
[&request](const lldb::TypeCategoryImplSP &category_sp) {
|
|
request.TryCompleteCurrentArg(category_sp->GetName(),
|
|
category_sp->GetDescription());
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
const ExecutionContext &exe_ctx = interpreter.GetExecutionContext();
|
|
if (!exe_ctx.HasProcessScope())
|
|
return;
|
|
|
|
ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList();
|
|
lldb::ThreadSP thread_sp;
|
|
for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) {
|
|
StreamString strm;
|
|
thread_sp->GetStatus(strm, 0, 1, 1, true, /*show_hidden*/ true);
|
|
request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()),
|
|
strm.GetString());
|
|
}
|
|
}
|
|
|
|
void CommandCompletions::ManagedPlugins(CommandInterpreter &interpreter,
|
|
CompletionRequest &request,
|
|
SearchFilter *searcher) {
|
|
PluginManager::AutoCompletePluginName(request.GetCursorArgumentPrefix(),
|
|
request);
|
|
}
|
|
|
|
void CommandCompletions::CompleteModifiableCmdPathArgs(
|
|
CommandInterpreter &interpreter, CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) {
|
|
// The only arguments constitute a command path, however, there might be
|
|
// options interspersed among the arguments, and we need to skip those. Do that
|
|
// by copying the args vector, and just dropping all the option bits:
|
|
Args args = request.GetParsedLine();
|
|
std::vector<size_t> to_delete;
|
|
for (auto &elem : opt_element_vector) {
|
|
to_delete.push_back(elem.opt_pos);
|
|
if (elem.opt_arg_pos != 0)
|
|
to_delete.push_back(elem.opt_arg_pos);
|
|
}
|
|
sort(to_delete.begin(), to_delete.end(), std::greater<size_t>());
|
|
for (size_t idx : to_delete)
|
|
args.DeleteArgumentAtIndex(idx);
|
|
|
|
// At this point, we should only have args, so now lookup the command up to
|
|
// the cursor element.
|
|
|
|
// There's nothing here but options. It doesn't seem very useful here to
|
|
// dump all the commands, so just return.
|
|
size_t num_args = args.GetArgumentCount();
|
|
if (num_args == 0)
|
|
return;
|
|
|
|
// There's just one argument, so we should complete its name:
|
|
StringList matches;
|
|
if (num_args == 1) {
|
|
interpreter.GetUserCommandObject(args.GetArgumentAtIndex(0), &matches,
|
|
nullptr);
|
|
request.AddCompletions(matches);
|
|
return;
|
|
}
|
|
|
|
// There was more than one path element, lets find the containing command:
|
|
Status error;
|
|
CommandObjectMultiword *mwc =
|
|
interpreter.VerifyUserMultiwordCmdPath(args, true, error);
|
|
|
|
// Something was wrong somewhere along the path, but I don't think there's
|
|
// a good way to go back and fill in the missing elements:
|
|
if (error.Fail())
|
|
return;
|
|
|
|
// This should never happen. We already handled the case of one argument
|
|
// above, and we can only get Success & nullptr back if there's a one-word
|
|
// leaf.
|
|
assert(mwc != nullptr);
|
|
|
|
mwc->GetSubcommandObject(args.GetArgumentAtIndex(num_args - 1), &matches);
|
|
if (matches.GetSize() == 0)
|
|
return;
|
|
|
|
request.AddCompletions(matches);
|
|
}
|