Index: testes/all.lua IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- testes/all.lua (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ testes/all.lua (date 1594850137246) @@ -184,6 +184,7 @@ dofile('bitwise.lua') assert(dofile('verybig.lua', true) == 10); collectgarbage() dofile('files.lua') +dofile('defer.lua') if #msgs > 0 then print("\ntests not performed:") Index: ldo.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- ldo.h (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ ldo.h (date 1594850124117) @@ -53,6 +53,7 @@ LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); +LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); #endif Index: ldo.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- ldo.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ ldo.c (date 1594850137277) @@ -88,7 +88,7 @@ }; -static void seterrorobj (lua_State *L, int errcode, StkId oldtop) { +void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { switch (errcode) { case LUA_ERRMEM: { /* memory error? */ setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */ @@ -98,6 +98,10 @@ setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); break; } + case CLOSEPROTECT: { + setnilvalue(oldtop); /* no error message */ + break; + } default: { setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ break; @@ -114,6 +118,7 @@ } else { /* thread has no error handler */ global_State *g = G(L); + errcode = luaF_close(L, L->stack, errcode); /* close all upvalues */ L->status = cast_byte(errcode); /* mark it as dead */ if (g->mainthread->errorJmp) { /* main thread has a handler? */ setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */ @@ -121,7 +126,7 @@ } else { /* no handler at all; abort */ if (g->panic) { /* panic function? */ - seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */ + luaD_seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */ if (L->ci->top < L->top) L->ci->top = L->top; /* pushing msg. can break this invariant */ lua_unlock(L); @@ -584,8 +589,8 @@ if (ci == NULL) return 0; /* no recovery point */ /* "finish" luaD_pcall */ oldtop = restorestack(L, ci->extra); - luaF_close(L, oldtop); - seterrorobj(L, status, oldtop); + luaF_close(L, oldtop, status); + luaD_seterrorobj(L, status, oldtop); L->ci = ci; L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ L->nny = 0; /* should be zero to be yieldable */ @@ -662,19 +667,17 @@ L->nny = 0; /* allow yields */ api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); status = luaD_rawrunprotected(L, resume, &nargs); - if (status == -1) /* error calling 'lua_resume'? */ - status = LUA_ERRRUN; - else { /* continue running after recoverable errors */ - while (errorstatus(status) && recover(L, status)) { - /* unroll continuation */ - status = luaD_rawrunprotected(L, unroll, &status); - } - if (errorstatus(status)) { /* unrecoverable error? */ - L->status = cast_byte(status); /* mark thread as 'dead' */ - seterrorobj(L, status, L->top); /* push error message */ - L->ci->top = L->top; - } - else lua_assert(status == L->status); /* normal end or yield */ + /* continue running after recoverable errors */ + while (errorstatus(status) && recover(L, status)) { + /* unroll continuation */ + status = luaD_rawrunprotected(L, unroll, &status); + } + if (!errorstatus(status)) + lua_assert(status == L->status); /* normal end or yield */ + else { /* unrecoverable error */ + L->status = cast_byte(status); /* mark thread as 'dead' */ + luaD_seterrorobj(L, status, L->top); /* push error message */ + L->ci->top = L->top; } L->nny = oldnny; /* restore 'nny' */ L->nCcalls--; @@ -729,11 +732,12 @@ status = luaD_rawrunprotected(L, func, u); if (status != LUA_OK) { /* an error occurred? */ StkId oldtop = restorestack(L, old_top); - luaF_close(L, oldtop); /* close possible pending closures */ - seterrorobj(L, status, oldtop); L->ci = old_ci; L->allowhook = old_allowhooks; L->nny = old_nny; + status = luaF_close(L, oldtop, status); /* close possible pending closures */ + oldtop = restorestack(L, old_top); + luaD_seterrorobj(L, status, oldtop); luaD_shrinkstack(L); } L->errfunc = old_errfunc; Index: lfunc.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lfunc.h (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lfunc.h (date 1594850137293) @@ -34,7 +34,8 @@ */ struct UpVal { TValue *v; /* points to stack or to its own value */ - lu_mem refcount; /* reference counter */ + unsigned int refcount; /* reference counter */ + unsigned int flags; /* Used to mark deferred values */ union { struct { /* (when open) */ UpVal *next; /* linked list */ @@ -46,13 +47,22 @@ #define upisopen(up) ((up)->v != &(up)->u.value) +/* +** Special "status" for 'luaF_close' +*/ + +/* close upvalues without running their closing methods */ +#define NOCLOSINGMETH (-1) + +/* close upvalues running all closing methods in protected mode */ +#define CLOSEPROTECT (-2) LUAI_FUNC Proto *luaF_newproto (lua_State *L); LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems); LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems); LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); -LUAI_FUNC void luaF_close (lua_State *L, StkId level); +LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status); LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, int pc); Index: lstate.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lstate.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lstate.c (date 1594850137347) @@ -241,7 +241,7 @@ static void close_state (lua_State *L) { global_State *g = G(L); - luaF_close(L, L->stack); /* close all upvalues for this thread */ + luaF_close(L, L->stack, CLOSEPROTECT); /* close all upvalues for this thread */ luaC_freeallobjects(L); /* collect all objects */ if (g->version) /* closing a fully built state? */ luai_userstateclose(L); @@ -284,13 +284,33 @@ void luaE_freethread (lua_State *L, lua_State *L1) { LX *l = fromstate(L1); - luaF_close(L1, L1->stack); /* close all upvalues for this thread */ + luaF_close(L1, L1->stack, NOCLOSINGMETH); /* close all upvalues for this thread */ lua_assert(L1->openupval == NULL); luai_userstatefree(L, L1); freestack(L1); luaM_free(L, l); } +int lua_resetthread (lua_State *L) { + CallInfo *ci; + int status; + lua_lock(L); + L->ci = ci = &L->base_ci; /* unwind CallInfo list */ + setnilvalue(L->stack); /* 'function' entry for basic 'ci' */ + ci->func = L->stack; + ci->callstatus = 0; + status = luaF_close(L, L->stack, CLOSEPROTECT); + if (status != CLOSEPROTECT) /* real errors? */ + luaD_seterrorobj(L, status, L->stack + 1); + else { + status = LUA_OK; + L->top = L->stack + 1; + } + ci->top = L->top + LUA_MINSTACK; + L->status = status; + lua_unlock(L); + return status; +} LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; Index: lfunc.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lfunc.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lfunc.c (date 1594850137293) @@ -14,6 +14,7 @@ #include "lua.h" +#include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" @@ -61,13 +62,15 @@ lua_assert(isintwups(L) || L->openupval == NULL); while (*pp != NULL && (p = *pp)->v >= level) { lua_assert(upisopen(p)); - if (p->v == level) /* found a corresponding upvalue? */ - return p; /* return it */ + if (p->v == level && !p->flags) /* found a corresponding upvalue that is not a deferred value? */ { + return p; /* return it */ + } pp = &p->u.open.next; } /* not found: create a new upvalue */ uv = luaM_new(L, UpVal); uv->refcount = 0; + uv->flags = 0; uv->u.open.next = *pp; /* link it to list of open upvalues */ uv->u.open.touched = 1; *pp = uv; @@ -79,20 +82,84 @@ return uv; } +static void calldeferred(lua_State *L, void *ud) { + UNUSED(ud); + luaD_callnoyield(L, L->top - 2, 0); +} + +/* +** Prepare deferred function plus its arguments for object 'obj' with +** error message 'err'. (This function assumes EXTRA_STACK.) +*/ +static int preparetocall(lua_State *L, TValue *func, TValue *err) { + StkId top = L->top; + setobj2s(L, top, func); /* will call deferred function */ + if (err) { + setobj2s(L, top + 1, err); /* and error msg. as 1st argument */ + } + else { + setnilvalue(top + 1); + } + L->top = top + 2; /* add function and arguments */ + return 1; +} -void luaF_close (lua_State *L, StkId level) { +/* +** Prepare and call a deferred function. If status is OK, code is still +** inside the original protected call, and so any error will be handled +** there. Otherwise, a previous error already activated the original +** protected call, and so the call to the deferred method must be +** protected here. (A status == -1 behaves like a previous +** error, to also run the closing method in protected mode). +** If status is OK, the call to the deferred method will be pushed +** at the top of the stack. Otherwise, values are pushed after +** the 'level' of the upvalue containing deferred function, as everything after +** that won't be used again. +*/ +static int calldeferredfunction(lua_State *L, StkId level, int status) { + TValue *uv = level; /* value being closed */ + if (status == LUA_OK) { + preparetocall(L, uv, NULL); /* something to call? */ + calldeferred(L, NULL); /* call closing method */ + } + else { /* must close the object in protected mode */ + ptrdiff_t oldtop; + level++; /* space for error message */ + oldtop = savestack(L, level + 1); /* top will be after that */ + luaD_seterrorobj(L, status, level); /* set error message */ + preparetocall(L, uv, level); + int newstatus = luaD_pcall(L, calldeferred, NULL, oldtop, 0); + if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */ + status = newstatus; /* this will be the new error */ + else { + /* leave original error (or nil) on top */ + L->top = restorestack(L, oldtop); + } + } + return status; +} + +int luaF_close (lua_State *L, StkId level, int status) { UpVal *uv; while (L->openupval != NULL && (uv = L->openupval)->v >= level) { lua_assert(upisopen(uv)); L->openupval = uv->u.open.next; /* remove from 'open' list */ - if (uv->refcount == 0) /* no references? */ - luaM_free(L, uv); /* free upvalue */ + if (uv->refcount == 0) { /* no references? */ + UpVal uv1 = *uv; /* copy the upvalue as we will free it below */ + luaM_free(L, uv); /* free upvalue before invoking any deferred functions */ + if (status != NOCLOSINGMETH && uv1.flags && ttisfunction(uv1.v)) { + ptrdiff_t levelrel = savestack(L, level); + status = calldeferredfunction(L, uv1.v, status); + level = restorestack(L, levelrel); + } + } else { setobj(L, &uv->u.value, uv->v); /* move value to upvalue slot */ uv->v = &uv->u.value; /* now current value lives here */ luaC_upvalbarrier(L, uv); } } + return status; } Index: llex.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- llex.h (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ llex.h (date 1594850124176) @@ -27,7 +27,7 @@ /* terminal symbols denoted by reserved words */ TK_AND = FIRST_RESERVED, TK_BREAK, TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, - TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, + TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_DEFER, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /* other terminal symbols */ TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, Index: lparser.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lparser.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lparser.c (date 1594850137347) @@ -52,6 +52,7 @@ lu_byte nactvar; /* # active locals outside the block */ lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isloop; /* true if 'block' is a loop */ + lu_byte insidetbc; /* true if inside the scope of a defer stmt (i.e. defer closure var) */ } BlockCnt; @@ -442,6 +443,7 @@ bl->firstlabel = fs->ls->dyd->label.n; bl->firstgoto = fs->ls->dyd->gt.n; bl->upval = 0; + bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); bl->previous = fs->bl; fs->bl = bl; lua_assert(fs->freereg == fs->nactvar); @@ -519,10 +521,17 @@ ** so that, if it invokes the GC, the GC knows which registers ** are in use at that time. */ -static void codeclosure (LexState *ls, expdesc *v) { +static void codeclosure (LexState *ls, expdesc *v, int deferred) { FuncState *fs = ls->fs->prev; + int pc = -1; + if (deferred) { + pc = luaK_codeABC(fs, OP_DEFER, 0, 0, 0); + } init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1)); luaK_exp2nextreg(fs, v); /* fix it at the last register */ + if (deferred) { + SETARG_A(fs->f->code[pc], v->u.info); + } } @@ -780,24 +789,26 @@ } -static void body (LexState *ls, expdesc *e, int ismethod, int line) { +static void body (LexState *ls, expdesc *e, int ismethod, int line, int deferred) { /* body -> '(' parlist ')' block END */ FuncState new_fs; BlockCnt bl; new_fs.f = addprototype(ls); new_fs.f->linedefined = line; open_func(ls, &new_fs, &bl); - checknext(ls, '('); - if (ismethod) { - new_localvarliteral(ls, "self"); /* create 'self' parameter */ - adjustlocalvars(ls, 1); - } - parlist(ls); - checknext(ls, ')'); + if (!deferred) { + checknext(ls, '('); + if (ismethod) { + new_localvarliteral(ls, "self"); /* create 'self' parameter */ + adjustlocalvars(ls, 1); + } + parlist(ls); + checknext(ls, ')'); + } statlist(ls); new_fs.f->lastlinedefined = ls->linenumber; check_match(ls, TK_END, TK_FUNCTION, line); - codeclosure(ls, e); + codeclosure(ls, e, deferred); close_func(ls); } @@ -972,7 +983,7 @@ } case TK_FUNCTION: { luaX_next(ls); - body(ls, v, 0, ls->linenumber); + body(ls, v, 0, ls->linenumber, 0); return; } default: { @@ -1429,12 +1440,19 @@ } -static void localfunc (LexState *ls) { +static void localfunc (LexState *ls, int defer) { expdesc b; FuncState *fs = ls->fs; - new_localvar(ls, str_checkname(ls)); /* new local variable */ + if (defer) { + static const char funcname[] = "(deferred function)"; + new_localvar(ls, luaX_newstring(ls, funcname, sizeof funcname-1)); /* new local variable */ + markupval(fs, fs->nactvar); + fs->bl->insidetbc = 1; /* in the scope of a defer closure variable */ + } else { + new_localvar(ls, str_checkname(ls)); /* new local variable */ + } adjustlocalvars(ls, 1); /* enter its scope */ - body(ls, &b, 0, ls->linenumber); /* function created in next register */ + body(ls, &b, 0, ls->linenumber, defer); /* function created in next register */ /* debug information will only see the variable after this point! */ getlocvar(fs, b.u.info)->startpc = fs->pc; } @@ -1480,7 +1498,7 @@ expdesc v, b; luaX_next(ls); /* skip FUNCTION */ ismethod = funcname(ls, &v); - body(ls, &b, ismethod, line); + body(ls, &b, ismethod, line, 0); luaK_storevar(ls->fs, &v, &b); luaK_fixline(ls->fs, line); /* definition "happens" in the first line */ } @@ -1513,7 +1531,7 @@ nret = explist(ls, &e); /* optional return values */ if (hasmultret(e.k)) { luaK_setmultret(fs, &e); - if (e.k == VCALL && nret == 1) { /* tail call? */ + if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */ SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar); } @@ -1572,10 +1590,15 @@ case TK_LOCAL: { /* stat -> localstat */ luaX_next(ls); /* skip LOCAL */ if (testnext(ls, TK_FUNCTION)) /* local function? */ - localfunc(ls); + localfunc(ls, 0); else localstat(ls); break; + } + case TK_DEFER: { /* stat -> deferstat */ + luaX_next(ls); /* skip DEFER */ + localfunc(ls, 1); + break; } case TK_DBCOLON: { /* stat -> label */ luaX_next(ls); /* skip double colon */ Index: llex.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- llex.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ llex.c (date 1594850124157) @@ -40,7 +40,7 @@ static const char *const luaX_tokens [] = { "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", - "in", "local", "nil", "not", "or", "repeat", + "in", "local", "defer", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "//", "..", "...", "==", ">=", "<=", "~=", "<<", ">>", "::", "", Index: testes/defer.lua IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- testes/defer.lua (date 1594849634130) +++ testes/defer.lua (date 1594849634130) @@ -0,0 +1,313 @@ +-- ================================================================ +-- Following section is an extract from the code.lua test +-- These functions test bytecode generation, and also provide +-- helper routines that we use later on in other test cases + +-- testing opcodes +function check (f, ...) + if not T then + return true + end + local arg = {...} + local c = T.listcode(f) + for i=1, #arg do + --print(arg[i], c[i]) + opcodes_coverage[arg[i]] = opcodes_coverage[arg[i]]+1 + assert(string.find(c[i], '- '..arg[i]..' *[AB][xs]?=%d')) + end + assert(c[#arg+2] == nil) +end + +-- Test defer statement +do + local y = 0 + local function x() + defer y = y + 1 end + defer y = y + 1 end + end + check(x, 'DEFER', 'CLOSURE', 'DEFER', 'CLOSURE', 'RETURN') + x() + assert(y == 2) + print 'Test 1 OK' +end + +-- Test defer statement +do + local y = 0 + local function x() + defer y = y + 1 end + error('raise error') + defer y = y + 2 end -- will not be called + end + pcall(x) + assert(y == 1) + print 'Test 2 OK' +end + +-- Test defer statement +do + local y = 0 + local function x() + defer y = y + 1 end + defer y = y + 2; error('err') end + defer y = y + 3 end + end + pcall(x) + assert(y == 6) + print 'Test 3 OK' +end + +-- Test defer statement in tailcalls +do + local y = 0 + local function x (n) + defer y = y + 1 end + if n > 0 then return x(n - 1) end + end + pcall(x, 3) + assert(y == 4) + print 'Test 4 OK' +end + +-- Simulate a test of resource closure with defer +do + local y = 0 + local z = { count = 0 } + z.__index = z; + function z:new() + local object = {} + setmetatable(object, z) + return object + end + function z:open(arg) + if (arg) then + z.count = z.count + 1 + return + end + y = 1 + error('error opening') + end + function z.close() + z.count = z.count - 1 + end + local function x(arg) + local f = z:new() + f:open(arg) + assert(z.count == 1) + defer f:close() end + end + x('filename') + assert(y == 0) + assert(z.count == 0) + pcall(x, false) + assert(z.count == 0) + assert(y == 1) + print 'Test 5 OK' +end + +--- Test stack reallocation in defer statement +do + local function x(a) if a <= 0 then return else x(a-1) end end + local y = 1000 + local function z(...) + -- recursive call to make stack + defer x(y) end + return ... + end + do + local a,b,c = z(1,2,3) + assert(a == 1 and b == 2 and c == 3) + a,b,c = z(3,2,1) + assert(a == 3 and b == 2 and c == 1) + end + print 'Test 6 OK' +end + +-- Adapted from Lua 5.4 +local function stack(n) n = ((n == 0) or stack(n - 1)) end + +local function func2close (f, x, y) + local obj = setmetatable({}, {__close = f}) + if x then + return x, obj, y + else + return obj + end +end + +do + local function t() + local a = {} + do + local b = false -- not to be closed + -- x is + local x = setmetatable({"x"}, {__close = function (self) + a[#a + 1] = self[1] end}) + defer getmetatable(x).__close(x) end + -- y is + local w, y, z = func2close(function (self, err) + assert(err == nil); a[#a + 1] = "y" + end, 10, 20) + defer getmetatable(y).__close(y) end + local c = nil -- not to be closed + a[#a + 1] = "in" + assert(w == 10 and z == 20) + end + a[#a + 1] = "out" + assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out") + end + t() + print 'Test 7 OK' +end + +do + local function t() + local X = false + + local x, closescope = func2close(function () stack(10); X = true end, 100) + assert(x == 100); x = 101; -- 'x' is not read-only + + -- closing functions do not corrupt returning values + local function foo (x) + local _ = closescope + defer getmetatable(_).__close(_) end + return x, X, 23 + end + + local a, b, c = foo(1.5) + assert(a == 1.5 and b == false and c == 23 and X == true) + + X = false + foo = function (x) + local _ = closescope + defer getmetatable(_).__close(_) end + local y = 15 + return y + end + + assert(foo() == 15 and X == true) + + X = false + foo = function () + local x = closescope + defer getmetatable(x).__close(x) end + return x + end + + assert(foo() == closescope and X == true) + end + t() + print 'Test 8 OK' +end + +do + local function t() + -- calls cannot be tail in the scope of to-be-closed variables + local X, Y + local function foo () + local _ = func2close(function () Y = 10 end) + defer getmetatable(_).__close(_) end + assert(X == true and Y == nil) -- 'X' not closed yet + return 1,2,3 + end + + local function bar () + local _ = func2close(function () X = false end) + defer getmetatable(_).__close(_) end + X = true + do + return foo() -- not a tail call! + end + end + + local a, b, c, d = bar() + assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil) + return foo, bar + end + local f,b = t() + print 'Test 9 OK' +end + +do + local function t() + -- an error in a wrapped coroutine closes variables + local x = false + local y = false + local co = coroutine.wrap(function () + local xv = func2close(function () x = true end) + defer getmetatable(xv).__close(xv) end + do + local yv = func2close(function () y = true end) + defer getmetatable(yv).__close(yv) end + coroutine.yield(100) -- yield doesn't close variable + end + coroutine.yield(200) -- yield doesn't close variable + error(23) -- error does + end) + + local b = co() + assert(b == 100 and not x and not y) + b = co() + assert(b == 200 and not x and y) + local a, b = pcall(co) + assert(not a and b == 23 and x and y) + end + t() + print 'Test 10 OK' +end + +-- a suspended coroutine should not close its variables when collected +do + function t() + local co + co = coroutine.wrap(function() + -- should not run + local x = func2close(function () os.exit(false) end) + defer getmetatable(x).__close(x) end + co = nil + coroutine.yield() + end) + co() -- start coroutine + assert(co == nil) -- eventually it will be collected + collectgarbage() + end + t() + print 'Test 11 OK' +end + +do + local function t() + -- error in a wrapped coroutine raising errors when closing a variable + local x = 0 + local co = coroutine.wrap(function () + local xx = func2close(function () x = x + 1; error("@YYY") end) + defer getmetatable(xx).__close(xx) end + local xv = func2close(function () x = x + 1; error("@XXX") end) + defer getmetatable(xv).__close(xv) end + coroutine.yield(100) + error(200) + end) + assert(co() == 100); assert(x == 0) + local st, msg = pcall(co); assert(x == 2) + assert(not st and msg == 200) -- should get first error raised + + local x = 0 + local y = 0 + co = coroutine.wrap(function () + local xx = func2close(function () y = y + 1; error("YYY") end) + defer getmetatable(xx).__close(xx) end + local xv = func2close(function () x = x + 1; error("XXX") end) + defer getmetatable(xv).__close(xv) end + coroutine.yield(100) + return 200 + end) + assert(co() == 100); assert(x == 0) + local st, msg = pcall(co) + assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX")) + assert(x == 1 and y == 1) + end + t() + print 'Test 12 OK' +end + +print 'OK' Index: lopcodes.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lopcodes.h (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lopcodes.h (date 1594850124198) @@ -230,11 +230,13 @@ OP_VARARG,/* A B R(A), R(A+1), ..., R(A+B-2) = vararg */ -OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ +OP_EXTRAARG,/* Ax extra (larger) argument for previous opcode */ +OP_DEFER /* A mark variable A "deferred" */ + } OpCode; -#define NUM_OPCODES (cast(int, OP_EXTRAARG) + 1) +#define NUM_OPCODES (cast(int, OP_DEFER) + 1) Index: lvm.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lvm.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lvm.c (date 1594850137378) @@ -737,7 +737,7 @@ /* execute a jump instruction */ #define dojump(ci,i,e) \ { int a = GETARG_A(i); \ - if (a != 0) luaF_close(L, ci->u.l.base + a - 1); \ + if (a != 0) Protect(luaF_close(L, ci->u.l.base + a - 1, LUA_OK)); \ ci->u.l.savedpc += GETARG_sBx(i) + e; } /* for test instructions, execute the jump instruction that follows it */ @@ -1159,7 +1159,7 @@ StkId lim = nci->u.l.base + getproto(nfunc)->numparams; int aux; /* close all upvalues from previous call */ - if (cl->p->sizep > 0) luaF_close(L, oci->u.l.base); + if (cl->p->sizep > 0) Protect(luaF_close(L, oci->u.l.base, NOCLOSINGMETH)); /* move new frame into old one */ for (aux = 0; nfunc + aux < lim; aux++) setobjs2s(L, ofunc + aux, nfunc + aux); @@ -1175,7 +1175,10 @@ } vmcase(OP_RETURN) { int b = GETARG_B(i); - if (cl->p->sizep > 0) luaF_close(L, base); + if (cl->p->sizep > 0) { + Protect(luaF_close(L, base, LUA_OK)); + ra = RA(i); + } b = luaD_poscall(L, ci, ra, (b != 0 ? b - 1 : cast_int(L->top - ra))); if (ci->callstatus & CIST_FRESH) /* local 'ci' still from callee */ return; /* external invocation: return */ @@ -1313,6 +1316,12 @@ vmcase(OP_EXTRAARG) { lua_assert(0); vmbreak; + } + vmcase(OP_DEFER) { + UpVal *up = luaF_findupval(L, ra); /* create new upvalue */ + up->flags = 1; /* mark it as deferred */ + setnilvalue(ra); /* initialize it with nil */ + vmbreak; } } } Index: lcorolib.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lcorolib.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lcorolib.c (date 1594850137262) @@ -75,8 +75,11 @@ lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); if (r < 0) { + int stat = lua_status(co); + if (stat != LUA_OK && stat != LUA_YIELD) + lua_resetthread(co); /* close variables in case of errors */ if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */ - luaL_where(L, 1); /* add extra info */ + luaL_where(L, 1); /* add extra info, if available */ lua_insert(L, -2); lua_concat(L, 2); } Index: lua.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lua.h (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lua.h (date 1594850137362) @@ -144,6 +144,7 @@ LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); LUA_API void (lua_close) (lua_State *L); LUA_API lua_State *(lua_newthread) (lua_State *L); +LUA_API int (lua_resetthread) (lua_State *L); LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); Index: lopcodes.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lopcodes.c (revision e7411fab800e2cfa810a1ba296356532eabdde40) +++ lopcodes.c (date 1594850124178) @@ -65,6 +65,7 @@ "CLOSURE", "VARARG", "EXTRAARG", + "DEFER", NULL }; @@ -119,6 +120,7 @@ ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */ ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */ ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */ - ,opmode(0, 0, OpArgU, OpArgU, iAx) /* OP_EXTRAARG */ + ,opmode(0, 0, OpArgU, OpArgU, iAx) /* OP_EXTRAARG */ + ,opmode(0, 1, OpArgN, OpArgN, iABC) /* OP_DEFER */ };