Post-hooking a function safely
From WoWWiki
Contents |
This HOWTO discusses parameter and return value handling in a safer-than-usual way.
This HOWTO deals with post-hooks. For details on pre-hooks, see HOWTO: Hook a function safely.
For more information on the actual hooking of functions, see HOWTO: Hook a Function.
How you usually do it
local orig_foo = foo function foo(a1, a2) local r1, r2, r3 = orig_foo(a1, a2) -- some code that looks at e.g. a1 or r1 return r1, r2, r3 end
The problem with this method is that it only handles a fixed number of args and returns. If the function's API is changed in the future it could break the function completely. Fortunately we have a way to guarantee that it'll work, both today, and in the future!
The safe way to do it
local orig_foo = foo function posthook(a1, a2, r1, r2, ...) --do something with the args (a1,a2) or return values (r1,r2) that we're interested in return r1, r2, ... -- this returns all the original's returns, which were passed to us end function foo(a1, a2, ...) return posthook(a1, a2, orig_foo(a1, a2, ...)) end
Or, if you're only interested in return values:
local orig_foo = foo function posthook(r1, r2, ...) --do something with r1,r2 (the first two return values) return r1, r2, ... -- this returns all the original's returns, which were passed to us end function foo(...) return posthook(orig_foo(...)) end
What this does is ensure that all args are passed to the original, even if you don't know how many args there are. We also ensure that all values are returned back. As an added bonus, the use of a local for saving the original function and making a proper tail call give us the best performance we can get, thus minimizing the cost of our hook.
Won't there be a huge performance impact?
The common pre-WoW-2.0 design, which used unpack(), created a garbage table every time the hook was called. The new design takes advantage of the new, more powerful '...' variable, removing this source of garbage. With Lua 5.1 we can ensure that all args are passed to the original, and all returns are returned out of our hook, without spending a table's worth of memory on every call.
If tainting is an issue...
function foo(a1)
-- do something with a1
end
hooksecurefunc("SomeBlizzardFunction", foo);
What's the difference here? hooksecurefunc() creates a secure hook, meaning that it will not taint code. This is critical in applications where the hooked function will need to be in a secure execution path; hooking without hooksecurefunc() in these cases will result in a taint of the execution path, and then hours of debugging because Blizzard APIs are no longer working the way they should. Unfortunately, hooksecurefunc() is much more expensive than the previously mentioned methods, so it should only be used when tainting is an issue.
