1 - Partial Statements The interpreter loop runs every line it receives, so partial Lua statements are not being handled properly. This is a problem for multiline breakpoint scripts since the interpreter loop, for this particular case, is just an abstraction to a partially parsed function body declaration. This patch addresses this issue and as a side effect improves the general Lua interpreter loop as well. It's now possible to write partial statements in the 'script' command. Example: (lldb) script >>> do ..> local a = 123 ..> print(a) ..> end 123 The technique implemented is the same as the one employed by Lua's own REPL implementation. Partial statements always errors out with the '<eof>' tag in the error message. 2 - CheckSyntax in Lua.h In order to support (1), we need an API for just checking the syntax of string buffers. 3 - Multiline scripted breakpoints Finally, with all the base features implemented this feature is straightforward. The interpreter loop behaves exactly the same, the difference is that it will aggregate all Lua statements into the body of the breakpoint function. An explicit 'quit' statement is needed to exit the interpreter loop. Example: (lldb) breakpoint command add -s lua Enter your Lua command(s). Type 'quit' to end. The commands are compiled as the body of the following Lua function function (frame, bp_loc, ...) end ..> print(456) ..> a = 123 ..> quit Differential Revision: https://reviews.llvm.org/D93481
185 lines
5.8 KiB
C++
185 lines
5.8 KiB
C++
//===-- Lua.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 "Lua.h"
|
|
#include "lldb/Host/FileSystem.h"
|
|
#include "lldb/Utility/FileSpec.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
|
|
using namespace lldb_private;
|
|
using namespace lldb;
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
|
|
|
|
// Disable warning C4190: 'LLDBSwigPythonBreakpointCallbackFunction' has
|
|
// C-linkage specified, but returns UDT 'llvm::Expected<bool>' which is
|
|
// incompatible with C
|
|
#if _MSC_VER
|
|
#pragma warning (push)
|
|
#pragma warning (disable : 4190)
|
|
#endif
|
|
|
|
extern "C" llvm::Expected<bool>
|
|
LLDBSwigLuaBreakpointCallbackFunction(lua_State *L,
|
|
lldb::StackFrameSP stop_frame_sp,
|
|
lldb::BreakpointLocationSP bp_loc_sp);
|
|
|
|
#if _MSC_VER
|
|
#pragma warning (pop)
|
|
#endif
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
static int lldb_print(lua_State *L) {
|
|
int n = lua_gettop(L);
|
|
lua_getglobal(L, "io");
|
|
lua_getfield(L, -1, "stdout");
|
|
lua_getfield(L, -1, "write");
|
|
for (int i = 1; i <= n; i++) {
|
|
lua_pushvalue(L, -1); // write()
|
|
lua_pushvalue(L, -3); // io.stdout
|
|
luaL_tolstring(L, i, nullptr);
|
|
lua_pushstring(L, i != n ? "\t" : "\n");
|
|
lua_call(L, 3, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Lua::Lua() : m_lua_state(luaL_newstate()) {
|
|
assert(m_lua_state);
|
|
luaL_openlibs(m_lua_state);
|
|
luaopen_lldb(m_lua_state);
|
|
lua_pushcfunction(m_lua_state, lldb_print);
|
|
lua_setglobal(m_lua_state, "print");
|
|
}
|
|
|
|
Lua::~Lua() {
|
|
assert(m_lua_state);
|
|
lua_close(m_lua_state);
|
|
}
|
|
|
|
llvm::Error Lua::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 == LUA_OK)
|
|
return llvm::Error::success();
|
|
|
|
llvm::Error e = llvm::make_error<llvm::StringError>(
|
|
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;
|
|
}
|
|
|
|
llvm::Error Lua::RegisterBreakpointCallback(void *baton, const char *body) {
|
|
lua_pushlightuserdata(m_lua_state, baton);
|
|
const char *fmt_str = "return function(frame, bp_loc, ...) {0} end";
|
|
std::string func_str = llvm::formatv(fmt_str, body).str();
|
|
if (luaL_dostring(m_lua_state, func_str.c_str()) != LUA_OK) {
|
|
llvm::Error e = llvm::make_error<llvm::StringError>(
|
|
llvm::formatv("{0}", lua_tostring(m_lua_state, -1)),
|
|
llvm::inconvertibleErrorCode());
|
|
// Pop error message from the stack.
|
|
lua_pop(m_lua_state, 2);
|
|
return e;
|
|
}
|
|
lua_settable(m_lua_state, LUA_REGISTRYINDEX);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Expected<bool>
|
|
Lua::CallBreakpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp,
|
|
lldb::BreakpointLocationSP bp_loc_sp) {
|
|
lua_pushlightuserdata(m_lua_state, baton);
|
|
lua_gettable(m_lua_state, LUA_REGISTRYINDEX);
|
|
return LLDBSwigLuaBreakpointCallbackFunction(m_lua_state, stop_frame_sp,
|
|
bp_loc_sp);
|
|
}
|
|
|
|
llvm::Error Lua::CheckSyntax(llvm::StringRef buffer) {
|
|
int error =
|
|
luaL_loadbuffer(m_lua_state, buffer.data(), buffer.size(), "buffer");
|
|
if (error == LUA_OK) {
|
|
// Pop buffer
|
|
lua_pop(m_lua_state, 1);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error e = llvm::make_error<llvm::StringError>(
|
|
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;
|
|
}
|
|
|
|
llvm::Error Lua::LoadModule(llvm::StringRef filename) {
|
|
FileSpec file(filename);
|
|
if (!FileSystem::Instance().Exists(file)) {
|
|
return llvm::make_error<llvm::StringError>("invalid path",
|
|
llvm::inconvertibleErrorCode());
|
|
}
|
|
|
|
ConstString module_extension = file.GetFileNameExtension();
|
|
if (module_extension != ".lua") {
|
|
return llvm::make_error<llvm::StringError>("invalid extension",
|
|
llvm::inconvertibleErrorCode());
|
|
}
|
|
|
|
int error = luaL_loadfile(m_lua_state, filename.data()) ||
|
|
lua_pcall(m_lua_state, 0, 1, 0);
|
|
if (error != LUA_OK) {
|
|
llvm::Error e = llvm::make_error<llvm::StringError>(
|
|
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;
|
|
}
|
|
|
|
ConstString module_name = file.GetFileNameStrippingExtension();
|
|
lua_setglobal(m_lua_state, module_name.GetCString());
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error Lua::ChangeIO(FILE *out, FILE *err) {
|
|
assert(out != nullptr);
|
|
assert(err != nullptr);
|
|
|
|
lua_getglobal(m_lua_state, "io");
|
|
|
|
lua_getfield(m_lua_state, -1, "stdout");
|
|
if (luaL_Stream *s = static_cast<luaL_Stream *>(
|
|
luaL_testudata(m_lua_state, -1, LUA_FILEHANDLE))) {
|
|
s->f = out;
|
|
lua_pop(m_lua_state, 1);
|
|
} else {
|
|
lua_pop(m_lua_state, 2);
|
|
return llvm::make_error<llvm::StringError>("could not get stdout",
|
|
llvm::inconvertibleErrorCode());
|
|
}
|
|
|
|
lua_getfield(m_lua_state, -1, "stderr");
|
|
if (luaL_Stream *s = static_cast<luaL_Stream *>(
|
|
luaL_testudata(m_lua_state, -1, LUA_FILEHANDLE))) {
|
|
s->f = out;
|
|
lua_pop(m_lua_state, 1);
|
|
} else {
|
|
lua_pop(m_lua_state, 2);
|
|
return llvm::make_error<llvm::StringError>("could not get stderr",
|
|
llvm::inconvertibleErrorCode());
|
|
}
|
|
|
|
lua_pop(m_lua_state, 1);
|
|
return llvm::Error::success();
|
|
}
|