diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -355,7 +355,7 @@ Desc<"When specified, debug the JIT code by setting a breakpoint on the " "first instruction and forcing breakpoints to not be ignored (-i0) and no " "unwinding to happen on error (-u0).">; - def expression_options_language : Option<"language", "l">, Groups<[1,2]>, + def expression_options_language : Option<"language", "l">, Groups<[1,2,3]>, Arg<"Language">, Desc<"Specifies the Language to use when parsing the " "expression. If not set the target.language setting is used.">; def expression_options_apply_fixits : Option<"apply-fixits", "X">, diff --git a/lldb/source/Plugins/CMakeLists.txt b/lldb/source/Plugins/CMakeLists.txt --- a/lldb/source/Plugins/CMakeLists.txt +++ b/lldb/source/Plugins/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(OperatingSystem) add_subdirectory(Platform) add_subdirectory(Process) +add_subdirectory(REPL) add_subdirectory(ScriptInterpreter) add_subdirectory(StructuredData) add_subdirectory(SymbolFile) diff --git a/lldb/source/Plugins/REPL/CMakeLists.txt b/lldb/source/Plugins/REPL/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/REPL/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Clang) diff --git a/lldb/source/Plugins/REPL/Clang/CMakeLists.txt b/lldb/source/Plugins/REPL/Clang/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/REPL/Clang/CMakeLists.txt @@ -0,0 +1,17 @@ +add_lldb_library(lldbPluginClangREPL PLUGIN + ClangREPL.cpp + + LINK_LIBS + lldbCore + lldbDataFormatters + lldbHost + lldbSymbol + lldbTarget + lldbUtility + lldbPluginClangCommon + lldbPluginCPPRuntime + lldbPluginTypeSystemClang + + LINK_COMPONENTS + Support +) diff --git a/lldb/source/Plugins/REPL/Clang/ClangREPL.h b/lldb/source/Plugins/REPL/Clang/ClangREPL.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/REPL/Clang/ClangREPL.h @@ -0,0 +1,65 @@ +//===-- ClangREPL.h ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_REPL_CLANG_CLANGREPL_H +#define LLDB_SOURCE_PLUGINS_REPL_CLANG_CLANGREPL_H + +#include "lldb/Expression/REPL.h" + +namespace lldb_private { +/// Implements a Clang-based REPL for C languages on top of LLDB's REPL +/// framework. +class ClangREPL : public REPL { +public: + ClangREPL(lldb::LanguageType language, Target &target); + + ~ClangREPL() override; + + static void Initialize(); + + static void Terminate(); + + static lldb::REPLSP CreateInstance(Status &error, lldb::LanguageType language, + Debugger *debugger, Target *target, + const char *repl_options); + + static lldb_private::ConstString GetPluginNameStatic() { + return ConstString("ClangREPL"); + } + +protected: + Status DoInitialization() override; + + ConstString GetSourceFileBasename() override; + + const char *GetAutoIndentCharacters() override; + + bool SourceIsComplete(const std::string &source) override; + + lldb::offset_t GetDesiredIndentation(const StringList &lines, + int cursor_position, + int tab_size) override; + + lldb::LanguageType GetLanguage() override; + + bool PrintOneVariable(Debugger &debugger, lldb::StreamFileSP &output_sp, + lldb::ValueObjectSP &valobj_sp, + ExpressionVariable *var = nullptr) override; + + void CompleteCode(const std::string ¤t_code, + CompletionRequest &request) override; + +private: + /// The specific C language of this REPL. + lldb::LanguageType m_language; + /// A regex matching the implicitly created LLDB result variables. + lldb_private::RegularExpression m_implicit_expr_result_regex; +}; +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_REPL_CLANG_CLANGREPL_H diff --git a/lldb/source/Plugins/REPL/Clang/ClangREPL.cpp b/lldb/source/Plugins/REPL/Clang/ClangREPL.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/REPL/Clang/ClangREPL.cpp @@ -0,0 +1,102 @@ +//===-- ClangREPL.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ClangREPL.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Expression/ExpressionVariable.h" + +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(ClangREPL) + +ClangREPL::ClangREPL(lldb::LanguageType language, Target &target) + : REPL(eKindClang, target), m_language(language), + m_implicit_expr_result_regex("\\$[0-9]+") {} + +ClangREPL::~ClangREPL() {} + +void ClangREPL::Initialize() { + LanguageSet languages; + // FIXME: There isn't a way to ask CPlusPlusLanguage and ObjCLanguage for + // a list of languages they support. + languages.Insert(lldb::LanguageType::eLanguageTypeC); + languages.Insert(lldb::LanguageType::eLanguageTypeC89); + languages.Insert(lldb::LanguageType::eLanguageTypeC99); + languages.Insert(lldb::LanguageType::eLanguageTypeC11); + languages.Insert(lldb::LanguageType::eLanguageTypeC_plus_plus); + languages.Insert(lldb::LanguageType::eLanguageTypeC_plus_plus_03); + languages.Insert(lldb::LanguageType::eLanguageTypeC_plus_plus_11); + languages.Insert(lldb::LanguageType::eLanguageTypeC_plus_plus_14); + languages.Insert(lldb::LanguageType::eLanguageTypeObjC); + languages.Insert(lldb::LanguageType::eLanguageTypeObjC_plus_plus); + PluginManager::RegisterPlugin(GetPluginNameStatic(), "C language REPL", + &CreateInstance, languages); +} + +void ClangREPL::Terminate() { + PluginManager::UnregisterPlugin(&CreateInstance); +} + +lldb::REPLSP ClangREPL::CreateInstance(Status &error, + lldb::LanguageType language, + Debugger *debugger, Target *target, + const char *repl_options) { + // Creating a dummy target if only a debugger is given isn't implemented yet. + if (!target) { + error.SetErrorString("must have a target to create a REPL"); + return nullptr; + } + lldb::REPLSP result = std::make_shared(language, *target); + target->SetREPL(language, result); + error = Status(); + return result; +} + +Status ClangREPL::DoInitialization() { return Status(); } + +ConstString ClangREPL::GetSourceFileBasename() { + return ConstString("repl.c"); +} + +const char *ClangREPL::GetAutoIndentCharacters() { return " "; } + +bool ClangREPL::SourceIsComplete(const std::string &source) { + // FIXME: There isn't a good way to know if the input source is complete or + // not, so just say that every single REPL line is ready to be parsed. + return !source.empty(); +} + +lldb::offset_t ClangREPL::GetDesiredIndentation(const StringList &lines, + int cursor_position, + int tab_size) { + // FIXME: Not implemented. + return LLDB_INVALID_OFFSET; +} + +lldb::LanguageType ClangREPL::GetLanguage() { return m_language; } + +bool ClangREPL::PrintOneVariable(Debugger &debugger, + lldb::StreamFileSP &output_sp, + lldb::ValueObjectSP &valobj_sp, + ExpressionVariable *var) { + // If a ExpressionVariable was passed, check first if that variable is just + // an automatically created expression result. These variables are already + // printed by the REPL so this is done to prevent printing the variable twice. + if (var) { + if (m_implicit_expr_result_regex.Execute(var->GetName().GetStringRef())) + return true; + } + valobj_sp->Dump(*output_sp); + return true; +} + +void ClangREPL::CompleteCode(const std::string ¤t_code, + CompletionRequest &request) { + // Not implemented. +} diff --git a/lldb/test/API/repl/clang/Makefile b/lldb/test/API/repl/clang/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/repl/clang/Makefile @@ -0,0 +1,2 @@ +C_SOURCES := main.c +include Makefile.rules diff --git a/lldb/test/API/repl/clang/TestClangREPL.py b/lldb/test/API/repl/clang/TestClangREPL.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/repl/clang/TestClangREPL.py @@ -0,0 +1,54 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test.lldbpexpect import PExpectTest + +class TestCase(PExpectTest): + + mydir = TestBase.compute_mydir(__file__) + + def expect_repl(self, expr, substrs=[]): + """ Evaluates the expression in the REPL and verifies that the list + of substrs is in the REPL output.""" + # Only single line expressions supported. + self.assertNotIn("\n", expr) + self.child.send(expr + "\n") + for substr in substrs: + self.child.expect_exact(substr) + # Look for the start of the next REPL input line. + self.current_repl_line_number += 1 + self.child.expect_exact(str(self.current_repl_line_number) + ">") + + # PExpect uses many timeouts internally and doesn't play well + # under ASAN on a loaded machine.. + @skipIfAsan + @skipIfEditlineSupportMissing + def test_basic_completion(self): + """Test that we can complete a simple multiline expression""" + self.build() + self.current_repl_line_number = 1 + + self.launch(executable=self.getBuildArtifact("a.out"), dimensions=(100,500)) + # Try launching the REPL before we have a running target. + self.expect("expression --repl -l c --", substrs=["REPL requires a running target process."]) + + self.expect("b main", substrs=["Breakpoint 1", "address ="]) + self.expect("run", substrs=["stop reason = breakpoint 1"]) + + # Start the REPL. + self.child.send("expression --repl -l c --\n") + self.child.expect_exact("1>") + + # Try evaluating a simple expression. + self.expect_repl("3 + 3", substrs=["(int) $0 = 6"]) + + # Try declaring a persistent variable. + self.expect_repl("long $persistent = 7; 5", + substrs=["(int) $1 = 5", + "(long) $persistent = 7"]) + + # Try using the persistent variable from before. + self.expect_repl("$persistent + 10", + substrs=["(long) $2 = 17"]) + + self.quit() diff --git a/lldb/test/API/repl/clang/main.c b/lldb/test/API/repl/clang/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/repl/clang/main.c @@ -0,0 +1,3 @@ +int main(int argc, char **argv) { + return 0; +}