Index: lldb/cmake/modules/LLDBConfig.cmake =================================================================== --- lldb/cmake/modules/LLDBConfig.cmake +++ lldb/cmake/modules/LLDBConfig.cmake @@ -124,6 +124,9 @@ add_definitions( -DHAVE_ROUND ) endif() +if (LLDB_ENABLE_LUA) + find_package(Lua REQUIRED) +endif() if (LLDB_ENABLE_LIBEDIT) find_package(LibEdit REQUIRED) Index: lldb/include/lldb/Core/IOHandler.h =================================================================== --- lldb/include/lldb/Core/IOHandler.h +++ lldb/include/lldb/Core/IOHandler.h @@ -52,6 +52,7 @@ REPL, ProcessIO, PythonInterpreter, + LuaInterpreter, PythonCode, Other }; Index: lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt +++ lldb/source/Plugins/ScriptInterpreter/Lua/CMakeLists.txt @@ -4,4 +4,7 @@ LINK_LIBS lldbCore lldbInterpreter - ) \ No newline at end of file + ) + +target_include_directories(lldbPluginScriptInterpreterLua PRIVATE ${LUA_INCLUDE_DIR}) +target_link_libraries(lldbPluginScriptInterpreterLua PRIVATE ${LUA_LIBRARIES}) Index: lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp +++ lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp @@ -10,32 +10,114 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/StreamFile.h" +#include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StringList.h" - +#include "lldb/Utility/Timer.h" #include "llvm/Support/Threading.h" +#ifndef LLDB_DISABLE_LIBEDIT +#include "lldb/Host/Editline.h" +#endif + +#include "lua.hpp" + +#define LUA_PROMPT ">>> " + #include using namespace lldb; using namespace lldb_private; +class Lua { +public: + Lua() : m_lua_state(luaL_newstate()) { + assert(m_lua_state); + luaL_openlibs(m_lua_state); + } + ~Lua() { + assert(m_lua_state); + luaL_openlibs(m_lua_state); + } + + llvm::Error Run(llvm::StringRef buffer) { + int error = + luaL_loadbuffer(m_lua_state, buffer.data(), buffer.size(), "buffer") || + lua_pcall(m_lua_state, 0, 0, 0); + if (!error) + return llvm::Error::success(); + + llvm::Error e = llvm::make_error( + llvm::formatv("{0}\n", lua_tostring(m_lua_state, -1)), + llvm::inconvertibleErrorCode()); + // Pop error message from the stack. + lua_pop(m_lua_state, 1); + return e; + } + +private: + lua_State *m_lua_state = nullptr; +}; + +class IOHandlerLuaInterpreter : public IOHandlerDelegate, + public IOHandlerEditline { +public: + IOHandlerLuaInterpreter(Debugger &debugger) + : IOHandlerEditline(debugger, IOHandler::Type::LuaInterpreter, "lua", + ">>> ", "..> ", true, debugger.GetUseColor(), 0, + *this, nullptr), + m_lua() {} + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &data) override { + if (llvm::Error error = m_lua.Run(data)) { + fprintf(GetOutputFILE(), "%s", llvm::toString(std::move(error)).c_str()); + } + } + + ~IOHandlerLuaInterpreter() override {} + +private: + Lua m_lua; +}; + ScriptInterpreterLua::ScriptInterpreterLua(Debugger &debugger) : ScriptInterpreter(debugger, eScriptLanguageLua) {} ScriptInterpreterLua::~ScriptInterpreterLua() {} bool ScriptInterpreterLua::ExecuteOneLine(llvm::StringRef command, - CommandReturnObject *, - const ExecuteScriptOptions &) { - m_debugger.GetErrorStream().PutCString( - "error: the lua script interpreter is not yet implemented.\n"); - return false; + CommandReturnObject *result, + const ExecuteScriptOptions &options) { + Lua l; + if (llvm::Error e = l.Run(command)) { + result->AppendErrorWithFormatv( + "lua failed attempting to evaluate '{0}': {1}\n", command, + llvm::toString(std::move(e))); + return false; + } + return true; } void ScriptInterpreterLua::ExecuteInterpreterLoop() { - m_debugger.GetErrorStream().PutCString( - "error: the lua script interpreter is not yet implemented.\n"); + static Timer::Category func_cat(LLVM_PRETTY_FUNCTION); + Timer scoped_timer(func_cat, LLVM_PRETTY_FUNCTION); + + Debugger &debugger = m_debugger; + + // At the moment, the only time the debugger does not have an input file + // handle is when this is called directly from lua, in which case it is + // both dangerous and unnecessary (not to mention confusing) to try to embed + // a running interpreter loop inside the already running lua interpreter + // loop, so we won't do it. + + if (!debugger.GetInputFile().IsValid()) + return; + + IOHandlerSP io_handler_sp(new IOHandlerLuaInterpreter(debugger)); + if (io_handler_sp) { + debugger.PushIOHandler(io_handler_sp); + } } void ScriptInterpreterLua::Initialize() { Index: lldb/test/CMakeLists.txt =================================================================== --- lldb/test/CMakeLists.txt +++ lldb/test/CMakeLists.txt @@ -144,6 +144,7 @@ # These values are not canonicalized within LLVM. llvm_canonicalize_cmake_booleans( LLDB_ENABLE_PYTHON + LLDB_ENABLE_LUA LLVM_ENABLE_ZLIB LLVM_ENABLE_SHARED_LIBS LLDB_IS_64_BITS) Index: lldb/test/Shell/ScriptInterpreter/Lua/lua.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Lua/lua.test @@ -0,0 +1,3 @@ +# REQUIRES: lua +# RUN: %lldb --script-language lua -o 'script print(1000+100+10+1)' 2>&1 | FileCheck %s +# CHECK: 1111 Index: lldb/test/Shell/lit.cfg.py =================================================================== --- lldb/test/Shell/lit.cfg.py +++ lldb/test/Shell/lit.cfg.py @@ -103,6 +103,9 @@ if config.lldb_enable_python: config.available_features.add('python') +if config.lldb_enable_lua: + config.available_features.add('lua') + if config.lldb_enable_lzma: config.available_features.add('lzma') Index: lldb/test/Shell/lit.site.cfg.py.in =================================================================== --- lldb/test/Shell/lit.site.cfg.py.in +++ lldb/test/Shell/lit.site.cfg.py.in @@ -19,6 +19,7 @@ config.host_triple = "@LLVM_HOST_TRIPLE@" config.lldb_bitness = 64 if @LLDB_IS_64_BITS@ else 32 config.lldb_enable_python = @LLDB_ENABLE_PYTHON@ +config.lldb_enable_lua = @LLDB_ENABLE_LUA@ config.lldb_build_directory = "@LLDB_TEST_BUILD_DIRECTORY@" # The shell tests use their own module caches. config.lldb_module_cache = os.path.join("@LLDB_TEST_MODULE_CACHE_LLDB@", "lldb-shell") Index: lldb/unittests/ScriptInterpreter/CMakeLists.txt =================================================================== --- lldb/unittests/ScriptInterpreter/CMakeLists.txt +++ lldb/unittests/ScriptInterpreter/CMakeLists.txt @@ -1,3 +1,6 @@ if (LLDB_ENABLE_PYTHON) add_subdirectory(Python) endif() +if (LLDB_ENABLE_LUA) + add_subdirectory(Lua) +endif() Index: lldb/unittests/ScriptInterpreter/Lua/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/unittests/ScriptInterpreter/Lua/CMakeLists.txt @@ -0,0 +1,11 @@ +add_lldb_unittest(ScriptInterpreterLuaTests + LuaTests.cpp + + LINK_LIBS + lldbHost + lldbPluginScriptInterpreterLua + lldbPluginPlatformLinux + LLVMTestingSupport + LINK_COMPONENTS + Support + ) \ No newline at end of file Index: lldb/unittests/ScriptInterpreter/Lua/LuaTests.cpp =================================================================== --- /dev/null +++ lldb/unittests/ScriptInterpreter/Lua/LuaTests.cpp @@ -0,0 +1,62 @@ +//===-- LuaTests.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 "Plugins/Platform/Linux/PlatformLinux.h" +#include "Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Target/Platform.h" +#include "lldb/Utility/Reproducer.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb_private::repro; +using namespace lldb; + +namespace { +class ScriptInterpreterTest : public ::testing::Test { +public: + void SetUp() override { + llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None)); + FileSystem::Initialize(); + HostInfo::Initialize(); + + // Pretend Linux is the host platform. + platform_linux::PlatformLinux::Initialize(); + ArchSpec arch("powerpc64-pc-linux"); + Platform::SetHostPlatform( + platform_linux::PlatformLinux::CreateInstance(true, &arch)); + } + void TearDown() override { + platform_linux::PlatformLinux::Terminate(); + HostInfo::Terminate(); + FileSystem::Terminate(); + Reproducer::Terminate(); + } +}; +} // namespace + +TEST_F(ScriptInterpreterTest, Plugin) { + EXPECT_EQ(ScriptInterpreterLua::GetPluginNameStatic(), "script-lua"); + EXPECT_EQ(ScriptInterpreterLua::GetPluginDescriptionStatic(), + "Lua script interpreter"); +} + +TEST_F(ScriptInterpreterTest, ExecuteOneLine) { + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + + ScriptInterpreterLua script_interpreter(*debugger_sp); + CommandReturnObject result; + EXPECT_TRUE(script_interpreter.ExecuteOneLine("foo = 1", &result)); + EXPECT_FALSE(script_interpreter.ExecuteOneLine("nil = foo", &result)); + EXPECT_TRUE(result.GetErrorData().startswith( + "error: lua failed attempting to evaluate 'nil = foo'")); +}