错误处理、加载*.lua文件

  • 关于lua_kernel_base::lua_kernel_base参考“初始化lua环境”。
  • lua_pcall时,会把db_traceback设为错误处理函数,功能是根据运行现场,补上更多出错时信息,让开发者能更容易定位错误。
  • 除了第一个package.lua,它实现了rose.require方法。其它lua文件都是通过rose.require进行加载。rose.require不仅调用lua_load加载lua文件,还会调用lua_call执行该文件顶层函数

 

一、错误处理

1.1 执行luaopen_debug(),向lua运行环境添加db_traceback方法

lua_kernel_base()会执行“luaL_requiref(L, lib->name, lib->func, 1)”,当中有个lib是“debug”,对应的func是luaopen_debug(),后者有条记录:{"traceback", db_traceback}。

经过这操作后,_ENV添加了一条记录:{_ENV.debug, [dblib]},[dblib]是一个表,当中有条记录:{"traceback", db_traceback}。

1.2 _ENV.debug表包含数种操作,只让保留“traceback”、“getinfo”

认为_ENV.debug表的一些方法有替代方案,避免将来会被调用,lua_kernel_base()会删除它们,但会保留“traceback”、“getinfo”

1.3 push_error_handler。向注册表添加一条记录:{key=err, debug.traceback}

lua_kernel_base::lua_kernel_base()会调用push_error_handler。

<librose>/scripts/lua_common.cpp
------
static const char executeKey[] = "err";

void push_error_handler(lua_State *L)
{
	luaW_getglobal(L, "debug", "traceback");
	lua_setfield(L, LUA_REGISTRYINDEX, executeKey);
}

push_error_handler向注册表添加一条记录:{key=err, _ENV.debug.traceback}。

1.4 lua_pcall时,将debug.traceback设为错误处理函数

运行时某个时刻,C要调用lua函数了,在将lua函数、参数入栈后,会调用luaW_pcall_internal。

int luaW_pcall_internal(lua_State *L, int nArgs, int nRets)
{
	// Load the error handler before the function and its arguments.
	lua_getfield(L, LUA_REGISTRYINDEX, executeKey);
	lua_insert(L, -2 - nArgs);

	int error_handler_index = lua_gettop(L) - nArgs - 1;

	// Call the function.
	// -1 - nArgs放在是待调用lua函数
	int errcode = lua_pcall(L, nArgs, nRets, -2 - nArgs);

	// lua_pcall会自动弹出被调函数及参数,但不会弹错误处理函数
	// 弹出错误处理函数
	lua_remove(L, error_handler_index);

	return errcode;
}

在调用lua_pcall时,第二个参数表示传递的参数数量,第三个参数是期望的返回值数量,第四个参数代表错误处理函数。

如果lua_pcall在运行过程中出现错误,它会返回一个错误码,并在栈中压入一条字符串类型的错误信息(但是仍会弹出函数及参数)。不过,如果有错误处理函数,在压入错误信息前,lua_pcall会先调用错误处理函数。我们可以通过lua_pcall最后一个参数指定这个错误处理函数,零表示没有错误处理函数,即最终的错误信息就是原来的消息;若传入非零参数,那么参数应该是该错误处理函数在栈中索引。在这种情况下,错误处理函数应该被压入栈,且位于待调用函数之下。

上面代码在调用lua_pcall时,stkidx = -1 - nArgs放的是待调用lua函数,错误处理函数位于它之下,即“-2 - nArgs”。

lua_pcall会自动弹出被调函数及参数,但不会弹出错误处理函数。error_handler_index是用正值表示的、错误处理函数在栈中的索引。

1.5 出错示例:error("Tried to access an empty package", 2)

为看到出错实例,让在lua脚本增加一条语句:error("Tried to access an empty package", 2);

error第一个参数是字符串表示的错误描述,第二个参数是要返回给lua_pcall的错误码。这条error被执行后,lua_pcall的返回值,即luaW_pcall_internal中的变量errcode将是2。

1.6 lua_pcall调用debug.traceback,即db_traceback

“如果有错误处理函数,在压入错误信息前,lua_pcall会先调用错误处理函数”,由于设置了db_traceback是错误处理函数,lua_pcall执行到“error”触发错误后,会先调用db_traceback,db_traceback则调用luaL_traceback。luaL_traceback根据运行现场,补上更多出错时信息,让开发者能更容易定位错误。

Tried to access an empty package
stack traceback:
	[C]: in function '.error'
	lua/package.lua:9: in main chunk

以上是luaL_traceback根据运行现场补充后的信息,当中指出出错的lua文件、行号。

1.7 lua_tostring(L, -1)得到错误信息

{
  int errcode = luaW_pcall_internal(L, nArgs, nRets);
  if (errcode != LUA_OK) {
    char const * msg = lua_tostring(L, -1);

    // msg就就是经过db_traceback补充后的出错信息    
    // 处理msg。一旦调用下面的lua_pop,msg这指针就无效了。

    lua_pop(L, 1);
    return false;
  }
  return true;
}

 

二、加载*.lua文件

void lua_kernel_base::load_core()
{
	lua_State* L = mState;
	luaW_getglobal(L, "rose", "require");
	lua_pushstring(L, "lua/core.lua");
	if (!protected_call(1, 1)) {
	   // 加载lua/core.lua失败
	}
}

以上是一个典型的加载lua文件示例,要加载的文件是lua/core.lua。核心是以“lua/core.lua”为参数,调用lua脚本函数rose.require。须要说下的是,rose.require不仅是调用lua_load加载lua文件,还会调用lua_call执行该文件顶层函数,见之下的lua_kernel_base::intf_dofile。

rose.require是什么时候加到lua运行环境?——lua_kernel_base()会加载lua/package.lua,package.lua实现了rose.require。

2.1 (lua函数)rose.require

<apps-res>/data/core/lua/package.lua
------
function rose.require(pkg_name)
	-- First, check if the package is already loaded
	local loaded_name = resolve_package(pkg_name)
	-- don't user cache. for applet, it's *.lua require load every time.
	-- if loaded_name and rose.package[loaded_name] then
	--	return rose.package[loaded_name]
	-- end
	if not loaded_name then
		rose.log("err", "Failed to load required package: " .. pkg_name, true)
		return nil
	end

resolve_package是个lua函数,如果pkg_name存在,返回的loaded_name就是pkg_name。

	-- Next, if it's a single file, load the package with dofile
	if rose.have_file(loaded_name, true) then
		local pkg = rose.dofile(loaded_name)
		rose.package[loaded_name] = pkg or empty_pkg

rose.have_file判断该文件是否存在,rose.dofile指向一个C函数,依次执行lua_load、lua_call。

		return pkg
	else -- If it's a directory, load all the files therein

loaded_name可能是个目录

		local files = rose.read_file(loaded_name)
		local pkg = {}
		for i = files.ndirs + 1, #files do
			if files[i]:sub(-4) == ".lua" then
				local subpkg_name = files[i]:sub(1, -5)
				pkg[subpkg_name] = rose.require(loaded_name .. '/' .. files[i])
			end
		end
		rose.package[loaded_name] = pkg
		return pkg
	end
end

rose.require只一个参数:pkg_name,指示要加载的lua文件。

2.2 luaW_getglobal

判断path[0].path[..].path[path.size()-1]是否是一个lua函数,是的话,把这函数压栈。执行完luaW_getglobal后,栈会增加一个单元,就是这个lua脚本函数。

bool luaW_getglobal(lua_State *L, const std::vector<std::string>& path)
{
	lua_pushglobaltable(L);

rose一定存在于_ENV中,首先把_ENV压栈。

	for (const std::string& s : path) {
		// 处理path中的#i单元
		if (!lua_istable(L, -1)) goto discard;
		lua_pushlstring(L, s.c_str(), s.size());
		lua_rawget(L, -2);
		// 
		lua_remove(L, -2);

针对:luaW_getglobal(L, "rose", "require");

第一轮for循环。以key=rose搜索_ENV,找到_ENV.rose这个表,入栈。并从栈出弹出表示_ENV的单元。

第二轮for循环。以key=require搜索_ENV.rose,找到rose.require这个lua函数,入栈。并从栈出弹出表示_ENV.rose的单元。

	}

	if (lua_isnil(L, -1)) {
		VALIDATE(false , null_str);
discard:
		lua_pop(L, 1);
		return false;
	}

	return true;
}

2.3 lua_kernel_base::intf_dofile

在lua脚本,rose.require会调用rose.dofile,它对应的是C函数:lua_kernel_base::intf_dofile。

int lua_kernel_base::intf_dofile(lua_State* L)
{
	luaL_checkstring(L, 1);
	lua_rotate(L, 1, -1);
	if (lua_fileops::load_file(L) != 1) return 0;
	//^ should end with the file contents loaded on the stack. 
	// actually it will call lua_error otherwise, the return 0 is redundant.
	lua_rotate(L, 1, 1);
	// Using a non-protected call here appears to fix an issue in plugins.
	// The protected call isn't technically necessary anyway, because this function is called from Lua code,
	// which should already be in a protected environment.
	lua_call(L, lua_gettop(L) - 1, LUA_MULTRET);
	return lua_gettop(L);
}

intf_dofile依次执行两个任务,一是lua_load加载lua文件,二是lua_call执行该lua文件顶层函数。

全部评论: 0

    写评论: