Support Hooks in GopherLua
This issue is created to introduce hooks in gopher-lua.
Implementation:
- A
Hookerinterface will be created, that hascallandStringfunctions.
type Hooker interface {
call(L *LState, cf *callFrame)
String() string
}
-
lhook,rhook,chookandcthookareHookertyped variables that will be declared inLStatestruct. andprevlineto keep track of last executed line.
type LState struct {
..............
chook Hooker
rhook Hooker
lhook Hooker
cthook Hooker
prevline int
}
- The
callfunction will be used to execute the behaviour of the hook.LineHookcall implementation.
func (lh *LHook) call(L *LState, cf *callFrame) {
currentline := cf.Fn.Proto.DbgSourcePositions[cf.Pc-1]
if currentline != 0 && cf.Fn != lh.callback && currentline != L.prevline {
L.reg.Push(lh.callback)
L.reg.Push(LString("l"))
L.reg.Push(LNumber(currentline))
L.callR(2, 0, -1)
L.prevline = currentline
}
}
- VM API: event may contain "lcr" (any not recognised letter will return error) and let count param
<= 0to keepcounthook disabled.
err := L.SetHook(callback *LFunction, event string, count int)
Progress:
I have already implemented the four hooks. Line hook required me to fix a bug (I guess) in compile.go that was ignoring the table items lines when the table expression gets compiled.
Test case: hook.lua
function tracer(event, line)
print("from hook", event, line)
end
debug.sethook(tracer, "l")
local obj = {
foo="bar",
baz="qux"
}
for k, v in pairs(obj) do
print(k, v)
end
local x = 5
while x == 5 do
x = 6
end
if x == 6 then
print(true)
end
if x == 7 then
print(false)
elseif x == 8 then
print(false)
else
print(true)
end
local y = 0
repeat
y = y + 1
until y == 3
print(y)
Glua's output:
from hook l 8
from hook l 9
from hook l 10
from hook l 13
from hook l 14
baz qux
from hook l 13
from hook l 14
foo bar
from hook l 13
from hook l 17
from hook l 18
from hook l 19
from hook l 20
from hook l 18
from hook l 22
from hook l 23
true
from hook l 26
from hook l 28
from hook l 31
true
from hook l 34
from hook l 36
from hook l 37
from hook l 36
from hook l 37
from hook l 36
from hook l 37
from hook l 39
3
Lua output:
from hook line 8
from hook line 9
from hook line 10
from hook line 13
from hook line 14
foo bar
from hook line 13
from hook line 14
baz qux
from hook line 13
from hook line 17
from hook line 18
from hook line 19
from hook line 18
from hook line 22
from hook line 23
true
from hook line 26
from hook line 28
from hook line 31
true
from hook line 34
from hook line 36
from hook line 37
from hook line 36
from hook line 37
from hook line 36
from hook line 37
from hook line 39
3
Outputs differences:
-
It is interesting that Lua looped over the
objtable from the last item to the first, while Glua started from the first item. (Not sure whether it's matter ofpairsimplementation orfor inloop). -
Lua jumps over
endlabel ofwhilestatement, while Glua counts it. -
As for
call,returnhooks, I can tell thatluais making way more calls to the callback thanglua. It's either I need to call the hooks in places I'm not aware of, or we're making less calls toOP_RETURNandOP_CALL.
Missing:
- ~return, call, count hooks.~
- Reset hooks.
- Tests.
- Benchmark.
Compiled and tested on:
- Intel Core i5 Skylake 6360U processor (MacBook Pro 13" 2016 base model).
- Go version go1.8.3 darwin/amd64
@yuin I pushed a PR for early review, and to let you confirm the implementation.
return, call and count hooks have been implemented.
Initial benchmark shows that if false condition has no effect at all.
Close issue?
any progress? need this pr
Indeed it would be very nice to see the getting merged and I think this would be a very useful feature. Currently its a bit hard to debug scripts running on gopher-lua.
@ysweid Since I need to analyse glua performance issues, I tried to use your hook implementation and had some problems: 1.bug found:in
https://github.com/ysweid/gopher-lua/blob/39e4eebec3f18f3d5de4d9ccc1410f4e5d37f90f/hook.go#L24
.You need to add a variable or recursively search for parent to avoid this: when a line hook 's callback function contains a function call, it appears that the callback function itself triggers the line hook,code like this:(add isCalled variable)
if currentline != 0 && cf.Fn != lh.callback && currentline != L.prevline{
lh.isCalled = true
L.reg.Push(lh.callback)
L.reg.Push(LString("line"))
L.reg.Push(LNumber(currentline))
L.callR(2, 0, -1)
L.prevline = currentline
lh.isCalled = false
}
or recursively search for parent and add a judgemental condition: !isFunctionInCallFrameChain(cf, rh.callback):
func isFunctionInCallFrameChain(cf *callFrame, fn *LFunction) bool {
if cf.Fn == fn {
return true
}
if cf.Parent != nil {
return isFunctionInCallFrameChain(cf.Parent, fn)
}
return false
}
2. two questions 2.1 I tried to use luatrace tool in gopher-lua,several places appear to behave differently from lua using the c language: when line and call hook are both used,c-lua first trigger call hook and then line hook,but glua is the opposite. I tried to change this but failed: my change like this:
return_value:=jumpTable[int(inst>>26)](L, inst, baseframe)
if L.lhook != nil {
L.lhook.call(L, cf)
}
if L.cthook != nil {
L.cthook.call(L, cf)
}
if return_value == 1 {
return
}
The reason I changed it is that I noticed that the jumpTable is only used here, and the call hook is added to the call function in the jumpTable,but it doesn't work.I'm curious as to why it didn't work. 2.2 Finally,I found in tailcall condition(such as this: return func()): gopher-lua looks to optimise all the tailcalls into one function with only once return hook trigger,but in c-lua it will trigger return hook whether or not is tailcall,I don't know where to change this,maybe in jumpTable? (because I noticed that there's only OP_TAILCALL function in jumpTable, do not have OP_TAILRETURN)?