diff options
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | src/lapi.js | 22 | ||||
| -rw-r--r-- | src/ldblib.js | 82 | ||||
| -rw-r--r-- | src/ldebug.js | 49 | ||||
| -rw-r--r-- | src/ldo.js | 38 | ||||
| -rw-r--r-- | src/lua.js | 27 | ||||
| -rw-r--r-- | src/lvm.js | 9 | ||||
| -rw-r--r-- | tests/ldblib.js | 44 | 
8 files changed, 269 insertions, 7 deletions
| @@ -40,12 +40,9 @@      - [ ] lua_pushfstring      - [ ] lua_pushlightuserdata      - [ ] lua_pushvfstring -    - [ ] lua_rawgetp      - [ ] lua_rawseti -    - [ ] lua_rawsetp      - [ ] lua_register      - [ ] lua_setallocf -    - [ ] lua_sethook      - [ ] lua_tocfunction  - [ ] Auxiliary library      - [x] ... @@ -87,6 +84,7 @@          - [x] debug.getregistry          - [x] debug.getupvalue          - [x] debug.getuservalue +        - [x] debug.sethook          - [x] debug.setlocal          - [x] debug.setmetatable          - [x] debug.setupvalue @@ -95,7 +93,6 @@          - [x] debug.upvalueid          - [x] debug.upvaluejoin          - [ ] debug.gethook -        - [ ] debug.sethook  - [ ] Run [Lua test suite](https://github.com/lua/tests)  - [ ] DOM API binding diff --git a/src/lapi.js b/src/lapi.js index caafc0a..bf9b381 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -403,6 +403,15 @@ const lua_rawset = function(L, idx) {      L.top -= 2;  }; +const lua_rawsetp = function(L, idx, p) { +    assert(1 < L.top - L.ci.funcOff, "not enough elements in the stack"); +    let o = index2addr(L, idx); +    assert(L, o.ttistable(), "table expected"); +    let k = p; +    o.__newindex(o, k, L.stack[L.top - 1]); +    L.top--; +}; +  /*  ** get functions (Lua -> stack)  */ @@ -436,6 +445,15 @@ const lua_rawgeti = function(L, idx, n) {      return L.stack[L.top - 1].ttnov();  }; +const lua_rawgetp = function(L, idx, p) { +    let t = index2addr(L, idx); +    assert(t.ttistable(), "table expected"); +    let k = p; +    L.stack[L.top++] = t.__index(t, k); +    assert(L.top <= L.ci.top, "stack overflow"); +    return L.stack[L.top - 1].ttnov(); +}; +  const lua_rawget = function(L, idx) {      let t = index2addr(L, idx); @@ -875,7 +893,7 @@ const lua_pcallk = function(L, nargs, nresults, errfunc, ctx, k) {          ci.extra = c.funcOff;          ci.u.c.old_errfunc = L.errfunc;          L.errfunc = func; -        // TODO: setoah(ci->callstatus, L->allowhook); +        ci.callstatus &= ~lstate.CIST_OAH | L.allowhook;          ci.callstatus |= lstate.CIST_YPCALL;  /* function can do error recovery */          ldo.luaD_call(L, c.funcOff, nresults);  /* do the call */          ci.callstatus &= ~lstate.CIST_YPCALL; @@ -1049,8 +1067,10 @@ module.exports.lua_pushvalue         = lua_pushvalue;  module.exports.lua_rawequal          = lua_rawequal;  module.exports.lua_rawget            = lua_rawget;  module.exports.lua_rawgeti           = lua_rawgeti; +module.exports.lua_rawgetp           = lua_rawgetp;  module.exports.lua_rawlen            = lua_rawlen;  module.exports.lua_rawset            = lua_rawset; +module.exports.lua_rawsetp           = lua_rawsetp;  module.exports.lua_remove            = lua_remove;  module.exports.lua_replace           = lua_replace;  module.exports.lua_rotate            = lua_rotate; diff --git a/src/ldblib.js b/src/ldblib.js index 27aca96..81f27fb 100644 --- a/src/ldblib.js +++ b/src/ldblib.js @@ -263,6 +263,87 @@ const db_upvaluejoin = function(L) {      return 0;  }; +/* +** The hook table at registry[HOOKKEY] maps threads to their current +** hook function. (We only need the unique address of 'HOOKKEY'.) +*/ +const HOOKKEY = lua.to_luastring("__hooks__"); + +const hooknames = ["call", "return", "line", "count", "tail call"].map(e => lua.to_luastring(e)); + +/* +** Call hook function registered at hook table for the current +** thread (if there is one) +*/ +const hookf = function(L, ar) { +    lapi.lua_rawgetp(L, lua.LUA_REGISTRYINDEX, HOOKKEY); +    lapi.lua_pushthread(L); +    if (lapi.lua_rawget(L, -2) === lua.CT.LUA_TFUNCTION) {  /* is there a hook function? */ +        lapi.lua_pushstring(L, hooknames[ar.event]);  /* push event name */ +        if (ar.currentline >= 0) +            lapi.lua_pushinteger(L, ar.currentline);  /* push current line */ +        else lapi.lua_pushnil(L); +        assert(ldebug.lua_getinfo(L, [char["l"], char["S"]], ar)); +        lapi.lua_call(L, 2, 0);  /* call hook function */ +    } +}; + +/* +** Convert a string mask (for 'sethook') into a bit mask +*/ +const makemask = function(smask, count) { +    let mask = 0; +    if (smask.indexOf(char["c"]) > -1) mask |= lua.LUA_MASKCALL; +    if (smask.indexOf(char["r"]) > -1) mask |= lua.LUA_MASKRET; +    if (smask.indexOf(char["l"]) > -1) mask |= lua.LUA_MASKLINE; +    if (count > 0) mask |= lua.LUA_MASKCOUNT; +    return mask; +}; + +/* +** Convert a bit mask (for 'gethook') into a string mask +*/ +const unmakemask = function(mask, smask) { +    let i = 0; +    if (mask & lua.LUA_MASKCALL) smask[i++] = char["c"]; +    if (mask & lua.LUA_MASKRET) smask[i++] = char["r"]; +    if (mask & lua.LUA_MASKLINE) smask[i++] = char["l"]; +    smask[i] = 0; +    return smask; +}; + +const db_sethook = function(L) { +    let mask, count, func; +    let thread = getthread(L); +    let L1 = thread.thread; +    let arg = thread.arg; +    if (lapi.lua_isnoneornil(L, arg+1)) {  /* no hook? */ +        lapi.lua_settop(L, arg+1); +        func = null; mask = 0; count = 0;  /* turn off hooks */ +    } +    else { +        const smask = lauxlib.luaL_checkstring(L, arg + 2); +        lauxlib.luaL_checktype(L, arg+1, lua.CT.LUA_TFUNCTION); +        count = lauxlib.luaL_optinteger(L, arg + 3, 0); +        func = hookf; mask = makemask(smask, count); +    } +    if (lapi.lua_rawgetp(L, lua.LUA_REGISTRYINDEX, HOOKKEY) === lua.CT.LUA_TNIL) { +        lapi.lua_createtable(L, 0, 2);  /* create a hook table */ +        lapi.lua_pushvalue(L, -1); +        lapi.lua_rawsetp(L, lua.LUA_REGISTRYINDEX, HOOKKEY);  /* set it in position */ +        lapi.lua_pushstring(L, [char["k"]]); +        lapi.lua_setfield(L, -2, lua.to_luastring("__mode"));  /** hooktable.__mode = "k" */ +        lapi.lua_pushvalue(L, -1); +        lapi.lua_setmetatable(L, -2);  /* setmetatable(hooktable) = hooktable */ +    } +    checkstack(L, L1, 1); +    lapi.lua_pushthread(L1); lapi.lua_xmove(L1, L, 1);  /* key (thread) */ +    lapi.lua_pushvalue(L, arg + 1);  /* value (hook function) */ +    lapi.lua_rawset(L, -3);  /* hooktable[L1] = new Lua hook */ +    ldebug.lua_sethook(L1, func, mask, count); +    return 0; +}; +  const db_traceback = function(L) {      let thread = getthread(L);      let L1 = thread.thread; @@ -284,6 +365,7 @@ const dblib = {      "getregistry":  db_getregistry,      "getupvalue":   db_getupvalue,      "getuservalue": db_getuservalue, +    "sethook":      db_sethook,      "setlocal":     db_setlocal,      "setmetatable": db_setmetatable,      "setupvalue":   db_setupvalue, diff --git a/src/ldebug.js b/src/ldebug.js index 44410a6..a73b7f7 100644 --- a/src/ldebug.js +++ b/src/ldebug.js @@ -38,6 +38,19 @@ const swapextra = function(L) {      }  }; +const lua_sethook = function(L, func, mask, count) { +    if (func === null || mask === 0) {  /* turn off hooks? */ +        mask = 0; +        func = null; +    } +    if (L.ci.callstatus & lstate.CIST_LUA) +        L.oldpc = L.ci.pcOff; +    L.hook = func; +    L.basehookcount = count; +    L.hookcount = L.basehookcount; +    L.hookmask = mask; +}; +  const lua_getstack = function(L, level, ar) {      let ci;      let status; @@ -577,6 +590,40 @@ const luaG_tointerror = function(L, p1, p2) {      luaG_runerror(L, lua.to_luastring(`number${lobject.jsstring(varinfo(L, p2))} has no integer representation`));  }; +const luaG_traceexec = function(L) { +    let ci = L.ci; +    let mask = L.hookmask; +    let counthook = (--L.hookcount === 0 && (mask & lua.LUA_MASKCOUNT)); +    if (counthook) +        L.hookcount = L.basehookcount;  /* reset count */ +    else if (!(mask & lua.LUA_MASKLINE)) +        return;  /* no line hook and count != 0; nothing to be done */ +    if (ci.callstatus & lstate.CIST_HOOKYIELD) {  /* called hook last time? */ +        ci.callstatus &= ~lstate.CIST_HOOKYIELD;  /* erase mark */ +        return;  /* do not call hook again (VM yielded, so it did not move) */ +    } +    if (counthook) +        ldo.luaD_hook(L, lua.LUA_HOOKCOUNT, -1);  /* call count hook */ +    if (mask & lua.LUA_MASKLINE) { +        let p = ci.func.p; +        let npc = ci.pcOff; // pcRel(ci.u.l.savedpc, p); +        let newline = p.lineinfo ? p.lineinfo[npc] : -1; +        if (npc === 0 ||  /* call linehook when enter a new function, */ +            ci.pcOff <= L.oldpc ||  /* when jump back (loop), or when */ +            newline !== p.lineinfo ? p.lineinfo[L.oldpc] : -1)  /* enter a new line */ +            ldo.luaD_hook(L, lua.LUA_HOOKLINE, newline);  /* call line hook */ +    } +    L.oldpc = ci.pcOff; +    if (L.status === TS.LUA_YIELD) {  /* did hook yield? */ +        if (counthook) +            L.hookcount = 1;  /* undo decrement to zero */ +        ci.u.l.savedpc--;  /* undo increment (resume will increment it again) */ +        ci.callstatus |= lstate.CIST_HOOKYIELD;  /* mark that it yielded */ +        ci.func = L.top - 1;  /* protect stack below results */ +        ldo.luaD_throw(L, TS.LUA_YIELD); +    } +}; +  module.exports.luaG_addinfo     = luaG_addinfo;  module.exports.luaG_concaterror = luaG_concaterror;  module.exports.luaG_errormsg    = luaG_errormsg; @@ -584,8 +631,10 @@ module.exports.luaG_opinterror  = luaG_opinterror;  module.exports.luaG_ordererror  = luaG_ordererror;  module.exports.luaG_runerror    = luaG_runerror;  module.exports.luaG_tointerror  = luaG_tointerror; +module.exports.luaG_traceexec   = luaG_traceexec;  module.exports.luaG_typeerror   = luaG_typeerror;  module.exports.lua_getinfo      = lua_getinfo;  module.exports.lua_getlocal     = lua_getlocal;  module.exports.lua_getstack     = lua_getstack; +module.exports.lua_sethook      = lua_sethook;  module.exports.lua_setlocal     = lua_setlocal; @@ -73,7 +73,8 @@ const luaD_precall = function(L, off, nresults) {              ci.funcOff = off;              ci.top = L.top + lua.LUA_MINSTACK;              ci.callstatus = 0; -            // TODO: hook +            if (L.hookmask & lua.LUA_MASKCALL) +                luaD_hook(L, lua.LUA_HOOKCALL, -1);              let n = f(L); /* do the actual call */              assert(n < L.top - L.ci.funcOff, "not enough elements in the stack"); @@ -129,6 +130,13 @@ const luaD_precall = function(L, off, nresults) {  const luaD_poscall = function(L, ci, firstResult, nres) {      let wanted = ci.nresults; + +    if (L.hookmask & (lua.LUA_MASKRET | lua.LUA_MASKLINE)) { +        if (L.hookmask & lua.LUA_MASKRET) +            luaD_hook(L, lua.LUA_HOOKRET, -1); +        L.oldpc = ci.previous.pcOff;  /* 'oldpc' for caller function */ +    } +      let res = ci.funcOff;      L.ci = ci.previous;      L.ciOff--; @@ -171,6 +179,33 @@ const moveresults = function(L, firstResult, res, nres, wanted) {      return true;  }; +/* +** Call a hook for the given event. Make sure there is a hook to be +** called. (Both 'L->hook' and 'L->hookmask', which triggers this +** function, can be changed asynchronously by signals.) +*/ +const luaD_hook = function(L, event, line) { +    let hook = L.hook; +    if (hook && L.allowhook) {  /* make sure there is a hook */ +        let ci = L.ci; +        let top = L.top; +        let ci_top = ci.top; +        let ar = new lua.lua_Debug(); +        ar.event = event; +        ar.currentline = line; +        ar.i_ci = ci; +        ci.top = L.top + lua.LUA_MINSTACK; +        L.allowhook = 0;  /* cannot call hooks inside a hook */ +        ci.callstatus |= lstate.CIST_HOOKED; +        hook(L, ar); +        assert(!L.allowhook); +        L.allowhook = 1; +        ci.top = ci_top; +        L.top = top; +        ci.callstatus &= ~lstate.CIST_HOOKED; +    } +}; +  const adjust_varargs = function(L, p, actual) {      let nfixargs = p.numparams;      /* move fixed parameters to final position */ @@ -566,6 +601,7 @@ module.exports.SParser              = SParser;  module.exports.adjust_varargs       = adjust_varargs;  module.exports.luaD_call            = luaD_call;  module.exports.luaD_callnoyield     = luaD_callnoyield; +module.exports.luaD_hook            = luaD_hook;  module.exports.luaD_pcall           = luaD_pcall;  module.exports.luaD_poscall         = luaD_poscall;  module.exports.luaD_precall         = luaD_precall; @@ -198,6 +198,24 @@ const to_luastring = function(str, maxBytesToWrite) {      return outU8Array;  }; +/* +** Event codes +*/ +const LUA_HOOKCALL     = 0; +const LUA_HOOKRET      = 1; +const LUA_HOOKLINE     = 2; +const LUA_HOOKCOUNT    = 3; +const LUA_HOOKTAILCALL = 4; + + +/* +** Event masks +*/ +const LUA_MASKCALL  = (1 << LUA_HOOKCALL); +const LUA_MASKRET   = (1 << LUA_HOOKRET); +const LUA_MASKLINE  = (1 << LUA_HOOKLINE); +const LUA_MASKCOUNT = (1 << LUA_HOOKCOUNT); +  module.exports.CT                      = CT;  module.exports.FENGARI_AUTHORS         = FENGARI_AUTHORS;  module.exports.FENGARI_COPYRIGHT       = FENGARI_COPYRIGHT; @@ -209,8 +227,17 @@ module.exports.FENGARI_VERSION_NUM     = FENGARI_VERSION_NUM;  module.exports.FENGARI_VERSION_RELEASE = FENGARI_VERSION_RELEASE;  module.exports.LUA_AUTHORS             = LUA_AUTHORS;  module.exports.LUA_COPYRIGHT           = LUA_COPYRIGHT; +module.exports.LUA_HOOKCALL            = LUA_HOOKCALL; +module.exports.LUA_HOOKCOUNT           = LUA_HOOKCOUNT; +module.exports.LUA_HOOKLINE            = LUA_HOOKLINE; +module.exports.LUA_HOOKRET             = LUA_HOOKRET; +module.exports.LUA_HOOKTAILCALL        = LUA_HOOKTAILCALL;  module.exports.LUA_INITVARVERSION      = LUA_INITVARVERSION;  module.exports.LUA_INIT_VAR            = LUA_INIT_VAR; +module.exports.LUA_MASKCALL            = LUA_MASKCALL; +module.exports.LUA_MASKCOUNT           = LUA_MASKCOUNT; +module.exports.LUA_MASKLINE            = LUA_MASKLINE; +module.exports.LUA_MASKRET             = LUA_MASKRET;  module.exports.LUA_MINSTACK            = LUA_MINSTACK;  module.exports.LUA_MULTRET             = -1;  module.exports.LUA_NUMTAGS             = LUA_NUMTAGS; @@ -123,8 +123,15 @@ const luaV_execute = function(L) {              base = ci.u.l.base;              i = ci.u.l.savedpc[ci.pcOff++]; + +            if (L.hookmask & (lua.LUA_MASKLINE | lua.LUA_MASKCOUNT)) { +                ldebug.luaG_traceexec(L); +                base = ci.u.l.base; +            } + +              ra = RA(L, base, i); -            opcode = i.opcode +            opcode = i.opcode;          }          if (i.breakpoint) // TODO: remove, used until lapi diff --git a/tests/ldblib.js b/tests/ldblib.js index 7dd35c3..8d00952 100644 --- a/tests/ldblib.js +++ b/tests/ldblib.js @@ -7,6 +7,50 @@ const lauxlib  = require("../src/lauxlib.js");  const lua      = require('../src/lua.js');  const linit    = require('../src/linit.js'); +test('debug.sethook', function (t) { +    let luaCode = ` +        result = "" + +        debug.sethook(function (event) +            result = result .. event .. " " +        end, "crl", 1) + +        local l = function() end + +        l() +        l() +        l() + +        return result +    `, L; +     +    t.plan(3); + +    t.doesNotThrow(function () { + +        L = lauxlib.luaL_newstate(); + +        linit.luaL_openlibs(L); + +        lauxlib.luaL_loadstring(L, lua.to_luastring(luaCode)); + +    }, "Lua program loaded without error"); + +    t.doesNotThrow(function () { + +        lapi.lua_call(L, 0, -1); + +    }, "Lua program ran without error"); + +    t.strictEqual( +        lapi.lua_tojsstring(L, -1), +        "return count line count line count line count return count line count line count return count line count line count return count line count line ", +        "Correct element(s) on the stack" +    ); + +}); + +  test('debug.getlocal', function (t) {      let luaCode = `          local alocal = "alocal" | 
