Recreating setfenv() in Lua 5.2 - lua

How can I recreate the functionality of setfenv in Lua 5.2? I'm having some trouble understanding exactly how you are supposed to use the new _ENV environment variable.
In Lua 5.1 you can use setfenv to sandbox any function quite easily.
--# Lua 5.1
print('_G', _G) -- address of _G
local foo = function()
print('env', _G) -- address of sandbox _G
bar = 1
end
-- create a simple sandbox
local env = { print = print }
env._G = env
-- set the environment and call the function
setfenv(foo, env)
foo()
-- we should have global in our environment table but not in _G
print(bar, env.bar)
Running this example shows an output:
_G table: 0x62d6b0
env table: 0x635d00
nil 1
I would like to recreate this simple example in Lua 5.2. Below is my attempt, but it does not work like the above example.
--# Lua 5.2
local function setfenv(f, env)
local _ENV = env or {} -- create the _ENV upvalue
return function(...)
print('upvalue', _ENV) -- address of _ENV upvalue
return f(...)
end
end
local foo = function()
print('_ENV', _ENV) -- address of function _ENV
bar = 1
end
-- create a simple sandbox
local env = { print = print }
env._G = env
-- set the environment and call the function
foo_env = setfenv(foo, env)
foo_env()
-- we should have global in our envoirnment table but not in _G
print(bar, env.bar)
Running this example shows the output:
upvalue table: 0x637e90
_ENV table: 0x6305f0
1 nil
I am aware of several other questions on this subject, but they mostly seem to be dealing with loading dynamic code (files or string) which work quite well using the new load function provided in Lua 5.2. Here I am specifically asking for a solution to run arbitrary functions in a sandbox. I would like to do this without using the debug library. According to the Lua documentation we should not have to rely on it.

You cannot change the environment of a function without using the debug library from Lua in Lua 5.2. Once a function has been created, that is the environment it has. The only way to modify this environment is by modifying its first upvalue, which requires the debug library.
The general idea with environments in Lua 5.2 is that the environment should be considered immutable outside of trickery (ie: the debug library). You create a function in an environment; once created there, that's the environment it has. Forever.
This is how environments were often used in Lua 5.1, but it was easy and sanctioned to modify the environment of anything with a casual function call. And if your Lua interpreter removed setfenv (to prevent users from breaking the sandbox), then the user code can't set the environment for their own functions internally. So the outside world gets a sandbox, but the inside world can't have a sandbox within the sandbox.
The Lua 5.2 mechanism makes it harder to modify the environment post function-creation, but it does allow you to set the environment during creation. Which lets you sandbox inside the sandbox.
So what you really want is to just rearrange your code like this:
local foo;
do
local _ENV = { print = print }
function foo()
print('env', _ENV)
bar = 1
end
end
foo is now sandboxed. And now, it's much harder for someone to break the sandbox.
As you can imagine, this has caused some contention among Lua developers.

It's a bit expensive, but if it's that important to you...
Why not use string.dump, and re-load the function into the right environment?
function setfenv(f, env)
return load(string.dump(f), nil, nil, env)
end
function foo()
herp(derp)
end
setfenv(foo, {herp = print, derp = "Hello, world!"})()

To recreate setfenv/getfenv in Lua 5.2 you can do the following:
if not setfenv then -- Lua 5.2
-- based on http://lua-users.org/lists/lua-l/2010-06/msg00314.html
-- this assumes f is a function
local function findenv(f)
local level = 1
repeat
local name, value = debug.getupvalue(f, level)
if name == '_ENV' then return level, value end
level = level + 1
until name == nil
return nil end
getfenv = function (f) return(select(2, findenv(f)) or _G) end
setfenv = function (f, t)
local level = findenv(f)
if level then debug.setupvalue(f, level, t) end
return f end
end
RPFeltz's answer (load(string.dump(f)...)) is a clever one and may work for you, but it doesn't deal with functions that have upvalues (other than _ENV).
There is also compat-env module that implements Lua 5.1 functions in Lua 5.2 and vice versa.

In Lua5.2 a sandboxeable function needs to specify that itself. One simple pattern you can use is have it receive _ENV as an argument
function(_ENV)
...
end
Or wrap it inside something that defines the env
local mk_func(_ENV)
return function()
...
end
end
local f = mk_func({print = print})
However, this explicit use of _ENV is less useful for sandboxing, since you can't always assume the other function will cooperate by having an _ENV variable. In that case, it depends on what you do. If you just want to load code from some other file then functions such as load and loadfile usually receive an optional environment parameter that you can use for sandboxing. Additionally, if the code you are trying to load is in string format you can use string manipulation to add _ENV variables yourself (say, by wrapping a function with an env parameter around it)
local code = 'return function(_ENV) return ' .. their_code .. 'end'
Finally, if you really need dynamic function environment manipulation, you can use the debug library to change the function's internal upvalue for _ENV. While using the debug library is not usually encouraged, I think it is acceptable if all the other alternatives didn't apply (I feel that in this case changing the function's environment is deep voodoo magic already so using the debug library is not much worse)

Related

Making local the _G variables & execute location

First doubt
For example, next can be stated as local very easily
local next = next
But how would it be with table.insert for example?
Making an anonymous function with table.insert inside on a local var would actually work?
Second doubt
Is it possible to know from where a function is being executed? This take us back to my first doubt, how can i ensure a _G variable is being executed locally
Really asking because besides default _G variables, i have few more added on my project
But how would it be with table.insert for example?
local table = table
how can i ensure a _G variable is being executed locally
It is still the same function value. You just added a local reference to it. Local variables can be looked up faster. You would have to call a function very often to really benefit from doing that.
This is opinionated but that opinion is shared among most programmers:
Don't waste time on premature optimization. Don't create local references for every global you encounter.
With load() you can give Lua code an own environment.
Normally (without that own environment) _G (5.1) or _ENV (since 5.3) is used.
Example you can play with...
> _VERSION
Lua 5.4
> load('do local tab, concat, insert = {}, concat, insert insert(tab, "Hello World!") return concat(tab) end', 'own_env', 't', {concat = table.concat, insert = table.insert})()
Hello World!
> load('do local tab, concat, insert = {}, concat, insert insert(tab, "Hello World!") return concat(tab) end', 'own_env', 't')()
[string "own_env"]:1: local 'insert' is not callable (a nil value)
stack traceback:
[string "own_env"]:1: in main chunk
(...tail calls...)
[C]: in ?
Source: https://www.lua.org/manual/5.4/manual.html#pdf-load

Lua strange behaviour with _ENV

Can anybody explain why lua 5.3.5 acts this way?
a="global"
local b="local"
function problem(_ENV)
a="fn_a"
b="fn_b"
end
problem{}
print(_VERSION)
print("a",a)
print("b",b)
--[[ https://www.lua.org/cgi-bin/demo output:
Lua 5.3
a global
b fn_b
]]
Why local variable can be changed after _ENV changed? Is it bug or feature?
Q: How to eliminate all upvalues?
A:
a="global"
local b="local"
-- load() actually eliminates all upvalues :-)
no_problem = assert(load[[
return function (_ENV)
a="fn_a"
b="fn_b"
end
]])()
no_problem{}
print(_VERSION)
print("a",a) --> a global
print("b",b) --> b local
You are creating upvalue with the following code:
local b="local"
function problem(_ENV)
b="fn_b"
end
During parsing of the function interpreter detects, that function refers to local variable from one of encompassing scopes that are visible from within the function and thus links it directly. Access to local variables precedes lookup of globals in the _ENV. If you put local b="local" after the function definition, it will not be altered by function calls.

How do I unit test Lua modules without OO?

In Lua I can write a simple module like so
local database = require 'database'
local M = {}
function M:GetData()
return database:GetData()
end
return M
Which when required, will load once, and all future versions will load the same copy.
If I wanted to take an object-oriented approach I could do something like:
local M = {}
M.__index = M
function M:GetData()
return self.database:GetData()
end
return function(database)
local newM = setmetatable({}, M)
newM.database = database
return newM
end
Where M is only loaded once, and each copy of newM just holds its own data and uses the methods of the original M.
When it comes to testing, with the OO approach I can just pass in a fake version of 'database' and check it gets called, but with the first approach I can't.
So my question is how can I make the first approach support DI/testing without making it class-like?
My thought was to wrap it in a closure something like this:
local mClosure = function(database)
local M = {}
function M:GetData()
return database:GetData()
end
return M
end
return mClosure
but then every time it is called it will create a new copy of M, so it will lose the benefits of both of the previous approaches.
That's clearly a use case for the Lua debug library. With that you can just modify the upvalues of your function and inject dependancies. Also consider that you can use require for this; just require your database module once, create small table that collects data and then redirects to the original module and put it in package.loaded so the next time you require it, the require call returns the modified version of the module. The OO approach is how you would do this kind of thing in a language like Ruby, but in Lua we have way nicer ways of tapping into a module or function without it being specifically designed for that purpose.
local real_db = require 'db'
local fake_db = setmetatable({}, {__index=db})
function fake_db.exec(query) print('running query: '..query) end -- dummy function
function fake_db.something(...) print('doing something'); real_db.something(...) end
package.loaded.db = fake_db
require 'my_tests' -- this in turn requires 'db', but gets the fake one
package.loaded.db = real_db
-- After this point, `require 'db'` will return the original module

LuaJIT setfenv not appearing to set further function calls to the given environment

I'm attempting to sandbox some functions using setfenv, and I recieve the following output:
123
nil
Why is testValue when calling sandboxTest() nil, but it's 123 when it's accessed in callSandboxedTest()?
Using LuaJIT 2.1.0-beta2 (Lua 5.1)
function sandboxTest()
print(testValue)
end
local aNumber = 123
function callSandboxedTest()
setfenv(1, {
print = print,
testValue = aNumber,
sandboxTest = sandboxTest
})
print(testValue)
sandboxTest()
end
callSandboxedTest()
Environments aren't part of the call stack. Every function has its own environment. So sandboxTest has an environment, as does callSandboxTest. Changing the environment of one function has no effect on the environment of another.
sandboxTest will continue to use the default environment, so it will access the regular global table to find testValue. And since testValue is never set in the global table, sandboxTest will get nil.
That's why, when maintaining a sandbox, it is very important to carefully choose what functionality to expose to the sandbox. If a function needs to be part of the sandbox, then that function needs to have its environment set.
That's why it's best to sandbox based on compiled Lua chunks rather than individual functions. When creating functions, the functions created will inherit the current environment.
You haven't modified the environment that sandboxTest is using; you only modified the environment of the current function. You can use setfenv to set the environment of a particular function by passing a function name (passing a number modifies the environment of a function in the call stack):
setfenv(sandboxTest, {
print = print,
testValue = aNumber,
sandboxTest = sandboxTest
})
This will print 123 123.

Declaring global variable inside a function in LUA

I have a function inside which I declared a global variable obs, inside a function and assigned some value.If I want to access this in some other lua file, it gives an error: "attempt to call obs a nil value, what do I need to do to able able to access it?
Here is the dummy code for it
//A.lua
function X()
obs = splitText(lk,MAXLEN)
end
//B.lua
function Y()
for i=1, #obs do //error at this line
end
end
There are a few ways to do this. With your current setup, you could do this:
a.lua
function x()
-- _G is the global table. this creates variable 'obs' attached to
-- the global table with the value 'some text value'
_G.obs = "some text value"
end
b.lua
require "a"
function y()
print(_G.obs); -- prints 'some text value' when invoked
end
x(); y();
Stuffing things in the global table is usually a terrible idea though, as any script anywhere else could potentially overwrite the value, nil out the variable, etc. a much better way imo would be to have your a.lua return its functionality in a table which you can capture in files which require it. this will allow you to define a getter function to return the 'obs' variable attached directly to your 'a.lua' functionality in its current state.
you probably want to do something like this for better portability (it is also much clearer which modules define which functionality this way):
a.lua
local obs_
function x()
obs_ = "some text value"
end
function get_obs()
return obs_
end
return { x = x, obs = get_obs }
b.lua
local a = require "a"
function y()
print(a.obs())
end
a.x()
y()
since you mentioned you can't use require, i'll assume you're working in some other framework that uses some other function to load libraries/files. in that case, you will probably have to just stuff everything in the global table. maybe something like this:
a.lua
-- this will attach functions a_x and a_get_obs() to the global table
local obs_
function _G.a_x()
obs_ = "some text value"
end
function _G.a_get_obs()
return obs_
end
b.lua
-- ignore this require, i'm assuming your framework has some other way loading
-- a.lua that i can't replicate
require "a"
function y()
print(_G.a_get_obs())
end
_G.a_x()
y()
Remember that some Lua programs inside other programs (Garry's Mod, World of Warcraft, Vera, Domoticz) uses _ENV instead of _G to limit their scope. So global variables has to be:
_ENV.variable = 1
instead of:
_G.variable = 1
The reason why this happens is because the developer wants to limit the standard Lua library to avoid that the user access methods like: os.exit().
To see if _ENV is used instead of _G, print it out and if it returns a table instead of nil, it's most likely used. You can also test with the following snippet:
print(getfenv(1) == _G)
print(getfenv(1) == _ENV)
Where the one to print true is the one you are using.

Resources