"use strict"; const { LUA_HOOKCALL, LUA_HOOKRET, LUA_HOOKTAILCALL, LUA_MASKCALL, LUA_MASKLINE, LUA_MASKRET, LUA_MINSTACK, LUA_MULTRET, LUA_SIGNATURE, constant_types: { LUA_TCCL, LUA_TLCF, LUA_TLCL, LUA_TNIL }, thread_status: { LUA_ERRMEM, LUA_ERRERR, LUA_ERRRUN, LUA_ERRSYNTAX, LUA_OK, LUA_YIELD }, lua_Debug, luastring_indexOf, to_luastring } = require('./defs.js'); const lapi = require('./lapi.js'); const ldebug = require('./ldebug.js'); const lfunc = require('./lfunc.js'); const { api_check, lua_assert, LUAI_MAXCCALLS } = require('./llimits.js'); const lobject = require('./lobject.js'); const lopcodes = require('./lopcodes.js'); const lparser = require('./lparser.js'); const lstate = require('./lstate.js'); const { luaS_newliteral } = require('./lstring.js'); const ltm = require('./ltm.js'); const { LUAI_MAXSTACK } = require('./luaconf.js'); const lundump = require('./lundump.js'); const lvm = require('./lvm.js'); const { MBuffer } = require('./lzio.js'); const adjust_top = function(L, newtop) { if (L.top < newtop) { while (L.top < newtop) L.stack[L.top++] = new lobject.TValue(LUA_TNIL, null); } else { while (L.top > newtop) delete L.stack[--L.top]; } }; const seterrorobj = function(L, errcode, oldtop) { let current_top = L.top; /* extend stack so that L.stack[oldtop] is sure to exist */ while (L.top < oldtop + 1) L.stack[L.top++] = new lobject.TValue(LUA_TNIL, null); switch (errcode) { case LUA_ERRMEM: { lobject.setsvalue2s(L, oldtop, luaS_newliteral(L, "not enough memory")); break; } case LUA_ERRERR: { lobject.setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); break; } default: { lobject.setobjs2s(L, oldtop, current_top - 1); } } while (L.top > oldtop + 1) delete L.stack[--L.top]; }; const ERRORSTACKSIZE = LUAI_MAXSTACK + 200; const luaD_reallocstack = function(L, newsize) { lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE); lua_assert(L.stack_last == L.stack.length - lstate.EXTRA_STACK); L.stack.length = newsize; L.stack_last = newsize - lstate.EXTRA_STACK; }; const luaD_growstack = function(L, n) { let size = L.stack.length; if (size > LUAI_MAXSTACK) luaD_throw(L, LUA_ERRERR); else { let needed = L.top + n + lstate.EXTRA_STACK; let newsize = 2 * size; if (newsize > LUAI_MAXSTACK) newsize = LUAI_MAXSTACK; if (newsize < needed) newsize = needed; if (newsize > LUAI_MAXSTACK) { /* stack overflow? */ luaD_reallocstack(L, ERRORSTACKSIZE); ldebug.luaG_runerror(L, to_luastring("stack overflow", true)); } else luaD_reallocstack(L, newsize); } }; const luaD_checkstack = function(L, n) { if (L.stack_last - L.top <= n) luaD_growstack(L, n); }; const stackinuse = function(L) { let lim = L.top; for (let ci = L.ci; ci !== null; ci = ci.previous) { if (lim < ci.top) lim = ci.top; } lua_assert(lim <= L.stack_last); return lim + 1; /* part of stack in use */ }; const luaD_shrinkstack = function(L) { let inuse = stackinuse(L); let goodsize = inuse + Math.floor(inuse / 8) + 2*lstate.EXTRA_STACK; if (goodsize > LUAI_MAXSTACK) goodsize = LUAI_MAXSTACK; /* respect stack limit */ if (L.stack.length > LUAI_MAXSTACK) /* had been handling stack overflow? */ lstate.luaE_freeCI(L); /* free all CIs (list grew because of an error) */ /* if thread is currently not handling a stack overflow and its good size is smaller than current size, shrink its stack */ if (inuse <= (LUAI_MAXSTACK - lstate.EXTRA_STACK) && goodsize < L.stack.length) luaD_reallocstack(L, goodsize); }; const luaD_inctop = function(L) { luaD_checkstack(L, 1); L.stack[L.top++] = new lobject.TValue(LUA_TNIL, null); }; /* ** Prepares a function call: checks the stack, creates a new CallInfo ** entry, fills in the relevant information, calls hook if needed. ** If function is a JS function, does the call, too. (Otherwise, leave ** the execution ('luaV_execute') to the caller, to allow stackless ** calls.) Returns true iff function has been executed (JS function). */ const luaD_precall = function(L, off, nresults) { let func = L.stack[off]; switch(func.type) { case LUA_TCCL: case LUA_TLCF: { let f = func.type === LUA_TCCL ? func.value.f : func.value; luaD_checkstack(L, LUA_MINSTACK); let ci = lstate.luaE_extendCI(L); ci.funcOff = off; ci.nresults = nresults; ci.func = func; ci.top = L.top + LUA_MINSTACK; lua_assert(ci.top <= L.stack_last); ci.callstatus = 0; if (L.hookmask & LUA_MASKCALL) luaD_hook(L, LUA_HOOKCALL, -1); let n = f(L); /* do the actual call */ if (typeof n !== "number" || n < 0 || (n|0) !== n) throw Error("invalid return value from JS function (expected integer)"); lapi.api_checknelems(L, n); luaD_poscall(L, ci, L.top - n, n); return true; } case LUA_TLCL: { let base; let p = func.value.p; let n = L.top - off - 1; let fsize = p.maxstacksize; luaD_checkstack(L, fsize); if (p.is_vararg) { base = adjust_varargs(L, p, n); } else { for (; n < p.numparams; n++) L.stack[L.top++] = new lobject.TValue(LUA_TNIL, null); // complete missing arguments base = off + 1; } let ci = lstate.luaE_extendCI(L); ci.funcOff = off; ci.nresults = nresults; ci.func = func; ci.l_base = base; ci.top = base + fsize; adjust_top(L, ci.top); ci.l_code = p.code; ci.l_savedpc = 0; ci.callstatus = lstate.CIST_LUA; if (L.hookmask & LUA_MASKCALL) callhook(L, ci); return false; } default: luaD_checkstack(L, 1); tryfuncTM(L, off, func); return luaD_precall(L, off, nresults); } }; const luaD_poscall = function(L, ci, firstResult, nres) { let wanted = ci.nresults; if (L.hookmask & (LUA_MASKRET | LUA_MASKLINE)) { if (L.hookmask & LUA_MASKRET) luaD_hook(L, LUA_HOOKRET, -1); L.oldpc = ci.previous.l_savedpc; /* 'oldpc' for caller function */ } let res = ci.funcOff; L.ci = ci.previous; L.ci.next = null; return moveresults(L, firstResult, res, nres, wanted); }; const moveresults = function(L, firstResult, res, nres, wanted) { switch (wanted) { case 0: break; case 1: { if (nres === 0) L.stack[res].setnilvalue(); else { lobject.setobjs2s(L, res, firstResult); /* move it to proper place */ } break; } case LUA_MULTRET: { for (let i = 0; i < nres; i++) lobject.setobjs2s(L, res + i, firstResult + i); for (let i=L.top; i>=(res + nres); i--) delete L.stack[i]; L.top = res + nres; return false; } default: { let i; if (wanted <= nres) { for (i = 0; i < wanted; i++) lobject.setobjs2s(L, res + i, firstResult + i); } else { for (i = 0; i < nres; i++) lobject.setobjs2s(L, res + i, firstResult + i); for (; i < wanted; i++) { if (res+i >= L.top) L.stack[res + i] = new lobject.TValue(LUA_TNIL, null); else L.stack[res + i].setnilvalue(); } } break; } } let newtop = res + wanted; /* top points after the last result */ for (let i=L.top; i>=newtop; i--) delete L.stack[i]; L.top = newtop; 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_Debug(); ar.event = event; ar.currentline = line; ar.i_ci = ci; luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ ci.top = L.top + LUA_MINSTACK; lua_assert(ci.top <= L.stack_last); L.allowhook = 0; /* cannot call hooks inside a hook */ ci.callstatus |= lstate.CIST_HOOKED; hook(L, ar); lua_assert(!L.allowhook); L.allowhook = 1; ci.top = ci_top; adjust_top(L, top); ci.callstatus &= ~lstate.CIST_HOOKED; } }; const callhook = function(L, ci) { let hook = LUA_HOOKCALL; ci.l_savedpc++; /* hooks assume 'pc' is already incremented */ if ((ci.previous.callstatus & lstate.CIST_LUA) && ci.previous.l_code[ci.previous.l_savedpc - 1].opcode == lopcodes.OpCodesI.OP_TAILCALL) { ci.callstatus |= lstate.CIST_TAIL; hook = LUA_HOOKTAILCALL; } luaD_hook(L, hook, -1); ci.l_savedpc--; /* correct 'pc' */ }; const adjust_varargs = function(L, p, actual) { let nfixargs = p.numparams; /* move fixed parameters to final position */ let fixed = L.top - actual; /* first fixed argument */ let base = L.top; /* final position of first argument */ let i; for (i = 0; i < nfixargs && i < actual; i++) { lobject.pushobj2s(L, L.stack[fixed + i]); L.stack[fixed + i].setnilvalue(); } for (; i < nfixargs; i++) L.stack[L.top++] = new lobject.TValue(LUA_TNIL, null); return base; }; const tryfuncTM = function(L, off, func) { let tm = ltm.luaT_gettmbyobj(L, func, ltm.TMS.TM_CALL); if (!tm.ttisfunction(tm)) ldebug.luaG_typeerror(L, func, to_luastring("call", true)); /* Open a hole inside the stack at 'func' */ lobject.pushobj2s(L, L.stack[L.top-1]); /* push top of stack again */ for (let p = L.top-2; p > off; p--) lobject.setobjs2s(L, p, p-1); /* move other items up one */ lobject.setobj2s(L, off, tm); /* tag method is the new function to be called */ }; /* ** Check appropriate error for stack overflow ("regular" overflow or ** overflow while handling stack overflow). If 'nCalls' is larger than ** LUAI_MAXCCALLS (which means it is handling a "regular" overflow) but ** smaller than 9/8 of LUAI_MAXCCALLS, does not report an error (to ** allow overflow handling to work) */ const stackerror = function(L) { if (L.nCcalls === LUAI_MAXCCALLS) ldebug.luaG_runerror(L, to_luastring("JS stack overflow", true)); else if (L.nCcalls >= LUAI_MAXCCALLS + (LUAI_MAXCCALLS >> 3)) luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ }; /* ** Call a function (JS or Lua). The function to be called is at func. ** The arguments are on the stack, right after the function. ** When returns, all the results are on the stack, starting at the original ** function position. */ const luaD_call = function(L, off, nResults) { if (++L.nCcalls >= LUAI_MAXCCALLS) stackerror(L); if (!luaD_precall(L, off, nResults)) lvm.luaV_execute(L); L.nCcalls--; }; const luaD_throw = function(L, errcode) { if (L.errorJmp) { /* thread has an error handler? */ L.errorJmp.status = errcode; /* set status */ throw L.errorJmp; } else { /* thread has no error handler */ let g = L.l_G; L.status = errcode; /* mark it as dead */ if (g.mainthread.errorJmp) { /* main thread has a handler? */ g.mainthread.stack[g.mainthread.top++] = L.stack[L.top - 1]; /* copy error obj. */ luaD_throw(g.mainthread, errcode); /* re-throw in main thread */ } else { /* no handler at all; abort */ let panic = g.panic; if (panic) { /* panic function? */ seterrorobj(L, errcode, L.top); /* assume EXTRA_STACK */ if (L.ci.top < L.top) L.ci.top = L.top; /* pushing msg. can break this invariant */ panic(L); /* call panic function (last chance to jump out) */ } throw new Error(`Aborted ${errcode}`); } } }; const luaD_rawrunprotected = function(L, f, ud) { let oldnCcalls = L.nCcalls; let lj = { status: LUA_OK, previous: L.errorJmp /* chain new error handler */ }; L.errorJmp = lj; try { f(L, ud); } catch (e) { if (lj.status === LUA_OK) { /* error was not thrown via luaD_throw, i.e. it is a JS error */ /* run user error handler (if it exists) */ let atnativeerror = L.l_G.atnativeerror; if (atnativeerror) { try { lj.status = LUA_OK; lapi.lua_pushcfunction(L, atnativeerror); lapi.lua_pushlightuserdata(L, e); luaD_callnoyield(L, L.top - 2, 1); /* Now run the message handler (if it exists) */ /* copy of luaG_errormsg without the throw */ if (L.errfunc !== 0) { /* is there an error handling function? */ let errfunc = L.errfunc; lobject.pushobj2s(L, L.stack[L.top - 1]); /* move argument */ lobject.setobjs2s(L, L.top - 2, errfunc); /* push function */ luaD_callnoyield(L, L.top - 2, 1); } lj.status = LUA_ERRRUN; } catch(e2) { if (lj.status === LUA_OK) { /* also failed */ lj.status = -1; } } } else { lj.status = -1; } } } L.errorJmp = lj.previous; L.nCcalls = oldnCcalls; return lj.status; }; /* ** Completes the execution of an interrupted C function, calling its ** continuation function. */ const finishCcall = function(L, status) { let ci = L.ci; /* must have a continuation and must be able to call it */ lua_assert(ci.c_k !== null && L.nny === 0); /* error status can only happen in a protected call */ lua_assert(ci.callstatus & lstate.CIST_YPCALL || status === LUA_YIELD); if (ci.callstatus & lstate.CIST_YPCALL) { /* was inside a pcall? */ ci.callstatus &= ~lstate.CIST_YPCALL; /* continuation is also inside it */ L.errfunc = ci.c_old_errfunc; /* with the same error function */ } /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already handled */ if (ci.nresults === LUA_MULTRET && L.ci.top < L.top) L.ci.top = L.top; let c_k = ci.c_k; /* don't want to call as method */ let n = c_k(L, status, ci.c_ctx); /* call continuation function */ lapi.api_checknelems(L, n); luaD_poscall(L, ci, L.top - n, n); /* finish 'luaD_precall' */ }; /* ** Executes "full continuation" (everything in the stack) of a ** previously interrupted coroutine until the stack is empty (or another ** interruption long-jumps out of the loop). If the coroutine is ** recovering from an error, 'ud' points to the error status, which must ** be passed to the first continuation function (otherwise the default ** status is LUA_YIELD). */ const unroll = function(L, ud) { if (ud !== null) /* error status? */ finishCcall(L, ud); /* finish 'lua_pcallk' callee */ while (L.ci !== L.base_ci) { /* something in the stack */ if (!(L.ci.callstatus & lstate.CIST_LUA)) /* C function? */ finishCcall(L, LUA_YIELD); /* complete its execution */ else { /* Lua function */ lvm.luaV_finishOp(L); /* finish interrupted instruction */ lvm.luaV_execute(L); /* execute down to higher C 'boundary' */ } } }; /* ** Try to find a suspended protected call (a "recover point") for the ** given thread. */ const findpcall = function(L) { for (let ci = L.ci; ci !== null; ci = ci.previous) { /* search for a pcall */ if (ci.callstatus & lstate.CIST_YPCALL) return ci; } return null; /* no pending pcall */ }; /* ** Recovers from an error in a coroutine. Finds a recover point (if ** there is one) and completes the execution of the interrupted ** 'luaD_pcall'. If there is no recover point, returns zero. */ const recover = function(L, status) { let ci = findpcall(L); if (ci === null) return 0; /* no recovery point */ /* "finish" luaD_pcall */ let oldtop = ci.extra; lfunc.luaF_close(L, oldtop); seterrorobj(L, status, oldtop); L.ci = ci; L.allowhook = ci.callstatus & lstate.CIST_OAH; /* restore original 'allowhook' */ L.nny = 0; /* should be zero to be yieldable */ luaD_shrinkstack(L); L.errfunc = ci.c_old_errfunc; return 1; /* continue running the coroutine */ }; /* ** Signal an error in the call to 'lua_resume', not in the execution ** of the coroutine itself. (Such errors should not be handled by any ** coroutine error handler and should not kill the coroutine.) */ const resume_error = function(L, msg, narg) { let ts = luaS_newliteral(L, msg); if (narg === 0) { lobject.pushsvalue2s(L, ts); api_check(L, L.top <= L.ci.top, "stack overflow"); } else { /* remove args from the stack */ for (let i=1; i= LUAI_MAXCCALLS) return resume_error(L, "JS stack overflow", nargs); L.nny = 0; /* allow yields */ lapi.api_checknelems(L, L.status === LUA_OK ? nargs + 1: nargs); let status = luaD_rawrunprotected(L, resume, nargs); if (status === -1) /* error calling 'lua_resume'? */ status = LUA_ERRRUN; else { /* continue running after recoverable errors */ while (status > LUA_YIELD && recover(L, status)) { /* unroll continuation */ status = luaD_rawrunprotected(L, unroll, status); } if (status > LUA_YIELD) { /* unrecoverable error? */ L.status = status; /* mark thread as 'dead' */ seterrorobj(L, status, L.top); /* push error message */ L.ci.top = L.top; } else lua_assert(status === L.status); /* normal end or yield */ } L.nny = oldnny; /* restore 'nny' */ L.nCcalls--; lua_assert(L.nCcalls === (from ? from.nCcalls : 0)); return status; }; const lua_isyieldable = function(L) { return L.nny === 0; }; const lua_yieldk = function(L, nresults, ctx, k) { let ci = L.ci; lapi.api_checknelems(L, nresults); if (L.nny > 0) { if (L !== L.l_G.mainthread) ldebug.luaG_runerror(L, to_luastring("attempt to yield across a JS-call boundary", true)); else ldebug.luaG_runerror(L, to_luastring("attempt to yield from outside a coroutine", true)); } L.status = LUA_YIELD; ci.extra = ci.funcOff; /* save current 'func' */ if (ci.callstatus & lstate.CIST_LUA) /* inside a hook? */ api_check(L, k === null, "hooks cannot continue after yielding"); else { ci.c_k = k; if (k !== null) /* is there a continuation? */ ci.c_ctx = ctx; /* save context */ ci.funcOff = L.top - nresults - 1; /* protect stack below results */ ci.func = L.stack[ci.funcOff]; luaD_throw(L, LUA_YIELD); } lua_assert(ci.callstatus & lstate.CIST_HOOKED); /* must be inside a hook */ return 0; /* return to 'luaD_hook' */ }; const lua_yield = function(L, n) { lua_yieldk(L, n, 0, null); }; const luaD_pcall = function(L, func, u, old_top, ef) { let old_ci = L.ci; let old_allowhooks = L.allowhook; let old_nny = L.nny; let old_errfunc = L.errfunc; L.errfunc = ef; let status = luaD_rawrunprotected(L, func, u); if (status !== LUA_OK) { lfunc.luaF_close(L, old_top); seterrorobj(L, status, old_top); L.ci = old_ci; L.allowhook = old_allowhooks; L.nny = old_nny; luaD_shrinkstack(L); } L.errfunc = old_errfunc; return status; }; /* ** Similar to 'luaD_call', but does not allow yields during the call */ const luaD_callnoyield = function(L, off, nResults) { L.nny++; luaD_call(L, off, nResults); L.nny--; }; /* ** Execute a protected parser. */ class SParser { constructor(z, name, mode) { /* data to 'f_parser' */ this.z = z; this.buff = new MBuffer(); /* dynamic structure used by the scanner */ this.dyd = new lparser.Dyndata(); /* dynamic structures used by the parser */ this.mode = mode; this.name = name; } } const checkmode = function(L, mode, x) { if (mode && luastring_indexOf(mode, x[0]) === -1) { lobject.luaO_pushfstring(L, to_luastring("attempt to load a %s chunk (mode is '%s')"), x, mode); luaD_throw(L, LUA_ERRSYNTAX); } }; const f_parser = function(L, p) { let cl; let c = p.z.zgetc(); /* read first character */ if (c === LUA_SIGNATURE[0]) { checkmode(L, p.mode, to_luastring("binary", true)); cl = lundump.luaU_undump(L, p.z, p.name); } else { checkmode(L, p.mode, to_luastring("text", true)); cl = lparser.luaY_parser(L, p.z, p.buff, p.dyd, p.name, c); } lua_assert(cl.nupvalues === cl.p.upvalues.length); lfunc.luaF_initupvals(L, cl); }; const luaD_protectedparser = function(L, z, name, mode) { let p = new SParser(z, name, mode); L.nny++; /* cannot yield during parsing */ let status = luaD_pcall(L, f_parser, p, L.top, L.errfunc); L.nny--; return status; }; module.exports.adjust_top = adjust_top; module.exports.luaD_call = luaD_call; module.exports.luaD_callnoyield = luaD_callnoyield; module.exports.luaD_checkstack = luaD_checkstack; module.exports.luaD_growstack = luaD_growstack; module.exports.luaD_hook = luaD_hook; module.exports.luaD_inctop = luaD_inctop; module.exports.luaD_pcall = luaD_pcall; module.exports.luaD_poscall = luaD_poscall; module.exports.luaD_precall = luaD_precall; module.exports.luaD_protectedparser = luaD_protectedparser; module.exports.luaD_rawrunprotected = luaD_rawrunprotected; module.exports.luaD_reallocstack = luaD_reallocstack; module.exports.luaD_throw = luaD_throw; module.exports.lua_isyieldable = lua_isyieldable; module.exports.lua_resume = lua_resume; module.exports.lua_yield = lua_yield; module.exports.lua_yieldk = lua_yieldk;