diff options
Diffstat (limited to 'lldb/source')
7 files changed, 509 insertions, 5 deletions
diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp index 08959ff8473..fdeaac24d0a 100644 --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -307,6 +307,74 @@ CommandObjectExpression::~CommandObjectExpression() = default; Options *CommandObjectExpression::GetOptions() { return &m_option_group; } +int CommandObjectExpression::HandleCompletion(CompletionRequest &request) { + 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); + + // 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; + + ExecutionContext exe_ctx(m_interpreter.GetExecutionContext()); + + Target *target = exe_ctx.GetTargetPtr(); + + if (!target) + target = GetDummyTarget(); + + if (!target) + return 0; + + unsigned cursor_pos = request.GetRawCursorPos(); + llvm::StringRef code = request.GetRawLine(); + + const std::size_t original_code_size = code.size(); + + // Remove the first token which is 'expr' or some alias/abbreviation of that. + code = llvm::getToken(code).second.ltrim(); + OptionsWithRaw args(code); + code = args.GetRawPart(); + + // The position where the expression starts in the command line. + assert(original_code_size >= code.size()); + std::size_t raw_start = original_code_size - code.size(); + + // Check if the cursor is actually in the expression string, and if not, we + // exit. + // FIXME: We should complete the options here. + if (cursor_pos < raw_start) + return 0; + + // Make the cursor_pos again relative to the start of the code string. + assert(cursor_pos >= raw_start); + cursor_pos -= raw_start; + + auto language = exe_ctx.GetFrameRef().GetLanguage(); + + Status error; + lldb::UserExpressionSP expr(target->GetUserExpressionForLanguage( + code, llvm::StringRef(), language, UserExpression::eResultTypeAny, + options, error)); + if (error.Fail()) + return 0; + + StringList matches; + expr->Complete(exe_ctx, matches, cursor_pos); + request.AddCompletions(matches); + return request.GetNumberOfMatches(); +} + static lldb_private::Status CanBeUsedForElementCountPrinting(ValueObject &valobj) { CompilerType type(valobj.GetCompilerType()); diff --git a/lldb/source/Commands/CommandObjectExpression.h b/lldb/source/Commands/CommandObjectExpression.h index 710f8714097..1e2a0d2ad00 100644 --- a/lldb/source/Commands/CommandObjectExpression.h +++ b/lldb/source/Commands/CommandObjectExpression.h @@ -62,6 +62,8 @@ public: Options *GetOptions() override; + int HandleCompletion(CompletionRequest &request) override; + protected: //------------------------------------------------------------------ // IOHandler::Delegate functions diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp index fa49a51f32a..c2bc18a04e9 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ASTResultSynthesizer.cpp @@ -87,7 +87,8 @@ void ASTResultSynthesizer::TransformTopLevelDecl(Decl *D) { SynthesizeObjCMethodResult(method_decl); } } else if (FunctionDecl *function_decl = dyn_cast<FunctionDecl>(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); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp index 405e29b2430..cfd5d9be111 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp +++ b/lldb/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" @@ -546,7 +548,248 @@ ClangExpressionParser::ClangExpressionParser(ExecutionContextScope *exe_scope, 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. + static 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. + static 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<GlobalCodeCompletionAllocator>()), 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 parantheses for the user. If the function has + // arguments, we at least complete the opening bracket. + if (const FunctionDecl *F = dyn_cast<FunctionDecl>(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<NamespaceDecl>(D)) { + if (!N->isAnonymousNamespace()) + ToInsert += "::"; + } + break; + } + case CodeCompletionResult::RK_Keyword: + ToInsert = R.Keyword; + break; + case CodeCompletionResult::RK_Macro: + // It's not clear if we want to complete any macros in the + 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, + SourceLocation OpenParLoc) 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<LLVMUserExpression *>(&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<ClangDiagnosticManagerAdapter *>( m_compiler->getDiagnostics().getClient()); @@ -559,8 +802,18 @@ unsigned ClangExpressionParser::Parse(DiagnosticManager &diagnostic_manager) { 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<PATH_MAX> result_path; if (FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir()) { @@ -605,14 +858,30 @@ unsigned ClangExpressionParser::Parse(DiagnosticManager &diagnostic_manager) { 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(); + // Lines and columns start at 1 in Clang, but code completion positions are + // indexed from 0, so we need to add 1 to the line and column here. + ++completion_line; + ++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(); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h index 6b17c6d5fb3..7fdc9c121ef 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h @@ -20,6 +20,10 @@ #include <string> #include <vector> +namespace clang { +class CodeCompleteConsumer; +} + namespace lldb_private { class IRExecutionUnit; @@ -58,6 +62,9 @@ public: //------------------------------------------------------------------ ~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,33 @@ public: 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. + /// The first line is represented by the value 0. + /// + /// @param[in] completion_column + /// The column in which the completion marker should be placed. + /// The first column is represented by the value 0. + /// + /// @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<llvm::LLVMContext> m_llvm_context; ///< The LLVM context to generate IR into std::unique_ptr<clang::FileManager> diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp index 2e61f704127..c43e9d7511f 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp @@ -402,6 +402,16 @@ llvm::Optional<lldb::LanguageType> ClangUserExpression::GetLanguageForExpr( "couldn't construct expression body"); return llvm::Optional<lldb::LanguageType>(); } + + // 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; } @@ -591,6 +601,119 @@ bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager, 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 = 0; + 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, /*keep result in memory*/ true); + + 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<lldb::addr_t> &args, lldb::addr_t struct_address, diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h index ac363bf9174..c26975e60e5 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h @@ -143,6 +143,9 @@ public: 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; } @@ -198,6 +201,10 @@ private: 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<unsigned> m_user_expression_start_pos; ResultDelegate m_result_delegate; }; |