lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Let's look at the common uses of setfenv and getfenv.

* Sandboxing untrusted code,

* Running a single piece of code in multiple environments. Thank you to Leo for pointing out the PiL example (which doesn't use setfenv but sort of cries out for doing so). That could, however, also be handle via loadin with a custom environment table that can then be updated with new values to feed in. There are at least two problems with the using setfenv to run a function in multiple alternative environments:

	* First, it isn't reentrant.

	* Second, it won't have an effect if the function with the modified environment calls other functions previously defined in the old environment.

The first problem would be fixed by my proposed move of the active environment into CallInfo. The second could only be fixed by introducing dynamic scoping.

* Changing the environment of a function further up the stack. module does this but it's pretty grotesque and again it is not reentrant or otherwise friendly to coroutines. Those issues would be addressed by my CallInfo proposal but note that the issue above environments on other functions not being affected is a problem either way.

* Inspecting the environment somewhere up the stack and/or modifying that table. This is another case of grotesque but some times useful.

Ignoring the ways to bring back the old functions or the use of their more powerful versions in the debug library, we lose most of this functionality.

loadin clarifies the process of sandboxing loaded code but it doesn't enable anything new. Still, it's nice to clarify the logic and not simply do a load followed by a setfenv.

in-do-end provides a way to execute a chunk of code in a custom environment and it's better about clean up than setfenv, but like the setfenv cases above it doesn't do anything about functions called from within the chunk. Many of the uses people then seem tempted to make of it then fail to work. For example:

	in env do f() end	-- not interesting unless env.f is what you were after

	in self do method() end -- method isn't going to be happy because it wasn't invoked as self:method()

	in env do print( x ) end -- assuming env.x exists but env.print doesn't this obviously doesn't work

Now, we can argue that setfenv didn't work correctly here either, but it also didn't tempt one to write such code only to then be disappointed.

It remains awkward to use for construction:

	local class = makeClass()

	in class do
		-- construct class
	end

Rather than:

	in class = makeClass() do
		-- construct class
	end

As the above shows, that's probably fixable though getting the semantics just right will probably take work.

For modules, to get around the package.seeall issues we need to either define the functions that care about the globals outside the module scope or have module return something other than the module itself so that we can write:

	in module( ... ) do
		-- construct module
	end

Oh. And we then don't have the module available for us to return from require should we want it. So, we write:

	local M, env = module( ... )

	in env do
		-- construct module
	end

	return M

But at that point, why not just make explicit reference to M everywhere we want to populate it?

Finally, we get the constructs based on textually concatenating in-do-end around a chunk of code. Like the stack twiddling use of setfenv, these feel grotesque to me though in a different way. Though one can do it, it just doesn't seem Lua-like to assemble code out of strings as a matter of standard practice. (I've done it in building binding libraries but that was specifically because I was interested in generating code.)

So, I can sympathize with the decision to deprecate setfenv and getfenv (though I've got some trepidation about shifting to the debug module). I view loadin as a decided improvement. But I'm not convinced yet that in-do-end really makes things enough better to be worth the syntax.

Mark