yuin / gopher-lua

GopherLua: VM and compiler for Lua in Go
MIT License
6.21k stars 648 forks source link

Support Hooks in GopherLua #130

Open ysweid opened 7 years ago

ysweid commented 7 years ago

This issue is created to introduce hooks in gopher-lua.

Implementation:

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:

Missing:

Compiled and tested on:

@yuin I pushed a PR for early review, and to let you confirm the implementation.

ysweid commented 7 years ago

return, call and count hooks have been implemented.

Initial benchmark shows that if false condition has no effect at all.

ghost commented 6 years ago

Close issue?

edolphin-ydf commented 4 years ago

any progress? need this pr

guerillagrow commented 4 years ago

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.

wufei-png commented 8 months ago

@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)?