Thread Tools Display Modes
08-13-17, 05:23 PM   #1
Thex
A Murloc Raider
AddOn Author - Click to view addons
Join Date: Aug 2017
Posts: 5
Making proxy table for _G

Hi,

I'm currently trying to develop a small tool to help with addons creation. The point would be that said tool would "detect" all functions and variables declared in the global scope, and print a warning saying like "don't use global scope" or something, just to help catching mistakes.

So i set up a proxy table for the _G table. I created a copy of it, then _G = {}, and gave it __newindex and __index metamethods to redirect all transit. At the end of the addon loading, i'm resetting _G to its original form, to avoid other addons using this.
Issue i'm having is that apparently, changing the value of _G directly (either by doing "_G = {}" or even "_G = _G") is not allowed, and makes you, oddly enough, unable to use your spellbar hotkeys (prompting a popup "[Your addon] has been blocked from an action only available to the Blizzard UI".

So i'm kind of sad to see that; and i was wondering if you guys had any idea how to bypass that issue (or how to make it more "secure" i guess, in the eyes of the API), please?

Edit: One way i could think of would be to create a local _G = GetFakeG() at the beginning of all of my scripts, where GetFakeG() would just give me an empty table with the indirection metatable, created in my cerberus.lua file; but that would mean having to get that local _G in every script, and that's something i would like to avoid. I'd like this tool to be "install, initialize and forget about it".


(Here is my code, just in case you're wondering about anything: )
Lua Code:
  1. local cerberusTextColour = "ffc42727";
  2. local cerberusPrefix = "|c" .. cerberusTextColour .. "Cerberus: |r"
  3. function Cerberus_Print(message)
  4.     print(cerberusPrefix .. message);
  5. end
  6.  
  7.  
  8. g_cerberus = g_cerberus or {};
  9. local currentAddonName = nil;
  10. local function GetCurrentAddonName()
  11.     return currentAddonName;
  12. end
  13. g_cerberus._G = function()
  14.     return g_cerberus[GetCurrentAddonName()];   -- Using getter function to avoid conflicts between mods
  15. end
  16.  
  17.  
  18. g_cerberus.RegisterAddon = function(addonName)
  19.  
  20.     if g_cerberus._G() ~= nil then
  21.         Cerberus_Print("Attempting to register addon which has already been registered (" .. addonName .. "). Try with a different addon name, or a more specific one.");
  22.         return;
  23.     end
  24.  
  25.     currentAddonName = addonName;
  26.     g_cerberus[addonName] = {};
  27.     g_cerberus[addonName]._GCopy = _G;
  28.     _G = {};
  29.  
  30.     setmetatable(_G,
  31.     {
  32.         __newindex = function(_, key, value)
  33.             Cerberus_Print("Attempting to store " .. (key or "nil") .. " in the global scope with value \"" .. tostring(value) .. "\". Make sure that's what you actually want.");
  34.             g_cerberus._G()._GCopy[key] = value;
  35.         end,
  36.  
  37.         __index = function(_, key)
  38.             return g_cerberus._G()._GCopy[key];
  39.         end
  40.     });
  41. end
  42.  
  43. ----- In another file to put at the end of the toc list
  44. if g_cerberus ~= nil and g_cerberus._G() ~= nil and g_cerberus._G()._GCopy ~= nil then
  45.     _G = g_cerberus._G()._GCopy;
  46. end

Last edited by Thex : 08-13-17 at 07:17 PM.
  Reply With Quote
08-13-17, 06:44 PM   #2
Resike
A Pyroguard Emberseer
AddOn Author - Click to view addons
Join Date: Mar 2010
Posts: 1,290
Not sure whats the point of doing this in game. There are better tools to do this on the files you working with.
  Reply With Quote
08-13-17, 06:56 PM   #3
Thex
A Murloc Raider
AddOn Author - Click to view addons
Join Date: Aug 2017
Posts: 5
Well it's mostly just cause it's fun to do, and it makes for some practice using metatables which i just recently learned about. But also i have been using the global scope too much on my past addons and would like to refactore them a bit, and such a tool could be useful (also i like having that cerberus table framework, which would allow me to use the same global container "name" for all my addons, so i'd like it to be like one tool ).

What other tools are you referring to?
  Reply With Quote
08-13-17, 08:07 PM   #4
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
If you using the environment control, you don't need to change the _G back at the end, and you won't write anything to the _G since your job is done in the private environment.

http://www.wowinterface.com/forums/s...5&postcount=11

With the environment control, we'll have more power with our codes, here is an example:

Lua Code:
  1. if val = ture then print("val is true") end

The "ture" is a spell error for 'true', and it'd be treated as nil, the code would run and we won't know the bug. With the environment control :

Lua Code:
  1. -- Modify the environment, using private table as environment
  2. setfenv(1, setmetatable( select(2, ...), { __index = function(self, key)
  3.     local val = _G[key]
  4.     if val ~= nil then rawset(self, key, val) else error(("Global variable %q can't be found"):format(key), 2) end
  5.     return val
  6. end } ))
  7.  
  8. local a = true
  9. if a == ture then print("Okay") end -- error Global variable "ture" can't be found

I also development plenty features based on it : http://www.wowinterface.com/forums/s...ad.php?t=55057
  Reply With Quote
08-14-17, 03:06 AM   #5
Resike
A Pyroguard Emberseer
AddOn Author - Click to view addons
Join Date: Mar 2010
Posts: 1,290
Originally Posted by Thex View Post
Well it's mostly just cause it's fun to do, and it makes for some practice using metatables which i just recently learned about. But also i have been using the global scope too much on my past addons and would like to refactore them a bit, and such a tool could be useful (also i like having that cerberus table framework, which would allow me to use the same global container "name" for all my addons, so i'd like it to be like one tool ).

What other tools are you referring to?
It depends on what text editor you using:
I maintain the stuff for Sublime Text 2-3:
https://github.com/Resike/WoWDevelopment

There is also a standalone exe which finds globals based on folders, or a luac build script (Made by Mik) which should work with every text editor that support build systems:
http://www.wowinterface.com/download...OTANADDON.html
  Reply With Quote
08-14-17, 07:11 PM   #6
Rainrider
A Firelord
AddOn Author - Click to view addons
Join Date: Nov 2008
Posts: 454
What you are interested in is called linting. A good linter for Lua is for example luacheck. For editor support check luacheck's related projects. One problem of that is that it does not support the WoW API, so calls to API functions will be treated as global access and you will get a warning for that. However not all global read/writes are bad and you can tell luacheck how to handle your specific case.

For your other use of sharing stuff between your addons, you would normally use a separate addon for this and list it as a required or optional dependency in the addons that are meant to use it. Basically you create a global table in it where all of your addons can add functionality or save state for use by other addons. It can also provide some basic API. This way you create a single global object and you don't litter _G.

If you need a messaging system (like the event system in WoW) to notify interested parties of occurred changes, take a look at Callback-Handler-1.0.

As for function environment control, please read the replies to the post that kurapica.igas linked in his/her reply. Both Phanx and SDPhantom explained what it is for and why such a proposal is an overreaction.
  Reply With Quote
08-19-17, 06:30 AM   #7
Thex
A Murloc Raider
AddOn Author - Click to view addons
Join Date: Aug 2017
Posts: 5
(Sorry for the delay, had a busy week )

@kurapica.igas
Well now, that's pretty much 100% exactly what i wanted to be able to do
At first i wanted to avoid stuff that would require the user to "register" every one of his files, but i've come to make peace with that idea, cause as you said, it removes the need to unregister the addon at the end of the loading, plus, it makes it able to catch global stuff declared within functions and other scopes, not only the ones declared on the global scope, which is great.
So i changed my tool a little to make it so instead of displaying a warning if you put stuff as global, it catches those global stuff and puts them into an addon-specific table, so you don't have to worry about naming your stuff in a very specific way, nor do you need to put everything manually in an object (which is annoying to do in my opinion).

I released this tool here, if any of you wants to take a look, and please don't hesitate to give me any feedback you might have, regarding optimization, conception or whatever subject I'm still fairly new to lua and i'll take any knowledge i can
https://mods.curse.com/addons/wow/274928-cerberus

@Rainrider
Yep that's what i ended up doing, in the end; except i made it into one lua "library" file instead of a standalone addon, for simplicity i guess? I mean, as a developper, i like having this right in my files instead of somewhere else waiting for me to call upon it.
For the event handler, i'm not sure why would one need a library for that? I mean it's nothing complicated. From the looks of the description, it seems like creating an eventsListener table with a Call() and Register() functions is all it does, which is approximatively 10 lines long in my addon

@Resike
Oh ok, got it. I didn't know sublime had that feature, that's pretty cool!
  Reply With Quote
08-25-17, 10:03 AM   #8
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
Although I've done plenty things with the environment control, I won't force you to try my way, I'll only provide suggestions based on your Cerberus :

Normally, the cost of calling meta-methods like __index isn't something so tiny that we can ignore.

Take a simple exmaple, if we take every normal table access as 1 operation, every meta-method call as 1 operation, every global API call as 1 operation :

Lua Code:
  1. for i = 1, 100 do
  2.     print(GetUnitName("player"))
  3. end

Within your environment, when we need to access the print API, we need 3 opers for your code g_cerberus[sCurrentlyLoadingAddonName][sKey] or _G[sKey] + 1 oper for the __index call.

For the code print(GetUnitName("player")), we need total 4 (get GetUnitName) + 1( call the GetUnitName) + 4 (get print) + 1 (call the print) = 10 opers. So for the 100 loops, total is 1000 opers.

If we call the code in the _G, the code print(GetUnitName("player")) should be 1 (get GetUnitName) + 1( call the GetUnitName) + 1 (get print) + 1 (call the print) = 4 opers. So for the 100 loops, total is 400 opers.

So the cost can't be ignored. To solve it, we need cache the accessed global API or tables in the private environment, and for the next time the code access them, the __index meta-method will not be triggered again, since they already existed in the private environment, and the previous code'd run the same way just like in the _G.
  Reply With Quote
08-25-17, 10:14 AM   #9
Thex
A Murloc Raider
AddOn Author - Click to view addons
Join Date: Aug 2017
Posts: 5
Hm right; makes sense. I didn't really think too much about that cause i just assumed the call to __index was made wheter or not __index did something, but that's C++ thinking, i think i should stop thinking that way
I'll try to implement that, thanks for the suggestion!
  Reply With Quote
09-03-17, 08:36 AM   #10
Thex
A Murloc Raider
AddOn Author - Click to view addons
Join Date: Aug 2017
Posts: 5
Right, so i added the caching, and actually completely removed the initial proxy table, since it was actually useless in the end.
I think it should be better now. Thanks for your input!
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Making proxy table for _G


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off