Index: include/lldb/Expression/ExpressionParser.h =================================================================== --- include/lldb/Expression/ExpressionParser.h +++ include/lldb/Expression/ExpressionParser.h @@ -49,6 +49,9 @@ //------------------------------------------------------------------ virtual ~ExpressionParser(){}; + virtual bool Complete(StringList &matches, unsigned line, unsigned pos, + unsigned typed_pos) = 0; + //------------------------------------------------------------------ /// Parse a single expression and convert it to IR using Clang. Don't wrap /// the expression in anything at all. Index: include/lldb/Expression/UserExpression.h =================================================================== --- include/lldb/Expression/UserExpression.h +++ include/lldb/Expression/UserExpression.h @@ -98,6 +98,34 @@ lldb_private::ExecutionPolicy execution_policy, bool keep_result_in_memory, bool generate_debug_info) = 0; + //------------------------------------------------------------------ + /// Attempts to find possible command line completions for the given + /// (possible incomplete) user expression. + /// + /// @param[in] exe_ctx + /// The execution context to use when looking up entities that + /// are needed for parsing and completing (locations of functions, types + /// of variables, persistent variables, etc.) + /// + /// @param[out] matches + /// The list of completions that should be appended with string + /// that would complete the current token at the cursor position. + /// Note that the string in the list replaces the current token + /// in the command line. + /// + /// @param[in] complete_pos + /// The position of the cursor inside the user expression string. + /// The completion process starts on the token that the cursor is in. + /// + /// @return + /// True if we added any completion results to the output; + /// false otherwise. + //------------------------------------------------------------------ + virtual bool Complete(ExecutionContext &exe_ctx, StringList &matches, + unsigned complete_pos) { + return false; + } + virtual bool CanInterpret() = 0; bool MatchesContext(ExecutionContext &exe_ctx); Index: packages/Python/lldbsuite/test/functionalities/expr_completion/.categories =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/expr_completion/.categories @@ -0,0 +1 @@ +cmdline Index: packages/Python/lldbsuite/test/functionalities/expr_completion/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/expr_completion/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../make + +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/expr_completion/TestExprCompletion.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/expr_completion/TestExprCompletion.py @@ -0,0 +1,264 @@ +""" +Test the lldb command line completion mechanism for the 'expr' command. +""" + +from __future__ import print_function + +import random +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbplatform +from lldbsuite.test import lldbutil + +class CommandLineExprCompletionTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24489") + def test_expr_completion(self): + self.build() + self.main_source = "main.cpp" + self.main_source_spec = lldb.SBFileSpec(self.main_source) + self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + + # Try the completion before we have a context to complete on. + self.assume_no_completions('expr some_expr') + self.assume_no_completions('expr ') + self.assume_no_completions('expr f') + + + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, + '// Break here', self.main_source_spec) + + # Completing member functions + self.complete_from_to('expr some_expr.FooNoArgs', + 'expr some_expr.FooNoArgsBar()') + self.complete_from_to('expr some_expr.FooWithArgs', + 'expr some_expr.FooWithArgsBar(') + self.complete_from_to('expr some_expr.FooWithMultipleArgs', + 'expr some_expr.FooWithMultipleArgsBar(') + self.complete_from_to('expr some_expr.FooUnderscore', + 'expr some_expr.FooUnderscoreBar_()') + self.complete_from_to('expr some_expr.FooNumbers', + 'expr some_expr.FooNumbersBar1()') + self.complete_from_to('expr some_expr.StaticMemberMethod', + 'expr some_expr.StaticMemberMethodBar()') + + # Completing static functions + self.complete_from_to('expr Expr::StaticMemberMethod', + 'expr Expr::StaticMemberMethodBar()') + + # Completing member variables + self.complete_from_to('expr some_expr.MemberVariab', + 'expr some_expr.MemberVariableBar') + + # Multiple completions + self.completions_contain('expr some_expr.', + ['some_expr.FooNumbersBar1()', + 'some_expr.FooUnderscoreBar_()', + 'some_expr.FooWithArgsBar(', + 'some_expr.MemberVariableBar']) + + self.completions_contain('expr some_expr.Foo', + ['some_expr.FooNumbersBar1()', + 'some_expr.FooUnderscoreBar_()', + 'some_expr.FooWithArgsBar(']) + + self.completions_contain('expr ', + ['static_cast', + 'reinterpret_cast', + 'dynamic_cast']) + + self.completions_contain('expr 1 + ', + ['static_cast', + 'reinterpret_cast', + 'dynamic_cast']) + + # Completion expr without spaces + # This is a bit awkward looking for the user, but that's how + # the completion API works at the moment. + self.completions_contain('expr 1+', + ['1+some_expr', "1+static_cast"]) + + # Test with spaces + self.complete_from_to('expr some_expr .FooNoArgs', + 'expr some_expr .FooNoArgsBar()') + self.complete_from_to('expr some_expr. FooNoArgs', + 'expr some_expr. FooNoArgsBar()') + self.complete_from_to('expr some_expr . FooNoArgs', + 'expr some_expr . FooNoArgsBar()') + self.complete_from_to('expr Expr :: StaticMemberMethod', + 'expr Expr :: StaticMemberMethodBar()') + self.complete_from_to('expr Expr ::StaticMemberMethod', + 'expr Expr ::StaticMemberMethodBar()') + self.complete_from_to('expr Expr:: StaticMemberMethod', + 'expr Expr:: StaticMemberMethodBar()') + + # Addrof and deref + self.complete_from_to('expr (*(&some_expr)).FooNoArgs', + 'expr (*(&some_expr)).FooNoArgsBar()') + self.complete_from_to('expr (*(&some_expr)) .FooNoArgs', + 'expr (*(&some_expr)) .FooNoArgsBar()') + self.complete_from_to('expr (* (&some_expr)) .FooNoArgs', + 'expr (* (&some_expr)) .FooNoArgsBar()') + self.complete_from_to('expr (* (& some_expr)) .FooNoArgs', + 'expr (* (& some_expr)) .FooNoArgsBar()') + + # Addrof and deref (part 2) + self.complete_from_to('expr (&some_expr)->FooNoArgs', + 'expr (&some_expr)->FooNoArgsBar()') + self.complete_from_to('expr (&some_expr) ->FooNoArgs', + 'expr (&some_expr) ->FooNoArgsBar()') + self.complete_from_to('expr (&some_expr) -> FooNoArgs', + 'expr (&some_expr) -> FooNoArgsBar()') + self.complete_from_to('expr (&some_expr)-> FooNoArgs', + 'expr (&some_expr)-> FooNoArgsBar()') + + # Builtin arg + self.complete_from_to('expr static_ca', + 'expr static_cast') + + # Types + self.complete_from_to('expr LongClassNa', + 'expr LongClassName') + self.complete_from_to('expr LongNamespaceName::NestedCla', + 'expr LongNamespaceName::NestedClass') + + # Namespaces + self.complete_from_to('expr LongNamespaceNa', + 'expr LongNamespaceName::') + + # String + self.complete_from_to('expr str.max_si', + 'expr str.max_size()') + self.complete_from_to('expr str.siz', + 'expr str.size()') + self.complete_from_to('expr (&str)->leng', + 'expr (&str)->length()') + + # Multiple arguments + self.complete_from_to('expr &some_expr + &some_e', + 'expr &some_expr + &some_expr') + self.complete_from_to('expr SomeLongVarNameWithCapitals + SomeLongVarName', + 'expr SomeLongVarNameWithCapitals + SomeLongVarNameWithCapitals') + self.complete_from_to('expr SomeIntVar + SomeIntV', + 'expr SomeIntVar + SomeIntVar') + + # Multiple statements + self.complete_from_to('expr long LocalVariable = 0; LocalVaria', + 'expr long LocalVariable = 0; LocalVariable') + + # Custom Decls + self.complete_from_to('expr auto l = [](int LeftHandSide, int bx){ return LeftHandS', + 'expr auto l = [](int LeftHandSide, int bx){ return LeftHandSide') + self.complete_from_to('expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.Mem', + 'expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.MemberName') + + # Completing function call arguments + self.complete_from_to('expr some_expr.FooWithArgsBar(some_exp', + 'expr some_expr.FooWithArgsBar(some_expr') + self.complete_from_to('expr some_expr.FooWithArgsBar(SomeIntV', + 'expr some_expr.FooWithArgsBar(SomeIntVar') + self.complete_from_to('expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVa', + 'expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVar') + + # Function return values + self.complete_from_to('expr some_expr.Self().FooNoArgs', + 'expr some_expr.Self().FooNoArgsBar()') + self.complete_from_to('expr some_expr.Self() .FooNoArgs', + 'expr some_expr.Self() .FooNoArgsBar()') + self.complete_from_to('expr some_expr.Self(). FooNoArgs', + 'expr some_expr.Self(). FooNoArgsBar()') + + def generate_random_expr(self, run_index): + """ + Generates a random expression. run_index seeds the rng, so + the output of this method is always the same for the same run_index value + """ + # Some random tokens we built our expression from. + tokens = [".", ",", "(", ")", "{", "}", "foo", "a", "some_expr", + "->", "$", "&", " ", "::", "std", ":", "*", "+", "string", + "size", "\"", "'", "\\"] + random.seed(run_index) + num_tokens = random.randint(1, 8) + result = "" + for i in range(num_tokens): + token = random.choice(tokens) + result += token + return result + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24489") + def test_stress_expr_completion(self): + """ + We don't want the completion parsing to cause lldb to crash. This test just throws + a few thousand (non-sensical) expressions at it to test this a bit. + This test passes if lldb doesn't crash during the run. + """ + self.build() + self.main_source = "main.cpp" + self.main_source_spec = lldb.SBFileSpec(self.main_source) + self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, + '// Break here', self.main_source_spec) + + # 2000 seems enough to make this test about the same length as the other test cases + # (which around 10s on my system). + for i in range(2000): + str_input = self.generate_random_expr(i) + interp = self.dbg.GetCommandInterpreter() + match_strings = lldb.SBStringList() + num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings) + + + def assume_no_completions(self, str_input): + interp = self.dbg.GetCommandInterpreter() + match_strings = lldb.SBStringList() + num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings) + + available_completions = [] + for m in match_strings: + available_completions.append(m) + + assert num_matches == 0, "Got matches, but didn't expect any: " + str(available_completions) + + def completions_contain(self, str_input, items): + interp = self.dbg.GetCommandInterpreter() + match_strings = lldb.SBStringList() + num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings) + common_match = match_strings.GetStringAtIndex(0) + + for item in items: + found = False + for m in match_strings: + if m == item: + found = True + if not found: + available_completions = [] + for m in match_strings: + available_completions.append(m) + assert found, "Couldn't find completion " + item + " in completions " + str(available_completions) + + def complete_from_to(self, str_input, pattern): + interp = self.dbg.GetCommandInterpreter() + match_strings = lldb.SBStringList() + num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings) + common_match = match_strings.GetStringAtIndex(0) + + if num_matches == 0: + compare_string = str_input + else: + if common_match != None and len(common_match) > 0: + compare_string = str_input + common_match + else: + compare_string = "" + for idx in range(1, num_matches+1): + compare_string += match_strings.GetStringAtIndex(idx) + "\n" + + self.expect( + compare_string, msg=COMPLETION_MSG( + str_input, pattern, compare_string), exe=False, substrs=[pattern]) Index: packages/Python/lldbsuite/test/functionalities/expr_completion/main.cpp =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/expr_completion/main.cpp @@ -0,0 +1,33 @@ +#include + +namespace LongNamespaceName { class NestedClass { long m; }; } + +class LongClassName { long i ; }; + +class Expr { +public: + int FooNoArgsBar() { return 1; } + int FooWithArgsBar(int i) { return i; } + int FooWithMultipleArgsBar(int i, int j) { return i + j; } + int FooUnderscoreBar_() { return 4; } + int FooNumbersBar1() { return 8; } + int MemberVariableBar = 0; + Expr &Self() { return *this; } + static int StaticMemberMethodBar() { return 82; } +}; + +int main() +{ + LongClassName a; + LongNamespaceName::NestedClass NestedFoo; + long SomeLongVarNameWithCapitals = 44; + int SomeIntVar = 33; + std::string str; + Expr some_expr; + some_expr.FooNoArgsBar(); + some_expr.FooWithArgsBar(1); + some_expr.FooUnderscoreBar_(); + some_expr.FooNumbersBar1(); + Expr::StaticMemberMethodBar(); + return 0; // Break here +} Index: packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- packages/Python/lldbsuite/test/lldbtest.py +++ packages/Python/lldbsuite/test/lldbtest.py @@ -184,9 +184,12 @@ return "Command '%s' returns successfully" % str -def COMPLETION_MSG(str_before, str_after): +def COMPLETION_MSG(str_before, str_after, str_got=None): '''A generic message generator for the completion mechanism.''' - return "'%s' successfully completes to '%s'" % (str_before, str_after) + if str_got != None: + return "'%s' successfully completes to '%s' instead of '%s'" % (str_before, str_after, str_got) + else: + return "'%s' successfully completes to '%s'" % (str_before, str_after) def EXP_MSG(str, actual, exe): Index: source/Commands/CommandObjectExpression.h =================================================================== --- source/Commands/CommandObjectExpression.h +++ source/Commands/CommandObjectExpression.h @@ -62,6 +62,11 @@ Options *GetOptions() override; + int HandleCompletion(Args &input, int &cursor_index, + int &cursor_char_position, int match_start_point, + int max_return_elements, bool &word_complete, + StringList &matches) override; + protected: //------------------------------------------------------------------ // IOHandler::Delegate functions Index: source/Commands/CommandObjectExpression.cpp =================================================================== --- source/Commands/CommandObjectExpression.cpp +++ source/Commands/CommandObjectExpression.cpp @@ -21,6 +21,7 @@ #include "lldb/Core/ValueObjectVariable.h" #include "lldb/DataFormatters/ValueObjectPrinter.h" #include "lldb/Expression/DWARFExpression.h" +#include "lldb/Expression/DiagnosticManager.h" #include "lldb/Expression/REPL.h" #include "lldb/Expression/UserExpression.h" #include "lldb/Host/Host.h" @@ -307,6 +308,126 @@ Options *CommandObjectExpression::GetOptions() { return &m_option_group; } +//------------------------------------------------------------------ +/// Converts a position given by the completion API (word position and character +/// position in the indexed word) to an absolute character position in the +/// argument string. +/// +/// @param[in] cmd +/// The argument string. +/// @param[in] word_pos +/// The position of the selected word. +/// @param[in] char_pos +/// The character position inside the selected word. +/// +/// @return +/// The absolute position inside the argument string. +/// +/// @example +/// WordPosToAbsolutePos("ab cd", 1, 1) == 4 (points to the 'd')s +static int WordPosToAbsolutePos(llvm::StringRef cmd, int word_pos, + const int char_pos) { + int result = 0; + + // Skip any leading whitespace. + while (!cmd.empty() && cmd.front() == ' ') { + cmd = cmd.drop_front(); + ++result; + } + + // If we have to search for a specific word, we iterate over the string until + // we find the word. + if (word_pos != 0) { + // Track if the last char was a whitepace. + bool last_was_whitespace = false; + int words_left = word_pos; + + while (!cmd.empty()) { + const char c = cmd.front(); + cmd = cmd.drop_front(); + // We have to keep track of whitespace to make see how many words we have + // iterated over so far. + if (c == ' ') + last_was_whitespace = true; + else if (last_was_whitespace) { + // We just left a whitespace part and entered a word, so we let's + // decrease our counter to keep track of where we are. + words_left--; + // We found the selected word. + if (words_left == 0) + break; + last_was_whitespace = false; + } + ++result; + } + } + // result now points to the start of the selected word, so we can just add the + // char_pos to get the final absolute position. + return result + char_pos; +} + +int CommandObjectExpression::HandleCompletion(Args &input, int &cursor_index, + int &cursor_char_position, + int match_start_point, + int max_return_elements, + bool &word_complete, + StringList &matches) { + // Don't use m_exe_ctx as this might be called asynchronously after the + // command object DoExecute has finished when doing multi-line expression + // that use an input reader... + ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); + + Target *target = exe_ctx.GetTargetPtr(); + + EvaluateExpressionOptions options; + options.SetCoerceToId(m_varobj_options.use_objc); + options.SetLanguage(m_command_options.language); + options.SetExecutionPolicy(lldb_private::eExecutionPolicyNever); + options.SetAutoApplyFixIts(false); + options.SetGenerateDebugInfo(false); + + if (!target) + target = GetDummyTarget(); + + if (target) { + std::string arg; + input.GetCommandString(arg); + + // We need a valid execution context with a frame pointer for this + // completion, so if we don't have one we should try to make a valid + // execution context. + if (m_interpreter.GetExecutionContext().GetFramePtr() == nullptr) + m_interpreter.UpdateExecutionContext(nullptr); + + // This didn't work, so let's get out before we start doing things that + // expect a valid frame pointer. + if (m_interpreter.GetExecutionContext().GetFramePtr() == nullptr) + return 0; + + // Like in the `Parse` method, we should create our own execution context. + ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); + + auto language = exe_ctx.GetFrameRef().GetLanguage(); + + Status error; + lldb::UserExpressionSP expr(target->GetUserExpressionForLanguage( + arg, llvm::StringRef(), language, UserExpression::eResultTypeAny, + options, error)); + if (error.Fail()) + return 0; + + // We got the index of the arg and the cursor index inside that arg as + // parameters, but for completing the whole expression we need to revert + // this back to the absolute offset of the cursor in the whole expression. + int absolute_pos = + WordPosToAbsolutePos(arg, cursor_index, cursor_char_position); + expr->Complete(exe_ctx, matches, absolute_pos); + return matches.GetSize(); + } + + return 0; +} + static lldb_private::Status CanBeUsedForElementCountPrinting(ValueObject &valobj) { CompilerType type(valobj.GetCompilerType()); Index: source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp =================================================================== --- source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp +++ source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp @@ -87,7 +87,8 @@ SynthesizeObjCMethodResult(method_decl); } } else if (FunctionDecl *function_decl = dyn_cast(D)) { - if (m_ast_context && + // When completing user input the body of the function may be a nullptr. + if (m_ast_context && function_decl->hasBody() && !function_decl->getNameInfo().getAsString().compare("$__lldb_expr")) { RecordPersistentTypes(function_decl); SynthesizeFunctionResult(function_decl); Index: source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h =================================================================== --- source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h +++ source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h @@ -20,6 +20,10 @@ #include #include +namespace clang { +class CodeCompleteConsumer; +} + namespace lldb_private { class IRExecutionUnit; @@ -58,6 +62,9 @@ //------------------------------------------------------------------ ~ClangExpressionParser() override; + bool Complete(StringList &matches, unsigned line, unsigned pos, + unsigned typed_pos) override; + //------------------------------------------------------------------ /// Parse a single expression and convert it to IR using Clang. Don't wrap /// the expression in anything at all. @@ -143,6 +150,31 @@ std::string GetClangTargetABI(const ArchSpec &target_arch); private: + //------------------------------------------------------------------ + /// Parses the expression. + /// + /// @param[in] diagnostic_manager + /// The diagnostic manager that should receive the diagnostics + /// from the parsing process. + /// + /// @param[in] completion + /// The completion consumer that should be used during parsing + /// (or a nullptr if no consumer should be attached). + /// + /// @param[in] completion_line + /// The line in which the completion marker should be placed. + /// + /// @param[in] completion_column + /// The column in which the completion marker should be placed. + /// + /// @return + /// The number of parsing errors. + //------------------------------------------------------------------- + unsigned ParseInternal(DiagnosticManager &diagnostic_manager, + clang::CodeCompleteConsumer *completion = nullptr, + unsigned completion_line = 0, + unsigned completion_column = 0); + std::unique_ptr m_llvm_context; ///< The LLVM context to generate IR into std::unique_ptr Index: source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp =================================================================== --- source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp +++ source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp @@ -34,6 +34,8 @@ #include "clang/Parse/ParseAST.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/Sema.h" #include "clang/Sema/SemaConsumer.h" #include "llvm/ADT/StringRef.h" @@ -544,7 +546,245 @@ ClangExpressionParser::~ClangExpressionParser() {} +namespace { + +//---------------------------------------------------------------------- +/// @class CodeComplete +/// +/// A code completion consumer for the clang Sema that is responsible for +/// creating the completion suggestions when a user requests completion +/// of an incomplete `expr` invocation. +//---------------------------------------------------------------------- +class CodeComplete : public CodeCompleteConsumer { + CodeCompletionTUInfo CCTUInfo; + + std::string expr; + unsigned position = 0; + StringList &matches; + + /// Returns true if the given character can be used in an identifier. + /// This also returns true for numbers because for completion we usually + /// just iterate backwards over iterators. + /// + /// Note: lldb uses '$' in its internal identifiers, so we also allow this. + bool IsIdChar(char c) { return c == '_' || std::isalnum(c) || c == '$'; } + + /// Returns true if the given character is used to separate arguments + /// in the command line of lldb. + bool IsTokenSeparator(char c) { return c == ' ' || c == '\t'; } + + /// Drops all tokens in front of the expression that are unrelated for + /// the completion of the cmd line. 'unrelated' means here that the token + /// is not interested for the lldb completion API result. + StringRef dropUnrelatedFrontTokens(StringRef cmd) { + if (cmd.empty()) + return cmd; + + // If we are at the start of a word, then all tokens are unrelated to + // the current completion logic. + if (IsTokenSeparator(cmd.back())) + return StringRef(); + + // Remove all previous tokens from the string as they are unrelated + // to completing the current token. + StringRef to_remove = cmd; + while (!to_remove.empty() && !IsTokenSeparator(to_remove.back())) { + to_remove = to_remove.drop_back(); + } + cmd = cmd.drop_front(to_remove.size()); + + return cmd; + } + + /// Removes the last identifier token from the given cmd line. + StringRef removeLastToken(StringRef cmd) { + while (!cmd.empty() && IsIdChar(cmd.back())) { + cmd = cmd.drop_back(); + } + return cmd; + } + + /// Attemps to merge the given completion from the given position into the + /// existing command. Returns the completion string that can be returned to + /// the lldb completion API. + std::string mergeCompletion(StringRef existing, unsigned pos, + StringRef completion) { + StringRef existing_command = existing.substr(0, pos); + // We rewrite the last token with the completion, so let's drop that + // token from the command. + existing_command = removeLastToken(existing_command); + // We also should remove all previous tokens from the command as they + // would otherwise be added to the completion that already has the + // completion. + existing_command = dropUnrelatedFrontTokens(existing_command); + return existing_command.str() + completion.str(); + } + +public: + /// Constructs a CodeComplete consumer that can be attached to a Sema. + /// @param[out] matches + /// The list of matches that the lldb completion API expects as a result. + /// This may already contain matches, so it's only allowed to append + /// to this variable. + /// @param[out] expr + /// The whole expression string that we are currently parsing. This + /// string needs to be equal to the input the user typed, and NOT the + /// final code that Clang is parsing. + /// @param[out] position + /// The character position of the user cursor in the `expr` parameter. + /// + CodeComplete(StringList &matches, std::string expr, unsigned position) + : CodeCompleteConsumer(CodeCompleteOptions(), false), + CCTUInfo(std::make_shared()), expr(expr), + position(position), matches(matches) {} + + /// Deregisters and destroys this code-completion consumer. + virtual ~CodeComplete() {} + + /// \name Code-completion filtering + /// Check if the result should be filtered out. + bool isResultFilteredOut(StringRef Filter, + CodeCompletionResult Result) override { + // This code is mostly copied from CodeCompleteConsumer. + switch (Result.Kind) { + case CodeCompletionResult::RK_Declaration: + return !( + Result.Declaration->getIdentifier() && + Result.Declaration->getIdentifier()->getName().startswith(Filter)); + case CodeCompletionResult::RK_Keyword: + return !StringRef(Result.Keyword).startswith(Filter); + case CodeCompletionResult::RK_Macro: + return !Result.Macro->getName().startswith(Filter); + case CodeCompletionResult::RK_Pattern: + return !StringRef(Result.Pattern->getAsString()).startswith(Filter); + } + // If we trigger this assert or the above switch yields a warning, then + // CodeCompletionResult has been enhanced with more kinds of completion + // results. Expand the switch above in this case. + assert(false && "Unknown completion result type?"); + // If we reach this, then we should just ignore whatever kind of unknown + // result we got back. We probably can't turn it into any kind of useful + // completion suggestion with the existing code. + return true; + } + + /// \name Code-completion callbacks + /// Process the finalized code-completion results. + void ProcessCodeCompleteResults(Sema &SemaRef, CodeCompletionContext Context, + CodeCompletionResult *Results, + unsigned NumResults) override { + + // The Sema put the incomplete token we try to complete in here during + // lexing, so we need to retrieve it here to know what we are completing. + StringRef Filter = SemaRef.getPreprocessor().getCodeCompletionFilter(); + + // Iterate over all the results. Filter out results we don't want and + // process the rest. + for (unsigned I = 0; I != NumResults; ++I) { + // Filter the results with the information from the Sema. + if (!Filter.empty() && isResultFilteredOut(Filter, Results[I])) + continue; + + CodeCompletionResult &R = Results[I]; + std::string ToInsert; + // Handle the different completion kinds that come from the Sema. + switch (R.Kind) { + case CodeCompletionResult::RK_Declaration: { + const NamedDecl *D = R.Declaration; + ToInsert = R.Declaration->getNameAsString(); + // If we have a function decl that has no arguments we want to + // complete the empty brackets for the user. If the function has + // arguments, we at least complete the opening bracket. + if (const FunctionDecl *F = dyn_cast(D)) { + if (F->getNumParams() == 0) { + ToInsert += "()"; + } else { + ToInsert += "("; + } + } + // If we try to complete a namespace, then we directly can append + // the '::'. + if (const NamespaceDecl *N = dyn_cast(D)) { + if (!N->isAnonymousNamespace()) + ToInsert += "::"; + } + break; + } + case CodeCompletionResult::RK_Keyword: + ToInsert = R.Keyword; + break; + case CodeCompletionResult::RK_Macro: + ToInsert = R.Macro->getName().str(); + break; + case CodeCompletionResult::RK_Pattern: + ToInsert = R.Pattern->getTypedText(); + break; + } + // At this point all information is in the ToInsert string. + + // We also filter some internal lldb identifiers here. The user + // shouldn't see these. + if (StringRef(ToInsert).startswith("$__lldb_")) + continue; + if (!ToInsert.empty()) { + // Merge the suggested Token into the existing command line to comply + // with the kind of result the lldb API expects. + std::string CompletionSuggestion = + mergeCompletion(expr, position, ToInsert); + matches.AppendString(CompletionSuggestion); + } + } + } + + /// \param S the semantic-analyzer object for which code-completion is being + /// done. + /// + /// \param CurrentArg the index of the current argument. + /// + /// \param Candidates an array of overload candidates. + /// + /// \param NumCandidates the number of overload candidates + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates) override { + // At the moment we don't filter out any overloaded candidates. + } + + CodeCompletionAllocator &getAllocator() override { + return CCTUInfo.getAllocator(); + } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; +} // namespace + +bool ClangExpressionParser::Complete(StringList &matches, unsigned line, + unsigned pos, unsigned typed_pos) { + DiagnosticManager mgr; + // We need the raw user expression here because that's what the CodeComplete + // class uses to provide completion suggestions. + // However, the `Text` method only gives us the transformed expression here. + // To actually get the raw user input here, we have to cast our expression to + // the LLVMUserExpression which exposes the right API. This should never fail + // as we always have a ClangUserExpression whenever we call this. + LLVMUserExpression &llvm_expr = *static_cast(&m_expr); + CodeComplete CC(matches, llvm_expr.GetUserText(), typed_pos); + // We don't need a code generator for parsing. + m_code_generator.reset(); + // Start parsing the expression with our custom code completion consumer. + ParseInternal(mgr, &CC, line, pos); + return true; +} + unsigned ClangExpressionParser::Parse(DiagnosticManager &diagnostic_manager) { + return ParseInternal(diagnostic_manager); +} + +unsigned +ClangExpressionParser::ParseInternal(DiagnosticManager &diagnostic_manager, + CodeCompleteConsumer *completion_consumer, + unsigned completion_line, + unsigned completion_column) { ClangDiagnosticManagerAdapter *adapter = static_cast( m_compiler->getDiagnostics().getClient()); @@ -557,8 +797,18 @@ clang::SourceManager &source_mgr = m_compiler->getSourceManager(); bool created_main_file = false; - if (m_compiler->getCodeGenOpts().getDebugInfo() == - codegenoptions::FullDebugInfo) { + + // Clang wants to do completion on a real file known by Clang's file manager, + // so we have to create one to make this work. + // TODO: We probably could also simulate to Clang's file manager that there + // is a real file that contains our code. + bool should_create_file = completion_consumer != nullptr; + + // We also want a real file on disk if we generate full debug info. + should_create_file |= m_compiler->getCodeGenOpts().getDebugInfo() == + codegenoptions::FullDebugInfo; + + if (should_create_file) { int temp_fd = -1; llvm::SmallString result_path; if (FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir()) { @@ -603,14 +853,29 @@ if (ClangExpressionDeclMap *decl_map = type_system_helper->DeclMap()) decl_map->InstallCodeGenerator(m_code_generator.get()); + // If we want to parse for code completion, we need to attach our code + // completion consumer to the Sema and specify a completion position. + // While parsing the Sema will call this consumer with the provided + // completion suggestions. + if (completion_consumer) { + auto main_file = source_mgr.getFileEntryForID(source_mgr.getMainFileID()); + auto &PP = m_compiler->getPreprocessor(); + // Columns start at 1, but code completion positions are indexed from 0, + // so we need to add 1 to the column here. + ++completion_column; + PP.SetCodeCompletionPoint(main_file, completion_line, completion_column); + } + if (ast_transformer) { ast_transformer->Initialize(m_compiler->getASTContext()); ParseAST(m_compiler->getPreprocessor(), ast_transformer, - m_compiler->getASTContext()); + m_compiler->getASTContext(), false, TU_Complete, + completion_consumer); } else { m_code_generator->Initialize(m_compiler->getASTContext()); ParseAST(m_compiler->getPreprocessor(), m_code_generator.get(), - m_compiler->getASTContext()); + m_compiler->getASTContext(), false, TU_Complete, + completion_consumer); } diag_buf->EndSourceFile(); Index: source/Plugins/ExpressionParser/Clang/ClangUserExpression.h =================================================================== --- source/Plugins/ExpressionParser/Clang/ClangUserExpression.h +++ source/Plugins/ExpressionParser/Clang/ClangUserExpression.h @@ -143,6 +143,9 @@ lldb_private::ExecutionPolicy execution_policy, bool keep_result_in_memory, bool generate_debug_info) override; + bool Complete(ExecutionContext &exe_ctx, StringList &matches, + unsigned complete_pos) override; + ExpressionTypeSystemHelper *GetTypeSystemHelper() override { return &m_type_system_helper; } @@ -199,6 +202,10 @@ lldb::TargetSP m_target_sp; }; + /// The absolute character position in the transformed source code where the + /// user code (as typed by the user) starts. If the variable is empty, then we + /// were not able to calculate this position. + llvm::Optional m_user_expression_start_pos; ResultDelegate m_result_delegate; }; Index: source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp =================================================================== --- source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp @@ -403,6 +403,16 @@ "couldn't construct expression body"); return llvm::Optional(); } + + // Find and store the start position of the original code inside the + // transformed code. We need this later for the code completion. + std::size_t original_start; + std::size_t original_end; + bool found_bounds = source_code->GetOriginalBodyBounds( + m_transformed_text, lang_type, original_start, original_end); + if (found_bounds) { + m_user_expression_start_pos = original_start; + } } return lang_type; } @@ -592,6 +602,121 @@ return true; } +//------------------------------------------------------------------ +/// Converts an absolute position inside a given code string into +/// a column/line pair. +/// +/// @param[in] abs_pos +/// A absolute position in the code string that we want to convert +/// to a column/line pair. +/// +/// @param[in] code +/// A multi-line string usually representing source code. +/// +/// @param[out] line +/// The line in the code that contains the given absolute position. +/// The first line in the string is indexed as 1. +/// +/// @param[out] column +/// The column in the line that contains the absolute position. +/// The first character in a line is indexed as 0. +//------------------------------------------------------------------ +static void AbsPosToLineColumnPos(unsigned abs_pos, llvm::StringRef code, + unsigned &line, unsigned &column) { + // Reset to code position to beginning of the file. + line = 1; + column = 0; + + assert(abs_pos <= code.size() && "Absolute position outside code string?"); + + // We have to walk up to the position and count lines/columns. + for (std::size_t i = 0; i < abs_pos; ++i) { + // If we hit a line break, we go back to column 0 and enter a new line. + // We only handle \n because that's what we internally use to make new + // lines for our temporary code strings. + if (code[i] == '\n') { + ++line; + column = 0; + continue; + } + ++column; + } +} + +bool ClangUserExpression::Complete(ExecutionContext &exe_ctx, + StringList &matches, unsigned complete_pos) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); + + // We don't want any visible feedback when completing an expression. Mostly + // because the results we get from an incomplete invocation are probably not + // correct. + DiagnosticManager diagnostic_manager; + + if (!PrepareForParsing(diagnostic_manager, exe_ctx)) + return false; + + lldb::LanguageType lang_type = lldb::LanguageType::eLanguageTypeUnknown; + if (auto new_lang = GetLanguageForExpr(diagnostic_manager, exe_ctx)) { + lang_type = new_lang.getValue(); + } + + if (log) + log->Printf("Parsing the following code:\n%s", m_transformed_text.c_str()); + + ////////////////////////// + // Parse the expression + // + + m_materializer_ap.reset(new Materializer()); + + ResetDeclMap(exe_ctx, m_result_delegate, + true /*Was a variable before TODO */); + + OnExit on_exit([this]() { ResetDeclMap(); }); + + if (!DeclMap()->WillParse(exe_ctx, m_materializer_ap.get())) { + diagnostic_manager.PutString( + eDiagnosticSeverityError, + "current process state is unsuitable for expression parsing"); + + return false; + } + + if (m_options.GetExecutionPolicy() == eExecutionPolicyTopLevel) { + DeclMap()->SetLookupsEnabled(true); + } + + Process *process = exe_ctx.GetProcessPtr(); + ExecutionContextScope *exe_scope = process; + + if (!exe_scope) + exe_scope = exe_ctx.GetTargetPtr(); + + ClangExpressionParser parser(exe_scope, *this, false); + + // We have to find the source code location where the user text is inside + // the transformed expression code. When creating the transformed text, we + // already stored the absolute position in the m_transformed_text string. The + // only thing left to do is to transform it into the line:column format that + // Clang expects. + + // The line and column of the user expression inside the transformed source + // code. + unsigned user_expr_line, user_expr_column; + if (m_user_expression_start_pos.hasValue()) + AbsPosToLineColumnPos(*m_user_expression_start_pos, m_transformed_text, + user_expr_line, user_expr_column); + else + return false; + + // The actual column where we have to complete is the start column of the + // user expression + the offset inside the user code that we were given. + const unsigned completion_column = user_expr_column + complete_pos; + parser.Complete(matches, user_expr_line, completion_column, complete_pos); + + return true; +} + bool ClangUserExpression::AddArguments(ExecutionContext &exe_ctx, std::vector &args, lldb::addr_t struct_address,