Exposing multiple objects to Lua from C++

Started by
18 comments, last by FRex 9 years, 7 months ago

The crash is becoming increasingly more frustrating and is showing all signs of memory corruption. I can't reproduce it in any other way save for with the above code, though.

I'll try to reduce the problem to a self-contained sample to tomorrow. Right now, as it is, I have no idea why it crashes, NULL byte included or not... All I can tell is that it seems to be dependent on the number of times the function is run and it only crashes if the a table row contains more than one field.

Advertisement

One very good, open source, LUA / C++ implementation is Moai. I highly recommend you look at their code for ideas.

https://github.com/moai/moai-dev/blob/master/src/moai-core/MOAILuaClass.cpp

The heart of it.

Alright then - my first reaction was that I was dealing with a sneaky buffer overflow somewhere in my code so I wrote a stack verifier for my memory manager and made sure I wasn't trashing any unwanted memory by accident. As it is, I'm not. So I started weeding out as much code as possible to come up with the simplest test case and finally managed to reproduce the problem on a small scale. I'd like to note first:

1) the crash is spontaneous - no exceptions are thrown. Depending on something I can't quite understand yet, though, the nature of the crash may either be instantaneous or may pop up a "xxx.exe has stopped working - Windows is checking for solutions" notification. Debugging halts in an unloaded frame. Adding output tracers and a counter to the code produces repeatable results and seems to indicate that the crash happens in lua_pushlstring

2) the number of iterations the script can run depends on what I'm writing into the table - I can't tell if it's the length of the string or not, but my initial suspicion is that Lua causes a segfault internally. I don't know of any way to debug it.

3) when creating the table from scratch each time I call GetActiveItem() from the script, the number of iterations I get out of it before it crashes in my current production code is 63. By changing what I put into the table I can get more iterations out of it, but the fact remains: I'm doing something wrong

4) I can create a whole lot of tables in advance (the below code creates 40000), but even then lua_getglobal() crashes, albeit after more times I run the script

The below code won't run when you copy-paste it. I didn't create a new project so I ended up copy-pasting it together - hence it might be missing a basic include file or whatnot.

To run it, you'll also have to replace the "lout << ??? << endl;" lines with printf() or whatever output functionality you have available to you and then simply call fnTestLuaTable() from something like main() or WinMain() to run the test. The create all-tables-at-once vs create-table-as-needed approach can be toggled by uncommenting the two lines in GetActiveItem2(). The test is pretty QnD, but it should compile and it behaves on par with what I'm encountering in my main code.

Note that I'm using the latest version of LuaJIT.


#include <windows.h>

extern "C" {
#include "../lua/LuaJIT-2.0.3/src/lua.h"
#include "../lua/LuaJIT-2.0.3/src/lauxlib.h"
#include "../lua/LuaJIT-2.0.3/src/lualib.h"
}

struct IEntity;

IEntity* curent;

static int loadEntity(IN lua_State *L)
{
IEntity** udata = (IEntity**)lua_newuserdata(L, sizeof(IEntity*));
*udata = curent;
luaL_getmetatable(L, "IEntity");
lua_setmetatable(L, -2);

return 1;
}

void LUAErr(lua_State* L, int status)
{
	if(status != 0)
		{
		lout << "LUA" << " (" << status << "): " << lua_tostring(L, -1) << std::endl;
		lua_pop(L, 1); // remove error message
		}
}

void luaExposeInterface(IN lua_State * L, IN const luaL_Reg listInterfaceFunctions[], IN char * sName)
{
	luaL_newmetatable(L, sName);
	luaL_setfuncs(L, listInterfaceFunctions, 0);
	lua_pushvalue(L, -1);
	lua_setfield(L, -1, "__index");
	lua_pushvalue(L, -2);
	lua_setfield(L, -2, "__metatable");
	lua_setglobal(L, sName);
}




static int GetActiveItem2(IN lua_State * L)
{
	static bool bTableCreated = false;

	static int ir = 0;

	//output the iteration number
	lout<< ir++ <<endl;

 	if(!bTableCreated)
 		{
//uncomment the next two lines to create a bunch of tables in advance
		//for(int t = 0; t < (int)40000; t++)
			{
			lua_newtable(L);

			for(int i = 0; i < (int)20; i++)
				{
				const char* ptr = "Force";
				lua_pushlstring(L, ptr, STRLEN(ptr));
				lua_pushnumber(L, 8);
				lua_settable(L, -3);
				}
			char sname[1024];
			sprintf_s(sname, "activeItem%d", 1);
			lua_setglobal(L, sname);
			}

		lua_getglobal(L, "activeItem1");
		lout << "create table" << endl;

		//bTableCreated = true;
		}
	else
		{
		lout << "load table" << endl;
		lua_getglobal(L, "activeItem1");
		}

	return 1;
}


static int AddVelocity2(IN lua_State * L)
{
	float fVelocity = (float)luaL_checknumber(L, 2);

	lout << fVelocity << endl;

	return 0;
}



void fnTestLuaTable()
{
	lua_State* L;

	L = lua_open();
	luaopen_base(L);
	luaopen_table(L);
	luaopen_io(L);
	luaopen_string(L);
	luaopen_math(L);
	luaopen_debug(L);
	luaopen_ffi(L);
	luaL_openlibs(L);

	IEntity* entity = NULL;
	curent = entity;

	static const luaL_Reg listEntityFuncs[] = {
 			{ "load", loadEntity },
			{ "GetActiveItem", GetActiveItem2 },
			{ "AddVelocity", AddVelocity2 },
			{ "__gc", NULL},
			{ NULL, NULL }
		};

	luaExposeInterface(L, listEntityFuncs, "IEntity");

	LUAErr(L, luaL_dofile(L, "test2.lua"));

//run it a bunch of times
	for(int i = 0; i < (int)2000; i++)
		{
		lua_getglobal(L, "OnEntityAction");

		lua_pushnumber(L, 0);
		LUAErr(L, lua_pcall(L, 1, 0, 0));
		}
}


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
//my own error piping class is initialized here
//errlogAttachStream(&out);
//errlogEnableConsole(true);

fnTestLuaTable();

return 0;
}

And the corresponding Lua file:


function OnEntityAction(iAction)
	player = IEntity:load()
	activeItem2 = player:GetActiveItem()
	
	player:AddVelocity(activeItem2["Force"])
end

I'm hoping the problem is something painfully simple - I'm really having difficulty stripping it down to anything more bare bones than this...

The fact that it runs okay for a number of times and then crashes with no warning or even a silent opt-out is worrisome to say the least, though.

luaExposeInterface and LUAErr are not defined in your example code

luaExposeInterface and LUAErr are not defined in your example code

Dagnabit - fixed.

There is no luaL_setfuncs in jit/5.1, are you sure you are running vanilla LuaJit and didn't rewrite the luaL_setfuncs function yourself?

Bah, indeed - I think I copied it from somewhere:


static void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
{
	luaL_checkstack(L, nup + 1, "too many upvalues");
	for(; l->name != NULL; l++)
		{  /* fill the table with given functions */
		int i;
		lua_pushstring(L, l->name);
		for(i = 0; i < nup; i++)  /* copy upvalues to the top */
			lua_pushvalue(L, -(nup + 1));
		lua_pushcclosure(L, l->func, nup);  /* closure with those upvalues */
		lua_settable(L, -(nup + 3));
		}
	lua_pop(L, nup);  /* remove upvalues */
}

It crashes deep in the GC collection cycle because of this line:

{ "__gc", NULL},

which makes it try call a NULL pointer as a function. If you want to omit a metamethod then just leave it out of the luaL_Reg array. It also works at first because GC is (by definition) lazy and ran only once in a while so it takes a while for your NULL __gc metamethod to be called.

Wow - thanks! Did you get that from the disassembly?

Two questions:

- if I omit it, will Lua's internal garbage collection be disabled? I'm not so much worried about leaked memory since I intend to pre-load everything anyway and the memory footprint shouldn't really be anything to worry about on PC. Hence, the question is rather - should I keep this in mind as something that needs addressing later?

- truth be told, I haven't had the chance to read up on Lua's garbage collection, but what does the line do anyway? Does it allow me to handle server-side object destruction in response to dynamic allocation? If so, I won't need it anyway as I don't really have the intention of doing any allocations that aren't directly controlled by the engine.

As an aside, it seems a bit daft that Lua crashes if it's passed a NULL pointer. More importantly, I'm not sure where I got this, but I'll be more careful to trust code found on the Internet :)

I got it from this backtrace from gdb which I got in a couple of gdb commands (literally two commands: run + backtrace)

#0 0x00000000 in ?? ()
#1 0x45bc973f in lj_BC_FUNCC () from /lib/libluajit-5.1.so.2
#2 0x45bd68e9 in gc_call_finalizer () from /lib/libluajit-5.1.so.2
#3 0x45bf1047 in gc_finalize () from /lib/libluajit-5.1.so.2
#4 0x45bf1171 in gc_onestep () from /lib/libluajit-5.1.so.2
#5 0x45bf1633 in lj_gc_step () from /lib/libluajit-5.1.so.2
#6 0x45c0923a in lua_pushlstring () from /lib/libluajit-5.1.so.2
#7 0x0804918a in GetActiveItem2 (L=0xb7fdc1c0) at main.cpp:86
#8 0x45bc973f in lj_BC_FUNCC () from /lib/libluajit-5.1.so.2
#9 0x45c0df05 in lua_pcall () from /lib/libluajit-5.1.so.2
#10 0x08049408 in fnTestLuaTable () at main.cpp:154
#11 0x08049439 in main () at main.cpp:164

1. No, __gc is just a finalizer for the object and has nothing to do with when GC is ran, for controlling GC there is lua_gc in C and collectgarbage in Lua.

2. It's just a finalizer, whether or not you need to use it depends on what you are doing exactly.

This topic is closed to new replies.

Advertisement