40 changes: 40 additions & 0 deletions lldb/packages/Python/lldbsuite/test/lldbtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,46 @@ def match(

return match_object


def complete_exactly(self, str_input, patterns):
self.complete_from_to(str_input, patterns, True)

def complete_from_to(self, str_input, patterns, turn_off_re_match=False):
"""Test that the completion mechanism completes str_input to patterns,
where patterns could be a pattern-string or a list of pattern-strings"""
# Patterns should not be None in order to proceed.
self.assertFalse(patterns is None)
# And should be either a string or list of strings. Check for list type
# below, if not, make a list out of the singleton string. If patterns
# is not a string or not a list of strings, there'll be runtime errors
# later on.
if not isinstance(patterns, list):
patterns = [patterns]

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"

for p in patterns:
if turn_off_re_match:
self.expect(
compare_string, msg=COMPLETION_MSG(
str_input, p, match_strings), exe=False, substrs=[p])
else:
self.expect(
compare_string, msg=COMPLETION_MSG(
str_input, p, match_strings), exe=False, patterns=[p])

def expect(
self,
str,
Expand Down
68 changes: 68 additions & 0 deletions lldb/source/Commands/CommandObjectExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Commands/CommandObjectExpression.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class CommandObjectExpression : public CommandObjectRaw,

Options *GetOptions() override;

int HandleCompletion(CompletionRequest &request) override;

protected:
//------------------------------------------------------------------
// IOHandler::Delegate functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
277 changes: 273 additions & 4 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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());
Expand All @@ -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()) {
Expand Down Expand Up @@ -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();
Expand Down
34 changes: 34 additions & 0 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <string>
#include <vector>

namespace clang {
class CodeCompleteConsumer;
}

namespace lldb_private {

class IRExecutionUnit;
Expand Down Expand Up @@ -58,6 +62,9 @@ class ClangExpressionParser : public ExpressionParser {
//------------------------------------------------------------------
~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.
Expand Down Expand Up @@ -143,6 +150,33 @@ class ClangExpressionParser : public ExpressionParser {
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>
Expand Down
123 changes: 123 additions & 0 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ class ClangUserExpression : public LLVMUserExpression {
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;
}
Expand Down Expand Up @@ -198,6 +201,10 @@ class ClangUserExpression : public LLVMUserExpression {
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;
};

Expand Down