diff --git a/patches/defer_statement_for_Lua_5_3.patch b/patches/defer_statement_for_Lua_5_3.patch new file mode 100644 index 0000000..91cabaa --- /dev/null +++ b/patches/defer_statement_for_Lua_5_3.patch @@ -0,0 +1,974 @@ +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 */ + }; +