From 829c53ef059e4ea45e62927811b556eb495a426b Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Fri, 23 Sep 2022 21:23:03 -0700 Subject: [PATCH] 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 --- COPYING | 1 + includes/Engines/LuaCommon/lualib/strict.lua | 31 +++++++++++++++++++ .../phpunit/Engines/LuaCommon/CommonTests.lua | 29 +++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 includes/Engines/LuaCommon/lualib/strict.lua diff --git a/COPYING b/COPYING index 666cc918..01d39919 100644 --- a/COPYING +++ b/COPYING @@ -5,6 +5,7 @@ Some included files and subdirectories are licensed with an MIT license, not with the GPL. These are: * 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/ustring/ (as described in README in that directory) * includes/Engines/LuaStandalone/binaries/ (as described in README in that directory) diff --git a/includes/Engines/LuaCommon/lualib/strict.lua b/includes/Engines/LuaCommon/lualib/strict.lua new file mode 100644 index 00000000..5655798a --- /dev/null +++ b/includes/Engines/LuaCommon/lualib/strict.lua @@ -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 diff --git a/tests/phpunit/Engines/LuaCommon/CommonTests.lua b/tests/phpunit/Engines/LuaCommon/CommonTests.lua index 6c34dd46..77c430b9 100644 --- a/tests/phpunit/Engines/LuaCommon/CommonTests.lua +++ b/tests/phpunit/Engines/LuaCommon/CommonTests.lua @@ -166,6 +166,22 @@ function test.noLeaksViaPackageLoaded() return 'ok' 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 = {} function test.loadData.get( ... ) @@ -300,6 +316,19 @@ return testframework.getTestProvider( { 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', func = mw.loadData, args = { 'Module:CommonTests-data-fail1' }, expect = "Module:CommonTests-data-fail1 returned string, table expected",