[Lldb-commits] [lldb] r297585 - Make file / directory completion work properly on Windows.
Zachary Turner via lldb-commits
lldb-commits at lists.llvm.org
Tue Apr 11 17:59:30 PDT 2017
Thanks for the heads up, I'll try to look at this tomorrow.
On Tue, Apr 11, 2017 at 5:55 PM Jason Molenda <jmolenda at apple.com> wrote:
> I noticed that the llvm.org sources crash when you do filename completion
> with a file in the current working directory:
>
> % build/Debug/lldb
> (lldb) file aaa[TAB]Assertion failed: (!SearchDir.empty()), function
> DiskFilesOrDirectories, file
> /Volumes/newwork/svn/lldb/source/Commands/CommandCompletions.cpp, line 179.
> Abort
>
>
> Or through the SB API:
>
> % build/Debug/lldb
> (lldb) scri
> >>> print lldb.debugger.GetCommandInterpreter().HandleCompletion("file
> /tmp/ll", 11, 0, -1, lldb.SBStringList())
> 0
> >>> print lldb.debugger.GetCommandInterpreter().HandleCompletion("file
> ll", 7, 0, -1, lldb.SBStringList())
> Assertion failed: (!SearchDir.empty()), function DiskFilesOrDirectories,
> file /Volumes/newwork/svn/lldb/source/Commands/CommandCompletions.cpp, line
> 179.
> Abort
>
>
> maybe it's related from r297585? There have been a few overlapping
> changes to CommandCompletions.cpp by you and Pavel recently, not sure
> exactly which it might be.
>
> If this can't be tested through the unit test framework, it would be easy
> to add an SB API test case, see above.
>
> Thanks
>
>
>
> > On Mar 12, 2017, at 11:18 AM, Zachary Turner via lldb-commits <
> lldb-commits at lists.llvm.org> wrote:
> >
> > Author: zturner
> > Date: Sun Mar 12 13:18:50 2017
> > New Revision: 297585
> >
> > URL: http://llvm.org/viewvc/llvm-project?rev=297585&view=rev
> > Log:
> > Make file / directory completion work properly on Windows.
> >
> > There were a couple of problems with this function on Windows. Different
> > separators and differences in how tilde expressions are resolved for
> > starters, but in addition there was no clear indication of what the
> > function's inputs or outputs were supposed to be, and there were no tests
> > to demonstrate its use.
> >
> > To more easily paper over the differences between Windows paths,
> > non-Windows paths, and tilde expressions, I've ported this function to
> use
> > LLVM-based directory iteration (in fact, I would like to eliminate all of
> > LLDB's directory iteration code entirely since LLVM's is cleaner / more
> > efficient (i.e. it invokes fewer stat calls)). and llvm's portable path
> > manipulation library.
> >
> > Since file and directory completion assumes you are referring to files
> and
> > directories on your local machine, it's safe to assume the path syntax
> > properties of the host in doing so, so LLVM's APIs are perfect for this.
> >
> > I've also added a fairly robust set of unit tests. Since you can't really
> > predict what users will be on your machine, or what their home
> directories
> > will be, I added an interface called TildeExpressionResolver, and in the
> > unit test I've mocked up a fake implementation that acts like a unix
> > password database. This allows us to configure some fake users and home
> > directories in the test, so we can exercise all of those hard-to-test
> > codepaths that normally otherwise depend on the host.
> >
> > Differential Revision: https://reviews.llvm.org/D30789
> >
> > Added:
> > lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h
> > lldb/trunk/source/Utility/TildeExpressionResolver.cpp
> > lldb/trunk/unittests/Interpreter/TestCompletion.cpp
> > Modified:
> > lldb/trunk/include/lldb/Interpreter/CommandCompletions.h
> > lldb/trunk/source/Commands/CommandCompletions.cpp
> > lldb/trunk/source/Utility/CMakeLists.txt
> > lldb/trunk/unittests/Interpreter/CMakeLists.txt
> >
> > Modified: lldb/trunk/include/lldb/Interpreter/CommandCompletions.h
> > URL:
> http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Interpreter/CommandCompletions.h?rev=297585&r1=297584&r2=297585&view=diff
> >
> ==============================================================================
> > --- lldb/trunk/include/lldb/Interpreter/CommandCompletions.h (original)
> > +++ lldb/trunk/include/lldb/Interpreter/CommandCompletions.h Sun Mar 12
> 13:18:50 2017
> > @@ -21,7 +21,10 @@
> > #include "lldb/Utility/RegularExpression.h"
> > #include "lldb/lldb-private.h"
> >
> > +#include "llvm/ADT/Twine.h"
> > +
> > namespace lldb_private {
> > +struct TildeExpressionResolver;
> > class CommandCompletions {
> > public:
> >
> //----------------------------------------------------------------------
> > @@ -76,12 +79,19 @@ public:
> > int max_return_elements, SearchFilter *searcher,
> > bool &word_complete, StringList &matches);
> >
> > + static int DiskFiles(const llvm::Twine &partial_file_name,
> > + StringList &matches, TildeExpressionResolver
> &Resolver);
> > +
> > static int DiskDirectories(CommandInterpreter &interpreter,
> > llvm::StringRef partial_file_name,
> > int match_start_point, int
> max_return_elements,
> > SearchFilter *searcher, bool &word_complete,
> > StringList &matches);
> >
> > + static int DiskDirectories(const llvm::Twine &partial_file_name,
> > + StringList &matches,
> > + TildeExpressionResolver &Resolver);
> > +
> > static int SourceFiles(CommandInterpreter &interpreter,
> > llvm::StringRef partial_file_name,
> > int match_start_point, int max_return_elements,
> >
> > Added: lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h
> > URL:
> http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h?rev=297585&view=auto
> >
> ==============================================================================
> > --- lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h (added)
> > +++ lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h Sun Mar 12
> 13:18:50 2017
> > @@ -0,0 +1,57 @@
> > +//===--------------------- TildeExpressionResolver.h ------------*- C++
> -*-===//
> > +//
> > +// The LLVM Compiler Infrastructure
> > +//
> > +// This file is distributed under the University of Illinois Open Source
> > +// License. See LICENSE.TXT for details.
> > +//
> >
> +//===----------------------------------------------------------------------===//
> > +
> > +#ifndef LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H
> > +#define LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H
> > +
> > +#include "llvm/ADT/SmallVector.h"
> > +#include "llvm/ADT/StringRef.h"
> > +#include "llvm/ADT/StringSet.h"
> > +
> > +namespace lldb_private {
> > +class TildeExpressionResolver {
> > +public:
> > + virtual ~TildeExpressionResolver();
> > +
> > + /// \brief Resolve a Tilde Expression contained according to bash
> rules.
> > + ///
> > + /// \param Expr Contains the tilde expression to resolve. A valid
> tilde
> > + /// expression must begin with a tilde and contain only
> non
> > + /// separator characters.
> > + ///
> > + /// \param Output Contains the resolved tilde expression, or the
> original
> > + /// input if the tilde expression could not be resolved.
> > + ///
> > + /// \returns true if \p Expr was successfully resolved, false
> otherwise.
> > + virtual bool ResolveExact(llvm::StringRef Expr,
> > + llvm::SmallVectorImpl<char> &Output) = 0;
> > +
> > + /// \brief Auto-complete a tilde expression with all matching values.
> > + ///
> > + /// \param Expr Contains the tilde expression prefix to resolve. See
> > + /// ResolveExact() for validity rules.
> > + ///
> > + /// \param Output Contains all matching home directories, each one
> > + /// itself unresolved (i.e. you need to call
> ResolveExact
> > + /// on each item to turn it into a real path).
> > + ///
> > + /// \returns true if there were any matches, false otherwise.
> > + virtual bool ResolvePartial(llvm::StringRef Expr,
> > + llvm::StringSet<> &Output) = 0;
> > +};
> > +
> > +class StandardTildeExpressionResolver : public TildeExpressionResolver {
> > +public:
> > + bool ResolveExact(llvm::StringRef Expr,
> > + llvm::SmallVectorImpl<char> &Output) override;
> > + bool ResolvePartial(llvm::StringRef Expr, llvm::StringSet<> &Output)
> override;
> > +};
> > +}
> > +
> > +#endif // #ifndef LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H
> >
> > Modified: lldb/trunk/source/Commands/CommandCompletions.cpp
> > URL:
> http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandCompletions.cpp?rev=297585&r1=297584&r2=297585&view=diff
> >
> ==============================================================================
> > --- lldb/trunk/source/Commands/CommandCompletions.cpp (original)
> > +++ lldb/trunk/source/Commands/CommandCompletions.cpp Sun Mar 12
> 13:18:50 2017
> > @@ -16,6 +16,7 @@
> > // C++ Includes
> > // Other libraries and framework includes
> > #include "llvm/ADT/SmallString.h"
> > +#include "llvm/ADT/StringSet.h"
> >
> > // Project includes
> > #include "lldb/Core/FileSpecList.h"
> > @@ -31,9 +32,11 @@
> > #include "lldb/Symbol/Variable.h"
> > #include "lldb/Target/Target.h"
> > #include "lldb/Utility/CleanUp.h"
> > +#include "lldb/Utility/TildeExpressionResolver.h"
> >
> > #include "llvm/ADT/SmallString.h"
> > #include "llvm/Support/FileSystem.h"
> > +#include "llvm/Support/Path.h"
> >
> > using namespace lldb_private;
> >
> > @@ -99,180 +102,127 @@ int CommandCompletions::SourceFiles(Comm
> > return matches.GetSize();
> > }
> >
> > -typedef struct DiskFilesOrDirectoriesBaton {
> > - const char *remainder;
> > - char *partial_name_copy;
> > - bool only_directories;
> > - bool *saw_directory;
> > - StringList *matches;
> > - char *end_ptr;
> > - size_t baselen;
> > -} DiskFilesOrDirectoriesBaton;
> > -
> > -FileSpec::EnumerateDirectoryResult
> > -DiskFilesOrDirectoriesCallback(void *baton, llvm::sys::fs::file_type
> file_type,
> > - const FileSpec &spec) {
> > - const char *name = spec.GetFilename().AsCString();
> > -
> > - const DiskFilesOrDirectoriesBaton *parameters =
> > - (DiskFilesOrDirectoriesBaton *)baton;
> > - char *end_ptr = parameters->end_ptr;
> > - char *partial_name_copy = parameters->partial_name_copy;
> > - const char *remainder = parameters->remainder;
> > -
> > - // Omit ".", ".." and any . files if the match string doesn't start
> with .
> > - if (name[0] == '.') {
> > - if (name[1] == '\0')
> > - return FileSpec::eEnumerateDirectoryResultNext;
> > - else if (name[1] == '.' && name[2] == '\0')
> > - return FileSpec::eEnumerateDirectoryResultNext;
> > - else if (remainder[0] != '.')
> > - return FileSpec::eEnumerateDirectoryResultNext;
> > - }
> > -
> > - // If we found a directory, we put a "/" at the end of the name.
> > -
> > - if (remainder[0] == '\0' || strstr(name, remainder) == name) {
> > - if (strlen(name) + parameters->baselen >= PATH_MAX)
> > - return FileSpec::eEnumerateDirectoryResultNext;
> > -
> > - strcpy(end_ptr, name);
> > -
> > - namespace fs = llvm::sys::fs;
> > - bool isa_directory = false;
> > - if (file_type == fs::file_type::directory_file)
> > - isa_directory = true;
> > - else if (file_type == fs::file_type::symlink_file)
> > - isa_directory = fs::is_directory(partial_name_copy);
> > -
> > - if (isa_directory) {
> > - *parameters->saw_directory = true;
> > - size_t len = strlen(parameters->partial_name_copy);
> > - partial_name_copy[len] = '/';
> > - partial_name_copy[len + 1] = '\0';
> > - }
> > - if (parameters->only_directories && !isa_directory)
> > - return FileSpec::eEnumerateDirectoryResultNext;
> > - parameters->matches->AppendString(partial_name_copy);
> > - }
> > -
> > - return FileSpec::eEnumerateDirectoryResultNext;
> > -}
> > -
> > -static int DiskFilesOrDirectories(llvm::StringRef partial_file_name,
> > +static int DiskFilesOrDirectories(const llvm::Twine &partial_name,
> > bool only_directories, bool
> &saw_directory,
> > - StringList &matches) {
> > - // I'm going to use the "glob" function with GLOB_TILDE for user
> directory
> > - // expansion.
> > - // If it is not defined on your host system, you'll need to implement
> it
> > - // yourself...
> > -
> > - size_t partial_name_len = partial_file_name.size();
> > -
> > - if (partial_name_len >= PATH_MAX)
> > - return matches.GetSize();
> > -
> > - // This copy of the string will be cut up into the directory part,
> and the
> > - // remainder. end_ptr below will point to the place of the remainder
> in this
> > - // string. Then when we've resolved the containing directory, and
> opened it,
> > - // we'll read the directory contents and overwrite the
> partial_name_copy
> > - // starting from end_ptr with each of the matches. Thus we will
> preserve the
> > - // form the user originally typed.
> > -
> > - char partial_name_copy[PATH_MAX];
> > - memcpy(partial_name_copy, partial_file_name.data(), partial_name_len);
> > - partial_name_copy[partial_name_len] = '\0';
> > -
> > - // We'll need to save a copy of the remainder for comparison, which
> we do
> > - // here.
> > - char remainder[PATH_MAX];
> > -
> > - // end_ptr will point past the last / in partial_name_copy, or if
> there is no
> > - // slash to the beginning of the string.
> > - char *end_ptr;
> > -
> > - end_ptr = strrchr(partial_name_copy, '/');
> > -
> > - // This will store the resolved form of the containing directory
> > - llvm::SmallString<64> containing_part;
> > -
> > - if (end_ptr == nullptr) {
> > - // There's no directory. If the thing begins with a "~" then this
> is a bare
> > - // user name.
> > - if (*partial_name_copy == '~') {
> > - // Nothing here but the user name. We could just put a slash on
> the end,
> > - // but for completeness sake we'll resolve the user name and only
> put a
> > - // slash
> > - // on the end if it exists.
> > - llvm::SmallString<64> resolved_username(partial_name_copy);
> > - FileSpec::ResolveUsername(resolved_username);
> > -
> > - // Not sure how this would happen, a username longer than
> PATH_MAX?
> > - // Still...
> > - if (resolved_username.size() == 0) {
> > - // The user name didn't resolve, let's look in the password
> database for
> > - // matches.
> > - // The user name database contains duplicates, and is not in
> > - // alphabetical order, so
> > - // we'll use a set to manage that for us.
> > - FileSpec::ResolvePartialUsername(partial_name_copy, matches);
> > - if (matches.GetSize() > 0)
> > - saw_directory = true;
> > - return matches.GetSize();
> > - } else {
> > - // The thing exists, put a '/' on the end, and return it...
> > - // FIXME: complete user names here:
> > - partial_name_copy[partial_name_len] = '/';
> > - partial_name_copy[partial_name_len + 1] = '\0';
> > - matches.AppendString(partial_name_copy);
> > - saw_directory = true;
> > - return matches.GetSize();
> > + StringList &matches,
> > + TildeExpressionResolver &Resolver) {
> > + matches.Clear();
> > +
> > + llvm::SmallString<256> CompletionBuffer;
> > + llvm::SmallString<256> Storage;
> > + partial_name.toVector(CompletionBuffer);
> > +
> > + if (CompletionBuffer.size() >= PATH_MAX)
> > + return 0;
> > +
> > + namespace fs = llvm::sys::fs;
> > + namespace path = llvm::sys::path;
> > +
> > + llvm::StringRef SearchDir;
> > + llvm::StringRef PartialItem;
> > +
> > + if (CompletionBuffer.startswith("~")) {
> > + llvm::StringRef Buffer(CompletionBuffer);
> > + size_t FirstSep = Buffer.find_if(path::is_separator);
> > +
> > + llvm::StringRef Username = Buffer.take_front(FirstSep);
> > + llvm::StringRef Remainder;
> > + if (FirstSep != llvm::StringRef::npos)
> > + Remainder = Buffer.drop_front(FirstSep + 1);
> > +
> > + llvm::SmallString<PATH_MAX> 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;
> > + saw_directory = Resolver->ResolvePartial(Username, MatchSet);
> > + for (const auto &S : MatchSet) {
> > + Resolved = S.getKey();
> > + path::append(Resolved, path::get_separator());
> > + matches.AppendString(Resolved);
> > + }
> > + saw_directory = (matches.GetSize() > 0);
> > }
> > - } else {
> > - // The containing part is the CWD, and the whole string is the
> remainder.
> > - containing_part = ".";
> > - strcpy(remainder, partial_name_copy);
> > - end_ptr = partial_name_copy;
> > + return matches.GetSize();
> > }
> > - } else {
> > - if (end_ptr == partial_name_copy) {
> > - // We're completing a file or directory in the root volume.
> > - containing_part = "/";
> > - } else {
> > - containing_part.append(partial_name_copy, end_ptr);
> > +
> > + // 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());
> > + saw_directory = true;
> > + matches.AppendString(CompletionBuffer);
> > + return 1;
> > }
> > - // Push end_ptr past the final "/" and set remainder.
> > - end_ptr++;
> > - strcpy(remainder, end_ptr);
> > - }
> >
> > - // Look for a user name in the containing part, and if it's there,
> resolve it
> > - // and stick the
> > - // result back into the containing_part:
> > -
> > - if (*partial_name_copy == '~') {
> > - FileSpec::ResolveUsername(containing_part);
> > - // User name doesn't exist, we're not getting any further...
> > - if (containing_part.empty())
> > - return matches.GetSize();
> > + // 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;
> > + SearchDir = Resolved;
> > + } else {
> > + SearchDir = path::parent_path(CompletionBuffer);
> > }
> >
> > - // Okay, containing_part is now the directory we want to open and
> look for
> > - // files:
> > + size_t FullPrefixLen = CompletionBuffer.size();
> > +
> > + PartialItem = path::filename(CompletionBuffer);
> > + if (PartialItem == ".")
> > + PartialItem = llvm::StringRef();
> >
> > - size_t baselen = end_ptr - partial_name_copy;
> > + assert(!SearchDir.empty());
> > + assert(!PartialItem.contains(path::get_separator()));
> >
> > - DiskFilesOrDirectoriesBaton parameters;
> > - parameters.remainder = remainder;
> > - parameters.partial_name_copy = partial_name_copy;
> > - parameters.only_directories = only_directories;
> > - parameters.saw_directory = &saw_directory;
> > - parameters.matches = &matches;
> > - parameters.end_ptr = end_ptr;
> > - parameters.baselen = baselen;
> > + // SearchDir now contains the directory to search in, and Prefix
> contains the
> > + // text we want to match against items in that directory.
> >
> > - FileSpec::EnumerateDirectory(containing_part.c_str(), true, true,
> true,
> > - DiskFilesOrDirectoriesCallback,
> ¶meters);
> > + std::error_code EC;
> > + fs::directory_iterator Iter(SearchDir, EC, false);
> > + fs::directory_iterator End;
> > + for (; Iter != End && !EC; Iter.increment(EC)) {
> > + auto &Entry = *Iter;
> > +
> > + auto Name = path::filename(Entry.path());
> > +
> > + // Omit ".", ".."
> > + if (Name == "." || Name == ".." || !Name.startswith(PartialItem))
> > + continue;
> > +
> > + // We have a match.
> > +
> > + fs::file_status st;
> > + if (EC = Entry.status(st))
> > + continue;
> > +
> > + // If it's a symlink, then we treat it as a directory as long as
> the target
> > + // is a directory.
> > + bool is_dir = fs::is_directory(st);
> > + if (fs::is_symlink_file(st)) {
> > + fs::file_status target_st;
> > + if (!fs::status(Entry.path(), target_st))
> > + is_dir = fs::is_directory(target_st);
> > + }
> > + 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) {
> > + saw_directory = true;
> > + path::append(CompletionBuffer, path::get_separator());
> > + }
> > +
> > + matches.AppendString(CompletionBuffer);
> > + }
> >
> > return matches.GetSize();
> > }
> > @@ -283,9 +233,17 @@ int CommandCompletions::DiskFiles(Comman
> > int max_return_elements,
> > SearchFilter *searcher, bool
> &word_complete,
> > StringList &matches) {
> > - int ret_val =
> > - DiskFilesOrDirectories(partial_file_name, false, word_complete,
> matches);
> > - word_complete = !word_complete;
> > + word_complete = false;
> > + StandardTildeExpressionResolver Resolver;
> > + return DiskFiles(partial_file_name, matches, Resolver);
> > +}
> > +
> > +int CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name,
> > + StringList &matches,
> > + TildeExpressionResolver &Resolver) {
> > + bool word_complete;
> > + int ret_val = DiskFilesOrDirectories(partial_file_name, false,
> word_complete,
> > + matches, Resolver);
> > return ret_val;
> > }
> >
> > @@ -293,9 +251,17 @@ int CommandCompletions::DiskDirectories(
> > CommandInterpreter &interpreter, llvm::StringRef partial_file_name,
> > int match_start_point, int max_return_elements, SearchFilter
> *searcher,
> > bool &word_complete, StringList &matches) {
> > - int ret_val =
> > - DiskFilesOrDirectories(partial_file_name, true, word_complete,
> matches);
> > word_complete = false;
> > + StandardTildeExpressionResolver Resolver;
> > + return DiskDirectories(partial_file_name, matches, Resolver);
> > +}
> > +
> > +int CommandCompletions::DiskDirectories(const llvm::Twine
> &partial_file_name,
> > + StringList &matches,
> > + TildeExpressionResolver
> &Resolver) {
> > + bool word_complete;
> > + int ret_val = DiskFilesOrDirectories(partial_file_name, true,
> word_complete,
> > + matches, Resolver);
> > return ret_val;
> > }
> >
> >
> > Modified: lldb/trunk/source/Utility/CMakeLists.txt
> > URL:
> http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff
> >
> ==============================================================================
> > --- lldb/trunk/source/Utility/CMakeLists.txt (original)
> > +++ lldb/trunk/source/Utility/CMakeLists.txt Sun Mar 12 13:18:50 2017
> > @@ -25,6 +25,7 @@ add_lldb_library(lldbUtility
> > StringExtractorGDBRemote.cpp
> > StringLexer.cpp
> > TaskPool.cpp
> > + TildeExpressionResolver.cpp
> > UserID.cpp
> > UriParser.cpp
> > UUID.cpp
> >
> > Added: lldb/trunk/source/Utility/TildeExpressionResolver.cpp
> > URL:
> http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/TildeExpressionResolver.cpp?rev=297585&view=auto
> >
> ==============================================================================
> > --- lldb/trunk/source/Utility/TildeExpressionResolver.cpp (added)
> > +++ lldb/trunk/source/Utility/TildeExpressionResolver.cpp Sun Mar 12
> 13:18:50 2017
> > @@ -0,0 +1,66 @@
> > +//===--------------------- TildeExpressionResolver.cpp ----------*- C++
> -*-===//
> > +//
> > +// The LLVM Compiler Infrastructure
> > +//
> > +// This file is distributed under the University of Illinois Open Source
> > +// License. See LICENSE.TXT for details.
> > +//
> >
> +//===----------------------------------------------------------------------===//
> > +
> > +#include "lldb/Utility/TildeExpressionResolver.h"
> > +
> > +#include "llvm/ADT/SmallString.h"
> > +#include "llvm/Support/FileSystem.h"
> > +#include "llvm/Support/Path.h"
> > +
> > +using namespace lldb_private;
> > +using namespace llvm;
> > +
> > +namespace fs = llvm::sys::fs;
> > +namespace path = llvm::sys::path;
> > +
> > +TildeExpressionResolver::~TildeExpressionResolver() {}
> > +
> > +bool StandardTildeExpressionResolver::ResolveExact(
> > + StringRef Expr, SmallVectorImpl<char> &Output) {
> > + // We expect the tilde expression to be ONLY the expression itself,
> and
> > + // contain
> > + // no separators.
> > + assert(!llvm::any_of(Expr, path::is_separator));
> > + assert(Expr.empty() || Expr[0] == '~');
> > +
> > + return !fs::real_path(Expr, Output, true);
> > +}
> > +
> > +bool StandardTildeExpressionResolver::ResolvePartial(StringRef Expr,
> > + StringSet<>
> &Output) {
> > + // We expect the tilde expression to be ONLY the expression itself,
> and
> > + // contain no separators.
> > + assert(!llvm::any_of(Expr, path::is_separator));
> > + assert(Expr.empty() || Expr[0] == '~');
> > +
> > + Output.clear();
> > +#if defined(LLVM_ON_WIN32)
> > + return false;
> > +#else
> > + if (Expr.empty())
> > + return false;
> > +
> > + SmallString<32> Buffer = "~";
> > + setpwent();
> > + struct passwd *user_entry;
> > + Expr = Expr.drop_front();
> > +
> > + while ((user_entry = getpwent()) != NULL) {
> > + StringRef ThisName(user_entry->pw_name);
> > + if (!ThisName.startswith(Expr))
> > + continue;
> > +
> > + Buffer.resize(1);
> > + Buffer.append(ThisName);
> > + Buffer.append(path::get_separator()) Output.insert(Buffer);
> > + }
> > +
> > + return true;
> > +#endif
> > +}
> >
> > Modified: lldb/trunk/unittests/Interpreter/CMakeLists.txt
> > URL:
> http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff
> >
> ==============================================================================
> > --- lldb/trunk/unittests/Interpreter/CMakeLists.txt (original)
> > +++ lldb/trunk/unittests/Interpreter/CMakeLists.txt Sun Mar 12 13:18:50
> 2017
> > @@ -1,5 +1,6 @@
> > add_lldb_unittest(InterpreterTests
> > TestArgs.cpp
> > + TestCompletion.cpp
> >
> > LINK_LIBS
> > lldbInterpreter
> >
> > Added: lldb/trunk/unittests/Interpreter/TestCompletion.cpp
> > URL:
> http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/TestCompletion.cpp?rev=297585&view=auto
> >
> ==============================================================================
> > --- lldb/trunk/unittests/Interpreter/TestCompletion.cpp (added)
> > +++ lldb/trunk/unittests/Interpreter/TestCompletion.cpp Sun Mar 12
> 13:18:50 2017
> > @@ -0,0 +1,346 @@
> > +//===-- TestCompletion.cpp --------------------------------------*- C++
> -*-===//
> > +//
> > +// The LLVM Compiler Infrastructure
> > +//
> > +// This file is distributed under the University of Illinois Open Source
> > +// License. See LICENSE.TXT for details.
> > +//
> >
> +//===----------------------------------------------------------------------===//
> > +
> > +#include "gtest/gtest.h"
> > +
> > +#include "lldb/Core/StringList.h"
> > +#include "lldb/Interpreter/CommandCompletions.h"
> > +#include "lldb/Utility/TildeExpressionResolver.h"
> > +
> > +#include "llvm/ADT/SmallString.h"
> > +#include "llvm/Support/FileSystem.h"
> > +#include "llvm/Support/Path.h"
> > +#include "llvm/Support/raw_ostream.h"
> > +
> > +namespace fs = llvm::sys::fs;
> > +namespace path = llvm::sys::path;
> > +using namespace llvm;
> > +using namespace lldb_private;
> > +
> > +#define ASSERT_NO_ERROR(x)
> \
> > + if (std::error_code ASSERT_NO_ERROR_ec = x) {
> \
> > + SmallString<128> MessageStorage;
> \
> > + raw_svector_ostream Message(MessageStorage);
> \
> > + Message << #x ": did not return errc::success.\n"
> \
> > + << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n"
> \
> > + << "error message: " << ASSERT_NO_ERROR_ec.message() <<
> "\n"; \
> > + GTEST_FATAL_FAILURE_(MessageStorage.c_str());
> \
> > + } else {
> \
> > + }
> > +
> > +namespace {
> > +
> > +class MockTildeExpressionResolver : public TildeExpressionResolver {
> > + StringRef CurrentUser;
> > + StringMap<StringRef> UserDirectories;
> > +
> > +public:
> > + explicit MockTildeExpressionResolver(StringRef CurrentUser, StringRef
> HomeDir)
> > + : CurrentUser(CurrentUser) {
> > + UserDirectories.insert(std::make_pair(CurrentUser, HomeDir));
> > + }
> > +
> > + void AddKnownUser(StringRef User, StringRef HomeDir) {
> > + assert(UserDirectories.find(User) == UserDirectories.end());
> > + UserDirectories.insert(std::make_pair(User, HomeDir));
> > + }
> > +
> > + void Clear() {
> > + CurrentUser = StringRef();
> > + UserDirectories.clear();
> > + }
> > +
> > + void SetCurrentUser(StringRef User) {
> > + assert(UserDirectories.find(User) != UserDirectories.end());
> > + CurrentUser = User;
> > + }
> > +
> > + bool ResolveExact(StringRef Expr, SmallVectorImpl<char> &Output)
> override {
> > + Output.clear();
> > +
> > + assert(!llvm::any_of(Expr, llvm::sys::path::is_separator));
> > + assert(Expr.empty() || Expr[0] == '~');
> > + Expr = Expr.drop_front();
> > + if (Expr.empty()) {
> > + auto Dir = UserDirectories[CurrentUser];
> > + Output.append(Dir.begin(), Dir.end());
> > + return true;
> > + }
> > +
> > + for (const auto &User : UserDirectories) {
> > + if (User.getKey() != Expr)
> > + continue;
> > + Output.append(User.getValue().begin(), User.getValue().end());
> > + return true;
> > + }
> > + return false;
> > + }
> > +
> > + bool ResolvePartial(StringRef Expr, StringSet<> &Output) override {
> > + Output.clear();
> > +
> > + assert(!llvm::any_of(Expr, llvm::sys::path::is_separator));
> > + assert(Expr.empty() || Expr[0] == '~');
> > + Expr = Expr.drop_front();
> > +
> > + SmallString<16> QualifiedName = "~";
> > + for (const auto &User : UserDirectories) {
> > + if (!User.getKey().startswith(Expr))
> > + continue;
> > + QualifiedName.resize(1);
> > + QualifiedName.append(User.getKey().begin(), User.getKey().end());
> > + Output.insert(QualifiedName);
> > + }
> > +
> > + return !Output.empty();
> > + }
> > +};
> > +
> > +class CompletionTest : public testing::Test {
> > +protected:
> > + /// Unique temporary directory in which all created filesystem
> entities must
> > + /// be placed. It is removed at the end of the test suite.
> > + static SmallString<128> BaseDir;
> > +
> > + static SmallString<128> DirFoo;
> > + static SmallString<128> DirFooA;
> > + static SmallString<128> DirFooB;
> > + static SmallString<128> DirFooC;
> > + static SmallString<128> DirBar;
> > + static SmallString<128> DirBaz;
> > + static SmallString<128> DirTestFolder;
> > +
> > + static SmallString<128> FileAA;
> > + static SmallString<128> FileAB;
> > + static SmallString<128> FileAC;
> > + static SmallString<128> FileFoo;
> > + static SmallString<128> FileBar;
> > + static SmallString<128> FileBaz;
> > +
> > + static void SetUpTestCase() {
> > + ASSERT_NO_ERROR(fs::createUniqueDirectory("FsCompletion", BaseDir));
> > + const char *DirNames[] = {"foo", "fooa", "foob", "fooc",
> > + "bar", "baz", "test_folder"};
> > + const char *FileNames[] = {"aa%%%%.tmp", "ab%%%%.tmp",
> "ac%%%%.tmp",
> > + "foo%%%%.tmp", "bar%%%%.tmp",
> "baz%%%%.tmp"};
> > + SmallString<128> *Dirs[] = {&DirFoo, &DirFooA, &DirFooB,
> &DirFooC,
> > + &DirBar, &DirBaz, &DirTestFolder};
> > + for (auto Dir : llvm::zip(DirNames, Dirs)) {
> > + auto &Path = *std::get<1>(Dir);
> > + Path = BaseDir;
> > + path::append(Path, std::get<0>(Dir));
> > + ASSERT_NO_ERROR(fs::create_directory(Path));
> > + }
> > +
> > + SmallString<128> *Files[] = {&FileAA, &FileAB, &FileAC,
> > + &FileFoo, &FileBar, &FileBaz};
> > + for (auto File : llvm::zip(FileNames, Files)) {
> > + auto &Path = *std::get<1>(File);
> > + Path = BaseDir;
> > + path::append(Path, std::get<0>(File));
> > + int FD;
> > + ASSERT_NO_ERROR(fs::createUniqueFile(Path, FD, Path));
> > + ::close(FD);
> > + }
> > + }
> > +
> > + static void TearDownTestCase() {
> > + ASSERT_NO_ERROR(fs::remove_directories(BaseDir));
> > + }
> > +
> > + static bool HasEquivalentFile(const Twine &Path, const StringList
> &Paths) {
> > + for (int I = 0; I < Paths.GetSize(); ++I) {
> > + if (fs::equivalent(Path, Paths[I]))
> > + return true;
> > + }
> > + return false;
> > + }
> > +
> > + static bool ContainsExactString(const Twine &Str, const StringList
> &Paths) {
> > + SmallString<16> Storage;
> > + StringRef Rendered = Str.toStringRef(Storage);
> > + for (int I = 0; I < Paths.GetSize(); ++I) {
> > + if (Paths[I] == Rendered)
> > + return true;
> > + }
> > + return false;
> > + }
> > +};
> > +
> > +SmallString<128> CompletionTest::BaseDir;
> > +
> > +SmallString<128> CompletionTest::DirFoo;
> > +SmallString<128> CompletionTest::DirFooA;
> > +SmallString<128> CompletionTest::DirFooB;
> > +SmallString<128> CompletionTest::DirFooC;
> > +SmallString<128> CompletionTest::DirBar;
> > +SmallString<128> CompletionTest::DirBaz;
> > +SmallString<128> CompletionTest::DirTestFolder;
> > +
> > +SmallString<128> CompletionTest::FileAA;
> > +SmallString<128> CompletionTest::FileAB;
> > +SmallString<128> CompletionTest::FileAC;
> > +SmallString<128> CompletionTest::FileFoo;
> > +SmallString<128> CompletionTest::FileBar;
> > +SmallString<128> CompletionTest::FileBaz;
> > +}
> > +
> > +TEST_F(CompletionTest, DirCompletionAbsolute) {
> > + // All calls to DiskDirectories() return only directories, even when
> > + // there are files which also match. The tests below all check this
> > + // by asserting an exact result count, and verifying against known
> > + // folders.
> > +
> > + StringList Results;
> > + // When a directory is specified that doesn't end in a slash, it
> searches
> > + // for that directory, not items under it.
> > + int Count = CommandCompletions::DiskDirectories2(BaseDir, Results);
> > + ASSERT_EQ(1, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(BaseDir, Results));
> > +
> > + // When the same directory ends with a slash, it finds all children.
> > + Count = CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/",
> Results);
> > + ASSERT_EQ(7, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirBar, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results));
> > +
> > + // When a partial name matches, it returns all matches. If it
> matches both
> > + // a full name AND some partial names, it returns all of them.
> > + Count =
> > + CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/foo",
> Results);
> > + ASSERT_EQ(4, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
> > +
> > + // If it matches only partial names, it still works as expected.
> > + Count = CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/b",
> Results);
> > + ASSERT_EQ(2, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(DirBar, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));
> > +}
> > +
> > +TEST_F(CompletionTest, FileCompletionAbsolute) {
> > + // All calls to DiskFiles() return both files and directories The
> tests below
> > + // all check this by asserting an exact result count, and verifying
> against
> > + // known folders.
> > +
> > + StringList Results;
> > + // When an item is specified that doesn't end in a slash but exactly
> matches
> > + // one item, it returns that item.
> > + int Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/fooa",
> Results);
> > + ASSERT_EQ(1, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
> > +
> > + // The previous check verified a directory match. But it should work
> for
> > + // files too.
> > + Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/aa",
> Results);
> > + ASSERT_EQ(1, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(FileAA, Results));
> > +
> > + // When it ends with a slash, it should find all files and
> directories.
> > + Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/", Results);
> > + ASSERT_EQ(13, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirBar, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results));
> > +
> > + EXPECT_TRUE(HasEquivalentFile(FileAA, Results));
> > + EXPECT_TRUE(HasEquivalentFile(FileAB, Results));
> > + EXPECT_TRUE(HasEquivalentFile(FileAC, Results));
> > + EXPECT_TRUE(HasEquivalentFile(FileFoo, Results));
> > + EXPECT_TRUE(HasEquivalentFile(FileBar, Results));
> > + EXPECT_TRUE(HasEquivalentFile(FileBaz, Results));
> > +
> > + // When a partial name matches, it returns all file & directory
> matches.
> > + Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/foo",
> Results);
> > + ASSERT_EQ(5, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
> > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
> > + EXPECT_TRUE(HasEquivalentFile(FileFoo, Results));
> > +}
> > +
> > +TEST_F(CompletionTest, DirCompletionUsername) {
> > + MockTildeExpressionResolver Resolver("James", BaseDir);
> > + Resolver.AddKnownUser("Kirk", DirFooB);
> > + Resolver.AddKnownUser("Lars", DirFooC);
> > + Resolver.AddKnownUser("Jason", DirFoo);
> > + Resolver.AddKnownUser("Larry", DirFooA);
> > +
> > + // Just resolving current user's home directory by itself should
> return the
> > + // directory.
> > + StringList Results;
> > + int Count = CommandCompletions::DiskDirectories2("~", Results,
> &Resolver);
> > + ASSERT_EQ(1, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(ContainsExactString(Twine("~") + path::get_separator(),
> Results));
> > +
> > + // With a slash appended, it should return all items in the directory.
> > + Count = CommandCompletions::DiskDirectories2("~/", Results,
> &Resolver);
> > + ASSERT_EQ(7, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~/foo") + path::get_separator(),
> Results));
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~/fooa") + path::get_separator(),
> Results));
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~/foob") + path::get_separator(),
> Results));
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~/fooc") + path::get_separator(),
> Results));
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~/bar") + path::get_separator(),
> Results));
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~/baz") + path::get_separator(),
> Results));
> > + EXPECT_TRUE(ContainsExactString(
> > + Twine("~/test_folder") + path::get_separator(), Results));
> > +
> > + // With ~username syntax it should return one match if there is an
> exact
> > + // match.
> > + // It shouldn't translate to the actual directory, it should keep the
> form the
> > + // user typed.
> > + Count = CommandCompletions::DiskDirectories2("~Lars", Results,
> &Resolver);
> > + ASSERT_EQ(1, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~Lars") + path::get_separator(),
> Results));
> > +
> > + // But with a username that is not found, no results are returned.
> > + Count = CommandCompletions::DiskDirectories2("~Dave", Results,
> &Resolver);
> > + ASSERT_EQ(0, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > +
> > + // And if there are multiple matches, it should return all of them.
> > + Count = CommandCompletions::DiskDirectories2("~La", Results,
> &Resolver);
> > + ASSERT_EQ(2, Count);
> > + ASSERT_EQ(Count, Results.GetSize());
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~Lars") + path::get_separator(),
> Results));
> > + EXPECT_TRUE(
> > + ContainsExactString(Twine("~Larry") + path::get_separator(),
> Results));
> > +}
> >
> >
> > _______________________________________________
> > lldb-commits mailing list
> > lldb-commits at lists.llvm.org
> > http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/lldb-commits/attachments/20170412/7d1e2fa1/attachment-0001.html>
More information about the lldb-commits
mailing list