luau/VM/src/laux.cpp

531 lines
14 KiB
C++
Raw Normal View History

// 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 "lualib.h"
#include "lobject.h"
#include "lstate.h"
#include "lstring.h"
#include "lapi.h"
#include "lgc.h"
#include "lnumutils.h"
#include <string.h>
2022-08-04 23:35:33 +01:00
// convert a stack index to positive
#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)
/*
** {======================================================
** Error-report functions
** =======================================================
*/
static const char* currfuncname(lua_State* L)
{
Closure* cl = L->ci > L->base_ci ? curr_func(L) : NULL;
const char* debugname = cl && cl->isC ? cl->c.debugname + 0 : NULL;
if (debugname && strcmp(debugname, "__namecall") == 0)
return L->namecall ? getstr(L->namecall) : NULL;
else
return debugname;
}
l_noret luaL_argerrorL(lua_State* L, int narg, const char* extramsg)
{
const char* fname = currfuncname(L);
if (fname)
luaL_error(L, "invalid argument #%d to '%s' (%s)", narg, fname, extramsg);
else
luaL_error(L, "invalid argument #%d (%s)", narg, extramsg);
}
l_noret luaL_typeerrorL(lua_State* L, int narg, const char* tname)
{
const char* fname = currfuncname(L);
const TValue* obj = luaA_toobject(L, narg);
if (obj)
{
if (fname)
luaL_error(L, "invalid argument #%d to '%s' (%s expected, got %s)", narg, fname, tname, luaT_objtypename(L, obj));
else
luaL_error(L, "invalid argument #%d (%s expected, got %s)", narg, tname, luaT_objtypename(L, obj));
}
else
{
if (fname)
luaL_error(L, "missing argument #%d to '%s' (%s expected)", narg, fname, tname);
else
luaL_error(L, "missing argument #%d (%s expected)", narg, tname);
}
}
static l_noret tag_error(lua_State* L, int narg, int tag)
{
luaL_typeerrorL(L, narg, lua_typename(L, tag));
}
void luaL_where(lua_State* L, int level)
{
lua_Debug ar;
if (lua_getinfo(L, level, "sl", &ar) && ar.currentline > 0)
{
lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline);
return;
}
2022-08-04 23:35:33 +01:00
lua_pushliteral(L, ""); // else, no information available...
}
l_noret luaL_errorL(lua_State* L, const char* fmt, ...)
{
va_list argp;
va_start(argp, fmt);
luaL_where(L, 1);
lua_pushvfstring(L, fmt, argp);
va_end(argp);
lua_concat(L, 2);
lua_error(L);
}
2022-08-04 23:35:33 +01:00
// }======================================================
int luaL_checkoption(lua_State* L, int narg, const char* def, const char* const lst[])
{
const char* name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
int i;
for (i = 0; lst[i]; i++)
if (strcmp(lst[i], name) == 0)
return i;
const char* msg = lua_pushfstring(L, "invalid option '%s'", name);
luaL_argerrorL(L, narg, msg);
}
int luaL_newmetatable(lua_State* L, const char* tname)
{
2022-08-04 23:35:33 +01:00
lua_getfield(L, LUA_REGISTRYINDEX, tname); // get registry.name
if (!lua_isnil(L, -1)) // name already in use?
return 0; // leave previous value on top, but return 0
lua_pop(L, 1);
2022-08-04 23:35:33 +01:00
lua_newtable(L); // create metatable
lua_pushvalue(L, -1);
2022-08-04 23:35:33 +01:00
lua_setfield(L, LUA_REGISTRYINDEX, tname); // registry.name = metatable
return 1;
}
void* luaL_checkudata(lua_State* L, int ud, const char* tname)
{
void* p = lua_touserdata(L, ud);
if (p != NULL)
2022-08-04 23:35:33 +01:00
{ // value is a userdata?
if (lua_getmetatable(L, ud))
2022-08-04 23:35:33 +01:00
{ // does it have a metatable?
lua_getfield(L, LUA_REGISTRYINDEX, tname); // get correct metatable
if (lua_rawequal(L, -1, -2))
2022-08-04 23:35:33 +01:00
{ // does it have the correct mt?
lua_pop(L, 2); // remove both metatables
return p;
}
}
}
2022-08-04 23:35:33 +01:00
luaL_typeerrorL(L, ud, tname); // else error
}
void luaL_checkstack(lua_State* L, int space, const char* mes)
{
if (!lua_checkstack(L, space))
luaL_error(L, "stack overflow (%s)", mes);
}
void luaL_checktype(lua_State* L, int narg, int t)
{
if (lua_type(L, narg) != t)
tag_error(L, narg, t);
}
void luaL_checkany(lua_State* L, int narg)
{
if (lua_type(L, narg) == LUA_TNONE)
luaL_error(L, "missing argument #%d", narg);
}
const char* luaL_checklstring(lua_State* L, int narg, size_t* len)
{
const char* s = lua_tolstring(L, narg, len);
if (!s)
tag_error(L, narg, LUA_TSTRING);
return s;
}
const char* luaL_optlstring(lua_State* L, int narg, const char* def, size_t* len)
{
if (lua_isnoneornil(L, narg))
{
if (len)
*len = (def ? strlen(def) : 0);
return def;
}
else
return luaL_checklstring(L, narg, len);
}
double luaL_checknumber(lua_State* L, int narg)
{
int isnum;
double d = lua_tonumberx(L, narg, &isnum);
if (!isnum)
tag_error(L, narg, LUA_TNUMBER);
return d;
}
double luaL_optnumber(lua_State* L, int narg, double def)
{
return luaL_opt(L, luaL_checknumber, narg, def);
}
int luaL_checkboolean(lua_State* L, int narg)
{
// This checks specifically for boolean values, ignoring
// all other truthy/falsy values. If the desired result
// is true if value is present then lua_toboolean should
// directly be used instead.
if (!lua_isboolean(L, narg))
tag_error(L, narg, LUA_TBOOLEAN);
return lua_toboolean(L, narg);
}
int luaL_optboolean(lua_State* L, int narg, int def)
{
return luaL_opt(L, luaL_checkboolean, narg, def);
}
int luaL_checkinteger(lua_State* L, int narg)
{
int isnum;
int d = lua_tointegerx(L, narg, &isnum);
if (!isnum)
tag_error(L, narg, LUA_TNUMBER);
return d;
}
int luaL_optinteger(lua_State* L, int narg, int def)
{
return luaL_opt(L, luaL_checkinteger, narg, def);
}
unsigned luaL_checkunsigned(lua_State* L, int narg)
{
int isnum;
unsigned d = lua_tounsignedx(L, narg, &isnum);
if (!isnum)
tag_error(L, narg, LUA_TNUMBER);
return d;
}
unsigned luaL_optunsigned(lua_State* L, int narg, unsigned def)
{
return luaL_opt(L, luaL_checkunsigned, narg, def);
}
const float* luaL_checkvector(lua_State* L, int narg)
{
const float* v = lua_tovector(L, narg);
if (!v)
tag_error(L, narg, LUA_TVECTOR);
return v;
}
const float* luaL_optvector(lua_State* L, int narg, const float* def)
{
return luaL_opt(L, luaL_checkvector, narg, def);
}
int luaL_getmetafield(lua_State* L, int obj, const char* event)
{
2022-08-04 23:35:33 +01:00
if (!lua_getmetatable(L, obj)) // no metatable?
return 0;
lua_pushstring(L, event);
lua_rawget(L, -2);
if (lua_isnil(L, -1))
{
2022-08-04 23:35:33 +01:00
lua_pop(L, 2); // remove metatable and metafield
return 0;
}
else
{
2022-08-04 23:35:33 +01:00
lua_remove(L, -2); // remove only metatable
return 1;
}
}
int luaL_callmeta(lua_State* L, int obj, const char* event)
{
obj = abs_index(L, obj);
2022-08-04 23:35:33 +01:00
if (!luaL_getmetafield(L, obj, event)) // no metafield?
return 0;
lua_pushvalue(L, obj);
lua_call(L, 1, 1);
return 1;
}
static int libsize(const luaL_Reg* l)
{
int size = 0;
for (; l->name; l++)
size++;
return size;
}
void luaL_register(lua_State* L, const char* libname, const luaL_Reg* l)
{
if (libname)
{
int size = libsize(l);
2022-08-04 23:35:33 +01:00
// check whether lib already exists
luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
2022-08-04 23:35:33 +01:00
lua_getfield(L, -1, libname); // get _LOADED[libname]
if (!lua_istable(L, -1))
2022-08-04 23:35:33 +01:00
{ // not found?
lua_pop(L, 1); // remove previous result
// try global variable (and create one if it does not exist)
if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL)
luaL_error(L, "name conflict for module '%s'", libname);
lua_pushvalue(L, -1);
2022-08-04 23:35:33 +01:00
lua_setfield(L, -3, libname); // _LOADED[libname] = new table
}
2022-08-04 23:35:33 +01:00
lua_remove(L, -2); // remove _LOADED table
}
for (; l->name; l++)
{
lua_pushcfunction(L, l->func, l->name);
lua_setfield(L, -2, l->name);
}
}
const char* luaL_findtable(lua_State* L, int idx, const char* fname, int szhint)
{
const char* e;
lua_pushvalue(L, idx);
do
{
e = strchr(fname, '.');
if (e == NULL)
e = fname + strlen(fname);
lua_pushlstring(L, fname, e - fname);
lua_rawget(L, -2);
if (lua_isnil(L, -1))
2022-08-04 23:35:33 +01:00
{ // no such field?
lua_pop(L, 1); // remove this nil
lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); // new table for field
lua_pushlstring(L, fname, e - fname);
lua_pushvalue(L, -2);
2022-08-04 23:35:33 +01:00
lua_settable(L, -4); // set new table into field
}
else if (!lua_istable(L, -1))
2022-08-04 23:35:33 +01:00
{ // field has a non-table value?
lua_pop(L, 2); // remove table and value
return fname; // return problematic part of the name
}
2022-08-04 23:35:33 +01:00
lua_remove(L, -2); // remove previous table
fname = e + 1;
} while (*e == '.');
return NULL;
}
2022-03-11 16:55:02 +00:00
const char* luaL_typename(lua_State* L, int idx)
{
2022-03-31 22:01:51 +01:00
const TValue* obj = luaA_toobject(L, idx);
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 16:13:53 +01:00
return obj ? luaT_objtypename(L, obj) : "no value";
2022-03-11 16:55:02 +00:00
}
/*
** {======================================================
** Generic Buffer manipulation
** =======================================================
*/
static size_t getnextbuffersize(lua_State* L, size_t currentsize, size_t desiredsize)
{
size_t newsize = currentsize + currentsize / 2;
// check for size overflow
if (SIZE_MAX - desiredsize < currentsize)
luaL_error(L, "buffer too large");
// growth factor might not be enough to satisfy the desired size
if (newsize < desiredsize)
newsize = desiredsize;
return newsize;
}
void luaL_buffinit(lua_State* L, luaL_Buffer* B)
{
// start with an internal buffer
B->p = B->buffer;
B->end = B->p + LUA_BUFFERSIZE;
B->L = L;
B->storage = nullptr;
}
char* luaL_buffinitsize(lua_State* L, luaL_Buffer* B, size_t size)
{
luaL_buffinit(L, B);
luaL_reservebuffer(B, size, -1);
return B->p;
}
char* luaL_extendbuffer(luaL_Buffer* B, size_t additionalsize, int boxloc)
{
lua_State* L = B->L;
if (B->storage)
LUAU_ASSERT(B->storage == tsvalue(L->top + boxloc));
char* base = B->storage ? B->storage->data : B->buffer;
size_t capacity = B->end - base;
size_t nextsize = getnextbuffersize(B->L, capacity, capacity + additionalsize);
TString* newStorage = luaS_bufstart(L, nextsize);
memcpy(newStorage->data, base, B->p - base);
// place the string storage at the expected position in the stack
if (base == B->buffer)
{
lua_pushnil(L);
lua_insert(L, boxloc);
}
setsvalue(L, L->top + boxloc, newStorage);
B->p = newStorage->data + (B->p - base);
B->end = newStorage->data + nextsize;
B->storage = newStorage;
return B->p;
}
void luaL_reservebuffer(luaL_Buffer* B, size_t size, int boxloc)
{
if (size_t(B->end - B->p) < size)
luaL_extendbuffer(B, size - (B->end - B->p), boxloc);
}
void luaL_addlstring(luaL_Buffer* B, const char* s, size_t len, int boxloc)
{
if (size_t(B->end - B->p) < len)
luaL_extendbuffer(B, len - (B->end - B->p), boxloc);
memcpy(B->p, s, len);
B->p += len;
}
void luaL_addvalue(luaL_Buffer* B)
{
lua_State* L = B->L;
size_t vl;
if (const char* s = lua_tolstring(L, -1, &vl))
{
if (size_t(B->end - B->p) < vl)
luaL_extendbuffer(B, vl - (B->end - B->p), -2);
memcpy(B->p, s, vl);
B->p += vl;
lua_pop(L, 1);
}
}
void luaL_pushresult(luaL_Buffer* B)
{
lua_State* L = B->L;
if (TString* storage = B->storage)
{
luaC_checkGC(L);
// if we finished just at the end of the string buffer, we can convert it to a mutable stirng without a copy
if (B->p == B->end)
{
setsvalue(L, L->top - 1, luaS_buffinish(L, storage));
}
else
{
setsvalue(L, L->top - 1, luaS_newlstr(L, storage->data, B->p - storage->data));
}
}
else
{
lua_pushlstring(L, B->buffer, B->p - B->buffer);
}
}
void luaL_pushresultsize(luaL_Buffer* B, size_t size)
{
B->p += size;
luaL_pushresult(B);
}
2022-08-04 23:35:33 +01:00
// }======================================================
const char* luaL_tolstring(lua_State* L, int idx, size_t* len)
{
2022-08-04 23:35:33 +01:00
if (luaL_callmeta(L, idx, "__tostring")) // is there a metafield?
{
if (!lua_isstring(L, -1))
luaL_error(L, "'__tostring' must return a string");
return lua_tolstring(L, -1, len);
}
switch (lua_type(L, idx))
{
case LUA_TNUMBER:
2022-03-04 16:36:33 +00:00
{
double n = lua_tonumber(L, idx);
char s[LUAI_MAXNUM2STR];
char* e = luai_num2str(s, n);
lua_pushlstring(L, s, e - s);
break;
2022-03-04 16:36:33 +00:00
}
case LUA_TSTRING:
lua_pushvalue(L, idx);
break;
case LUA_TBOOLEAN:
lua_pushstring(L, (lua_toboolean(L, idx) ? "true" : "false"));
break;
case LUA_TNIL:
lua_pushliteral(L, "nil");
break;
case LUA_TVECTOR:
{
const float* v = lua_tovector(L, idx);
2022-03-04 16:36:33 +00:00
char s[LUAI_MAXNUM2STR * LUA_VECTOR_SIZE];
char* e = s;
for (int i = 0; i < LUA_VECTOR_SIZE; ++i)
{
2022-03-04 16:36:33 +00:00
if (i != 0)
{
2022-03-04 16:36:33 +00:00
*e++ = ',';
*e++ = ' ';
}
2022-03-04 16:36:33 +00:00
e = luai_num2str(e, v[i]);
}
2022-03-04 16:36:33 +00:00
lua_pushlstring(L, s, e - s);
break;
}
default:
{
const void* ptr = lua_topointer(L, idx);
unsigned long long enc = lua_encodepointer(L, uintptr_t(ptr));
lua_pushfstring(L, "%s: 0x%016llx", luaL_typename(L, idx), enc);
break;
}
}
return lua_tolstring(L, -1, len);
}