// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details #include "ldebug.h" #include "lapi.h" #include "lfunc.h" #include "lmem.h" #include "lgc.h" #include "ldo.h" #include "lbytecode.h" #include #include static const char* getfuncname(Closure* f); static int currentpc(lua_State* L, CallInfo* ci) { return pcRel(ci->savedpc, ci_func(ci)->l.p); } static int currentline(lua_State* L, CallInfo* ci) { return luaG_getline(ci_func(ci)->l.p, currentpc(L, ci)); } static Proto* getluaproto(CallInfo* ci) { return (isLua(ci) ? cast_to(Proto*, ci_func(ci)->l.p) : NULL); } int lua_getargument(lua_State* L, int level, int n) { if (unsigned(level) >= unsigned(L->ci - L->base_ci)) return 0; CallInfo* ci = L->ci - level; Proto* fp = getluaproto(ci); int res = 0; if (fp && n > 0) { if (n <= fp->numparams) { luaC_checkthreadsleep(L); luaA_pushobject(L, ci->base + (n - 1)); res = 1; } else if (fp->is_vararg && n < ci->base - ci->func) { luaC_checkthreadsleep(L); luaA_pushobject(L, ci->func + n); res = 1; } } return res; } const char* lua_getlocal(lua_State* L, int level, int n) { if (unsigned(level) >= unsigned(L->ci - L->base_ci)) return 0; CallInfo* ci = L->ci - level; Proto* fp = getluaproto(ci); const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL; if (var) { luaC_checkthreadsleep(L); luaA_pushobject(L, ci->base + var->reg); } const char* name = var ? getstr(var->varname) : NULL; return name; } const char* lua_setlocal(lua_State* L, int level, int n) { if (unsigned(level) >= unsigned(L->ci - L->base_ci)) return 0; CallInfo* ci = L->ci - level; Proto* fp = getluaproto(ci); const LocVar* var = fp ? luaF_getlocal(fp, n, currentpc(L, ci)) : NULL; if (var) setobjs2s(L, ci->base + var->reg, L->top - 1); L->top--; /* pop value */ const char* name = var ? getstr(var->varname) : NULL; return name; } static int auxgetinfo(lua_State* L, const char* what, lua_Debug* ar, Closure* f, CallInfo* ci) { int status = 1; for (; *what; what++) { switch (*what) { case 's': { if (f->isC) { ar->source = "=[C]"; ar->what = "C"; ar->linedefined = -1; } else { ar->source = getstr(f->l.p->source); ar->what = "Lua"; ar->linedefined = luaG_getline(f->l.p, 0); } luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); break; } case 'l': { if (ci) { ar->currentline = isLua(ci) ? currentline(L, ci) : -1; } else { ar->currentline = f->isC ? -1 : luaG_getline(f->l.p, 0); } break; } case 'u': { ar->nupvals = f->nupvalues; break; } case 'a': { if (f->isC) { ar->isvararg = 1; ar->nparams = 0; } else { ar->isvararg = f->l.p->is_vararg; ar->nparams = f->l.p->numparams; } break; } case 'n': { ar->name = ci ? getfuncname(ci_func(ci)) : getfuncname(f); break; } default:; } } return status; } int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar) { int status = 0; Closure* f = NULL; CallInfo* ci = NULL; if (level < 0) { StkId func = L->top + level; api_check(L, ttisfunction(func)); f = clvalue(func); } else if (unsigned(level) < unsigned(L->ci - L->base_ci)) { ci = L->ci - level; LUAU_ASSERT(ttisfunction(ci->func)); f = clvalue(ci->func); } if (f) { status = auxgetinfo(L, what, ar, f, ci); if (strchr(what, 'f')) { luaC_checkthreadsleep(L); setclvalue(L, L->top, f); incr_top(L); } } return status; } static const char* getfuncname(Closure* cl) { if (cl->isC) { if (cl->c.debugname) { return cl->c.debugname; } } else { Proto* p = cl->l.p; if (p->debugname) { return getstr(p->debugname); } } return nullptr; } l_noret luaG_typeerrorL(lua_State* L, const TValue* o, const char* op) { const char* t = luaT_objtypename(L, o); luaG_runerror(L, "attempt to %s a %s value", op, t); } l_noret luaG_forerrorL(lua_State* L, const TValue* o, const char* what) { const char* t = luaT_objtypename(L, o); luaG_runerror(L, "invalid 'for' %s (number expected, got %s)", what, t); } l_noret luaG_concaterror(lua_State* L, StkId p1, StkId p2) { const char* t1 = luaT_objtypename(L, p1); const char* t2 = luaT_objtypename(L, p2); luaG_runerror(L, "attempt to concatenate %s with %s", t1, t2); } l_noret luaG_aritherror(lua_State* L, const TValue* p1, const TValue* p2, TMS op) { const char* t1 = luaT_objtypename(L, p1); const char* t2 = luaT_objtypename(L, p2); const char* opname = luaT_eventname[op] + 2; // skip __ from metamethod name if (t1 == t2) luaG_runerror(L, "attempt to perform arithmetic (%s) on %s", opname, t1); else luaG_runerror(L, "attempt to perform arithmetic (%s) on %s and %s", opname, t1, t2); } l_noret luaG_ordererror(lua_State* L, const TValue* p1, const TValue* p2, TMS op) { const char* t1 = luaT_objtypename(L, p1); const char* t2 = luaT_objtypename(L, p2); const char* opname = (op == TM_LT) ? "<" : (op == TM_LE) ? "<=" : "=="; luaG_runerror(L, "attempt to compare %s %s %s", t1, opname, t2); } l_noret luaG_indexerror(lua_State* L, const TValue* p1, const TValue* p2) { const char* t1 = luaT_objtypename(L, p1); const char* t2 = luaT_objtypename(L, p2); const TString* key = ttisstring(p2) ? tsvalue(p2) : 0; if (key && key->len <= 64) // limit length to make sure we don't generate very long error messages for very long keys luaG_runerror(L, "attempt to index %s with '%s'", t1, getstr(key)); else luaG_runerror(L, "attempt to index %s with %s", t1, t2); } static void pusherror(lua_State* L, const char* msg) { CallInfo* ci = L->ci; if (isLua(ci)) { char buff[LUA_IDSIZE]; /* add file:line information */ luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE); int line = currentline(L, ci); luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); } else { lua_pushstring(L, msg); } } l_noret luaG_runerrorL(lua_State* L, const char* fmt, ...) { va_list argp; va_start(argp, fmt); char result[LUA_BUFFERSIZE]; vsnprintf(result, sizeof(result), fmt, argp); va_end(argp); pusherror(L, result); luaD_throw(L, LUA_ERRRUN); } void luaG_pusherror(lua_State* L, const char* error) { pusherror(L, error); } void luaG_breakpoint(lua_State* L, Proto* p, int line, bool enable) { if (p->lineinfo) { for (int i = 0; i < p->sizecode; ++i) { // note: we keep prologue as is, instead opting to break at the first meaningful instruction if (LUAU_INSN_OP(p->code[i]) == LOP_PREPVARARGS) continue; if (luaG_getline(p, i) != line) continue; // lazy copy of the original opcode array; done when the first breakpoint is set if (!p->debuginsn) { p->debuginsn = luaM_newarray(L, p->sizecode, uint8_t, p->memcat); for (int j = 0; j < p->sizecode; ++j) p->debuginsn[j] = LUAU_INSN_OP(p->code[j]); } uint8_t op = enable ? LOP_BREAK : LUAU_INSN_OP(p->debuginsn[i]); // patch just the opcode byte, leave arguments alone p->code[i] &= ~0xff; p->code[i] |= op; LUAU_ASSERT(LUAU_INSN_OP(p->code[i]) == op); // note: this is important! // we only patch the *first* instruction in each proto that's attributed to a given line // this can be changed, but if requires making patching a bit more nuanced so that we don't patch AUX words break; } } for (int i = 0; i < p->sizep; ++i) { luaG_breakpoint(L, p->p[i], line, enable); } } bool luaG_onbreak(lua_State* L) { if (L->ci == L->base_ci) return false; if (!isLua(L->ci)) return false; return LUAU_INSN_OP(*L->ci->savedpc) == LOP_BREAK; } int luaG_getline(Proto* p, int pc) { LUAU_ASSERT(pc >= 0 && pc < p->sizecode); if (!p->lineinfo) return 0; return p->abslineinfo[pc >> p->linegaplog2] + p->lineinfo[pc]; } void lua_singlestep(lua_State* L, int enabled) { L->singlestep = bool(enabled); } void lua_breakpoint(lua_State* L, int funcindex, int line, int enabled) { const TValue* func = luaA_toobject(L, funcindex); api_check(L, ttisfunction(func) && !clvalue(func)->isC); luaG_breakpoint(L, clvalue(func)->l.p, line, bool(enabled)); } static size_t append(char* buf, size_t bufsize, size_t offset, const char* data) { size_t size = strlen(data); size_t copy = offset + size >= bufsize ? bufsize - offset - 1 : size; memcpy(buf + offset, data, copy); return offset + copy; } const char* lua_debugtrace(lua_State* L) { static char buf[4096]; const int limit1 = 10; const int limit2 = 10; int depth = int(L->ci - L->base_ci); size_t offset = 0; lua_Debug ar; for (int level = 0; lua_getinfo(L, level, "sln", &ar); ++level) { if (ar.source) offset = append(buf, sizeof(buf), offset, ar.short_src); if (ar.currentline > 0) { char line[32]; sprintf(line, ":%d", ar.currentline); offset = append(buf, sizeof(buf), offset, line); } if (ar.name) { offset = append(buf, sizeof(buf), offset, " function "); offset = append(buf, sizeof(buf), offset, ar.name); } offset = append(buf, sizeof(buf), offset, "\n"); if (depth > limit1 + limit2 && level == limit1 - 1) { char skip[32]; sprintf(skip, "... (+%d frames)\n", int(depth - limit1 - limit2)); offset = append(buf, sizeof(buf), offset, skip); level = depth - limit2 - 1; } } LUAU_ASSERT(offset < sizeof(buf)); buf[offset] = '\0'; return buf; }