Lua Environment & Security (Retail — Patch 12.0.0)
Comprehensive reference for the WoW Lua sandbox, security model, taint system, secure execution, timers, hooks, logging, and restricted actions.
Source: https://warcraft.wiki.gg/wiki/World_of_Warcraft_API
Secure Execution: https://warcraft.wiki.gg/wiki/Secure_Execution_and_Tainting
Lua Functions: https://warcraft.wiki.gg/wiki/Lua_functions
Current as of: Patch 12.0.0 (Build 65655) — January 28, 2026
Scope: Retail only.
Scope
This skill covers:
- Lua Sandbox — WoW's Lua 5.1 environment, restricted standard library, blocked functions
- Taint System — How addon code becomes tainted and what tainted code cannot do
- Secure Execution — Protected functions, secure frames, secure handlers
- Combat Lockdown — What addons can and cannot do during combat
- C_Timer — Timer functions (After, NewTicker, NewTimer)
- Hooks — hooksecurefunc, securecallfunction, securecallmethod
- C_RestrictedActions — Addon restriction state queries
- C_Log — Logging utilities
- FrameScript — Frame script environment, secret values, scrubbing
- Debugging — Error handling, stack traces, debugging utilities
When to Use This Skill
Use this skill when you need to:
- Understand what Lua functions are available vs blocked in WoW
- Work with or debug taint issues
- Write code that interacts with secure/protected frames
- Use timers, delayed execution, or ticker patterns
- Hook existing functions safely
- Understand combat lockdown restrictions
- Handle addon restriction states (12.0.0 instance restrictions)
- Log messages for debugging
- Work with secret values and the FrameScript sandbox
WoW Lua 5.1 Sandbox
WoW runs Lua 5.1.4 with significant modifications. The following standard library functions are blocked or removed:
Blocked Standard Functions
| Blocked |
Reason |
loadfile() |
No filesystem access |
dofile() |
No filesystem access |
io.* |
No filesystem access |
os.execute() |
No shell access |
os.exit() |
Cannot close client |
os.remove() |
No filesystem |
os.rename() |
No filesystem |
os.tmpname() |
No filesystem |
os.getenv() |
No environment access |
package.* |
No package system |
require() |
No module loading |
module() |
No module system |
newproxy() |
Removed |
getfenv() |
Limited — returns read-only |
setfenv() |
Very restricted |
collectgarbage() |
Limited modes |
Available Standard Functions
Most core Lua functions work normally:
- All
string.*, table.*, math.* functions
type(), tostring(), tonumber(), rawget(), rawset(), rawequal(), rawlen()
pairs(), ipairs(), next(), select(), unpack()
pcall(), xpcall(), error(), assert()
setmetatable(), getmetatable()
coroutine.* (full coroutine support)
os.time(), os.date(), os.clock(), os.difftime()
print() — outputs to default chat frame
WoW-Added Global Functions
| Function |
Description |
strsplit(delimiter, str [, pieces]) |
Split string by delimiter |
strsplittable(delimiter, str [, pieces]) |
Split to table |
strjoin(delimiter, ...) |
Join strings |
strtrim(str [, chars]) |
Trim whitespace |
tContains(table, value) |
Table contains value? |
tInsert(table, value) |
Insert into table (alias) |
tDeleteItem(table, value) |
Remove first occurrence of value |
tInvert(table) |
Invert key/value pairs |
wipe(table) |
Clear table (preserving reference) |
CopyTable(table [, shallow]) |
Deep or shallow copy |
MergeTable(dest, source) |
Merge source into dest |
Mixin(object, ...) |
Copy mixin methods to object |
CreateFromMixins(...) |
Create new object from mixins |
CreateAndInitFromMixin(mixin, ...) |
Create + call Init |
format(formatString, ...) |
Alias for string.format |
tostringall(...) |
Convert all args to strings |
DevTools_Dump(value, startKey) |
Dump value for debugging |
Taint System
All addon code runs as "tainted" (insecure). Blizzard UI code runs as "secure" (untainted). The taint system prevents addons from calling protected functions or modifying secure frames.
How Taint Works
- Any variable set by addon code becomes tainted
- Tainted values propagate — if tainted data flows into Blizzard code, it taints that path
- Protected functions check taint before executing — they fail if execution path is tainted
- Secure frames inherit security from their creation context
Checking Taint
-- Check if a global variable is tainted
local isTainted, source = issecurevariable("SomeGlobalVar")
-- isTainted: false = secure, true = tainted
-- source: string name of the addon that tainted it (or nil if secure)
-- Check table field
local isTainted, source = issecurevariable(someTable, "someKey")
Common Taint Pitfalls
-- WRONG — This taints the Blizzard settings table
Settings.RegisterAddOnCategory = myFunc -- TAINT!
-- WRONG — Modifying secure frame in insecure context
local btn = PlayerFrame -- This is a secure Blizzard frame
btn:SetAttribute("type", "spell") -- TAINT — can cause action blocked errors
-- RIGHT — Use hooksecurefunc for observation without tainting
hooksecurefunc("SomeBlizzardFunction", function(...)
-- Your code runs AFTER the original — doesn't taint
end)
Secure Execution & Protected Functions
Protected Function Restrictions
Functions marked #protected can only be called from:
- Secure (Blizzard) code
- Secure click handlers triggered by hardware events
- Inside
SecureActionButtonTemplate handlers
Protected functions include:
- All combat-related casting:
CastSpellByName(), CastSpellByID(), UseAction()
- Item use:
UseItemByName(), UseContainerItem() (in combat)
- Target changes:
TargetUnit(), AssistUnit(), FocusUnit()
- Movement:
MoveForwardStart(), JumpOrAscendStart()
- UI state:
SetAttribute() on secure frames (in combat)
Combat Lockdown
-- Check if in combat lockdown
if InCombatLockdown() then
-- Cannot: create/destroy secure frames, change secure attributes
-- Cannot: set points on secure frames, change parent/visibility of secure frames
-- Can: read attributes, modify non-secure frames, queue changes for later
return
end
-- Queue changes for after combat
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function()
-- Combat ended — safe to modify secure frames now
DoSecureFrameChanges()
end)
Secure Handlers & Templates
-- SecureActionButtonTemplate — allows protected actions via user clicks
local btn = CreateFrame("Button", "MySecureBtn", UIParent, "SecureActionButtonTemplate")
btn:SetAttribute("type", "spell")
btn:SetAttribute("spell", "Fireball")
-- When clicked by hardware event, this will cast Fireball
-- SecureHandlerBaseTemplate — run secure snippets
local frame = CreateFrame("Frame", nil, UIParent, "SecureHandlerBaseTemplate")
frame:SetAttribute("_onstate-combat", [[
-- This snippet runs in the secure environment
if newstate == "combat" then
self:Hide()
else
self:Show()
end
]])
RegisterStateDriver(frame, "combat", "[combat] combat; nocombat")
State Drivers
-- Register a state driver for automatic secure attribute updates
RegisterStateDriver(frame, "stateName", "conditionalString")
-- e.g., RegisterStateDriver(frame, "visibility", "[combat] hide; show")
UnregisterStateDriver(frame, "stateName")
C_Timer — Timer API
Wiki: https://warcraft.wiki.gg/wiki/API_C_Timer.After
Timer Functions
| Function |
Returns |
Description |
C_Timer.After(seconds, callback) |
— |
One-shot timer |
C_Timer.NewTimer(seconds, callback) |
timer |
Cancellable one-shot timer |
C_Timer.NewTicker(seconds, callback [, iterations]) |
ticker |
Repeating timer |
Timer Object Methods
local timer = C_Timer.NewTimer(5, function()
print("5 seconds elapsed")
end)
timer:Cancel() -- Cancel before it fires
local ticker = C_Timer.NewTicker(1, function()
print("Every second")
end, 10) -- Stop after 10 iterations
ticker:Cancel() -- Or cancel early
-- Simple delay (non-cancellable)
C_Timer.After(2, function()
print("2 seconds later")
end)
Hooks — Function Hooking
hooksecurefunc
The primary safe hooking mechanism. Your hook runs after the original function, without tainting it.
-- Hook a global function
hooksecurefunc("UseAction", function(slot, checkCursor, onSelf)
print("Action used:", slot)
end)
-- Hook a method on an object
hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
-- Runs after GameTooltip:SetUnitAura
end)
-- IMPORTANT: You CANNOT prevent the original from executing
-- IMPORTANT: You CANNOT modify the return values
-- IMPORTANT: Your hook does NOT taint the original function
securecallfunction / securecallmethod
-- Call a function in secure context (if possible)
securecallfunction(func, arg1, arg2)
-- Call a method in secure context
securecallmethod(object, "MethodName", arg1, arg2)
C_RestrictedActions — Addon Restriction State
New in 12.0.0. Tracks when addon restrictions are active (e.g., inside instances).
| Function |
Returns |
Description |
C_RestrictedActions.GetAddOnRestrictionState(type) |
state |
Current restriction state |
C_RestrictedActions.IsAddOnRestrictionActive(type) |
active |
Is restriction currently active? |
C_RestrictedActions.CheckAllowProtectedFunctions(object [, silent]) |
protectedFunctionsAllowed |
Can object call protected funcs? |
InCombatLockdown() |
inCombatLockdown |
Combat lockdown active? |
Restriction Events
| Event |
Description |
ADDON_RESTRICTION_STATE_CHANGED |
Restriction state changed (entering/leaving instance) |
PLAYER_REGEN_DISABLED |
Entering combat |
PLAYER_REGEN_ENABLED |
Leaving combat |
C_Log — Logging
| Function |
Description |
C_Log.LogMessage(message) |
Log info message |
C_Log.LogWarningMessage(message) |
Log warning |
C_Log.LogErrorMessage(message) |
Log error |
C_Log.LogMessageWithPriority(priority, message) |
Log with specific priority |
Note: ConsolePrint() was removed in 12.0.0. Use C_Log.LogMessage() instead.
FrameScript Functions
WoW provides special FrameScript functions for working with the secure/secret value system:
| Function |
Returns |
Description |
issecurevariable([table,] name) |
isSecure, taintSource |
Check taint status |
issecretvalue(value) |
isSecret |
Is value a secret? |
issecrettable(table) |
isSecretOrContentsSecret |
Is table or contents secret? |
canaccessvalue(value) |
isAccessible |
Can addon access this value? |
hasanysecretvalues(values) |
isAnyValueSecret |
Any arg secret? |
scrubsecretvalues(values) |
scrubbed |
Replace secrets with nil |
secretwrap(values) |
wrapped |
Wrap values as secrets |
mapvalues(func, values) |
mapped |
Map function over values (secret-safe) |
securecallfunction(func, ...) |
results |
Call in secure context |
securecallmethod(obj, method, ...) |
results |
Call method in secure context |
forceinsecure() |
— |
Force insecure execution |
seterrorhandler(handler) |
— |
Set global error handler |
geterrorhandler() |
handler |
Get current error handler |
Debugging Utilities
Error Handling
-- Set a custom error handler
seterrorhandler(function(msg)
-- msg is the error string
print("ERROR:", msg)
end)
-- Protected call with error handling
local success, err = pcall(function()
-- Code that might error
end)
if not success then
print("Error:", err)
end
-- xpcall with message handler
local success, err = xpcall(function()
error("something broke")
end, function(msg)
return msg .. "\n" .. debugstack(2)
end)
Debug Stack & Info
-- Get a stack trace
local stack = debugstack([thread,] [start [, count1 [, count2]]])
-- Get debug info
local info = debuglocals([thread,] [level])
-- Profile timing
debugprofilestart()
-- ... code to measure ...
local elapsed = debugprofilestop() -- microseconds
Slash Commands for Debugging
-- /dump expression — evaluates and prints
-- /run code — executes Lua code
-- /script code — same as /run
-- /console cvarName [value] — get/set console variables
Common Patterns
Deferred Initialization (Wait for Login)
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function(self, event)
-- Safe to initialize — player is logged in
self:UnregisterEvent(event)
InitializeAddon()
end)
Safe OnUpdate Throttle
local elapsed = 0
local THROTTLE = 0.1 -- 100ms
frame:SetScript("OnUpdate", function(self, dt)
elapsed = elapsed + dt
if elapsed < THROTTLE then return end
elapsed = 0
-- Do periodic work
end)
Post-Combat Action Queue
local pendingActions = {}
local function QueueAction(action)
if InCombatLockdown() then
tinsert(pendingActions, action)
else
action()
end
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:SetScript("OnEvent", function()
for _, action in ipairs(pendingActions) do
action()
end
wipe(pendingActions)
end)
Graceful Secret Value Handling (12.0.0)
-- When values might be secret, pass them directly to widgets
local name = UnitName(unit) -- may be secret
myFontString:SetText(name) -- widgets accept secrets
-- Check if a value is secret before trying operations
if not issecretvalue(someValue) then
-- Safe to compare, do arithmetic, etc.
if someValue == "expected" then ... end
else
-- Cannot inspect — pass to UI widget directly
myWidget:SetText(someValue)
end
Gotchas & Restrictions
- No
require() — WoW has no module system. Use the TOC file to control load order. Libraries are embedded directly.
setfenv() / getfenv() — Severely restricted. Do not rely on environment manipulation.
collectgarbage() — Only "count" mode works. Cannot force GC collection.
- Taint is sticky — Once a variable is tainted, it stays tainted. Even if you set it back to the original value, the taint remains.
print() goes to chat — Unlike standard Lua, print() outputs to the default chat frame, not stdout.
- String library additions — WoW adds
strsplit, strjoin, strtrim, and strmatch as globals (in addition to string.match).
- No
os.exit() — Cannot terminate the client programmatically.
- Coroutines work — Full coroutine support is available and commonly used for async patterns.
- Secret values (12.0.0) — Some API returns are now opaque "secret" values that cannot be inspected, compared, or used in arithmetic. See the
wow-api-important instructions for full details.
- Instance restrictions (12.0.0) —
SendAddonMessage() is blocked in instances. Design addons to work without inter-player communication during instanced content.
1---2name: wow-api-lua-environment3description: Complete reference for the WoW Lua 5.1 runtime environment, restrictions, secure execution, taint system, addon security model, timers, hooks, frame scripting, logging, and restricted actions. Covers hooksecurefunc, C_Timer, securecallfunction, issecurevariable, taint propagation, combat lockdown, protected frames, InCombatLockdown, C_RestrictedActions, C_Log, and the FrameScript sandbox. Use when working with Lua restrictions, secure code, taint, timers, hooks, addon security, debugging, or the WoW Lua sandbox.4---5
6# Lua Environment & Security (Retail — Patch 12.0.0)
7
8Comprehensive reference for the WoW Lua sandbox, security model, taint system, secure execution, timers, hooks, logging, and restricted actions.
9
10> **Source:** https://warcraft.wiki.gg/wiki/World_of_Warcraft_API
11> **Secure Execution:** https://warcraft.wiki.gg/wiki/Secure_Execution_and_Tainting
12> **Lua Functions:** https://warcraft.wiki.gg/wiki/Lua_functions
13> **Current as of:** Patch 12.0.0 (Build 65655) — January 28, 2026
14> **Scope:** Retail only.
15
16## Scope
17
18This skill covers:
19
20- **Lua Sandbox** — WoW's Lua 5.1 environment, restricted standard library, blocked functions
21- **Taint System** — How addon code becomes tainted and what tainted code cannot do
22- **Secure Execution** — Protected functions, secure frames, secure handlers
23- **Combat Lockdown** — What addons can and cannot do during combat
24- **C_Timer** — Timer functions (After, NewTicker, NewTimer)
25- **Hooks** — hooksecurefunc, securecallfunction, securecallmethod
26- **C_RestrictedActions** — Addon restriction state queries
27- **C_Log** — Logging utilities
28- **FrameScript** — Frame script environment, secret values, scrubbing
29- **Debugging** — Error handling, stack traces, debugging utilities
30
31## When to Use This Skill
32
33Use this skill when you need to:
34- Understand what Lua functions are available vs blocked in WoW
35- Work with or debug taint issues
36- Write code that interacts with secure/protected frames
37- Use timers, delayed execution, or ticker patterns
38- Hook existing functions safely
39- Understand combat lockdown restrictions
40- Handle addon restriction states (12.0.0 instance restrictions)
41- Log messages for debugging
42- Work with secret values and the FrameScript sandbox
43
44---
45
46## WoW Lua 5.1 Sandbox
47
48WoW runs **Lua 5.1.4** with significant modifications. The following standard library functions are **blocked or removed**:
49
50### Blocked Standard Functions
51
52| Blocked | Reason |
53|---------|--------|
54| `loadfile()` | No filesystem access |
55| `dofile()` | No filesystem access |
56| `io.*` | No filesystem access |
57| `os.execute()` | No shell access |
58| `os.exit()` | Cannot close client |
59| `os.remove()` | No filesystem |
60| `os.rename()` | No filesystem |
61| `os.tmpname()` | No filesystem |
62| `os.getenv()` | No environment access |
63| `package.*` | No package system |
64| `require()` | No module loading |
65| `module()` | No module system |
66| `newproxy()` | Removed |
67| `getfenv()` | Limited — returns read-only |
68| `setfenv()` | Very restricted |
69| `collectgarbage()` | Limited modes |
70
71### Available Standard Functions
72
73Most core Lua functions work normally:
74- All `string.*`, `table.*`, `math.*` functions
75- `type()`, `tostring()`, `tonumber()`, `rawget()`, `rawset()`, `rawequal()`, `rawlen()`
76- `pairs()`, `ipairs()`, `next()`, `select()`, `unpack()`
77- `pcall()`, `xpcall()`, `error()`, `assert()`
78- `setmetatable()`, `getmetatable()`
79- `coroutine.*` (full coroutine support)
80- `os.time()`, `os.date()`, `os.clock()`, `os.difftime()`
81- `print()` — outputs to default chat frame
82
83### WoW-Added Global Functions
84
85| Function | Description |
86|----------|-------------|
87| `strsplit(delimiter, str [, pieces])` | Split string by delimiter |
88| `strsplittable(delimiter, str [, pieces])` | Split to table |
89| `strjoin(delimiter, ...)` | Join strings |
90| `strtrim(str [, chars])` | Trim whitespace |
91| `tContains(table, value)` | Table contains value? |
92| `tInsert(table, value)` | Insert into table (alias) |
93| `tDeleteItem(table, value)` | Remove first occurrence of value |
94| `tInvert(table)` | Invert key/value pairs |
95| `wipe(table)` | Clear table (preserving reference) |
96| `CopyTable(table [, shallow])` | Deep or shallow copy |
97| `MergeTable(dest, source)` | Merge source into dest |
98| `Mixin(object, ...)` | Copy mixin methods to object |
99| `CreateFromMixins(...)` | Create new object from mixins |
100| `CreateAndInitFromMixin(mixin, ...)` | Create + call Init |
101| `format(formatString, ...)` | Alias for string.format |
102| `tostringall(...)` | Convert all args to strings |
103| `DevTools_Dump(value, startKey)` | Dump value for debugging |
104
105---
106
107## Taint System
108
109All addon code runs as **"tainted"** (insecure). Blizzard UI code runs as **"secure"** (untainted). The taint system prevents addons from calling protected functions or modifying secure frames.
110
111### How Taint Works
112
1131. Any variable set by addon code becomes **tainted**
1142. Tainted values **propagate** — if tainted data flows into Blizzard code, it taints that path
1153. Protected functions check taint before executing — they fail if execution path is tainted
1164. Secure frames inherit security from their creation context
117
118### Checking Taint
119
120```lua
121-- Check if a global variable is tainted
122local isTainted, source = issecurevariable("SomeGlobalVar")
123-- isTainted: false = secure, true = tainted
124-- source: string name of the addon that tainted it (or nil if secure)
125
126-- Check table field
127local isTainted, source = issecurevariable(someTable, "someKey")
128```
129
130### Common Taint Pitfalls
131
132```lua
133-- WRONG — This taints the Blizzard settings table
134Settings.RegisterAddOnCategory = myFunc -- TAINT!
135
136-- WRONG — Modifying secure frame in insecure context
137local btn = PlayerFrame -- This is a secure Blizzard frame
138btn:SetAttribute("type", "spell") -- TAINT — can cause action blocked errors
139
140-- RIGHT — Use hooksecurefunc for observation without tainting
141hooksecurefunc("SomeBlizzardFunction", function(...)
142 -- Your code runs AFTER the original — doesn't taint
143end)
144```
145
146---
147
148## Secure Execution & Protected Functions
149
150### Protected Function Restrictions
151
152Functions marked `#protected` can only be called from:
153- Secure (Blizzard) code
154- Secure click handlers triggered by hardware events
155- Inside `SecureActionButtonTemplate` handlers
156
157Protected functions include:
158- All combat-related casting: `CastSpellByName()`, `CastSpellByID()`, `UseAction()`
159- Item use: `UseItemByName()`, `UseContainerItem()` (in combat)
160- Target changes: `TargetUnit()`, `AssistUnit()`, `FocusUnit()`
161- Movement: `MoveForwardStart()`, `JumpOrAscendStart()`
162- UI state: `SetAttribute()` on secure frames (in combat)
163
164### Combat Lockdown
165
166```lua
167-- Check if in combat lockdown
168if InCombatLockdown() then
169 -- Cannot: create/destroy secure frames, change secure attributes
170 -- Cannot: set points on secure frames, change parent/visibility of secure frames
171 -- Can: read attributes, modify non-secure frames, queue changes for later
172 return
173end
174
175-- Queue changes for after combat
176local frame = CreateFrame("Frame")
177frame:RegisterEvent("PLAYER_REGEN_ENABLED")
178frame:SetScript("OnEvent", function()
179 -- Combat ended — safe to modify secure frames now
180 DoSecureFrameChanges()
181end)
182```
183
184### Secure Handlers & Templates
185
186```lua
187-- SecureActionButtonTemplate — allows protected actions via user clicks
188local btn = CreateFrame("Button", "MySecureBtn", UIParent, "SecureActionButtonTemplate")
189btn:SetAttribute("type", "spell")
190btn:SetAttribute("spell", "Fireball")
191-- When clicked by hardware event, this will cast Fireball
192
193-- SecureHandlerBaseTemplate — run secure snippets
194local frame = CreateFrame("Frame", nil, UIParent, "SecureHandlerBaseTemplate")
195frame:SetAttribute("_onstate-combat", [[
196 -- This snippet runs in the secure environment
197 if newstate == "combat" then
198 self:Hide()
199 else
200 self:Show()
201 end
202]])
203RegisterStateDriver(frame, "combat", "[combat] combat; nocombat")
204```
205
206### State Drivers
207
208```lua
209-- Register a state driver for automatic secure attribute updates
210RegisterStateDriver(frame, "stateName", "conditionalString")
211-- e.g., RegisterStateDriver(frame, "visibility", "[combat] hide; show")
212
213UnregisterStateDriver(frame, "stateName")
214```
215
216---
217
218## C_Timer — Timer API
219
220> **Wiki:** https://warcraft.wiki.gg/wiki/API_C_Timer.After
221
222### Timer Functions
223
224| Function | Returns | Description |
225|----------|---------|-------------|
226| `C_Timer.After(seconds, callback)` | — | One-shot timer |
227| `C_Timer.NewTimer(seconds, callback)` | `timer` | Cancellable one-shot timer |
228| `C_Timer.NewTicker(seconds, callback [, iterations])` | `ticker` | Repeating timer |
229
230### Timer Object Methods
231
232```lua
233local timer = C_Timer.NewTimer(5, function()
234 print("5 seconds elapsed")
235end)
236timer:Cancel() -- Cancel before it fires
237
238local ticker = C_Timer.NewTicker(1, function()
239 print("Every second")
240end, 10) -- Stop after 10 iterations
241ticker:Cancel() -- Or cancel early
242
243-- Simple delay (non-cancellable)
244C_Timer.After(2, function()
245 print("2 seconds later")
246end)
247```
248
249---
250
251## Hooks — Function Hooking
252
253### hooksecurefunc
254
255The primary safe hooking mechanism. Your hook runs **after** the original function, without tainting it.
256
257```lua
258-- Hook a global function
259hooksecurefunc("UseAction", function(slot, checkCursor, onSelf)
260 print("Action used:", slot)
261end)
262
263-- Hook a method on an object
264hooksecurefunc(GameTooltip, "SetUnitAura", function(self, ...)
265 -- Runs after GameTooltip:SetUnitAura
266end)
267
268-- IMPORTANT: You CANNOT prevent the original from executing
269-- IMPORTANT: You CANNOT modify the return values
270-- IMPORTANT: Your hook does NOT taint the original function
271```
272
273### securecallfunction / securecallmethod
274
275```lua
276-- Call a function in secure context (if possible)
277securecallfunction(func, arg1, arg2)
278
279-- Call a method in secure context
280securecallmethod(object, "MethodName", arg1, arg2)
281```
282
283---
284
285## C_RestrictedActions — Addon Restriction State
286
287New in 12.0.0. Tracks when addon restrictions are active (e.g., inside instances).
288
289| Function | Returns | Description |
290|----------|---------|-------------|
291| `C_RestrictedActions.GetAddOnRestrictionState(type)` | `state` | Current restriction state |
292| `C_RestrictedActions.IsAddOnRestrictionActive(type)` | `active` | Is restriction currently active? |
293| `C_RestrictedActions.CheckAllowProtectedFunctions(object [, silent])` | `protectedFunctionsAllowed` | Can object call protected funcs? |
294| `InCombatLockdown()` | `inCombatLockdown` | Combat lockdown active? |
295
296### Restriction Events
297
298| Event | Description |
299|-------|-------------|
300| `ADDON_RESTRICTION_STATE_CHANGED` | Restriction state changed (entering/leaving instance) |
301| `PLAYER_REGEN_DISABLED` | Entering combat |
302| `PLAYER_REGEN_ENABLED` | Leaving combat |
303
304---
305
306## C_Log — Logging
307
308| Function | Description |
309|----------|-------------|
310| `C_Log.LogMessage(message)` | Log info message |
311| `C_Log.LogWarningMessage(message)` | Log warning |
312| `C_Log.LogErrorMessage(message)` | Log error |
313| `C_Log.LogMessageWithPriority(priority, message)` | Log with specific priority |
314
315> **Note:** `ConsolePrint()` was removed in 12.0.0. Use `C_Log.LogMessage()` instead.
316
317---
318
319## FrameScript Functions
320
321WoW provides special FrameScript functions for working with the secure/secret value system:
322
323| Function | Returns | Description |
324|----------|---------|-------------|
325| `issecurevariable([table,] name)` | `isSecure, taintSource` | Check taint status |
326| `issecretvalue(value)` | `isSecret` | Is value a secret? |
327| `issecrettable(table)` | `isSecretOrContentsSecret` | Is table or contents secret? |
328| `canaccessvalue(value)` | `isAccessible` | Can addon access this value? |
329| `hasanysecretvalues(values)` | `isAnyValueSecret` | Any arg secret? |
330| `scrubsecretvalues(values)` | `scrubbed` | Replace secrets with nil |
331| `secretwrap(values)` | `wrapped` | Wrap values as secrets |
332| `mapvalues(func, values)` | `mapped` | Map function over values (secret-safe) |
333| `securecallfunction(func, ...)` | `results` | Call in secure context |
334| `securecallmethod(obj, method, ...)` | `results` | Call method in secure context |
335| `forceinsecure()` | — | Force insecure execution |
336| `seterrorhandler(handler)` | — | Set global error handler |
337| `geterrorhandler()` | `handler` | Get current error handler |
338
339---
340
341## Debugging Utilities
342
343### Error Handling
344
345```lua
346-- Set a custom error handler
347seterrorhandler(function(msg)
348 -- msg is the error string
349 print("ERROR:", msg)
350end)
351
352-- Protected call with error handling
353local success, err = pcall(function()
354 -- Code that might error
355end)
356if not success then
357 print("Error:", err)
358end
359
360-- xpcall with message handler
361local success, err = xpcall(function()
362 error("something broke")
363end, function(msg)
364 return msg .. "\n" .. debugstack(2)
365end)
366```
367
368### Debug Stack & Info
369
370```lua
371-- Get a stack trace
372local stack = debugstack([thread,] [start [, count1 [, count2]]])
373
374-- Get debug info
375local info = debuglocals([thread,] [level])
376
377-- Profile timing
378debugprofilestart()
379-- ... code to measure ...
380local elapsed = debugprofilestop() -- microseconds
381```
382
383### Slash Commands for Debugging
384
385```lua
386-- /dump expression — evaluates and prints
387-- /run code — executes Lua code
388-- /script code — same as /run
389-- /console cvarName [value] — get/set console variables
390```
391
392---
393
394## Common Patterns
395
396### Deferred Initialization (Wait for Login)
397
398```lua
399local frame = CreateFrame("Frame")
400frame:RegisterEvent("PLAYER_LOGIN")
401frame:SetScript("OnEvent", function(self, event)
402 -- Safe to initialize — player is logged in
403 self:UnregisterEvent(event)
404 InitializeAddon()
405end)
406```
407
408### Safe OnUpdate Throttle
409
410```lua
411local elapsed = 0
412local THROTTLE = 0.1 -- 100ms
413frame:SetScript("OnUpdate", function(self, dt)
414 elapsed = elapsed + dt
415 if elapsed < THROTTLE then return end
416 elapsed = 0
417 -- Do periodic work
418end)
419```
420
421### Post-Combat Action Queue
422
423```lua
424local pendingActions = {}
425
426local function QueueAction(action)
427 if InCombatLockdown() then
428 tinsert(pendingActions, action)
429 else
430 action()
431 end
432end
433
434local frame = CreateFrame("Frame")
435frame:RegisterEvent("PLAYER_REGEN_ENABLED")
436frame:SetScript("OnEvent", function()
437 for _, action in ipairs(pendingActions) do
438 action()
439 end
440 wipe(pendingActions)
441end)
442```
443
444### Graceful Secret Value Handling (12.0.0)
445
446```lua
447-- When values might be secret, pass them directly to widgets
448local name = UnitName(unit) -- may be secret
449myFontString:SetText(name) -- widgets accept secrets
450
451-- Check if a value is secret before trying operations
452if not issecretvalue(someValue) then
453 -- Safe to compare, do arithmetic, etc.
454 if someValue == "expected" then ... end
455else
456 -- Cannot inspect — pass to UI widget directly
457 myWidget:SetText(someValue)
458end
459```
460
461---
462
463## Gotchas & Restrictions
464
4651. **No `require()`** — WoW has no module system. Use the TOC file to control load order. Libraries are embedded directly.
4662. **`setfenv()` / `getfenv()`** — Severely restricted. Do not rely on environment manipulation.
4673. **`collectgarbage()`** — Only `"count"` mode works. Cannot force GC collection.
4684. **Taint is sticky** — Once a variable is tainted, it stays tainted. Even if you set it back to the original value, the taint remains.
4695. **`print()` goes to chat** — Unlike standard Lua, `print()` outputs to the default chat frame, not stdout.
4706. **String library additions** — WoW adds `strsplit`, `strjoin`, `strtrim`, and `strmatch` as globals (in addition to `string.match`).
4717. **No `os.exit()`** — Cannot terminate the client programmatically.
4728. **Coroutines work** — Full coroutine support is available and commonly used for async patterns.
4739. **Secret values (12.0.0)** — Some API returns are now opaque "secret" values that cannot be inspected, compared, or used in arithmetic. See the `wow-api-important` instructions for full details.
47410. **Instance restrictions (12.0.0)** — `SendAddonMessage()` is blocked in instances. Design addons to work without inter-player communication during instanced content.