Lua strange behaviour with _ENV - lua

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.

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

How does debug.traceback get its information?

With the below code from the Lua demo page, I was trying to get the name of the function being pcalled.
function test()
local info = debug.getinfo(1);
for k, v in pairs(info) do
print(k, v);
end;
end;
pcall(function()
test();
end);
This was a success, as I received the following output, containing the name:
source =input
func function: 0x25a1830
nparams 0
short_src input
isvararg false
name test
namewhat global
istailcall false
linedefined 1
lastlinedefined 7
nups 1
currentline 2
what Lua
If I change the code to the following, I no longer get that information:
function test()
local info = debug.getinfo(1);
for k, v in pairs(info) do
print(k, v);
end;
end;
pcall(test);
The output is as follows:
func function: 0x21ee790
linedefined 1
nups 1
short_src input
namewhat
lastlinedefined 7
isvararg false
istailcall false
what Lua
source =input
currentline 2
nparams 0
If, however, I change the code to the following, I can obtain the name of the function passed to pcall:
function test()
local traceback = debug.traceback();
print(traceback);
end;
pcall(test);
With the output being as follows:
stack traceback:
input:2: in function 'test'
[C]: in function 'pcall'
input:7: in main chunk
[C]: in function 'pcall'
demo.lua:49: in main chunk
[C]: in ?
How does debug.traceback get this extra information, and using solely Lua is there a way to get it without extracting it from debug.traceback's return value?
debug.getinfo and debug.traceback get their info from various sources, some of them hacky. For instance, the function name is generally extracted from the source code of the calling function: whatever name the code used to look up the thing it called, that's what gets used as the name. (That's why your second code snippet didn't give you the name: pcall doesn't have Lua bytecode backing it up, so it can't tell you "test" unless there's a function in the middle to call it "test".) Lua functions do not have inherent names, any more than Lua integers do; a function is just another kind of value, which can be automatically assigned to a variable via some syntactic sugar.
Functions are values. Values don't have names.
Functions are not "declared" as in some other languages. A function value is created when a function definition is evaluated.
Debugging output is just trying to be helpful in simple cases by giving the name of a variable associated with calling the function value.

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.

Recreating setfenv() in Lua 5.2

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)

What is the meaning of 'attempt to index upvalue'

I am taking my first steps programming in Lua and get this error when I run my script:
attempt to index upvalue 'base' (a function value)
It's probably due to something very basic that I haven't grasped yet, but I can't find any good information about it when googling. Could someone explain to me what it means?
In this case it looks base is a function, but you're trying to index it like a table (eg. base[5] or base.somefield).
The 'upvalue' part is just telling you what kind of variable base is, in this case an upvalue (aka external local variable).
One "local" too many?
As Mike F explained, an "upvalue" is an external local variable. This error often occurs when a variable has been declared local in a forward declaration and then declared local again when it is initialized. This leaves the forward declared variable with a value of nil. This code snippet demonstrates what not to do:
local foo -- a forward declaration
local function useFoo()
print( foo.bar ) -- foo is an upvalue and this will produce the error in question
-- not only is foo.bar == nil at this point, but so is foo
end
local function f()
-- one LOCAL too many coming up...
local foo = {} -- this is a **new** foo with function scope
foo.bar = "Hi!"
-- the local foo has been initialized to a table
-- the upvalue (external local variable) foo declared above is not
-- initialized
useFoo()
end
f()
In this case, removing the local in front of foo when it is initialized in f() fixes the example, i.e.
foo = {}
foo.bar = "Hi!"
Now the call to useFoo() will produce the desired output
Hi!

Resources