5

I have been trying to create a function that runs an event/function once a certain function is called. I want the usage to be like this:

local a = nil

a = hookfunction(print, function() -- When print() is called...
    warn("print(...) called.") 
    return nil
end)

I know there is a debug.sethook() function, but I don't quite know how to use it.

3 Answers 3

1

There are two ways to do this, but each has some pitfalls you need to deal with.

One way is to use monkeypatching. This will change the print function to a different function that will do what you need and then call the original function.

Something like this:

local origprint = print;
(_G or _ENV).print = function(...)
  io.write("print is called\n")
  return origprint(...)
end
print("foo", 1)

The main pitfall with this option is that if some component saves the value of print before it's monkeypatched, the subsequent usage of it is not going to be affected by your patch.

Another option is to use debug hook:

debug.sethook(function()
    local info = debug.getinfo(2)
    if info.name then
      io.write(info.name, " is called\n")
    end
  end, "c")
function foo()
  print("do something")
end
foo()

This should print:

foo is called
print is called
do something

The main pitfall with this approach is that it doesn't guarantee that it will output the function name as you'd expect, because the values may be associated with different names/containers for them.

For example, this will print a is called:

local a = print
a("do something")

And this will print ? is called:

local a = {print}
a[1]("do something")
Sign up to request clarification or add additional context in comments.

Comments

1

Simplest way would be to wrap the original function:

do
  local _print = print

  function print(...)
    _print("print(...) called.")
    _print(...)
  end
end

print("hello world")

output:

print(...) called.

hello world

what we did hear was stored the original print in a local variable _print which is an upvalue to the new global print function we created after storing the orginal.

Comments

1

The question is lacking a very important detail: Why?

If you want this for debugging, then debug.sethook is probably the more fitting option. As Paul Kulchenko pointed out, debug.getinfo might return the wrong function name, but it also returns the function object itself, which you could use to look up a canonical name in a table. This could also double as a filter for what functions to monitor:

local watch = {}
debug.sethook(function()
   local info = debug.getinfo(2)
   if watch[info.func] then
      print(watch[info.func], "was called")
      -- This *should* not cause a recursion, as
      -- the hook *should* be disabled inside the hook
      -- callback.
   end
end, "c")

-- Somewhere else in your code
watch[print] = "print"

print("foobar")

However, if you intend to use this for anything other than debugging, you should avoid debug.sethook whenever possible, as it may mess with other libraries that rely on debug hooks (like luacov to name an example); in this case, it'd be better to replace the function:

do local p = print
   function print(...)
      p("Print was called!")
      return p(...)
   end
end

but keep in mind that you shouldn't change the behavior of ahy functions or you might break code somewhere else. This also includes the runtime; if you make a very performant function do lots of heavy lifting, that might completely break performance in unexpected ways.

Also keep in mind that some libraries localize global functions that they expect to use. While this is generally very bad practice, there seems to be a myth that this magically makes Lua code faster (there's a reason for this myth, but in most instances, it's not true); this means your replacement has to happen before any of those libraries are loaded.

For that same reason, by the way, you shouldn't usually localize globals unless you know you need a persisteny reference to the original function as is the case with the p variable in the example above.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.