aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Giannangeli <giann008@gmail.com>2017-04-12 07:55:38 +0200
committerBenoit Giannangeli <giann008@gmail.com>2017-04-12 09:18:43 +0200
commitfc08312ebf8cf01a53b4826acce0f1c3aedcdc53 (patch)
tree5b1c3a1276037b482102bfe2c36bcb31c459acac
parent58fcb1d7f55424de904fd6ae53954b46d82aae8c (diff)
downloadfengari-fc08312ebf8cf01a53b4826acce0f1c3aedcdc53.tar.gz
fengari-fc08312ebf8cf01a53b4826acce0f1c3aedcdc53.tar.bz2
fengari-fc08312ebf8cf01a53b4826acce0f1c3aedcdc53.zip
debug.traceback
-rw-r--r--README.md3
-rw-r--r--src/lapi.js5
-rw-r--r--src/lauxlib.js80
-rw-r--r--src/ldblib.js17
-rw-r--r--src/ldebug.js11
-rw-r--r--src/lobject.js6
-rw-r--r--tests/ldblib.js117
7 files changed, 219 insertions, 20 deletions
diff --git a/README.md b/README.md
index cb4ad18..112e227 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,6 @@
- [ ] luaL_ref
- [ ] luaL_setmetatable
- [ ] luaL_testudata
- - [ ] luaL_traceback
- [ ] luaL_unref
- [ ] Standard library
- [x] Base lib
@@ -88,6 +87,7 @@
- [ ] Debug
- [x] debug.debug
- [x] debug.getlocal
+ - [x] debug.traceback
- [ ] debug.gethook
- [ ] debug.getinfo
- [ ] debug.getmetatable
@@ -99,7 +99,6 @@
- [ ] debug.setmetatable
- [ ] debug.setupvalue
- [ ] debug.setuservalue
- - [ ] debug.traceback
- [ ] debug.upvalueid
- [ ] debug.upvaluejoin
- [ ] Run [Lua test suite](https://github.com/lua/tests)
diff --git a/src/lapi.js b/src/lapi.js
index 7f8cda8..fcb07d6 100644
--- a/src/lapi.js
+++ b/src/lapi.js
@@ -173,9 +173,8 @@ const lua_rotate = function(L, idx, n) {
};
const lua_copy = function(L, fromidx, toidx) {
- let fr = index2addr_(L, fromidx);
- let to = index2addr_(L, toidx);
- L.stack[to] = fr;
+ let from = index2addr(L, fromidx);
+ L.stack[index2addr_(L, toidx)] = new TValue(from.type, from.value);
};
const lua_remove = function(L, idx) {
diff --git a/src/lauxlib.js b/src/lauxlib.js
index 03ca210..311e2eb 100644
--- a/src/lauxlib.js
+++ b/src/lauxlib.js
@@ -6,6 +6,7 @@ const assert = require('assert');
const lstate = require('./lstate.js');
const lapi = require('./lapi.js');
const lua = require('./lua.js');
+const char = lua.char;
const ldebug = require('./ldebug.js');
const lobject = require('./lobject.js');
const CT = lua.constant_types;
@@ -20,6 +21,9 @@ class luaL_Buffer {
}
}
+const LEVELS1 = 10; /* size of the first part of the stack */
+const LEVELS2 = 11; /* size of the second part of the stack */
+
/*
** search for 'objidx' in table at index -1.
** return 1 + string at top if find a good name.
@@ -54,22 +58,83 @@ const findfield = function(L, objidx, level) {
*/
const pushglobalfuncname = function(L, ar) {
let top = lapi.lua_gettop(L);
- ldebug.lua_getinfo(L, 'f', ar); /* push function */
+ ldebug.lua_getinfo(L, [char['f']], ar); /* push function */
lapi.lua_getfield(L, lua.LUA_REGISTRYINDEX, lua.to_luastring(LUA_LOADED_TABLE));
if (findfield(L, top + 1, 2)) {
let name = lapi.lua_tostring(L, -1);
- if (name.jsstring().startsWith("_G.")) {
- lapi.lua_pushstring(L, name.slice(3)); /* name start with '_G.'? */
- lapi.lua_remove(L, -2); /* name start with '_G.'? */
+ if (lobject.jsstring(name).startsWith("_G.")) { /* name start with '_G.'? */
+ lapi.lua_pushstring(L, name.slice(3)); /* push name without prefix */
+ lapi.lua_remove(L, -2); /* remove original name */
}
- lapi.lua_copy(L, -1, top + 1); /* name start with '_G.'? */
- lapi.lua_pop(L, 2); /* name start with '_G.'? */
+ lapi.lua_copy(L, -1, top + 1); /* move name to proper place */
+ lapi.lua_pop(L, 2); /* remove pushed values */
+ return 1;
} else {
lapi.lua_settop(L, top); /* remove function and global table */
return 0;
}
};
+const sv = s => s ? s : [];
+
+const pushfuncname = function(L, ar) {
+ if (pushglobalfuncname(L, ar)) { /* try first a global name */
+ lapi.lua_pushstring(L, lua.to_luastring("function '").concat(lapi.lua_tostring(L, -1)).concat([char["'"]]));
+ lapi.lua_remove(L, -2); /* remove name */
+ }
+ else if (ar.namewhat) /* is there a name from code? */
+ lapi.lua_pushstring(L, sv(ar.namewhat).concat(char[" "], char["'"], ...sv(ar.name.value), char["'"])); /* use it */
+ else if (ar.what && ar.what[0] === char['m']) /* main? */
+ lapi.lua_pushliteral(L, "main chunk");
+ else if (ar.what && ar.what[0] != char['C']) /* for Lua functions, use <file:line> */
+ lapi.lua_pushstring(L, lua.to_luastring("function <").concat(...sv(ar.short_src), char[':'], ...lua.to_luastring(`${ar.linedefined}>`)));
+ else /* nothing left... */
+ lapi.lua_pushliteral(L, "?");
+};
+
+const lastlevel = function(L) {
+ let ar = new lua.lua_Debug();
+ let li = 1;
+ let le = 1;
+ /* find an upper bound */
+ while (ldebug.lua_getstack(L, le, ar)) { li = le; le *= 2; }
+ /* do a binary search */
+ while (li < le) {
+ let m = Math.floor((li + le)/2);
+ if (ldebug.lua_getstack(L, m, ar)) li = m + 1;
+ else le = m;
+ }
+ return le - 1;
+};
+
+const luaL_traceback = function(L, L1, msg, level) {
+ let ar = new lua.lua_Debug();
+ let top = lapi.lua_gettop(L);
+ let last = lastlevel(L1);
+ let n1 = last - level > LEVELS1 + LEVELS2 ? LEVELS1 : -1;
+ if (msg)
+ lapi.lua_pushstring(L, msg.concat(char['\n']));
+ luaL_checkstack(L, 10, null);
+ lapi.lua_pushliteral(L, "stack traceback:");
+ while (ldebug.lua_getstack(L1, level++, ar)) {
+ if (n1-- === 0) { /* too many levels? */
+ lapi.lua_pushliteral(L, "\n\t..."); /* add a '...' */
+ level = last - LEVELS2 + 1; /* and skip to last ones */
+ } else {
+ ldebug.lua_getinfo(L1, lua.to_luastring("Slnt"), ar);
+ lapi.lua_pushstring(L, [char['\n'], char['\t'], char['.'], char['.'], char['.']].concat(ar.short_src));
+ if (ar.currentline > 0)
+ lapi.lua_pushliteral(L, `${ar.currentline}:`);
+ lapi.lua_pushliteral(L, " in ");
+ pushfuncname(L, ar);
+ if (ar.istailcall)
+ lapi.lua_pushliteral(L, "\n\t(...tail calls..)");
+ lapi.lua_concat(L, lapi.lua_gettop(L) - top);
+ }
+ }
+ lapi.lua_concat(L, lapi.lua_gettop(L) - top);
+};
+
const panic = function(L) {
throw new Error(`PANIC: unprotected error in call to Lua API (${lapi.lua_tojsstring(L, -1)})`);
};
@@ -82,7 +147,7 @@ const luaL_argerror = function(L, arg, extramsg) {
ldebug.lua_getinfo(L, 'n', ar);
- if (ar.namewhat === 'method') {
+ if (ar.namewhat === lua.to_luastring('method')) {
arg--; /* do not count 'self' */
if (arg === 0) /* error is in the self argument itself? */
return luaL_error(L, lua.to_luastring(`calling '${lobject.jsstring(ar.name)}' on bad self (${lobject.jsstring(extramsg)})`));
@@ -586,6 +651,7 @@ module.exports.luaL_pushresult = luaL_pushresult;
module.exports.luaL_requiref = luaL_requiref;
module.exports.luaL_setfuncs = luaL_setfuncs;
module.exports.luaL_tolstring = luaL_tolstring;
+module.exports.luaL_traceback = luaL_traceback;
module.exports.luaL_typename = luaL_typename;
module.exports.luaL_where = luaL_where;
module.exports.lua_writestringerror = lua_writestringerror;
diff --git a/src/ldblib.js b/src/ldblib.js
index 426cef2..318e616 100644
--- a/src/ldblib.js
+++ b/src/ldblib.js
@@ -84,8 +84,23 @@ const db_upvalueid = function(L) {
return 1;
};
+const db_traceback = function(L) {
+ let thread = getthread(L);
+ let L1 = thread.thread;
+ let arg = thread.arg;
+ let msg = lapi.lua_tostring(L, arg + 1);
+ if (msg === null && !lapi.lua_isnoneornil(L, arg + 1)) /* non-string 'msg'? */
+ lapi.lua_pushvalue(L, arg + 1); /* return it untouched */
+ else {
+ let level = lauxlib.luaL_optinteger(L, arg + 2, L === L1 ? 1 : 0);
+ lauxlib.luaL_traceback(L, L1, msg, level);
+ }
+ return 1;
+};
+
const dblib = {
- "getlocal": db_getlocal,
+ "getlocal": db_getlocal,
+ "traceback": db_traceback,
"upvalueid": db_upvalueid
};
diff --git a/src/ldebug.js b/src/ldebug.js
index 5a5fc34..ce62abc 100644
--- a/src/ldebug.js
+++ b/src/ldebug.js
@@ -194,10 +194,13 @@ const auxgetinfo = function(L, what, ar, f, ci) {
break;
}
case 'n': {
- ar.namewhat = getfuncname(L, ci, ar.name);
- if (ar.namewhat === null) {
- ar.namewhat = [];
+ let r = getfuncname(L, ci);
+ if (r === null) {
+ ar.namewhat = null;
ar.name = null;
+ } else {
+ ar.namewhat = r.funcname;
+ ar.name = r.name;
}
break;
}
@@ -395,7 +398,7 @@ const funcnamefromcode = function(L, ci) {
let tm = 0; /* (initial value avoids warnings) */
let p = ci.func.p; /* calling function */
- let pc = ci.pcOff; /* calling instruction index */
+ let pc = ci.pcOff - 1; /* calling instruction index */
let i = p.code[pc]; /* calling instruction */
if (ci.callstatus & lstate.CIST_HOOKED) {
diff --git a/src/lobject.js b/src/lobject.js
index 66a0dcb..618e801 100644
--- a/src/lobject.js
+++ b/src/lobject.js
@@ -306,12 +306,12 @@ const luaO_chunkid = function(source, bufflen) {
out = RETS.concat(source.slice(1, l - bufflen));
}
} else { /* string; format as [string "source"] */
- let nli = source.indexOf('\n'); /* find first new line (if any) */
- let nl = nli ? source.slice(nli) : null;
+ let nli = source.indexOf(char['\n']); /* find first new line (if any) */
+ let nl = nli > -1 ? source.slice(nli) : null;
out = PRE; /* add prefix */
bufflen -= PRE.length + RETS.length + POS.length + 1; /* save space for prefix+suffix+'\0' */
if (l < bufflen && nl === null) { /* small one-line source? */
- out = out.conat(source); /* keep it */
+ out = out.concat(source); /* keep it */
} else {
if (nl !== null) l = nl.length - source.length; /* stop at first newline */
if (l > bufflen) l = bufflen;
diff --git a/tests/ldblib.js b/tests/ldblib.js
index 475c031..c95ab9e 100644
--- a/tests/ldblib.js
+++ b/tests/ldblib.js
@@ -89,3 +89,120 @@ test('debug.upvalueid', function (t) {
);
});
+
+
+test('debug.traceback (with a global)', function (t) {
+ let luaCode = `
+ local trace
+
+ rec = function(n)
+ n = n or 0
+ if n < 10 then
+ rec(n + 1)
+ else
+ trace = debug.traceback()
+ end
+ end
+
+ rec()
+
+ return trace
+ `, L;
+
+ t.plan(3);
+
+ t.doesNotThrow(function () {
+
+ L = lauxlib.luaL_newstate();
+
+ linit.luaL_openlibs(L);
+
+ luaCode = lua.to_luastring(luaCode);
+ lauxlib.luaL_loadbuffer(L, luaCode, luaCode.length, lua.to_luastring("traceback-test"));
+
+ }, "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),
+`stack traceback:
+\t...[string "traceback-test"]9: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]7: in function 'rec'
+\t...[string "traceback-test"]15: in main chunk`,
+ "Correct element(s) on the stack"
+ );
+
+});
+
+
+test('debug.traceback (with a upvalue)', function (t) {
+ let luaCode = `
+ local trace
+ local rec
+
+ rec = function(n)
+ n = n or 0
+ if n < 10 then
+ rec(n + 1)
+ else
+ trace = debug.traceback()
+ end
+ end
+
+ rec()
+
+ return trace
+ `, L;
+
+ t.plan(3);
+
+ t.doesNotThrow(function () {
+
+ L = lauxlib.luaL_newstate();
+
+ linit.luaL_openlibs(L);
+
+ luaCode = lua.to_luastring(luaCode);
+ lauxlib.luaL_loadbuffer(L, luaCode, luaCode.length, lua.to_luastring("traceback-test"));
+
+ }, "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),
+`stack traceback:
+\t...[string "traceback-test"]10: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in upvalue 'rec'
+\t...[string "traceback-test"]8: in local 'rec'
+\t...[string "traceback-test"]16: in main chunk`,
+ "Correct element(s) on the stack"
+ );
+
+});