--[[
Swatter - An AddOn debugging aid for World of Warcraft.
$Id$
Copyright (C) 2006 Norganna
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
]]
Swatter = {
origHandler = geterrorhandler(),
origItemRef = SetItemRef,
nilFrame = {
GetName = function() return "Global" end
},
errorOrder = {},
HISTORY_SIZE = 50,
}
SwatterData = {
enabled = true,
autoshow = true,
errors = {},
}
function Swatter.ChatMsg(msg)
DEFAULT_CHAT_FRAME:AddMessage(msg)
end
local chat = Swatter.ChatMsg
function Swatter.IsEnabled()
return SwatterData.enabled
end
-- Test: /run Swatter.OnError("Test")
function Swatter.OnError(msg, frame, stack, etype, ...)
if (not SwatterData.enabled) then
if (not etype) then
return Swatter.origHandler(msg, frame)
else
return UIParent_OnEvent(etype, ...)
end
end
msg = msg or ""
frame = frame or Swatter.nilFrame
stack = stack or debugstack(2, 20, 20)
local context
if (not frame.Swatter) then frame.Swatter = {} end
local id = frame.Swatter[msg]
if (not id) then
context = "Anonymous"
if (frame) then
context = "Unnamed"
if (frame:GetName()) then
context = frame:GetName()
end
end
table.insert(SwatterData.errors, {
context = context,
message = msg,
stack = stack,
count = 0,
})
id = table.getn(SwatterData.errors)
frame.Swatter[msg] = id
else
context = SwatterData.errors[id].context
for pos, errid in ipairs(Swatter.errorOrder) do
if (errid == id) then
table.remove(Swatter.errorOrder, pos)
break
end
end
end
table.insert(Swatter.errorOrder, id)
local err = SwatterData.errors[id]
local count = err.count or 0
if (count < 1000) then err.count = count + 1 end
if (count == 0) then
if (etype == "ADDON_ACTION_BLOCKED") then
if (not Swatter.blockWarn) then
chat("|cffffaa11Warning only: Swatter found blocked actions:|r |Hswatter:"..id.."|h|cffff3311["..context.."]|r|h")
chat("|cffffaa11Note: Swatter will continue to catch blocked actions but this is the last time this session that we'll tell you about it.|r")
Swatter.blockWarn = true
end
elseif (SwatterData.autoshow) then
Swatter.ErrorUpdate()
Swatter.Error:Show()
else
chat("|cffffaa11Swatter caught error:|r |Hswatter:"..id.."|h|cffff3311["..context.."]|r|h")
end
end
end
function Swatter.NamedFrame(name)
if (not Swatter.named) then Swatter.named = {} end
if (not Swatter.named[name]) then
Swatter.named[name] = {
name = name,
GetName = function(obj) return obj.name end,
}
end
return Swatter.named[name]
end
-- Error occured in: Global
-- Count: 1
-- Message: [string "bla()"] line 1:
-- attempt to call global 'bla' (a nil value)
-- Debug:
-- [C]: in function `bla'
-- [string "bla()"]:1: in main chunk
-- [C]: in function `RunScript'
-- Interface\FrameXML\ChatFrame.lua:1788: in function `value'
-- Interface\FrameXML\ChatFrame.lua:3008: in function `ChatEdit_ParseText'
-- Interface\FrameXML\ChatFrame.lua:2734: in function `ChatEdit_SendText'
-- Interface\FrameXML\ChatFrame.lua:2756: in function `ChatEdit_OnEnterPressed'
-- [string "ChatFrameEditBox:OnEnterPressed"]:2: in function <[string "ChatFrameEditBox:OnEnterPressed"]:1>
function Swatter.OnEvent(frame, event, ...)
if (event == "ADDON_LOADED") then
local addon = select(1, ...)
if (addon:lower() == "!swatter") then
-- We need to cleanup our error history
if (not SwatterData.errors) then SwatterData.errors = {} end
local ec = table.getn(SwatterData.errors) or 0
if (ec > Swatter.HISTORY_SIZE) then
local remove = ec - Swatter.HISTORY_SIZE
for i=1, remove do
table.remove(SwatterData.errors, 1)
end
end
for pos, err in ipairs(SwatterData.errors) do
table.insert(Swatter.errorOrder, pos)
end
frame:UnregisterEvent("ADDON_LOADED")
return
end
elseif (event == "ADDON_ACTION_BLOCKED") then
local addon, func = select(1, ...)
Swatter.OnError(string.format("Warning: AddOn %s attempted to call a protected function (%s) from a tainted execution path.", addon, func), Swatter.NamedFrame("AddOn: "..addon), debugstack(2, 20, 20), event, ...)
elseif (event == "ADDON_ACTION_FORBIDDEN") then
local addon, func = select(1, ...)
Swatter.OnError(string.format("Warning: AddOn %s attempted to call a forbidden function (%s) from a tainted execution path.", addon, func), Swatter.NamedFrame("AddOn: "..addon), debugstack(2, 20, 20), event, ...)
end
end
function Swatter.SetItemRef(...)
local msg = select(1, ...)
local id = select(3, msg:find("^swatter:(%d+)"))
id = tonumber(id)
if (id) then
for pos, errid in ipairs(Swatter.errorOrder) do
if (errid == id) then
Swatter.Error:Show()
return Swatter.ErrorDisplay(pos)
end
end
else
return Swatter.origItemRef(...)
end
end
function Swatter.ErrorShow()
Swatter.Error.pos = table.getn(Swatter.errorOrder)
Swatter.ErrorDisplay()
end
function Swatter.ErrorDisplay(id)
if id then Swatter.Error.pos = id else id = Swatter.Error.pos end
Swatter.ErrorUpdate()
local errid = Swatter.errorOrder[id]
if (not errid) then
Swatter.Error.curError = "Unknown error at position "..id
Swatter.ErrorUpdate()
return
end
local err = SwatterData.errors[errid]
if (not err) then
Swatter.Error.curError = "Unknown error at index "..errid
Swatter.ErrorUpdate()
return
end
local message = err.message:gsub("(.-):(%d+): ", "%1 line %2:\n "):gsub("Interface(\\%w+\\)", "..%1"):gsub(": in function `(.-)`", ": %1"):gsub("|", "||")
local trace = " "..err.stack:gsub("Interface\\AddOns\\", ""):gsub("Interface(\\%w+\\)", "..%1"):gsub(": in function `(.-)'", ": %1()"):gsub(": in function <(.-)>", ":\n %1"):gsub(": in main chunk ", ": "):gsub("\n", "\n ")
local count = err.count
if (count > 999) then count = "\226\136\158" --[[Infinity]] end
Swatter.Error.curError = "Error occured in: "..(err.context or "Anonymous").."\nCount: "..count.."\nMessage: "..message.."\n".."Debug:\n"..trace.."\n"
Swatter.ErrorUpdate()
Swatter.Error:Show()
end
function Swatter.ErrorDone()
Swatter.Error:Hide()
end
function Swatter.ErrorPrev()
local cur = Swatter.Error.pos or 1
if (cur > 1) then
Swatter.ErrorDisplay(cur - 1)
else
Swatter.ErrorUpdate()
end
end
function Swatter.ErrorNext()
local cur = Swatter.Error.pos or 1
local max = table.getn(Swatter.errorOrder) or 0
if (cur < max) then
Swatter.ErrorDisplay(cur + 1)
else
Swatter.ErrorUpdate()
end
end
function Swatter.UpdateNextPrev()
local cur = Swatter.Error.pos or 1
local max = table.getn(Swatter.errorOrder) or 0
if (max > cur) then Swatter.Error.Next:Enable() else Swatter.Error.Next:Disable() end
if (cur > 1) then Swatter.Error.Prev:Enable() else Swatter.Error.Prev:Disable() end
end
function Swatter.ErrorUpdate()
if (not Swatter.Error.curError) then Swatter.Error.curError = "" end
Swatter.Error.Box:SetText(Swatter.Error.curError)
Swatter.Error.Scroll:UpdateScrollChildRect()
Swatter.Error.Box:ClearFocus()
Swatter.UpdateNextPrev()
end
-- Create our error message frame
Swatter.Error = CreateFrame("Frame", "", UIParent)
Swatter.Error:Hide()
Swatter.Error:SetPoint("CENTER", "UIParent", "CENTER")
Swatter.Error:SetFrameStrata("DIALOG")
Swatter.Error:SetHeight(280)
Swatter.Error:SetWidth(500)
Swatter.Error:SetBackdrop({
bgFile = "Interface/Tooltips/UI-Tooltip-Background",
edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
tile = true, tileSize = 32, edgeSize = 32,
insets = { left = 9, right = 9, top = 9, bottom = 9 }
})
Swatter.Error:SetBackdropColor(0,0,0, 0.8)
Swatter.Error:SetScript("OnShow", Swatter.ErrorShow)
Swatter.Error.Done = CreateFrame("Button", "", Swatter.Error, "OptionsButtonTemplate")
Swatter.Error.Done:SetText("Close")
Swatter.Error.Done:SetPoint("BOTTOMRIGHT", Swatter.Error, "BOTTOMRIGHT", -10, 10)
Swatter.Error.Done:SetScript("OnClick", Swatter.ErrorDone)
Swatter.Error.Next = CreateFrame("Button", "", Swatter.Error, "OptionsButtonTemplate")
Swatter.Error.Next:SetText("Next >")
Swatter.Error.Next:SetPoint("BOTTOMRIGHT", Swatter.Error.Done, "BOTTOMLEFT", -5, 0)
Swatter.Error.Next:SetScript("OnClick", Swatter.ErrorNext)
Swatter.Error.Prev = CreateFrame("Button", "", Swatter.Error, "OptionsButtonTemplate")
Swatter.Error.Prev:SetText("< Prev")
Swatter.Error.Prev:SetPoint("BOTTOMRIGHT", Swatter.Error.Next, "BOTTOMLEFT", -5, 0)
Swatter.Error.Prev:SetScript("OnClick", Swatter.ErrorPrev)
Swatter.Error.Scroll = CreateFrame("ScrollFrame", "SwatterErrorInputScroll", Swatter.Error, "UIPanelScrollFrameTemplate")
Swatter.Error.Scroll:SetPoint("TOPLEFT", Swatter.Error, "TOPLEFT", 20, -20)
Swatter.Error.Scroll:SetPoint("RIGHT", Swatter.Error, "RIGHT", -30, 0)
Swatter.Error.Scroll:SetPoint("BOTTOM", Swatter.Error.Done, "TOP", 0, 10)
Swatter.Error.Box = CreateFrame("EditBox", "SwatterErrorEditBox", Swatter.Error.Scroll)
Swatter.Error.Box:SetWidth(450)
Swatter.Error.Box:SetHeight(85)
Swatter.Error.Box:SetMultiLine(true)
Swatter.Error.Box:SetAutoFocus(false)
Swatter.Error.Box:SetFontObject(GameFontHighlight)
Swatter.Error.Box:SetScript("OnEscapePressed", Swatter.ErrorDone)
Swatter.Error.Box:SetScript("OnTextChanged", Swatter.ErrorUpdate)
Swatter.Error.Scroll:SetScrollChild(Swatter.Error.Box)
seterrorhandler(Swatter.OnError)
Swatter.Frame = CreateFrame("Frame")
Swatter.Frame:Show()
Swatter.Frame:SetScript("OnEvent", Swatter.OnEvent)
Swatter.Frame:RegisterEvent("ADDON_LOADED")
Swatter.Frame:RegisterEvent("ADDON_ACTION_FORBIDDEN")
Swatter.Frame:RegisterEvent("ADDON_ACTION_BLOCKED")
SetItemRef = Swatter.SetItemRef
UIParent:UnregisterEvent("ADDON_ACTION_FORBIDDEN")
UIParent:UnregisterEvent("ADDON_ACTION_BLOCKED")
SLASH_SWATTER1 = "/swatter"
SLASH_SWATTER2 = "/swat"
SlashCmdList["SWATTER"] = function(msg)
if (not msg or msg == "" or msg == "help") then
chat("Swatter help:")
chat(" /swat enable - Enables swatter")
chat(" /swat disable - Disables swatter")
chat(" /swat show - Shows the last error box again")
chat(" /swat autoshow - Enables swatter autopopup upon error")
chat(" /swat noauto - Swatter will only show an error in chat")
elseif (msg == "show") then
Swatter.Error:Show()
elseif (msg == "enable") then
SwatterData.enabled = true
chat("Swatter will now catch errors")
elseif (msg == "disable") then
SwatterData.enabled = false
chat("Swatter will no longer catch errors")
elseif (msg == "autoshow") then
SwatterData.autoshow = true
chat("Swatter will popup the first time it sees an error")
elseif (msg == "noautoshow") then
SwatterData.autoshow = false
chat("Swatter will print into chat instead of popping up")
end
end