From 6ccd24eff58340c00db2877c4558a63c6b859442 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Tue, 19 Jan 2021 10:03:13 -0300 Subject: [PATCH] Simpler handling of errors when creating tbc variables New field 'lua_State.ptbc' keeps to-be-closed variable until its upvalue is created, so that it can be closed in case of a memory-allocation error. --- ldo.c | 1 + lfunc.c | 37 ++++++++++++++++--------------------- lstate.c | 4 ++-- lstate.h | 1 + manual/manual.of | 4 ---- testes/locals.lua | 13 +++++-------- 6 files changed, 25 insertions(+), 35 deletions(-) diff --git a/ldo.c b/ldo.c index 45cfd592..9e3d6955 100644 --- a/ldo.c +++ b/ldo.c @@ -163,6 +163,7 @@ static void correctstack (lua_State *L, StkId oldstack, StkId newstack) { if (oldstack == newstack) return; /* stack address did not change */ L->top = (L->top - oldstack) + newstack; + lua_assert(L->ptbc == NULL); for (up = L->openupval; up != NULL; up = up->u.open.next) up->v = s2v((uplevel(up) - oldstack) + newstack); for (ci = L->ci; ci != NULL; ci = ci->previous) { diff --git a/lfunc.c b/lfunc.c index 13e44d46..81ac9f0a 100644 --- a/lfunc.c +++ b/lfunc.c @@ -155,32 +155,19 @@ static void prepcallclosemth (lua_State *L, StkId level, int status, int yy) { /* -** Try to create a to-be-closed upvalue -** (can raise a memory-allocation error) -*/ -static void trynewtbcupval (lua_State *L, void *ud) { - newupval(L, 1, cast(StkId, ud), &L->openupval); -} - - -/* -** Create a to-be-closed upvalue. If there is a memory error -** when creating the upvalue, the closing method must be called here, -** as there is no upvalue to call it later. +** Create a to-be-closed upvalue. If there is a memory allocation error, +** 'ptbc' keeps the object so it can be closed as soon as possible. +** (Since memory errors have no handler, that will happen before any +** stack reallocation.) */ void luaF_newtbcupval (lua_State *L, StkId level) { TValue *obj = s2v(level); lua_assert(L->openupval == NULL || uplevel(L->openupval) < level); if (!l_isfalse(obj)) { /* false doesn't need to be closed */ - int status; checkclosemth(L, level, obj); - status = luaD_rawrunprotected(L, trynewtbcupval, level); - if (unlikely(status != LUA_OK)) { /* memory error creating upvalue? */ - lua_assert(status == LUA_ERRMEM); - luaD_seterrorobj(L, LUA_ERRMEM, level + 1); /* save error message */ - callclosemethod(L, s2v(level), s2v(level + 1), 0); - luaD_throw(L, LUA_ERRMEM); /* throw memory error */ - } + L->ptbc = level; /* in case of allocation error */ + newupval(L, 1, level, &L->openupval); + L->ptbc = NULL; /* no errors */ } } @@ -196,11 +183,19 @@ void luaF_unlinkupval (UpVal *uv) { /* ** Close all upvalues up to the given stack level. A 'status' equal ** to NOCLOSINGMETH closes upvalues without running any __close -** metamethods. +** metamethods. If there is a pending to-be-closed value, close +** it before anything else. */ void luaF_close (lua_State *L, StkId level, int status, int yy) { UpVal *uv; StkId upl; /* stack index pointed by 'uv' */ + if (unlikely(status == LUA_ERRMEM && L->ptbc != NULL)) { + upl = L->ptbc; + L->ptbc = NULL; /* remove from "list" before closing */ + prepcallclosemth(L, upl, status, yy); + } + else + lua_assert(L->ptbc == NULL); /* must be empty for other status */ while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) { TValue *slot = &uv->u.value; /* new position for value */ lua_assert(uplevel(uv) < L->top); diff --git a/lstate.c b/lstate.c index 92ccbf9b..c708a162 100644 --- a/lstate.c +++ b/lstate.c @@ -253,6 +253,7 @@ static void preinit_thread (lua_State *L, global_State *g) { 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; @@ -263,6 +264,7 @@ static void preinit_thread (lua_State *L, global_State *g) { L->status = LUA_OK; L->errfunc = 0; L->oldpc = 0; + L->ptbc = NULL; } @@ -296,7 +298,6 @@ LUA_API lua_State *lua_newthread (lua_State *L) { setthvalue2s(L, L->top, L1); api_incr_top(L); preinit_thread(L1, g); - L1->nCcalls = 0; L1->hookmask = L->hookmask; L1->basehookcount = L->basehookcount; L1->hook = L->hook; @@ -363,7 +364,6 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { preinit_thread(L, g); g->allgc = obj2gco(L); /* by now, only object is the main thread */ L->next = NULL; - L->nCcalls = 0; incnny(L); /* main thread is always non yieldable */ g->frealloc = f; g->ud = ud; diff --git a/lstate.h b/lstate.h index 38248e57..65d45264 100644 --- a/lstate.h +++ b/lstate.h @@ -308,6 +308,7 @@ struct lua_State { int basehookcount; int hookcount; volatile l_signalT hookmask; + StkId ptbc; /* pending to-be-closed variable */ }; diff --git a/manual/manual.of b/manual/manual.of index 2fe332a4..89069281 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -4358,10 +4358,6 @@ nor modified before a corresponding call to @Lid{lua_closeslot}. This function should not be called for an index that is equal to or below an active to-be-closed index. -In the case of an out-of-memory error, -the value in the given index is immediately closed, -as if it was already marked. - Note that, both in case of errors and of a regular return, by the time the @idx{__close} metamethod runs, the @N{C stack} was already unwound, diff --git a/testes/locals.lua b/testes/locals.lua index 8506195e..24a95d18 100644 --- a/testes/locals.lua +++ b/testes/locals.lua @@ -539,15 +539,17 @@ if rawget(_G, "T") then local _, msg = pcall(foo) assert(msg == "not enough memory") + local closemsg local close = func2close(function (self, msg) T.alloccount() - assert(msg == "not enough memory") + closemsg = msg end) -- set a memory limit and return a closing object to remove the limit local function enter (count) stack(10) -- reserve some stack space T.alloccount(count) + closemsg = nil return close end @@ -558,12 +560,7 @@ if rawget(_G, "T") then end local _, msg = pcall(test) - assert(msg == "not enough memory") - - -- now use metamethod for closing - close = setmetatable({}, {__close = function () - T.alloccount() - end}) + assert(msg == "not enough memory" and closemsg == "not enough memory") -- repeat test with extra closing upvalues local function test () @@ -580,7 +577,7 @@ if rawget(_G, "T") then end local _, msg = pcall(test) - assert(msg == 1000) + assert(msg == 1000 and closemsg == "not enough memory") do -- testing 'toclose' in C string buffer collectgarbage()