Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
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 <close>
+ local x = setmetatable({"x"}, {__close = function (self)
+ a[#a + 1] = self[1] end})
+ defer getmetatable(x).__close(x) end
+ -- y is <close>
+ 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",
"//", "..", "...", "==", ">=", "<=", "~=",
"<<", ">>", "::", "<eof>",
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 */