mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Scribunto
synced 2024-12-25 06:13:08 +00:00
fc3104e9c6
`mw.clone` is called on the global table for each new `{{#invoke:}}`, which is a major contributor to loading times on large pages. This change introduces various speed optimisations that make it around 15% faster: * `recursiveClone` is declared outside of the main function, to avoid generating a new closure each time `mw.clone` is called. * This means `tableRefs` has to be passed as an argument to `recursiveClone` instead of being an upvalue, which also decreases access time. * `recursiveClone` is only called after a `type` then `tableRefs` check confirms a value is an unseen table, meaning it's only called when needed, not for every key/value pair (which add noticeable overhead when cloning large tables). * `getmetable` is only called once, not twice, by using a local variable. * `tableRefs[elt]` is only accessed once, by using a ternary operator. * Any global accesses have been replaced by local ones. Bug: T357199 Change-Id: I51593a971f0be728255723ca300d1ce692ead256
149 lines
4.2 KiB
Lua
149 lines
4.2 KiB
Lua
-- This file is for anything that needs to be set up before a Lua engine can
|
|
-- start. Things in this file may run more than once, so avoid putting anything
|
|
-- other than function definitions in it. Also, because this can run before
|
|
-- PHP can do anything, mw_interface is unavailable here.
|
|
|
|
mw = mw or {}
|
|
|
|
-- Extend pairs and ipairs to recognize __pairs and __ipairs, if they don't already
|
|
do
|
|
local t = {}
|
|
setmetatable( t, { __pairs = function() return 1, 2, 3 end } )
|
|
local f = pairs( t )
|
|
if f ~= 1 then
|
|
local old_pairs = pairs
|
|
pairs = function ( t )
|
|
local mt = getmetatable( t )
|
|
local f, s, var = ( mt and mt.__pairs or old_pairs )( t )
|
|
return f, s, var
|
|
end
|
|
local old_ipairs = ipairs
|
|
ipairs = function ( t )
|
|
local mt = getmetatable( t )
|
|
local f, s, var = ( mt and mt.__ipairs or old_ipairs )( t )
|
|
return f, s, var
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Reduce precision on os.clock to mitigate timing attacks
|
|
do
|
|
local old_clock = os.clock
|
|
os.clock = function ()
|
|
local v = old_clock()
|
|
return math.floor( v * 50000 + 0.5 ) / 50000
|
|
end
|
|
end
|
|
|
|
--- Do a "deep copy" of a table or other value.
|
|
do
|
|
-- Declare global variables as locals to reduce access times
|
|
local getmetatable = getmetatable
|
|
local pairs = pairs
|
|
local setmetatable = setmetatable
|
|
local type = type
|
|
local function recursiveClone( val, tableRefs )
|
|
local retVal = {}
|
|
-- Encode circular references correctly
|
|
tableRefs[val] = retVal
|
|
|
|
local mt = getmetatable( val )
|
|
if mt then
|
|
setmetatable( retVal, tableRefs[mt] or recursiveClone( mt, tableRefs ) )
|
|
end
|
|
|
|
for key, elt in pairs( val ) do
|
|
if type( elt ) == 'table' then
|
|
retVal[key] = tableRefs[elt] or recursiveClone( elt, tableRefs )
|
|
else
|
|
retVal[key] = elt
|
|
end
|
|
end
|
|
return retVal
|
|
end
|
|
|
|
function mw.clone( val )
|
|
if type( val ) ~= 'table' then
|
|
return val
|
|
end
|
|
return recursiveClone( val, {} )
|
|
end
|
|
end
|
|
|
|
--- Make isolation-safe setfenv and getfenv functions
|
|
--
|
|
-- @param protectedEnvironments A table where the keys are protected environment
|
|
-- tables. These environments cannot be accessed with getfenv(), and
|
|
-- functions with these environments cannot be modified or accessed using
|
|
-- integer indexes to setfenv(). However, functions with these environments
|
|
-- can have their environment set with setfenv() with a function value
|
|
-- argument.
|
|
--
|
|
-- @param protectedFunctions A table where the keys are protected functions,
|
|
-- which cannot have their environments set by setfenv() with a function
|
|
-- value argument.
|
|
--
|
|
-- @return setfenv
|
|
-- @return getfenv
|
|
function mw.makeProtectedEnvFuncs( protectedEnvironments, protectedFunctions )
|
|
local old_setfenv = setfenv
|
|
local old_getfenv = getfenv
|
|
|
|
local function my_setfenv( func, newEnv )
|
|
if type( func ) == 'number' then
|
|
local stackIndex = math.floor( func )
|
|
if stackIndex <= 0 then
|
|
error( "'setfenv' cannot set the global environment, it is protected", 2 )
|
|
end
|
|
if stackIndex > 10 then
|
|
error( "'setfenv' cannot set an environment at a level greater than 10", 2 )
|
|
end
|
|
|
|
-- Add one because we are still in Lua and 1 is right here
|
|
stackIndex = stackIndex + 1
|
|
|
|
local env = old_getfenv( stackIndex )
|
|
if env == nil or protectedEnvironments[ env ] then
|
|
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
|
|
end
|
|
func = old_setfenv( stackIndex, newEnv )
|
|
elseif type( func ) == 'function' then
|
|
if protectedFunctions[func] then
|
|
error( "'setfenv' cannot be called on a protected function", 2 )
|
|
end
|
|
local env = old_getfenv( func )
|
|
if env == nil or protectedEnvironments[ env ] then
|
|
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
|
|
end
|
|
old_setfenv( func, newEnv )
|
|
else
|
|
error( "'setfenv' can only be called with a function or integer as the first argument", 2 )
|
|
end
|
|
return func
|
|
end
|
|
|
|
local function my_getfenv( func )
|
|
local env
|
|
if type( func ) == 'number' then
|
|
if func <= 0 then
|
|
error( "'getfenv' cannot get the global environment" )
|
|
end
|
|
env = old_getfenv( func + 1 )
|
|
elseif type( func ) == 'function' then
|
|
env = old_getfenv( func )
|
|
else
|
|
error( "'getfenv' cannot get the global environment" )
|
|
end
|
|
|
|
if protectedEnvironments[env] then
|
|
return nil
|
|
else
|
|
return env
|
|
end
|
|
end
|
|
|
|
return my_setfenv, my_getfenv
|
|
end
|
|
|
|
return mw
|