Skip to content

Commit 824ad3b

Browse files
committed
[lldb] Fix module name tab completion
Module names can be matched either by a full path or just their basename. The completion machinery tried to do both, but had several bugs: - it always inserted the basename as a completion candidate, even if the string being completed was a full path - due to FileSpec canonicalization, it lost information about trailing slashes (it treated "lib/<TAB>" as "lib<TAB>", even though it's clear the former was trying to complete a directory name) - due to both of the previous issues, the completion candidates could end up being shorter than the string being completed, which caused crashes (string out of range errors) when attempting to substitute the results. This patch rewrites to logic to remove these kinds of issues: - basename and full path completion are handled separately - full path completion is attempted always, basename only if the input string does not contain a slash - the code remembers both the canonical and original spelling or the completed argument. The canonical arg is used for matching, while the original spelling is used for completion. This way "/foo///.//b<TAB>" can still match "/foo/bar", but it will complete to "/foo///.//bar".
1 parent 80b78f5 commit 824ad3b

File tree

2 files changed

+72
-21
lines changed

2 files changed

+72
-21
lines changed

lldb/source/Commands/CommandCompletions.cpp

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "llvm/ADT/STLExtras.h"
910
#include "llvm/ADT/SmallString.h"
11+
#include "llvm/ADT/StringRef.h"
1012
#include "llvm/ADT/StringSet.h"
1113

1214
#include "lldb/Breakpoint/Watchpoint.h"
@@ -262,9 +264,26 @@ class ModuleCompleter : public Completer {
262264
public:
263265
ModuleCompleter(CommandInterpreter &interpreter, CompletionRequest &request)
264266
: Completer(interpreter, request) {
265-
FileSpec partial_spec(m_request.GetCursorArgumentPrefix());
266-
m_file_name = partial_spec.GetFilename().GetCString();
267-
m_dir_name = partial_spec.GetDirectory().GetCString();
267+
llvm::StringRef request_str = m_request.GetCursorArgumentPrefix();
268+
// We can match the full path, or the file name only. The full match will be
269+
// attempted always, the file name match only if the request does not
270+
// contain a path separator.
271+
272+
// Preserve both the path as spelled by the user (used for completion) and
273+
// the canonical version (used for matching).
274+
m_spelled_path = request_str;
275+
m_canonical_path = FileSpec(m_spelled_path).GetPath();
276+
if (!m_spelled_path.empty() &&
277+
llvm::sys::path::is_separator(m_spelled_path.back()) &&
278+
!llvm::StringRef(m_canonical_path).ends_with(m_spelled_path.back())) {
279+
m_canonical_path += m_spelled_path.back();
280+
}
281+
282+
bool has_separator = llvm::find_if(request_str, [](char c) {
283+
return llvm::sys::path::is_separator(c);
284+
}) != request_str.end();
285+
m_file_name =
286+
has_separator ? llvm::sys::path::get_separator() : request_str;
268287
}
269288

270289
lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; }
@@ -273,22 +292,18 @@ class ModuleCompleter : public Completer {
273292
SymbolContext &context,
274293
Address *addr) override {
275294
if (context.module_sp) {
276-
const char *cur_file_name =
277-
context.module_sp->GetFileSpec().GetFilename().GetCString();
278-
const char *cur_dir_name =
279-
context.module_sp->GetFileSpec().GetDirectory().GetCString();
280-
281-
bool match = false;
282-
if (m_file_name && cur_file_name &&
283-
strstr(cur_file_name, m_file_name) == cur_file_name)
284-
match = true;
285-
286-
if (match && m_dir_name && cur_dir_name &&
287-
strstr(cur_dir_name, m_dir_name) != cur_dir_name)
288-
match = false;
289-
290-
if (match) {
291-
m_request.AddCompletion(cur_file_name);
295+
// Attempt a full path match.
296+
std::string cur_path = context.module_sp->GetFileSpec().GetPath();
297+
llvm::StringRef cur_path_view = cur_path;
298+
if (cur_path_view.consume_front(m_canonical_path))
299+
m_request.AddCompletion((m_spelled_path + cur_path_view).str());
300+
301+
// And a file name match.
302+
if (m_file_name) {
303+
llvm::StringRef cur_file_name =
304+
context.module_sp->GetFileSpec().GetFilename().GetStringRef();
305+
if (cur_file_name.starts_with(*m_file_name))
306+
m_request.AddCompletion(cur_file_name);
292307
}
293308
}
294309
return Searcher::eCallbackReturnContinue;
@@ -297,8 +312,9 @@ class ModuleCompleter : public Completer {
297312
void DoCompletion(SearchFilter *filter) override { filter->Search(*this); }
298313

299314
private:
300-
const char *m_file_name;
301-
const char *m_dir_name;
315+
std::optional<llvm::StringRef> m_file_name;
316+
llvm::StringRef m_spelled_path;
317+
std::string m_canonical_path;
302318

303319
ModuleCompleter(const ModuleCompleter &) = delete;
304320
const ModuleCompleter &operator=(const ModuleCompleter &) = delete;

lldb/test/API/functionalities/completion/TestCompletion.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,3 +906,38 @@ def test_ambiguous_command(self):
906906
def test_ambiguous_subcommand(self):
907907
"""Test completing a subcommand of an ambiguous command"""
908908
self.complete_from_to("settings s ta", [])
909+
910+
def test_shlib_name(self):
911+
self.build()
912+
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
913+
self.assertTrue(target, VALID_TARGET)
914+
self.registerSharedLibrariesWithTarget(target, ["shared"])
915+
916+
basenames = []
917+
paths = []
918+
for m in target.modules:
919+
basenames.append(m.file.basename)
920+
paths.append(m.file.fullpath)
921+
922+
# To see all the diffs
923+
self.maxDiff = None
924+
925+
# An empty string completes to everything
926+
self.completions_match("target symbols add -s ", basenames + paths)
927+
928+
# Base name completion
929+
self.completions_match("target symbols add -s a.", ["a.out"])
930+
931+
# Full path completion
932+
prefix = os.path.commonpath(paths)
933+
self.completions_match("target symbols add -s '" + prefix, paths)
934+
935+
# Full path, but ending with a path separator
936+
prefix_sep = prefix + os.path.sep
937+
self.completions_match("target symbols add -s '" + prefix_sep, paths)
938+
939+
# The completed path should match the spelling of the input, so if the
940+
# input contains a double separator, so should the completions.
941+
prefix_sep_sep = prefix_sep + os.path.sep
942+
paths_sep = [prefix_sep_sep + p[len(prefix_sep) :] for p in paths]
943+
self.completions_match("target symbols add -s '" + prefix_sep_sep, paths_sep)

0 commit comments

Comments
 (0)