mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-14 22:21:10 +00:00
567 lines
14 KiB
C++
567 lines
14 KiB
C++
// 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 <string.h>
|
|
#include <stdio.h>
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauDebuggerBreakpointHitOnNextBestLine, false);
|
|
|
|
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 = f->l.p->linedefined;
|
|
}
|
|
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 : f->l.p->linedefined;
|
|
}
|
|
|
|
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_stackdepth(lua_State* L)
|
|
{
|
|
return int(L->ci - L->base_ci);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
l_noret luaG_methoderror(lua_State* L, const TValue* p1, const TString* p2)
|
|
{
|
|
const char* t1 = luaT_objtypename(L, p1);
|
|
|
|
luaG_runerror(L, "attempt to call missing method '%s' of %s", getstr(p2), t1);
|
|
}
|
|
|
|
l_noret luaG_readonlyerror(lua_State* L)
|
|
{
|
|
luaG_runerror(L, "attempt to modify a readonly table");
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static int getmaxline(Proto* p)
|
|
{
|
|
int result = -1;
|
|
|
|
for (int i = 0; i < p->sizecode; ++i)
|
|
{
|
|
int line = luaG_getline(p, i);
|
|
result = result < line ? line : result;
|
|
}
|
|
|
|
for (int i = 0; i < p->sizep; ++i)
|
|
{
|
|
int psize = getmaxline(p->p[i]);
|
|
result = result < psize ? psize : result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Find the line number with instructions. If the provided line doesn't have any instruction, it should return the next line number with
|
|
// instructions.
|
|
static int getnextline(Proto* p, int line)
|
|
{
|
|
int closest = -1;
|
|
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;
|
|
|
|
int current = luaG_getline(p, i);
|
|
if (current >= line)
|
|
{
|
|
closest = current;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < p->sizep; ++i)
|
|
{
|
|
// Find the closest line number to the intended one.
|
|
int candidate = getnextline(p->p[i], line);
|
|
if (closest == -1 || (candidate >= line && candidate < closest))
|
|
{
|
|
closest = candidate;
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
int lua_breakpoint(lua_State* L, int funcindex, int line, int enabled)
|
|
{
|
|
int target = -1;
|
|
|
|
if (FFlag::LuauDebuggerBreakpointHitOnNextBestLine)
|
|
{
|
|
const TValue* func = luaA_toobject(L, funcindex);
|
|
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
|
|
|
|
Proto* p = clvalue(func)->l.p;
|
|
// Find line number to add the breakpoint to.
|
|
target = getnextline(p, line);
|
|
|
|
if (target != -1)
|
|
{
|
|
// Add breakpoint on the exact line
|
|
luaG_breakpoint(L, p, target, bool(enabled));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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));
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
static void getcoverage(Proto* p, int depth, int* buffer, size_t size, void* context, lua_Coverage callback)
|
|
{
|
|
memset(buffer, -1, size * sizeof(int));
|
|
|
|
for (int i = 0; i < p->sizecode; ++i)
|
|
{
|
|
Instruction insn = p->code[i];
|
|
if (LUAU_INSN_OP(insn) != LOP_COVERAGE)
|
|
continue;
|
|
|
|
int line = luaG_getline(p, i);
|
|
int hits = LUAU_INSN_E(insn);
|
|
|
|
LUAU_ASSERT(size_t(line) < size);
|
|
buffer[line] = buffer[line] < hits ? hits : buffer[line];
|
|
}
|
|
|
|
const char* debugname = p->debugname ? getstr(p->debugname) : NULL;
|
|
int linedefined = p->linedefined;
|
|
|
|
callback(context, debugname, linedefined, depth, buffer, size);
|
|
|
|
for (int i = 0; i < p->sizep; ++i)
|
|
getcoverage(p->p[i], depth + 1, buffer, size, context, callback);
|
|
}
|
|
|
|
void lua_getcoverage(lua_State* L, int funcindex, void* context, lua_Coverage callback)
|
|
{
|
|
const TValue* func = luaA_toobject(L, funcindex);
|
|
api_check(L, ttisfunction(func) && !clvalue(func)->isC);
|
|
|
|
Proto* p = clvalue(func)->l.p;
|
|
|
|
size_t size = getmaxline(p) + 1;
|
|
if (size == 0)
|
|
return;
|
|
|
|
int* buffer = luaM_newarray(L, size, int, 0);
|
|
|
|
getcoverage(p, 0, buffer, size, context, callback);
|
|
|
|
luaM_freearray(L, buffer, size, int, 0);
|
|
}
|
|
|
|
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];
|
|
snprintf(line, sizeof(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];
|
|
snprintf(skip, sizeof(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;
|
|
}
|