mediawiki-extensions-Scribunto/includes/Engines/LuaCommon/lualib/mwInit.lua
theknightwho fc3104e9c6 Optimisations for mw.clone
`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
2024-05-24 02:08:41 +00:00

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