cosmopolitan/third_party/lua/lstate.c
Michael Lenaghan 0dbf01bf1d
Bring Lua to 5.4.6. (#1214)
This essentially re-does the work of #875 on top of master.

This is what I did to check that Cosmo's Lua extensions still worked:

```
$ build/bootstrap/make MODE=aarch64 o/aarch64/third_party/lua/lua
$ ape o/aarch64/third_party/lua/lua
>: 10
10
>: 010
8
>: 0b10
2
>: string.byte("\e")
27
>: "Hello, %s" % {"world"}
Hello, world
>: "*" * 3
***
```

`luaL_traceback2` was used to show the stack trace with parameter
values; it's used in `LuaCallWithTrace`, which is used in Redbean to run
Lua code. You should be able to see the extended stack trace by running
something like this: `redbean -e "function a(b)c()end a(2)"` (with
"params" indicating the extended stack trace):

```
stack traceback:
 [string "function a(b)c()end a(2)"]:1: in function 'a', params: b = 2;
 [string "function a(b)c()end a(2)"]:1: in main chunk
```
@pkulchenko confirmed that I get the expected result with the updated
code.

This is what I did to check that Lua itself still worked:

```
$ cd third_party/lua/test/
$ ape ../../../o/aarch64/third_party/lua/lua all.lua
```

There's one test failure, in `files.lua`:

```
***** FILE 'files.lua'*****
testing i/o
../../../o/aarch64/third_party/lua/lua: files.lua:84: assertion failed!
stack traceback:
[C]: in function 'assert'
files.lua:84: in main chunk
(...tail calls...)
all.lua:195: in main chunk
[C]: in ?
.>>> closing state <<<
```

That isn't a result of these changes; the same test is failing in
master.

The failure is here:

```lua
if not _port then   -- invalid seek
  local status, msg, code = io.stdin:seek("set", 1000)
  assert(not status and type(msg) == "string" and type(code) == "number")
end
```

The test expects a seek to offset 1,000 on stdin to fail — but it
doesn't. `status` ends up being the new offset rather than `nil`.

If I comment out that one test, the remaining tests succeed.
2024-06-15 20:13:08 -04:00

470 lines
14 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
╚──────────────────────────────────────────────────────────────────────────────╝
│ │
│ Lua │
│ Copyright © 2004-2023 Lua.org, PUC-Rio. │
│ │
│ Permission is hereby granted, free of charge, to any person obtaining │
│ a copy of this software and associated documentation files (the │
│ "Software"), to deal in the Software without restriction, including │
│ without limitation the rights to use, copy, modify, merge, publish, │
│ distribute, sublicense, and/or sell copies of the Software, and to │
│ permit persons to whom the Software is furnished to do so, subject to │
│ the following conditions: │
│ │
│ The above copyright notice and this permission notice shall be │
│ included in all copies or substantial portions of the Software. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │
│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │
│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │
│ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY │
│ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, │
│ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE │
│ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
│ │
╚─────────────────────────────────────────────────────────────────────────────*/
#define lstate_c
#define LUA_CORE
#include "libc/str/str.h"
#include "libc/time.h"
#include "third_party/lua/lapi.h"
#include "third_party/lua/ldebug.h"
#include "third_party/lua/ldo.h"
#include "third_party/lua/lfunc.h"
#include "third_party/lua/lgc.h"
#include "third_party/lua/llex.h"
#include "third_party/lua/lmem.h"
#include "third_party/lua/lprefix.h"
#include "third_party/lua/lstate.h"
#include "third_party/lua/lstring.h"
#include "third_party/lua/ltable.h"
#include "third_party/lua/ltm.h"
#include "third_party/lua/lua.h"
__static_yoink("lua_notice");
/*
** thread state + extra space
*/
typedef struct LX {
lu_byte extra_[LUA_EXTRASPACE];
lua_State l;
} LX;
/*
** Main thread combines a thread state and the global state
*/
typedef struct LG {
LX l;
global_State g;
} LG;
#define fromstate(L) (cast(LX *, cast(lu_byte *, (L)) - offsetof(LX, l)))
/*
** A macro to create a "random" seed when a state is created;
** the seed is used to randomize string hashes.
*/
#if !defined(luai_makeseed)
/*
** Compute an initial seed with some level of randomness.
** Rely on Address Space Layout Randomization (if present) and
** current time.
*/
#define addbuff(b,p,e) \
{ size_t t = cast_sizet(e); \
memcpy(b + p, &t, sizeof(t)); p += sizeof(t); }
static unsigned int luai_makeseed (lua_State *L) {
char buff[3 * sizeof(size_t)];
unsigned int h = cast_uint(time(NULL));
int p = 0;
addbuff(buff, p, L); /* heap variable */
addbuff(buff, p, &h); /* local variable */
addbuff(buff, p, &lua_newstate); /* public function */
lua_assert(p == sizeof(buff));
return luaS_hash(buff, p, h);
}
#endif
/*
** set GCdebt to a new value keeping the value (totalbytes + GCdebt)
** invariant (and avoiding underflows in 'totalbytes')
*/
void luaE_setdebt (global_State *g, l_mem debt) {
l_mem tb = gettotalbytes(g);
lua_assert(tb > 0);
if (debt < tb - MAX_LMEM)
debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */
g->totalbytes = tb - debt;
g->GCdebt = debt;
}
LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) {
UNUSED(L); UNUSED(limit);
return LUAI_MAXCCALLS; /* warning?? */
}
CallInfo *luaE_extendCI (lua_State *L) {
CallInfo *ci;
lua_assert(L->ci->next == NULL);
ci = luaM_new(L, CallInfo);
lua_assert(L->ci->next == NULL);
L->ci->next = ci;
ci->previous = L->ci;
ci->next = NULL;
ci->u.l.trap = 0;
L->nci++;
return ci;
}
/*
** free all CallInfo structures not in use by a thread
*/
void luaE_freeCI (lua_State *L) {
CallInfo *ci = L->ci;
CallInfo *next = ci->next;
ci->next = NULL;
while ((ci = next) != NULL) {
next = ci->next;
luaM_free(L, ci);
L->nci--;
}
}
/*
** free half of the CallInfo structures not in use by a thread,
** keeping the first one.
*/
void luaE_shrinkCI (lua_State *L) {
CallInfo *ci = L->ci->next; /* first free CallInfo */
CallInfo *next;
if (ci == NULL)
return; /* no extra elements */
while ((next = ci->next) != NULL) { /* two extra elements? */
CallInfo *next2 = next->next; /* next's next */
ci->next = next2; /* remove next from the list */
L->nci--;
luaM_free(L, next); /* free next */
if (next2 == NULL)
break; /* no more elements */
else {
next2->previous = ci;
ci = next2; /* continue */
}
}
}
/*
** Called when 'getCcalls(L)' larger or equal to LUAI_MAXCCALLS.
** If equal, raises an overflow error. If value is larger than
** LUAI_MAXCCALLS (which means it is handling an overflow) but
** not much larger, does not report an error (to allow overflow
** handling to work).
*/
void luaE_checkcstack (lua_State *L) {
if (getCcalls(L) == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (getCcalls(L) >= (LUAI_MAXCCALLS / 10 * 11))
luaD_throw(L, LUA_ERRERR); /* error while handling stack error */
}
LUAI_FUNC void luaE_incCstack (lua_State *L) {
L->nCcalls++;
if (l_unlikely(getCcalls(L) >= LUAI_MAXCCALLS))
luaE_checkcstack(L);
}
static void stack_init (lua_State *L1, lua_State *L) {
int i; CallInfo *ci;
/* initialize stack array */
L1->stack.p = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue);
L1->tbclist.p = L1->stack.p;
for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++)
setnilvalue(s2v(L1->stack.p + i)); /* erase new stack */
L1->top.p = L1->stack.p;
L1->stack_last.p = L1->stack.p + BASIC_STACK_SIZE;
/* initialize first ci */
ci = &L1->base_ci;
ci->next = ci->previous = NULL;
ci->callstatus = CIST_C;
ci->func.p = L1->top.p;
ci->u.c.k = NULL;
ci->nresults = 0;
setnilvalue(s2v(L1->top.p)); /* 'function' entry for this 'ci' */
L1->top.p++;
ci->top.p = L1->top.p + LUA_MINSTACK;
L1->ci = ci;
}
static void freestack (lua_State *L) {
if (L->stack.p == NULL)
return; /* stack not completely built yet */
L->ci = &L->base_ci; /* free the entire 'ci' list */
luaE_freeCI(L);
lua_assert(L->nci == 0);
luaM_freearray(L, L->stack.p, stacksize(L) + EXTRA_STACK); /* free stack */
}
/*
** Create registry table and its predefined values
*/
static void init_registry (lua_State *L, global_State *g) {
/* create registry */
Table *registry = luaH_new(L);
sethvalue(L, &g->l_registry, registry);
luaH_resize(L, registry, LUA_RIDX_LAST, 0);
/* registry[LUA_RIDX_MAINTHREAD] = L */
setthvalue(L, &registry->array[LUA_RIDX_MAINTHREAD - 1], L);
/* registry[LUA_RIDX_GLOBALS] = new table (table of globals) */
sethvalue(L, &registry->array[LUA_RIDX_GLOBALS - 1], luaH_new(L));
}
/*
** open parts of the state that may cause memory-allocation errors.
*/
static void f_luaopen (lua_State *L, void *ud) {
global_State *g = G(L);
UNUSED(ud);
stack_init(L, L); /* init stack */
init_registry(L, g);
luaS_init(L);
luaT_init(L);
luaX_init(L);
g->gcstp = 0; /* allow gc */
setnilvalue(&g->nilvalue); /* now state is complete */
luai_userstateopen(L);
}
/*
** preinitialize a thread with consistent values without allocating
** any memory (to avoid errors)
*/
static void preinit_thread (lua_State *L, global_State *g) {
G(L) = g;
L->stack.p = NULL;
L->ci = NULL;
L->nci = 0;
L->twups = L; /* thread has no upvalues */
L->nCcalls = 0;
L->errorJmp = NULL;
L->hook = NULL;
L->hookmask = 0;
L->basehookcount = 0;
L->allowhook = 1;
resethookcount(L);
L->openupval = NULL;
L->status = LUA_OK;
L->errfunc = 0;
L->oldpc = 0;
}
static void close_state (lua_State *L) {
global_State *g = G(L);
if (!completestate(g)) /* closing a partially built state? */
luaC_freeallobjects(L); /* just collect its objects */
else { /* closing a fully built state */
L->ci = &L->base_ci; /* unwind CallInfo list */
luaD_closeprotected(L, 1, LUA_OK); /* close all upvalues */
luaC_freeallobjects(L); /* collect all objects */
luai_userstateclose(L);
}
luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size);
freestack(L);
lua_assert(gettotalbytes(g) == sizeof(LG));
(*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */
}
/**
* lua_newthread [-0, +1, m]
*
* Creates a new thread, pushes it on the stack, and returns a pointer to a
* lua_State that represents this new thread. The new thread returned by this
* function shares with the original thread its global environment, but has
* an independent execution stack.
*
* Threads are subject to garbage collection, like any Lua object.
*/
LUA_API lua_State *lua_newthread (lua_State *L) {
global_State *g = G(L);
GCObject *o;
lua_State *L1;
lua_lock(L);
luaC_checkGC(L);
/* create new thread */
o = luaC_newobjdt(L, LUA_TTHREAD, sizeof(LX), offsetof(LX, l));
L1 = gco2th(o);
/* anchor it on L stack */
setthvalue2s(L, L->top.p, L1);
api_incr_top(L);
preinit_thread(L1, g);
L1->hookmask = L->hookmask;
L1->basehookcount = L->basehookcount;
L1->hook = L->hook;
resethookcount(L1);
/* initialize L1 extra space */
memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread),
LUA_EXTRASPACE);
luai_userstatethread(L, L1);
stack_init(L1, L); /* init stack */
lua_unlock(L);
return L1;
}
void luaE_freethread (lua_State *L, lua_State *L1) {
LX *l = fromstate(L1);
luaF_closeupval(L1, L1->stack.p); /* close all upvalues */
lua_assert(L1->openupval == NULL);
luai_userstatefree(L, L1);
freestack(L1);
luaM_free(L, l);
}
int luaE_resetthread (lua_State *L, int status) {
CallInfo *ci = L->ci = &L->base_ci; /* unwind CallInfo list */
setnilvalue(s2v(L->stack.p)); /* 'function' entry for basic 'ci' */
ci->func.p = L->stack.p;
ci->callstatus = CIST_C;
if (status == LUA_YIELD)
status = LUA_OK;
L->status = LUA_OK; /* so it can run __close metamethods */
status = luaD_closeprotected(L, 1, status);
if (status != LUA_OK) /* errors? */
luaD_seterrorobj(L, status, L->stack.p + 1);
else
L->top.p = L->stack.p + 1;
ci->top.p = L->top.p + LUA_MINSTACK;
luaD_reallocstack(L, cast_int(ci->top.p - L->stack.p), 0);
return status;
}
LUA_API int lua_closethread (lua_State *L, lua_State *from) {
int status;
lua_lock(L);
L->nCcalls = (from) ? getCcalls(from) : 0;
status = luaE_resetthread(L, L->status);
lua_unlock(L);
return status;
}
/*
** Deprecated! Use 'lua_closethread' instead.
*/
LUA_API int lua_resetthread (lua_State *L) {
return lua_closethread(L, NULL);
}
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
int i;
lua_State *L;
global_State *g;
LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
if (l == NULL) return NULL;
L = &l->l.l;
g = &l->g;
L->tt = LUA_VTHREAD;
g->currentwhite = bitmask(WHITE0BIT);
L->marked = luaC_white(g);
preinit_thread(L, g);
g->allgc = obj2gco(L); /* by now, only object is the main thread */
L->next = NULL;
incnny(L); /* main thread is always non yieldable */
g->frealloc = f;
g->ud = ud;
g->warnf = NULL;
g->ud_warn = NULL;
g->mainthread = L;
g->seed = luai_makeseed(L);
g->gcstp = GCSTPGC; /* no GC while building state */
g->strt.size = g->strt.nuse = 0;
g->strt.hash = NULL;
setnilvalue(&g->l_registry);
g->panic = NULL;
g->gcstate = GCSpause;
g->gckind = KGC_INC;
g->gcstopem = 0;
g->gcemergency = 0;
g->finobj = g->tobefnz = g->fixedgc = NULL;
g->firstold1 = g->survival = g->old1 = g->reallyold = NULL;
g->finobjsur = g->finobjold1 = g->finobjrold = NULL;
g->sweepgc = NULL;
g->gray = g->grayagain = NULL;
g->weak = g->ephemeron = g->allweak = NULL;
g->twups = NULL;
g->totalbytes = sizeof(LG);
g->GCdebt = 0;
g->lastatomic = 0;
setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */
setgcparam(g->gcpause, LUAI_GCPAUSE);
setgcparam(g->gcstepmul, LUAI_GCMUL);
g->gcstepsize = LUAI_GCSTEPSIZE;
setgcparam(g->genmajormul, LUAI_GENMAJORMUL);
g->genminormul = LUAI_GENMINORMUL;
for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL;
if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) {
/* memory allocation error: free partial state */
close_state(L);
L = NULL;
}
return L;
}
LUA_API void lua_close (lua_State *L) {
lua_lock(L);
L = G(L)->mainthread; /* only the main thread can be closed */
close_state(L);
}
void luaE_warning (lua_State *L, const char *msg, int tocont) {
lua_WarnFunction wf = G(L)->warnf;
if (wf != NULL)
wf(G(L)->ud_warn, msg, tocont);
}
/*
** Generate a warning from an error message
*/
void luaE_warnerror (lua_State *L, const char *where) {
TValue *errobj = s2v(L->top.p - 1); /* error object */
const char *msg = (ttisstring(errobj))
? svalue(errobj)
: "error object is not a string";
/* produce warning "error in %s (%s)" (where, msg) */
luaE_warning(L, "error in ", 1);
luaE_warning(L, where, 1);
luaE_warning(L, " (", 1);
luaE_warning(L, msg, 1);
luaE_warning(L, ")", 0);
}