summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Giannangeli <giann008@gmail.com>2017-04-14 08:59:00 +0200
committerBenoit Giannangeli <giann008@gmail.com>2017-04-14 11:06:19 +0200
commit43c97cbc2904d2bac87c61515bbc16c38a091548 (patch)
tree31dee0450518dc6a6aebd913e450bcd8ed4131b2
parentfd613ef1da5e3eeb10d13351ccf217e33b30b1dd (diff)
downloadfengari-43c97cbc2904d2bac87c61515bbc16c38a091548.tar.gz
fengari-43c97cbc2904d2bac87c61515bbc16c38a091548.tar.bz2
fengari-43c97cbc2904d2bac87c61515bbc16c38a091548.zip
hooks
-rw-r--r--README.md5
-rw-r--r--src/lapi.js22
-rw-r--r--src/ldblib.js82
-rw-r--r--src/ldebug.js49
-rw-r--r--src/ldo.js38
-rw-r--r--src/lua.js27
-rw-r--r--src/lvm.js9
-rw-r--r--tests/ldblib.js44
8 files changed, 269 insertions, 7 deletions
diff --git a/README.md b/README.md
index e32f8f5..01fcbde 100644
--- a/README.md
+++ b/README.md
@@ -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;
diff --git a/src/ldo.js b/src/ldo.js
index 6a74cad..0fcbf0e 100644
--- a/src/ldo.js
+++ b/src/ldo.js
@@ -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;
diff --git a/src/lua.js b/src/lua.js
index d4f620b..1423750 100644
--- a/src/lua.js
+++ b/src/lua.js
@@ -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;
diff --git a/src/lvm.js b/src/lvm.js
index ad1e807..6febe4b 100644
--- a/src/lvm.js
+++ b/src/lvm.js
@@ -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"