Index: lfunc.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lfunc.h b/lfunc.h --- a/lfunc.h (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lfunc.h (date 1620822689672) @@ -52,7 +52,7 @@ LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nupvals); LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); -LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level); +LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level, int deferred); LUAI_FUNC void luaF_closeupval (lua_State *L, StkId level); LUAI_FUNC void luaF_close (lua_State *L, StkId level, int status, int yy); LUAI_FUNC void luaF_unlinkupval (UpVal *uv); Index: lfunc.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lfunc.c b/lfunc.c --- a/lfunc.c (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lfunc.c (date 1620822689672) @@ -99,6 +99,16 @@ return newupval(L, 0, level, pp); } +/* DEFER patch */ +static void calldefermethod (lua_State *L, TValue *func, TValue *err) { + if (!ttisfunction(func)) + return; + StkId top = L->top; + setobj2s(L, top, func); /* will call defer function */ + setobj2s(L, top + 1, err); /* and error msg. as 1st argument */ + L->top = top + 2; /* add function and arguments */ + luaD_callnoyield(L, top, 0); +} /* ** Call closing method for object 'obj' with error message 'err'. The @@ -150,7 +160,10 @@ errobj = s2v(level + 1); /* error object goes after 'uv' */ luaD_seterrorobj(L, status, level + 1); /* set error object */ } - callclosemethod(L, uv, errobj, yy); + if (level->tbclist.is_deferred) + calldefermethod(L, uv, errobj); + else + callclosemethod(L, uv, errobj, yy); } @@ -166,16 +179,20 @@ /* ** Insert a variable in the list of to-be-closed variables. */ -void luaF_newtbcupval (lua_State *L, StkId level) { +void luaF_newtbcupval (lua_State *L, StkId level, int deferred) { lua_assert(level > L->tbclist); - if (l_isfalse(s2v(level))) - return; /* false doesn't need to be closed */ - checkclosemth(L, level); /* value must have a close method */ + if (!deferred) { + if (l_isfalse(s2v(level))) + return; /* false doesn't need to be closed */ + checkclosemth(L, level); /* value must have a close method */ + } while (cast_uint(level - L->tbclist) > MAXDELTA) { L->tbclist += MAXDELTA; /* create a dummy node at maximum delta */ L->tbclist->tbclist.delta = 0; + L->tbclist->tbclist.is_deferred = 0; } level->tbclist.delta = cast(unsigned short, level - L->tbclist); + level->tbclist.is_deferred = deferred ? 1 : 0; L->tbclist = level; } Index: lobject.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lobject.h b/lobject.h --- a/lobject.h (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lobject.h (date 1620822881235) @@ -147,6 +147,7 @@ TValue val; struct { TValuefields; + lu_byte is_deferred; /* 1 if deferred function, else 0 means regular TBC */ unsigned short delta; } tbclist; } StackValue; Index: lapi.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lapi.c b/lapi.c --- a/lapi.c (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lapi.c (date 1620822689657) @@ -1266,7 +1266,7 @@ o = index2stack(L, idx); nresults = L->ci->nresults; api_check(L, L->tbclist < o, "given index below or equal a marked one"); - luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ + luaF_newtbcupval(L, o, 0); /* create new to-be-closed upvalue */ if (!hastocloseCfunc(nresults)) /* function not marked yet? */ L->ci->nresults = codeNresults(nresults); /* mark it */ lua_assert(hastocloseCfunc(L->ci->nresults)); Index: testes/defer.lua IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/testes/defer.lua b/testes/defer.lua new file mode 100644 --- /dev/null (date 1620823329755) +++ b/testes/defer.lua (date 1620823329755) @@ -0,0 +1,314 @@ +-- ================================================================ +-- These tests are mostly adapted from Lua 5.4 tests for TBC variables +-- 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 = 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 string.find(msg, "@YYY")) -- 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(x == 1 and y == 1) + -- should get first error raised + assert(not st and string.find(msg, "%w+%.%w+:%d+: YYY")) + end + t() + print 'Test 12 OK' +end + +print 'OK' Index: lvm.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lvm.c b/lvm.c --- a/lvm.c (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lvm.c (date 1620822689826) @@ -1541,7 +1541,7 @@ } vmcase(OP_TBC) { /* create new to-be-closed upvalue */ - halfProtect(luaF_newtbcupval(L, ra)); + halfProtect(luaF_newtbcupval(L, ra, 0)); vmbreak; } vmcase(OP_JMP) { @@ -1752,7 +1752,7 @@ } vmcase(OP_TFORPREP) { /* create to-be-closed upvalue (if needed) */ - halfProtect(luaF_newtbcupval(L, ra + 3)); + halfProtect(luaF_newtbcupval(L, ra + 3, 0)); pc += GETARG_Bx(i); i = *(pc++); /* go to next instruction */ lua_assert(GET_OPCODE(i) == OP_TFORCALL && ra == RA(i)); @@ -1810,6 +1810,10 @@ halfProtect(pushclosure(L, p, cl->upvals, base, ra)); checkGC(L, ra + 1); vmbreak; + } + vmcase(OP_DEFER) { + halfProtect(luaF_newtbcupval(L, ra, 1)); + vmbreak; } vmcase(OP_VARARG) { int n = GETARG_C(i) - 1; /* required results */ Index: llex.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/llex.c b/llex.c --- a/llex.c (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/llex.c (date 1620822689703) @@ -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: ljumptab.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/ljumptab.h b/ljumptab.h --- a/ljumptab.h (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/ljumptab.h (date 1620822689703) @@ -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.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/llex.h b/llex.h --- a/llex.h (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/llex.h (date 1620822689726) @@ -33,7 +33,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: lopnames.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lopnames.h b/lopnames.h --- a/lopnames.h (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lopnames.h (date 1620822689788) @@ -93,6 +93,7 @@ "TFORLOOP", "SETLIST", "CLOSURE", + "DEFER", "VARARG", "VARARGPREP", "EXTRAARG", Index: lopcodes.c IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lopcodes.c b/lopcodes.c --- a/lopcodes.c (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lopcodes.c (date 1620822689757) @@ -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 =================================================================== diff --git a/lparser.c b/lparser.c --- a/lparser.c (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lparser.c (date 1620822689804) @@ -707,10 +707,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); + } } @@ -975,24 +982,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); } @@ -1168,7 +1177,7 @@ } case TK_FUNCTION: { luaX_next(ls); - body(ls, v, 0, ls->linenumber); + body(ls, v, 0, ls->linenumber, 0); return; } default: { @@ -1674,13 +1683,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; } @@ -1775,7 +1792,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 */ } @@ -1868,10 +1885,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: lopcodes.h IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/lopcodes.h b/lopcodes.h --- a/lopcodes.h (revision a03ceb4b47666501369a6b300e00f57c1dc1c708) +++ b/lopcodes.h (date 1620822689757) @@ -300,6 +300,7 @@ OP_SETLIST,/* A B C k R[A][C+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 */