Add strict.lua to replace "Module:No globals"

For the most part, it is a good idea to avoid global variables and use
`local` variables instead. Quoting from the ScopeTutorial[1], "The
general rule is to always use local variables, unless it's necessary for
every part of your program to be able to access the variable (which is
very rare)."

Wikimedia module authors have written "Module:No globals", which errors
on the use of any global variable. On the English Wikipedia, this is
used on 32% of pages (18 million). Wikidata[2] indicates that it's been
copied to 334 other wikis.

Lua itself distributes an extra named "strict.lua"[3], which is what
this is based off of. Similar to bit32.lua, this is a pure-Lua library
that can be imported/enabled with `require( "strict" )` at the top of a
module.

The two changes I made from Lua's strict is to exempt the `arg` key,
which is used internally by Scribunto, and remove `what()`, since we
don't enable access to `debug.getinfo()` for security reasons.

[1] https://lua-users.org/wiki/ScopeTutorial
[2] https://www.wikidata.org/wiki/Q16748603
[3] http://www.lua.org/extras/5.1/strict.lua

Bug: T209310
Change-Id: I46ee6f630ac6b26c68c31becd1f3b9d961bcab29
This commit is contained in:
Kunal Mehta 2022-09-23 21:23:03 -07:00 committed by jenkins-bot
parent 58a9707c8f
commit 829c53ef05
3 changed files with 61 additions and 0 deletions

View file

@ -5,6 +5,7 @@ Some included files and subdirectories are licensed with an MIT license, not
with the GPL. These are: with the GPL. These are:
* includes/Engines/LuaCommon/lualib/package.lua (as described in that file) * includes/Engines/LuaCommon/lualib/package.lua (as described in that file)
* includes/Engines/LuaCommon/lualib/strict.lua (as described in that file)
* includes/Engines/LuaCommon/lualib/luabit/ (as described in readme.txt in that directory) * includes/Engines/LuaCommon/lualib/luabit/ (as described in readme.txt in that directory)
* includes/Engines/LuaCommon/lualib/ustring/ (as described in README in that directory) * includes/Engines/LuaCommon/lualib/ustring/ (as described in README in that directory)
* includes/Engines/LuaStandalone/binaries/ (as described in README in that directory) * includes/Engines/LuaStandalone/binaries/ (as described in README in that directory)

View file

@ -0,0 +1,31 @@
-- strict.lua (fork of http://www.lua.org/extras/5.1/strict.lua)
-- checks uses of undeclared global variables
-- All global variables must be 'declared' through a regular assignment
-- (even assigning nil will do) in a main chunk before being used
-- anywhere or assigned to inside a function.
-- distributed under the Lua license: http://www.lua.org/license.html
-- Scribunto modifications:
-- * exempt arg, used by require()
-- * remove what(), since debug.getinfo isn't allowed
local error, rawset, rawget = error, rawset, rawget
local mt = getmetatable(_G)
if mt == nil then
mt = {}
setmetatable(_G, mt)
end
mt.__newindex = function (t, n, v)
if n ~= "arg" then
error("assign to undeclared variable '"..n.."'", 2)
end
rawset(t, n, v)
end
mt.__index = function (t, n)
if n ~= "arg" then
error("variable '"..n.."' is not declared", 2)
end
return rawget(t, n)
end

View file

@ -166,6 +166,22 @@ function test.noLeaksViaPackageLoaded()
return 'ok' return 'ok'
end end
function test.strictGood()
require( 'strict' )
local foo = "bar"
return foo
end
function test.strictBad1()
require( 'strict' )
return bar
end
function test.strictBad2()
require( 'strict' )
bar = "foo"
end
test.loadData = {} test.loadData = {}
function test.loadData.get( ... ) function test.loadData.get( ... )
@ -300,6 +316,19 @@ return testframework.getTestProvider( {
expect = { 'ok' }, expect = { 'ok' },
}, },
{ name = 'strict on good code raises no errors',
func = test.strictGood,
expect = { 'bar' },
},
{ name = 'strict on code reading from a global errors',
func = test.strictBad1,
expect = "variable 'bar' is not declared",
},
{ name = 'strict on code setting from a global errors',
func = test.strictBad2,
expect = "assign to undeclared variable 'bar'",
},
{ name = 'mw.loadData, returning non-table', { name = 'mw.loadData, returning non-table',
func = mw.loadData, args = { 'Module:CommonTests-data-fail1' }, func = mw.loadData, args = { 'Module:CommonTests-data-fail1' },
expect = "Module:CommonTests-data-fail1 returned string, table expected", expect = "Module:CommonTests-data-fail1 returned string, table expected",