First off, this isn't for beginners. It uses a few more advanced concepts to implement what it does. Use carefully, and ask if you're unsure of how to use it. When in doubt, use the premade function at the end.
So, many times now I've run into functions I've tested for others with... problems. Specifically, many functions have been made primarily by beginning coders which create a large number of variables outside of a namespace, sometimes overwriting important functions or otherwise mussing up the global environment. For the odd times when rewriting these isn't reasonable, feasible, or possible for whatever reason, I wrote up a little patch script.
First off, you'll need the deepcopy function from
http://lua-users.org/wiki/CopyTable. I've included it below, with the adjustment to deepcopy metatables as well:
function deepcopy(object)
local lookup_table = {}
local function _copy(object)
if type(object) ~= "table" then
return object
elseif lookup_table[object] then
return lookup_table[object]
end
local new_table = {}
lookup_table[object] = new_table
for index, value in pairs(object) do
new_table[_copy(index)] = _copy(value)
end
return setmetatable(new_table, _copy(getmetatable(object)))
end
return _copy(object)
end
From there, let's say we have a function named exfunc. More often than not when I find need of this, it's instead a larger script which I've wrapped in the function. Either is fine for this example.
-- This table is your new working environment for this function
-- This can be a local if you don't want persistent changes
-- Change the name as you please on a per-function basis, here and in the setmetatable and setfenv calls
exenv = {}
-- This table is the new metatable for the environment
-- Don't adjust this without knowing what you're doing
local envmeta = {
-- Ensure transparent access to all globals
__index = _G,
-- Ensure new writes don't affect global environment's tables
__newindex = function(t, k, v)
rawset(t, k, deepcopy(v))
end
}
-- Set the metatable for the environment to allow the magic to work
setmetatable(exenv, envmeta)
-- Set the default environment of the function to our new one
setfenv(exfunc, exenv)
The end result is that your original function now runs cleanly, still writes all it wants, and doesn't mess up anything for other scripts. All variables, functions, etc created will actually be created in the exenv table, the function will have no basic ability to change globals, it will retain the ability to access all global functions, variables, tables, etc without issue, and all changes made can be reversed simply by using this:
setfenv(exfunc, getfenv(0))
The setfenv function returns the function on execution, allowing setfenv(exfunc, exenv)() to be used if immediate execution is desired. This isn't required, the environment change is applied to the function persistently.
Worth note: This isn't completely foolproof technically. Using rawset can bypass the deepcopy protection, changing the local environment will quite obviously mess this up, and changes to the environment's metatable will interfere with it as well. On the other hand, if the script is doing any of these things, chances are the author should know better than to write in such a way where this is necessary.
It's possible to wrap this all into a simple function to implement quickly as well, like so:
function isoenv(f, t)
local function deepcopy(object)
local lookup_table = {}
local function _copy(object)
if type(object) ~= "table" then
return object
elseif lookup_table[object] then
return lookup_table[object]
end
local new_table = {}
lookup_table[object] = new_table
for index, value in pairs(object) do
new_table[_copy(index)] = _copy(value)
end
return setmetatable(new_table, _copy(getmetatable(object)))
end
return _copy(object)
end
local newenv
if type(t) == "table" then
newenv = t
elseif type(t) == "string" then
local newenv = _G[t] or {}
_G[t] = newenv
else
error("Function Isolation failure:\nTables supplied must be either a table or a string.", 0)
end
local isofunc
if type(f) == "function" then isofunc = f
elseif type(f) == "string" then isofunc = loadstring("return "..f)()
else error("Function Isolation failure:\nFunctions supplied must be either a function or a string.", 0)
end
local envmeta = {
__index = _G,
__newindex = function(t, k, v)
rawset(t, k, deepcopy(v))
end
}
setmetatable(newenv, envmeta)
setfenv(isofunc, newenv)
end
Using the above, the same earlier code could be accomplished like so:
isoenv("exfunc", "extable")