trac-pro.lua /size: 5841 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['trac-pro'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local getmetatable, setmetatable, rawset, type, next = getmetatable, setmetatable, rawset, type, next
10
11-- The protection implemented here is probably not that tight but good enough to catch
12-- problems due to naive usage.
13--
14-- There's a more extensive version (trac-xxx.lua) that supports nesting.
15--
16-- This will change when we have _ENV in lua 5.2+
17
18local trace_namespaces = false  trackers.register("system.namespaces", function(v) trace_namespaces = v end)
19
20local report_system = logs.reporter("system","protection")
21
22namespaces       = namespaces or { }
23local namespaces = namespaces
24
25local registered = { }
26
27local function report_index(k,name)
28    if trace_namespaces then
29        report_system("reference to %a in protected namespace %a: %s",k,name)
30        debugger.showtraceback(report_system)
31    else
32        report_system("reference to %a in protected namespace %a",k,name)
33    end
34end
35
36local function report_newindex(k,name)
37    if trace_namespaces then
38        report_system("assignment to %a in protected namespace %a: %s",k,name)
39        debugger.showtraceback(report_system)
40    else
41        report_system("assignment to %a in protected namespace %a",k,name)
42    end
43end
44
45local function register(name)
46    local data = name == "global" and _G or _G[name]
47    if not data then
48        return -- error
49    end
50    registered[name] = data
51    local m = getmetatable(data)
52    if not m then
53        m = { }
54        setmetatable(data,m)
55    end
56    local index, newindex = { }, { }
57    m.__saved__index = m.__index
58    m.__no__index = function(t,k)
59        if not index[k] then
60            index[k] = true
61            report_index(k,name)
62        end
63        return nil
64    end
65    m.__saved__newindex = m.__newindex
66    m.__no__newindex = function(t,k,v)
67        if not newindex[k] then
68            newindex[k] = true
69            report_newindex(k,name)
70        end
71        rawset(t,k,v)
72    end
73    m.__protection__depth = 0
74end
75
76local function private(name) -- maybe save name
77    local data = registered[name]
78    if not data then
79        data = _G[name]
80        if not data then
81            data = { }
82            _G[name] = data
83        end
84        register(name)
85    end
86    return data
87end
88
89local function protect(name)
90    local data = registered[name]
91    if not data then
92        return
93    end
94    local m = getmetatable(data)
95    local pd = m.__protection__depth
96    if pd > 0 then
97        m.__protection__depth = pd + 1
98    else
99        m.__save_d_index, m.__saved__newindex = m.__index, m.__newindex
100        m.__index, m.__newindex = m.__no__index, m.__no__newindex
101        m.__protection__depth = 1
102    end
103end
104
105local function unprotect(name)
106    local data = registered[name]
107    if not data then
108        return
109    end
110    local m = getmetatable(data)
111    local pd = m.__protection__depth
112    if pd > 1 then
113        m.__protection__depth = pd - 1
114    else
115        m.__index, m.__newindex = m.__saved__index, m.__saved__newindex
116        m.__protection__depth = 0
117    end
118end
119
120local function protectall()
121    for name, _ in next, registered do
122        if name ~= "global" then
123            protect(name)
124        end
125    end
126end
127
128local function unprotectall()
129    for name, _ in next, registered do
130        if name ~= "global" then
131            unprotect(name)
132        end
133    end
134end
135
136namespaces.register     = register        -- register when defined
137namespaces.private      = private         -- allocate and register if needed
138namespaces.protect      = protect
139namespaces.unprotect    = unprotect
140namespaces.protectall   = protectall
141namespaces.unprotectall = unprotectall
142
143namespaces.private("namespaces") registered = { } register("global") -- unreachable
144
145directives.register("system.protect", function(v)
146    if v then
147        protectall()
148    else
149        unprotectall()
150    end
151end)
152
153directives.register("system.checkglobals", function(v)
154    if v then
155        report_system("enabling global namespace guard")
156        protect("global")
157    else
158        report_system("disabling global namespace guard")
159        unprotect("global")
160    end
161end)
162
163-- dummy section (will go to luat-dum.lua)
164
165--~ if not namespaces.private then
166--~     -- somewhat protected
167--~     local registered = { }
168--~     function namespaces.private(name)
169--~         local data = registered[name]
170--~         if data then
171--~             return data
172--~         end
173--~         local data = _G[name]
174--~         if not data then
175--~             data = { }
176--~             _G[name] = data
177--~         end
178--~         registered[name] = data
179--~         return data
180--~     end
181--~     function namespaces.protectall(list)
182--~         for name, data in next, list or registered do
183--~             setmetatable(data, { __newindex = function() print(string.format("table %s is protected",name)) end })
184--~         end
185--~     end
186--~     namespaces.protectall { namespaces = namespaces }
187--~ end
188
189--~ directives.enable("system.checkglobals")
190
191--~ namespaces.register("resolvers","trackers")
192--~ namespaces.protect("resolvers")
193--~ namespaces.protect("resolvers")
194--~ namespaces.protect("resolvers")
195--~ namespaces.unprotect("resolvers")
196--~ namespaces.unprotect("resolvers")
197--~ namespaces.unprotect("resolvers")
198--~ namespaces.protect("trackers")
199
200--~ resolvers.x = true
201--~ resolvers.y = true
202--~ trackers.a  = ""
203--~ resolvers.z = true
204--~ oeps        = { }
205
206--~ resolvers = namespaces.private("resolvers")
207--~ fonts = namespaces.private("fonts")
208--~ directives.enable("system.protect")
209--~ namespaces.protectall()
210--~ resolvers.xx = { }
211