1 //===-- CommandCompletions.cpp ----------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "llvm/ADT/SmallString.h" 10 #include "llvm/ADT/StringSet.h" 11 12 #include "lldb/Core/FileSpecList.h" 13 #include "lldb/Core/Module.h" 14 #include "lldb/Core/PluginManager.h" 15 #include "lldb/Host/FileSystem.h" 16 #include "lldb/Interpreter/CommandCompletions.h" 17 #include "lldb/Interpreter/CommandInterpreter.h" 18 #include "lldb/Interpreter/OptionValueProperties.h" 19 #include "lldb/Symbol/CompileUnit.h" 20 #include "lldb/Symbol/Variable.h" 21 #include "lldb/Utility/FileSpec.h" 22 #include "lldb/Utility/StreamString.h" 23 #include "lldb/Utility/TildeExpressionResolver.h" 24 25 #include "llvm/Support/FileSystem.h" 26 #include "llvm/Support/Path.h" 27 28 using namespace lldb_private; 29 30 CommandCompletions::CommonCompletionElement 31 CommandCompletions::g_common_completions[] = { 32 {eCustomCompletion, nullptr}, 33 {eSourceFileCompletion, CommandCompletions::SourceFiles}, 34 {eDiskFileCompletion, CommandCompletions::DiskFiles}, 35 {eDiskDirectoryCompletion, CommandCompletions::DiskDirectories}, 36 {eSymbolCompletion, CommandCompletions::Symbols}, 37 {eModuleCompletion, CommandCompletions::Modules}, 38 {eSettingsNameCompletion, CommandCompletions::SettingsNames}, 39 {ePlatformPluginCompletion, CommandCompletions::PlatformPluginNames}, 40 {eArchitectureCompletion, CommandCompletions::ArchitectureNames}, 41 {eVariablePathCompletion, CommandCompletions::VariablePath}, 42 {eNoCompletion, nullptr} // This one has to be last in the list. 43 }; 44 45 bool CommandCompletions::InvokeCommonCompletionCallbacks( 46 CommandInterpreter &interpreter, uint32_t completion_mask, 47 CompletionRequest &request, SearchFilter *searcher) { 48 bool handled = false; 49 50 if (completion_mask & eCustomCompletion) 51 return false; 52 53 for (int i = 0;; i++) { 54 if (g_common_completions[i].type == eNoCompletion) 55 break; 56 else if ((g_common_completions[i].type & completion_mask) == 57 g_common_completions[i].type && 58 g_common_completions[i].callback != nullptr) { 59 handled = true; 60 g_common_completions[i].callback(interpreter, request, searcher); 61 } 62 } 63 return handled; 64 } 65 66 void CommandCompletions::SourceFiles(CommandInterpreter &interpreter, 67 CompletionRequest &request, 68 SearchFilter *searcher) { 69 // Find some way to switch "include support files..." 70 SourceFileCompleter completer(interpreter, false, request); 71 72 if (searcher == nullptr) { 73 lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); 74 SearchFilterForUnconstrainedSearches null_searcher(target_sp); 75 completer.DoCompletion(&null_searcher); 76 } else { 77 completer.DoCompletion(searcher); 78 } 79 } 80 81 static void DiskFilesOrDirectories(const llvm::Twine &partial_name, 82 bool only_directories, 83 CompletionRequest &request, 84 TildeExpressionResolver &Resolver) { 85 llvm::SmallString<256> CompletionBuffer; 86 llvm::SmallString<256> Storage; 87 partial_name.toVector(CompletionBuffer); 88 89 if (CompletionBuffer.size() >= PATH_MAX) 90 return; 91 92 namespace path = llvm::sys::path; 93 94 llvm::StringRef SearchDir; 95 llvm::StringRef PartialItem; 96 97 if (CompletionBuffer.startswith("~")) { 98 llvm::StringRef Buffer(CompletionBuffer); 99 size_t FirstSep = 100 Buffer.find_if([](char c) { return path::is_separator(c); }); 101 102 llvm::StringRef Username = Buffer.take_front(FirstSep); 103 llvm::StringRef Remainder; 104 if (FirstSep != llvm::StringRef::npos) 105 Remainder = Buffer.drop_front(FirstSep + 1); 106 107 llvm::SmallString<256> Resolved; 108 if (!Resolver.ResolveExact(Username, Resolved)) { 109 // We couldn't resolve it as a full username. If there were no slashes 110 // then this might be a partial username. We try to resolve it as such 111 // but after that, we're done regardless of any matches. 112 if (FirstSep == llvm::StringRef::npos) { 113 llvm::StringSet<> MatchSet; 114 Resolver.ResolvePartial(Username, MatchSet); 115 for (const auto &S : MatchSet) { 116 Resolved = S.getKey(); 117 path::append(Resolved, path::get_separator()); 118 request.AddCompletion(Resolved, "", CompletionMode::Partial); 119 } 120 } 121 return; 122 } 123 124 // If there was no trailing slash, then we're done as soon as we resolve 125 // the expression to the correct directory. Otherwise we need to continue 126 // looking for matches within that directory. 127 if (FirstSep == llvm::StringRef::npos) { 128 // Make sure it ends with a separator. 129 path::append(CompletionBuffer, path::get_separator()); 130 request.AddCompletion(CompletionBuffer, "", CompletionMode::Partial); 131 return; 132 } 133 134 // We want to keep the form the user typed, so we special case this to 135 // search in the fully resolved directory, but CompletionBuffer keeps the 136 // unmodified form that the user typed. 137 Storage = Resolved; 138 llvm::StringRef RemainderDir = path::parent_path(Remainder); 139 if (!RemainderDir.empty()) { 140 // Append the remaining path to the resolved directory. 141 Storage.append(path::get_separator()); 142 Storage.append(RemainderDir); 143 } 144 SearchDir = Storage; 145 } else { 146 SearchDir = path::parent_path(CompletionBuffer); 147 } 148 149 size_t FullPrefixLen = CompletionBuffer.size(); 150 151 PartialItem = path::filename(CompletionBuffer); 152 153 // path::filename() will return "." when the passed path ends with a 154 // directory separator. We have to filter those out, but only when the 155 // "." doesn't come from the completion request itself. 156 if (PartialItem == "." && path::is_separator(CompletionBuffer.back())) 157 PartialItem = llvm::StringRef(); 158 159 if (SearchDir.empty()) { 160 llvm::sys::fs::current_path(Storage); 161 SearchDir = Storage; 162 } 163 assert(!PartialItem.contains(path::get_separator())); 164 165 // SearchDir now contains the directory to search in, and Prefix contains the 166 // text we want to match against items in that directory. 167 168 FileSystem &fs = FileSystem::Instance(); 169 std::error_code EC; 170 llvm::vfs::directory_iterator Iter = fs.DirBegin(SearchDir, EC); 171 llvm::vfs::directory_iterator End; 172 for (; Iter != End && !EC; Iter.increment(EC)) { 173 auto &Entry = *Iter; 174 llvm::ErrorOr<llvm::vfs::Status> Status = fs.GetStatus(Entry.path()); 175 176 if (!Status) 177 continue; 178 179 auto Name = path::filename(Entry.path()); 180 181 // Omit ".", ".." 182 if (Name == "." || Name == ".." || !Name.startswith(PartialItem)) 183 continue; 184 185 bool is_dir = Status->isDirectory(); 186 187 // If it's a symlink, then we treat it as a directory as long as the target 188 // is a directory. 189 if (Status->isSymlink()) { 190 FileSpec symlink_filespec(Entry.path()); 191 FileSpec resolved_filespec; 192 auto error = fs.ResolveSymbolicLink(symlink_filespec, resolved_filespec); 193 if (error.Success()) 194 is_dir = fs.IsDirectory(symlink_filespec); 195 } 196 197 if (only_directories && !is_dir) 198 continue; 199 200 // Shrink it back down so that it just has the original prefix the user 201 // typed and remove the part of the name which is common to the located 202 // item and what the user typed. 203 CompletionBuffer.resize(FullPrefixLen); 204 Name = Name.drop_front(PartialItem.size()); 205 CompletionBuffer.append(Name); 206 207 if (is_dir) { 208 path::append(CompletionBuffer, path::get_separator()); 209 } 210 211 CompletionMode mode = 212 is_dir ? CompletionMode::Partial : CompletionMode::Normal; 213 request.AddCompletion(CompletionBuffer, "", mode); 214 } 215 } 216 217 static void DiskFilesOrDirectories(const llvm::Twine &partial_name, 218 bool only_directories, StringList &matches, 219 TildeExpressionResolver &Resolver) { 220 CompletionResult result; 221 std::string partial_name_str = partial_name.str(); 222 CompletionRequest request(partial_name_str, partial_name_str.size(), result); 223 DiskFilesOrDirectories(partial_name, only_directories, request, Resolver); 224 result.GetMatches(matches); 225 } 226 227 static void DiskFilesOrDirectories(CompletionRequest &request, 228 bool only_directories) { 229 StandardTildeExpressionResolver resolver; 230 DiskFilesOrDirectories(request.GetCursorArgumentPrefix(), only_directories, 231 request, resolver); 232 } 233 234 void CommandCompletions::DiskFiles(CommandInterpreter &interpreter, 235 CompletionRequest &request, 236 SearchFilter *searcher) { 237 DiskFilesOrDirectories(request, /*only_dirs*/ false); 238 } 239 240 void CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name, 241 StringList &matches, 242 TildeExpressionResolver &Resolver) { 243 DiskFilesOrDirectories(partial_file_name, false, matches, Resolver); 244 } 245 246 void CommandCompletions::DiskDirectories(CommandInterpreter &interpreter, 247 CompletionRequest &request, 248 SearchFilter *searcher) { 249 DiskFilesOrDirectories(request, /*only_dirs*/ true); 250 } 251 252 void CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name, 253 StringList &matches, 254 TildeExpressionResolver &Resolver) { 255 DiskFilesOrDirectories(partial_file_name, true, matches, Resolver); 256 } 257 258 void CommandCompletions::Modules(CommandInterpreter &interpreter, 259 CompletionRequest &request, 260 SearchFilter *searcher) { 261 ModuleCompleter completer(interpreter, request); 262 263 if (searcher == nullptr) { 264 lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); 265 SearchFilterForUnconstrainedSearches null_searcher(target_sp); 266 completer.DoCompletion(&null_searcher); 267 } else { 268 completer.DoCompletion(searcher); 269 } 270 } 271 272 void CommandCompletions::Symbols(CommandInterpreter &interpreter, 273 CompletionRequest &request, 274 SearchFilter *searcher) { 275 SymbolCompleter completer(interpreter, request); 276 277 if (searcher == nullptr) { 278 lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); 279 SearchFilterForUnconstrainedSearches null_searcher(target_sp); 280 completer.DoCompletion(&null_searcher); 281 } else { 282 completer.DoCompletion(searcher); 283 } 284 } 285 286 void CommandCompletions::SettingsNames(CommandInterpreter &interpreter, 287 CompletionRequest &request, 288 SearchFilter *searcher) { 289 // Cache the full setting name list 290 static StringList g_property_names; 291 if (g_property_names.GetSize() == 0) { 292 // Generate the full setting name list on demand 293 lldb::OptionValuePropertiesSP properties_sp( 294 interpreter.GetDebugger().GetValueProperties()); 295 if (properties_sp) { 296 StreamString strm; 297 properties_sp->DumpValue(nullptr, strm, OptionValue::eDumpOptionName); 298 const std::string &str = strm.GetString(); 299 g_property_names.SplitIntoLines(str.c_str(), str.size()); 300 } 301 } 302 303 for (const std::string &s : g_property_names) 304 request.TryCompleteCurrentArg(s); 305 } 306 307 void CommandCompletions::PlatformPluginNames(CommandInterpreter &interpreter, 308 CompletionRequest &request, 309 SearchFilter *searcher) { 310 PluginManager::AutoCompletePlatformName(request.GetCursorArgumentPrefix(), 311 request); 312 } 313 314 void CommandCompletions::ArchitectureNames(CommandInterpreter &interpreter, 315 CompletionRequest &request, 316 SearchFilter *searcher) { 317 ArchSpec::AutoComplete(request); 318 } 319 320 void CommandCompletions::VariablePath(CommandInterpreter &interpreter, 321 CompletionRequest &request, 322 SearchFilter *searcher) { 323 Variable::AutoComplete(interpreter.GetExecutionContext(), request); 324 } 325 326 CommandCompletions::Completer::Completer(CommandInterpreter &interpreter, 327 CompletionRequest &request) 328 : m_interpreter(interpreter), m_request(request) {} 329 330 CommandCompletions::Completer::~Completer() = default; 331 332 // SourceFileCompleter 333 334 CommandCompletions::SourceFileCompleter::SourceFileCompleter( 335 CommandInterpreter &interpreter, bool include_support_files, 336 CompletionRequest &request) 337 : CommandCompletions::Completer(interpreter, request), 338 m_include_support_files(include_support_files), m_matching_files() { 339 FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); 340 m_file_name = partial_spec.GetFilename().GetCString(); 341 m_dir_name = partial_spec.GetDirectory().GetCString(); 342 } 343 344 lldb::SearchDepth CommandCompletions::SourceFileCompleter::GetDepth() { 345 return lldb::eSearchDepthCompUnit; 346 } 347 348 Searcher::CallbackReturn 349 CommandCompletions::SourceFileCompleter::SearchCallback(SearchFilter &filter, 350 SymbolContext &context, 351 Address *addr) { 352 if (context.comp_unit != nullptr) { 353 if (m_include_support_files) { 354 FileSpecList supporting_files = context.comp_unit->GetSupportFiles(); 355 for (size_t sfiles = 0; sfiles < supporting_files.GetSize(); sfiles++) { 356 const FileSpec &sfile_spec = 357 supporting_files.GetFileSpecAtIndex(sfiles); 358 const char *sfile_file_name = sfile_spec.GetFilename().GetCString(); 359 const char *sfile_dir_name = sfile_spec.GetFilename().GetCString(); 360 bool match = false; 361 if (m_file_name && sfile_file_name && 362 strstr(sfile_file_name, m_file_name) == sfile_file_name) 363 match = true; 364 if (match && m_dir_name && sfile_dir_name && 365 strstr(sfile_dir_name, m_dir_name) != sfile_dir_name) 366 match = false; 367 368 if (match) { 369 m_matching_files.AppendIfUnique(sfile_spec); 370 } 371 } 372 } else { 373 const char *cur_file_name = 374 context.comp_unit->GetPrimaryFile().GetFilename().GetCString(); 375 const char *cur_dir_name = 376 context.comp_unit->GetPrimaryFile().GetDirectory().GetCString(); 377 378 bool match = false; 379 if (m_file_name && cur_file_name && 380 strstr(cur_file_name, m_file_name) == cur_file_name) 381 match = true; 382 383 if (match && m_dir_name && cur_dir_name && 384 strstr(cur_dir_name, m_dir_name) != cur_dir_name) 385 match = false; 386 387 if (match) { 388 m_matching_files.AppendIfUnique(context.comp_unit->GetPrimaryFile()); 389 } 390 } 391 } 392 return Searcher::eCallbackReturnContinue; 393 } 394 395 void CommandCompletions::SourceFileCompleter::DoCompletion( 396 SearchFilter *filter) { 397 filter->Search(*this); 398 // Now convert the filelist to completions: 399 for (size_t i = 0; i < m_matching_files.GetSize(); i++) { 400 m_request.AddCompletion( 401 m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString()); 402 } 403 } 404 405 // SymbolCompleter 406 407 static bool regex_chars(const char comp) { 408 return llvm::StringRef("[](){}+.*|^$\\?").contains(comp); 409 } 410 411 CommandCompletions::SymbolCompleter::SymbolCompleter( 412 CommandInterpreter &interpreter, CompletionRequest &request) 413 : CommandCompletions::Completer(interpreter, request) { 414 std::string regex_str; 415 if (!m_request.GetCursorArgumentPrefix().empty()) { 416 regex_str.append("^"); 417 regex_str.append(m_request.GetCursorArgumentPrefix()); 418 } else { 419 // Match anything since the completion string is empty 420 regex_str.append("."); 421 } 422 std::string::iterator pos = 423 find_if(regex_str.begin() + 1, regex_str.end(), regex_chars); 424 while (pos < regex_str.end()) { 425 pos = regex_str.insert(pos, '\\'); 426 pos = find_if(pos + 2, regex_str.end(), regex_chars); 427 } 428 m_regex = RegularExpression(regex_str); 429 } 430 431 lldb::SearchDepth CommandCompletions::SymbolCompleter::GetDepth() { 432 return lldb::eSearchDepthModule; 433 } 434 435 Searcher::CallbackReturn CommandCompletions::SymbolCompleter::SearchCallback( 436 SearchFilter &filter, SymbolContext &context, Address *addr) { 437 if (context.module_sp) { 438 SymbolContextList sc_list; 439 const bool include_symbols = true; 440 const bool include_inlines = true; 441 context.module_sp->FindFunctions(m_regex, include_symbols, include_inlines, 442 sc_list); 443 444 SymbolContext sc; 445 // Now add the functions & symbols to the list - only add if unique: 446 for (uint32_t i = 0; i < sc_list.GetSize(); i++) { 447 if (sc_list.GetContextAtIndex(i, sc)) { 448 ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled); 449 // Ensure that the function name matches the regex. This is more than a 450 // sanity check. It is possible that the demangled function name does 451 // not start with the prefix, for example when it's in an anonymous 452 // namespace. 453 if (!func_name.IsEmpty() && m_regex.Execute(func_name.GetStringRef())) 454 m_match_set.insert(func_name); 455 } 456 } 457 } 458 return Searcher::eCallbackReturnContinue; 459 } 460 461 void CommandCompletions::SymbolCompleter::DoCompletion(SearchFilter *filter) { 462 filter->Search(*this); 463 collection::iterator pos = m_match_set.begin(), end = m_match_set.end(); 464 for (pos = m_match_set.begin(); pos != end; pos++) 465 m_request.AddCompletion((*pos).GetCString()); 466 } 467 468 // ModuleCompleter 469 CommandCompletions::ModuleCompleter::ModuleCompleter( 470 CommandInterpreter &interpreter, CompletionRequest &request) 471 : CommandCompletions::Completer(interpreter, request) { 472 FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); 473 m_file_name = partial_spec.GetFilename().GetCString(); 474 m_dir_name = partial_spec.GetDirectory().GetCString(); 475 } 476 477 lldb::SearchDepth CommandCompletions::ModuleCompleter::GetDepth() { 478 return lldb::eSearchDepthModule; 479 } 480 481 Searcher::CallbackReturn CommandCompletions::ModuleCompleter::SearchCallback( 482 SearchFilter &filter, SymbolContext &context, Address *addr) { 483 if (context.module_sp) { 484 const char *cur_file_name = 485 context.module_sp->GetFileSpec().GetFilename().GetCString(); 486 const char *cur_dir_name = 487 context.module_sp->GetFileSpec().GetDirectory().GetCString(); 488 489 bool match = false; 490 if (m_file_name && cur_file_name && 491 strstr(cur_file_name, m_file_name) == cur_file_name) 492 match = true; 493 494 if (match && m_dir_name && cur_dir_name && 495 strstr(cur_dir_name, m_dir_name) != cur_dir_name) 496 match = false; 497 498 if (match) { 499 m_request.AddCompletion(cur_file_name); 500 } 501 } 502 return Searcher::eCallbackReturnContinue; 503 } 504 505 void CommandCompletions::ModuleCompleter::DoCompletion(SearchFilter *filter) { 506 filter->Search(*this); 507 } 508