How do I improve Lua internal error messages to include line numbers?

Viewed 766

How do I improve the error logging to include line numbers? This is for an internal error that's thrown as I'm reading a Lua file. The only error message I get is attempt to index a function value, no line number or filename.

void handleLuaError(lua_State* L, const char* msg, const char* filename) {
  handleError("%s %s: %s", msg, filename, lua_tostring(luaState, -1));
  lua_pop(L, 1);
  luaL_traceback (L, L, msg, 2);
  char* result = 0;
  if (lua_isstring(L, -1)) result = strdup_s(lua_tostring(L, -1));
  lua_pop(L, 1);
  SPDLOG_WARN("traceback: {}", result);
}

void readLua(const char *filename) {
  SPDLOG_INFO("readLua {}", filename);
  if (luaL_loadfile(luaState, filename) || lua_pcall(luaState, 0, 0, 0)) {
    handleLuaError(luaState, "Reading file", filename);
  }
}

I tried adding luaL_traceback, as you can see, but the only message I get from luaL_traceback is stack traceback:, no actual traceback. I read that lua_pcall can trash the stack, and that I need to do some debug logging when the error happens, such as in an error handler, and not after lua_pcall has returned. Someone suggested I need to use xpcall and provide a custom error handler, but I can't find any documentation or example code on how to call xpcall in C++.

How do I set a custom error handler? Or is there a way to just tell Lua to put more info in internal errors? When I see my particular error message in other people's StackOverflow posts, the error message (attempt to index ...) shows filename and line number, so it should be possible to get filename and line number with this error.

Here's the Lua file which is causing the error:

addAchievement({
  code = "AchNewCity",
  name = "A New City",
  text = "Welcome to NewCity. " ..
  "Your first task is to " ..
  "build some roads. Click " ..
  "on the Transportation Tool in the " ..
  "bottom left corner.",
  condition = "true",
  effect = "FRoadTool,FRoadStreet",
  hint = ""
});
1 Answers

If in doubt check for example in original source code. Lua has an interpreter lua.c that does exactly what you are trying to accomplish. You are interested in msghandler, docall and report.

lua_pcall accepts an index to message handler as fourth argument. It can be used to retrieve information about error's environment and so on. After lua_pcall returns you can't do that (stack unwinds by then).

Your code should look more like:

int handleLuaError(lua_State* L) {
  const char * msg = lua_tostring(L, -1);
  luaL_traceback(L, L, msg, 2);
  lua_remove(L, -2); // Remove error/"msg" from stack.
  return 1; // Traceback is returned.
}

void readLua(const char *filename) {
  SPDLOG_INFO("readLua {}", filename);
  lua_pushcfunction(luaState, handleLuaError);
  if (LUA_OK != (luaL_loadfile(luaState, filename) || lua_pcall(luaState, 0, 0, -2))) {
     //                                                 handleLuaError's index ^^^
     handleError("Reading file %s: %s", filename, lua_tostring(luaState, -1));
     lua_pop(luaState, 1); // Pop traceback from stack.
  }
  lua_pop(luaState, 1); // Pop handleLuaError from stack.
}

Now, that's a rather simple implementation. You should consider splitting luaL_loadfile and lua_pcall to react accordingly to errors related directly to loading a file. Again, you can refer to interpreter's source code: dofile and then dochunk.

Additionally, the example above lacks most of important checks, especially test if lua_tostring was successful, unless you are more than 100% sure that the returned error can always be converted easily into a string.

It seems that you have two states coexisting: luaState and sometimes L. I suggest looking into that, it may spare you some problems in future.

One more thing, in addition to luaL_traceback you may also be interested in luaL_where. It's used internally with luaL_error.

Related