From fc08312ebf8cf01a53b4826acce0f1c3aedcdc53 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 12 Apr 2017 07:55:38 +0200 Subject: debug.traceback --- README.md | 3 +- src/lapi.js | 5 +-- src/lauxlib.js | 80 ++++++++++++++++++++++++++++++++++---- src/ldblib.js | 17 +++++++- src/ldebug.js | 11 ++++-- src/lobject.js | 6 +-- tests/ldblib.js | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 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 */ + 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" + ); + +}); -- cgit v1.2.3-70-g09d2