summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-07-07 17:13:44 +0200
committerwm4 <wm4@nowhere>2014-07-07 18:00:41 +0200
commit02ca42c7f17cb03b79a4ce7625d669361b45fc94 (patch)
tree8eb70902fdf08009396e80796a176ed4f861534f
parent0ea2bc2778b6b41e5e4360dbc74b9e00119b40ba (diff)
downloadmpv-02ca42c7f17cb03b79a4ce7625d669361b45fc94.tar.bz2
mpv-02ca42c7f17cb03b79a4ce7625d669361b45fc94.tar.xz
lua: redo error handling, print backtraces
The original goal was just adding backtraces, however making the code safe (especially wrt. to out of memory Lua errors) was hard. So this commit also restructures error handling to make it conceptually simpler. Now all Lua code is run inside a Lua error handling, except the calls for creating and destroying the Lua context, and calling the wrapper C function in a safe way. The new error handling is actually conceptually simpler and more correct, because you can just call any Lua function at initialization, without having to worry whwther it throws errors or not. Unfortunately, Lua 5.2 removes lua_cpcall(), so we have to emulate it. There isn't any way to emulate it in a way that works the same on 5.1 and 5.2 with the same semantics in error cases, so ifdeffery is needed. The debug.traceback() function also behaves weirdly differently between the Lua versions, so its output is not as nice as it could be (empty extra line).
-rw-r--r--player/lua.c172
1 files changed, 90 insertions, 82 deletions
diff --git a/player/lua.c b/player/lua.c
index 25f29c3f09..a06887b7cd 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -68,6 +68,7 @@ static const char * const builtin_lua_scripts[][2] = {
// Represents a loaded script. Each has its own Lua state.
struct script_ctx {
const char *name;
+ const char *filename;
lua_State *state;
struct mp_log *log;
struct mpv_handle *client;
@@ -75,6 +76,19 @@ struct script_ctx {
int suspended;
};
+#if LUA_VERSION_NUM <= 501
+#define mp_cpcall lua_cpcall
+#else
+// Curse whoever had this stupid idea. Curse whoever thought it would be a good
+// idea not to include an emulated lua_cpcall() even more.
+static int mp_cpcall (lua_State *L, lua_CFunction func, void *ud)
+{
+ lua_pushcfunction(L, func); // doesn't allocate in 5.2 (but does in 5.1)
+ lua_pushlightuserdata(L, ud);
+ return lua_pcall(L, 1, 0, 0);
+}
+#endif
+
static struct script_ctx *get_ctx(lua_State *L)
{
lua_getfield(L, LUA_REGISTRYINDEX, "ctx");
@@ -89,34 +103,18 @@ static struct MPContext *get_mpctx(lua_State *L)
return get_ctx(L)->mpctx;
}
-static int wrap_cpcall(lua_State *L)
+static int error_handler(lua_State *L)
{
- lua_CFunction fn = lua_touserdata(L, -1);
- lua_pop(L, 1);
- return fn(L);
-}
+ struct script_ctx *ctx = get_ctx(L);
-// Call the given function fn under a Lua error handler (similar to lua_cpcall).
-// Pass the given number of args from the Lua stack to fn.
-// Returns 0 (and empty stack) on success.
-// Returns LUA_ERR[RUN|MEM|ERR] otherwise, with the error value on the stack.
-static int mp_cpcall(lua_State *L, lua_CFunction fn, int args)
-{
- // Don't use lua_pushcfunction() - it allocates memory on Lua 5.1.
- // Instead, emulate C closures by making wrap_cpcall call fn.
- lua_pushlightuserdata(L, fn); // args... fn
- // Will always succeed if mp_lua_init() set it up correctly.
- lua_getfield(L, LUA_REGISTRYINDEX, "wrap_cpcall"); // args... fn wrap_cpcall
- lua_insert(L, -(args + 2)); // wrap_cpcall args... fn
- return lua_pcall(L, args + 1, 0, 0);
-}
+ if (luaL_loadstring(L, "return debug.traceback('', 3)") == 0) { // e fn|err
+ lua_call(L, 0, 1); // e backtrace
+ const char *tr = lua_tostring(L, -1);
+ MP_WARN(ctx, "%s\n", tr);
+ }
+ lua_pop(L, 1); // e
-static void report_error(lua_State *L)
-{
- struct script_ctx *ctx = get_ctx(L);
- const char *err = lua_tostring(L, -1);
- MP_WARN(ctx, "Error: %s\n", err ? err : "[unknown]");
- lua_pop(L, 1);
+ return 1;
}
// Check client API error code:
@@ -133,30 +131,18 @@ static int check_error(lua_State *L, int err)
return 2;
}
-static int run_event_loop(lua_State *L)
-{
- lua_getglobal(L, "mp_event_loop");
- if (lua_isnil(L, -1))
- luaL_error(L, "no event loop function\n");
- lua_call(L, 0, 0);
- return 0;
-}
-
static void add_functions(struct script_ctx *ctx);
-static int load_file(struct script_ctx *ctx, const char *fname)
+static void load_file(lua_State *L, const char *fname)
{
- int r = 0;
- lua_State *L = ctx->state;
+ struct script_ctx *ctx = get_ctx(L);
char *res_name = mp_get_user_path(NULL, ctx->mpctx->global, fname);
MP_VERBOSE(ctx, "loading file %s\n", res_name);
- if (luaL_loadfile(L, res_name) || lua_pcall(L, 0, 0, 0)) {
- report_error(L);
- r = -1;
- }
- assert(lua_gettop(L) == 0);
- talloc_free(res_name);
- return r;
+ int r = luaL_loadfile(L, res_name);
+ talloc_free(res_name); // careful to not leak this on Lua errors
+ if (r)
+ lua_error(L);
+ lua_call(L, 0, 0);
}
static int load_builtin(lua_State *L)
@@ -173,22 +159,21 @@ static int load_builtin(lua_State *L)
return 1;
}
}
+ luaL_error(L, "builtin module '%s' not found\n", name);
return 0;
}
// Execute "require " .. name
-static bool require(lua_State *L, const char *name)
+static void require(lua_State *L, const char *name)
{
struct script_ctx *ctx = get_ctx(L);
MP_VERBOSE(ctx, "loading %s\n", name);
// Lazy, but better than calling the "require" function manually
char buf[80];
snprintf(buf, sizeof(buf), "require '%s'", name);
- if (luaL_loadstring(L, buf) || lua_pcall(L, 0, 0, 0)) {
- report_error(L);
- return false;
- }
- return true;
+ if (luaL_loadstring(L, buf))
+ lua_error(L);
+ lua_call(L, 0, 0);
}
// Push the table of a module. If it doesn't exist, it's created.
@@ -208,31 +193,38 @@ static void push_module_table(lua_State *L, const char *module)
lua_remove(L, -2); // module
}
-static int load_lua(struct mpv_handle *client, const char *fname)
+static int load_scripts(lua_State *L)
{
- struct MPContext *mpctx = mp_client_get_core(client);
- int r = -1;
+ struct script_ctx *ctx = get_ctx(L);
+ const char *fname = ctx->filename;
- struct script_ctx *ctx = talloc_ptrtype(NULL, ctx);
- *ctx = (struct script_ctx) {
- .mpctx = mpctx,
- .client = client,
- .name = mpv_client_name(client),
- .log = mp_client_get_log(client),
- };
+ require(L, "mp.defaults");
- lua_State *L = ctx->state = luaL_newstate();
- if (!L)
- goto error_out;
+ if (fname[0] == '@') {
+ require(L, fname);
+ } else {
+ load_file(L, fname);
+ }
+
+ lua_getglobal(L, "mp_event_loop"); // fn
+ if (lua_isnil(L, -1))
+ luaL_error(L, "no event loop function\n");
+ lua_call(L, 0, 0); // -
+
+ return 0;
+}
+
+static int run_lua(lua_State *L)
+{
+ struct script_ctx *ctx = lua_touserdata(L, -1);
+ lua_pop(L, 1); // -
+
+ luaL_openlibs(L);
// used by get_ctx()
lua_pushlightuserdata(L, ctx); // ctx
lua_setfield(L, LUA_REGISTRYINDEX, "ctx"); // -
- lua_pushcfunction(L, wrap_cpcall); // closure
- lua_setfield(L, LUA_REGISTRYINDEX, "wrap_cpcall"); // -
-
- luaL_openlibs(L);
add_functions(ctx); // mp
push_module_table(L, "mp"); // mp
@@ -275,24 +267,40 @@ static int load_lua(struct mpv_handle *client, const char *fname)
assert(lua_gettop(L) == 0);
- if (!require(L, "mp.defaults")) {
- report_error(L);
- goto error_out;
- }
+ // run this under an error handler that can do backtraces
+ lua_pushcfunction(L, error_handler); // errf
+ lua_pushcfunction(L, load_scripts); // errf fn
+ if (lua_pcall(L, 0, 0, -2)) // errf [error]
+ MP_FATAL(ctx, "Lua error: %s\n", lua_tostring(L, -1));
- assert(lua_gettop(L) == 0);
+ return 0;
+}
- if (fname[0] == '@') {
- if (!require(L, fname))
- goto error_out;
- } else {
- if (load_file(ctx, fname) < 0)
- goto error_out;
- }
+static int load_lua(struct mpv_handle *client, const char *fname)
+{
+ struct MPContext *mpctx = mp_client_get_core(client);
+ int r = -1;
- // Call the script's event loop runs until the script terminates and unloads.
- if (mp_cpcall(L, run_event_loop, 0) != 0)
- report_error(L);
+ struct script_ctx *ctx = talloc_ptrtype(NULL, ctx);
+ *ctx = (struct script_ctx) {
+ .mpctx = mpctx,
+ .client = client,
+ .name = mpv_client_name(client),
+ .log = mp_client_get_log(client),
+ .filename = fname,
+ };
+
+ lua_State *L = ctx->state = luaL_newstate();
+ if (!L)
+ goto error_out;
+
+ if (mp_cpcall(L, run_lua, ctx)) {
+ const char *err = "unknown error";
+ if (lua_type(L, -1) == LUA_TSTRING) // avoid allocation
+ err = lua_tostring(L, -1);
+ MP_FATAL(ctx, "Lua error: %s\n", err);
+ goto error_out;
+ }
r = 0;