Advertisement

Lua error handling

Started by April 28, 2011 07:43 PM
6 comments, last by Josh Klint 13 years, 7 months ago
I want to be able to determine which line of a script was being executed when a crash in the program occurs.

Here is my C++ function to crash the program:
void GenerateCrash()
{
Entity* entity = NULL
entity->SetColor(1,1,1,1);
}

Here is my Lua script, using LuaBind to expose the command:
print("Generating crash...")
GenerateCrash()

I know lua_pcall has an error function you can pass to it, but I can't find any example of how this works. I guess you indicate the position in the stack that you pushed the error function to, but what is the syntax of this function?

10x Faster Performance for VR: www.ultraengine.com

I found one thread with some information:
http://www.gamedev.net/topic/446781-lua-more-detailed-error-information/page__view__findpost__p__3960907

I am getting the stack traceback and running a script I have loaded. I am not calling a specific function, I am just calling the main entry point of the script:
lua_getglobal(L, "debug");
lua_getfield(L,-1,"traceback");
lua_remove(L,-2);
lua_getglobal(L, ""); //is this a problem? How do I go back to the "main" entry point?

if (lua_pcall(L,0,LUA_MULTRET,-2)!=0)
{
Print(lua_tostring(L,-1));
}


The addition of the traceback function (indicated by the -2 argument in lua_pcall) causes the addition of this line text to the printed error message:
"stack traceback:"

No further information is available. The error occurs inside a Lua function, so it should have something to print:
print("Running script...")

function dostuff()
a = 5
b = 6
model = nil
model:SetPosition(1,2,3,0)
c = a +b
end

dostuff()

10x Faster Performance for VR: www.ultraengine.com

Advertisement
Here's my Lua functions. If I throw an exception in the try block, it gets caught. If the Lua script calls a C++ function that throws an exception, no exception is caught by the catch statement.
void Interpreter::HandleError()
{
Print("Error: "+std::string(lua_tostring(L,-1)));
}

void Interpreter::HandleException()
{
std::string err = "Lua debugger not found.";
lua_getglobal(L,"debug");
if (lua_istable(L,-1)!=0)
{
lua_getfield(L,-1,"traceback");
if (lua_isfunction(L,-1)!=0)
{
if (lua_pcall(L,0,1,0)==0)
{
if (lua_isstring(L,-1)!=0)
{
err = lua_tostring(L,-1);
}
}
}
}
Print("Error: "+err);
}

bool Interpreter::ExecuteString(std::string& source)
{
bool result = false;
int errorhandler = 0;
int size = GetStackSize();

if (luaL_loadbuffer(L,source.c_str(),source.length(),"")==0)
{
result = Invoke(0,0);
}
else
{
HandleError();
}
SetStackSize(size);
return result;
}

bool Interpreter::ExecuteFile(const std::string& path)
{
bool result = false;
int errorhandler = 0;
int size = GetStackSize();
Bank* bank = LoadBank(path);

if (bank==NULL) return false;
if (luaL_loadbuffer(L,bank->buf,bank->GetSize(),path.c_str())==0)
{
result = Invoke(0,0);
}
else
{
HandleError();
}
delete bank;
SetStackSize(size);
return result;
}

bool Interpreter::Invoke(const int& in, const int& out)
{
try
{
if (lua_pcall(L,in,out,0)==0)
{
return true;
}
else
{
HandleError();
}
}
catch(...)
{
HandleException();
}
}
}

10x Faster Performance for VR: www.ultraengine.com

I created a new C++ function:
void RuntimeError(const std::string& error)
{
throw std::exception(error.c_str());
}

Here is the Lua script that calls it:
print("Creating an error...")

RuntimeError("An error has occurred!")

Here is the code that runs the script:
bool Interpreter::Invoke(const int& in, const int& out)
{
try
{
if (lua_pcall(L,in,out,0)==0)
{
return true;
}
else
{
//If you throw an exception, it gets displayed here, which isn't what we want:
HandleError();
}
}
//Exceptions should be getting caught here:
catch(luabind::error& e)
{
Print("Caught the exception!");
exit(1);
}
return false;
}

Instead of catching an exception, the Lua error log just prints out "std-exception: 'An error has occurred!'"

I am using MSVC, and I know about the issue described here:
http://www.rasterbar.com/products/luabind/docs.html#structured-exceptions-msvc

The code they said to add has no effect on behavior.

10x Faster Performance for VR: www.ultraengine.com

It's been quite some time since I last fiddled with lua / luabind , but I last had this:


std::string Script::getError() {
const char* estr = lua_tostring(state, -1);
lua_pop(state, 1);
return std::string(estr);
}

bool Script::doFile(const filesystem::path& file) {
if (!filesystem::exists(file)) {
logger << Logger::error << "File: " << file.file_string() << " does not exist for doFile call." << std::endl;
return false;
}

try {
luabind::call_function<void>(state, "dofile", file.file_string());
} catch (...) {
logger << Logger::warn << "Failed to parse script file: " << file.file_string() << std::endl
<< getError() << std::endl;
return false;
}
logger << Logger::info << "Parsed script file: " << file.file_string() << std::endl;
return true;
}


Where I'd replaced the global lua error function like so:


-- Change the standard error function so that it shows the debug traceback.
local error = error
_G.error = function(m, l)
error("\nerror msg: "..m.."\n"..debug.traceback(), (l or 1) + 1)
end


It's been too long for me to really remember if there were any problems with this, but I think it worked more or less correctly, at least in returning a useful error message.
That doesn't have anything to do with catching exceptions a C++ function throws when called by a Lua script.

10x Faster Performance for VR: www.ultraengine.com

Advertisement
Ah, I had to read the Luabind docs more carefully. LuaBind exceptions are just errors that occur in the LuaBind code, which should hopefully never happen. I was thinking exceptions you throw would get turned into LuaBind exceptions, but they actually just get turned into a regular Lua error you can print out.

The problem now is by the time pcall returns, the Lua stack is not in the same state as it was when the error occurred, so you can't just catch the exception and print out the Lua call stack.

I don't know if it's possible to call debug.trace once an exception has been thrown, before lua_pcall returns, but it is possible to make my own RuntimeError() function which first performs a debug.trace if it is being called from a Lua state.

10x Faster Performance for VR: www.ultraengine.com

I'll share my interpreter code. This prints out all the variables in a Lua state and the call stack. You can call the Interpreter::GetCallStack() function inside a Lua program and print out the result. I just made a DebugStop() function and exposed it to Lua to do this:
void DebugStop()
{
if (CurrentInterpreter!=NULL) Print(CurrentInterpreter->GetCallStack());
}


#include "le3.h"

namespace le3
{
Interpreter* CurrentInterpreter = NULL;

void ErrorCallback(lua_State* L)
{
Print("Error callback!");
}

/* int panic(lua_State* L)
{
Print("Panic called!");
return 0;
}*/

Interpreter::Interpreter() : L(NULL)
{
L=lua_open();
//Don't understand how to use this:
//lua_atpanic(L, panic);
//lua_register( L, "_ALERT", panic );

luaL_openlibs(L);
luabind::open(L);

//luabind::set_error_callback(ErrorCallback)
//luabind::register_exception_handler<my_exception>(&translate_my_exception);

using namespace luabind;
module(L)
[
def("RuntimeError", &RuntimeError),
def("DebugStop", &DebugStop)
];
}

Interpreter::~Interpreter()
{
if (L!=NULL)
{
lua_close(L);
L = NULL;
}
}

int Interpreter::GetStackSize()
{
return lua_gettop(L);
}

void Interpreter::SetStackSize(const int& size)
{
int currentsize = GetStackSize();
if (size<currentsize) lua_pop(L,currentsize-size);
}

void Interpreter::HandleError()
{
Print("Error: "+std::string(lua_tostring(L,-1)));
}

std::string Interpreter::GetVariableValue(const int& index)
{
std::string value;

if (lua_isnumber(L,index))
{
return (String(lua_tonumber(L,index)));
}
else
{
if (lua_isstring(L,index))
{
return (lua_tostring(L,index));
}
else
{
if (lua_istable(L,index))
{
return "{}";
//This can cause an infinite loop if you have tables that reference each other
/*value="{";
int size = GetStackSize();
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1))
{
std::string var = GetVariableValue(-2);
Print(var);
value += ""+var;
value += "="+GetVariableValue(-1);
value += ", ";
}
SetStackSize(size);
value += "}";
return value;*/
}
else
{
if lua_isnil(L,index)
{
return ("nil");
}
}
}
}
return "";
}

int Interpreter::GetCallStackSize()
{
int level = 0;
lua_Debug ar;

while (true)
{
if (lua_getstack(L, level, &ar) == 0) return level;
level += 1;
}
}

std::string Interpreter::GetCallStack()
{
std::string err;
lua_Debug ar;
int level = 0;
std::string tab;
int size = GetCallStackSize();
int i;
const char* name;

//Display call stack. I'm going to make the main function '0' and reverse the level numbers
//because that makes more sense to me, but remember 0 = the current level.
for (level=0; level<size; level++)
{
lua_getstack(L, size -1- level, &ar);
Print(tab+"[Level "+String(level)+"]");
Print(tab+"{");
lua_getinfo(L, "nSluf", &ar);

Print(tab+" source: "+std::string(ar.source));
Print(tab+" short_src: "+std::string(ar.short_src));
Print(tab+" linedefined: "+String(ar.linedefined));
Print(tab+" lastlinedefined: "+String(ar.lastlinedefined));
Print(tab+" what: "+std::string(ar.what));
Print(tab+" currentline: "+String(ar.currentline));
if (ar.name!=NULL) Print(tab+" name: "+std::string(ar.name));
if (ar.namewhat!=NULL) Print(tab+" namewhat: "+std::string(ar.namewhat));
Print(tab+" nups: "+String(ar.nups));

//Display locals
Print(tab+" locals:");
i = 1;
while (true)
{
name = lua_getlocal(L, &ar, i++);
if (name==NULL) break;
if (name!="(*temporary)")
{
Print(tab+" "+std::string(name)+" = "+GetVariableValue());
}
lua_pop(L,1);
}

Print("");
tab += " ";
}

//Add closing brackets
for (level=0; level<size; level++)
{
tab = "";
for (i=0; i<size-level-1; i++) tab+=" ";
Print(tab+"}");
}
Print("");

//Display globals
Print("Globals:");
lua_getglobal(L,"_G");
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1))
{
if (!lua_isfunction(L,-1))
{
Print(GetVariableValue(-2)+" = "+GetVariableValue(-1));
}
}
lua_pop(L,1);
return err;
}

bool Interpreter::ExecuteString(std::string& source)
{
bool result = false;
int errorhandler = 0;
int size = GetStackSize();
if (luaL_loadbuffer(L,source.c_str(),source.length(),"")==0)
{
result = Invoke(0,0);
}
else
{
HandleError();
}
SetStackSize(size);
return result;
}

bool Interpreter::ExecuteFile(const std::string& path)
{
bool result = false;
int errorhandler = 0;
int size = GetStackSize();

if (luaL_loadfile(L,path.c_str())==0)
{
result = Invoke(0,0);
}
else
{
HandleError();
}
SetStackSize(size);
return result;
}

bool Interpreter::Invoke(const int& in, const int& out)
{
try
{
CurrentInterpreter = this;
if (lua_pcall(L,in,out,0)==0)
{
return true;
}
else
{
HandleError();
}
}
catch(luabind::error& e)
{
Print("LuaBind exception occurred. This should never happen.");
exit(1);
}
return false;
}


}

10x Faster Performance for VR: www.ultraengine.com


Ah, I had to read the Luabind docs more carefully. LuaBind exceptions are just errors that occur in the LuaBind code, which should hopefully never happen. I was thinking exceptions you throw would get turned into LuaBind exceptions, but they actually just get turned into a regular Lua error you can print out.

The problem now is by the time pcall returns, the Lua stack is not in the same state as it was when the error occurred, so you can't just catch the exception and print out the Lua call stack.

I don't know if it's possible to call debug.trace once an exception has been thrown, before lua_pcall returns, but it is possible to make my own RuntimeError() function which first performs a debug.trace if it is being called from a Lua state.



Yes I agree. You have a very insightful post my friend.

This topic is closed to new replies.

Advertisement