简体   繁体   English

从 C++ 为字符串 function 设置 _ENV

[英]Set _ENV from C++ for string function

In my project I'm executing some lua functions contained in an XML file.在我的项目中,我正在执行 XML 文件中包含的一些 lua 函数。 I read the XML from C++, parse the code strings, execute them and get the result.我从 C++ 中读取 XML,解析代码字符串,执行它们并得到结果。 All of the related questions I have found either used a dedicated .lua file or did it in Lua directly and I couldn't find a solution that worked for my case.我发现的所有相关问题要么使用专用的.lua文件,要么直接在 Lua 中进行,但我找不到适合我的情况的解决方案。

I can't modify the functions in the file and they all have the following signature:我无法修改文件中的函数,它们都具有以下签名:

function() --or function ()
    --do stuff
    return foo
end

from C++ i load them like so:从 C++ 我像这样加载它们:

lua_State *L = luaL_newstate();
luaL_openlibs(L);
std::string code = get_code_from_XML();
std::string wrapped_code = "return " + code;
luaL_loadstring(L, wrapped_code.c_str());
if (lua_pcall(L, 0, 1, 0)){
    return 1;
}

argNum = load_arg_number();

if (lua_pcall(L, argNum, 1, 0)){
    return 1;
}
return 0;

Everything works but running arbitrary Lua code from an XML string doesn't seem very safe so i wanted to set up a whitelist of functions that the code can use.一切正常,但从 XML 字符串运行任意 Lua 代码似乎不是很安全,所以我想设置代码可以使用的函数的白名单。

Following this lua-users discussion i created my list of allowed functions like:这个lua-users 讨论之后,我创建了允许的函数列表,例如:

// Of course my list is bigger
std::string whitelist = "sandbox_env = {ipairs = ipairs} _ENV = sandbox_env"

The problem is that I don't understand how to load it to be available in the functions that I'm calling.问题是我不明白如何加载它以便在我调用的函数中可用。

I tried doing it like on the lua-users website:我试着像在 lua-users 网站上那样做:

 std::string Lua_sandboxed_script_to_run( Lua_sandboxing_script + Lua_script_to_run ) if (luaL_dostring(sandboxed_L, Lua_sandboxed_script_to_run)) { // error checking }

But this results in the function not being loaded correctly and the Lua error但这会导致 function 未正确加载和 Lua 错误

Trying to execute a string value尝试执行字符串值

I also tried to do:我也尝试过:

luaL_loadstring(L, whitelist.c_str());
lua_getglobal(L, "_ENV");
lua_setupvalue(L, -2, 1);

Before executing the loaded XML function, this doesn't crash the program but doesn't set the _ENV for the called function either.在执行加载的 XML function 之前,这不会使程序崩溃,但也不会为调用的 function 设置_ENV

The only way I have found to have what I want is to parse the function string with C++ searching for () , insert whitelist after it and then luaL_loadstring and lua_pcall it twice to execute it.我发现我想要的唯一方法是用 C++ 搜索()解析 function 字符串,在它之后插入whitelist ,然后luaL_loadstringlua_pcall两次以执行它。

Like this:像这样:

.
.
size_t n = code.find("()") + 2;
code.insert(n, whitelist);
std::string wrapped_code = "return " + code;
.
.

This works and sets my custom _ENV for the function but seems to me like a really hacky way to go about it.这有效并为 function 设置了我的自定义 _ENV,但在我看来,这对 go 来说是一种非常老套的方式。

How can I set the _ENV variable for a string function loaded from C in a nicer way?如何以更好的方式为从 C 加载的字符串 function 设置 _ENV 变量?

Bonus points if there is a way to save it once for the whole lua_State instead of every time I call a function.如果有办法为整个lua_State保存一次,而不是每次调用 function 时,将获得奖励积分。

Before we dive into examples let's explain how (and at what level) we can more-or-less sandbox code in Lua:在深入研究示例之前,让我们解释一下如何(以及在什么级别)我们可以在 Lua 中或多或少地编写沙箱代码:

  1. Globally - remove or never add unwanted modules/functions into the global environment.全局 - 删除或从不将不需要的模块/功能添加到全局环境中。

  2. Chunk(ly) - modify the _ENV upvalue of a chunk. Chunk(ly) - 修改块的_ENV上值。

  3. Locally - modify _ENV through a local or upvalue in Lua script.本地 - 通过 Lua 脚本中的本地或上值修改_ENV It can be propagated then to children via upvalue.然后它可以通过 upvalue 传播给孩子。

Looking at the examples provided in the question you tried to do either 2 or 3 .查看您尝试执行23的问题中提供的示例。

I'm not sure what are your needs but I will try to provide you examples for each of the approaches listed above.我不确定您的需求是什么,但我会尝试为您提供上面列出的每种方法的示例。 Please note that those examples are not the ultimate ones, are not the only possible approaches and do not take yours return function ()... end wrapper.请注意,这些示例不是最终示例,也不是唯一可能的方法,并且不要让您的方法return function ()... end包装。 Pick any that suits you.选择任何适合您的。

Globally全球范围内

This one is very straight-forward.这个非常直截了当。 If you don't plan to use all of the libraries provided...如果您不打算使用提供的所有库...

lua_State * L = luaL_newstate();
luaL_openlibs(L); // <-- Remove this

... then just don't load them: ...然后就不要加载它们:

lua_State * L = luaL_newstate();
// Instead of luaL_openlibs:
luaL_requiref(L, "_G", luaopen_base, 1);
luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1);
luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1);
// luaL_requiref pushes to stack - clean it up:
lua_pop(L, 3);
// Load and execute desired scripts here.

Refer to 6 Standard Libraries and linit.c for list of available libraries.有关可用库的列表,请参阅6 标准库linit.c See also: luaL_requiref .另见: luaL_requiref

In addition to selective library loading you can also remove selected globals by setting them to nil :除了选择性库加载之外,您还可以通过将选定的全局变量设置为nil来删除它们:

lua_pushnil(L);
lua_setglobal(L, "print");

You set this once for the lua_State.您为 lua_State 设置一次。

Chunk(ly)大块(ly)

Let's limit ourselves to a very basic operation of changing the first upvalue of loaded main chunk.让我们将自己限制在一个非常基本的操作,即更改加载的主块的第一个上值。 The first upvalue of such chunk is expected to be _ENV (reference below).此类块的第一个上值预计为_ENV (参考下文)。

Note that I said main chunk.请注意,我说的是主要块。 In general you will rarely load chunks that expect something else than _ENV as their first upvalue but it is always nice to remember about such case.通常,您很少会加载期望除_ENV之外的其他内容作为其第一个上值的块,但记住这种情况总是很高兴。

Anyway, consider following example:无论如何,请考虑以下示例:

lua_State * L = luaL_newstate();
luaL_openlibs(L);
luaL_loadstring(L, "print2 \"Hello there\"");
// (A) Create a table with desired environment and place it at the top of the stack.
//     Replace this comment with any of the options described below
lua_setupvalue(L, -2, 1);
lua_call(L, 0, 0);

You can achieve ( A ) in various ways.您可以通过各种方式实现 ( A )。 You could use Lua C API:您可以使用 Lua C API:

lua_newtable(L);
lua_pushliteral(L, "print2");
lua_getglobal(L, "print");
lua_settable(L, -3);

Or you could do it via dostring :或者你可以通过dostring做到这一点:

lua_dostring(L, "return { print2 = print }");

Or you could try yet another approach, similar to one from the question:或者您可以尝试另一种方法,类似于问题中的一种:

lua_dostring(L, "sandbox_env = { print2 = print }"); // Without _ENV = sandbox_env
lua_getglobal(L, "sandbox_env");

General reference on environments: 2.2 Environments and the Global Environment .关于环境的一般参考: 2.2 环境和全球环境

Seelua_setupvalue documentation for usage details.有关使用详情,请参阅lua_setupvalue文档。

See another example in load_aux .请参阅load_aux中的另一个示例。 Note that this is part of the source code for load function available in Lua.请注意,这是用于load Lua 中的 function 的源代码的一部分。 This function allows to set environment of loaded chunk through one of its arguments (see load ).此 function 允许通过其 arguments 之一设置加载块的环境(请参阅load )。

Locally本地

You could either modify upvalue _ENV or override it with a local directly in the Lua script.您可以修改 upvalue _ENV或直接在 Lua 脚本中用本地覆盖它。 Let's do the latter:让我们做后者:

local _ENV = { print2 = print }
(function ()
   print2("Hello there")
end)()

This means you could wrap loaded script and run it like this:这意味着您可以包装加载的脚本并像这样运行它:

std::string loaded_script /* = ... */;
std::string wrapped_script = "local _ENV = { print2 = print }; (" + loaded_script + ")()";
luaL_loadstring(L, wrapped_script.c_str());
lua_call(L, 0, 0);

This will work with return function ()... end wrap, too (instead of (...)() ).这也适用于return function ()... end wrap(而不是(...)() )。 That's because local _ENV will be passed as upvalue to returned anonymous function.这是因为local _ENV将作为上值传递给返回的匿名 function。

Refer to Environments Tutorial for more verbose explanation on environments manipulation inside Lua.有关 Lua 内部环境操作的更详细说明,请参阅环境教程

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM