diff options
| author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-04-16 15:41:44 -0300 |
|---|---|---|
| committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-04-16 15:41:44 -0300 |
| commit | 681297187ec45268e872b26753c441586c12bdd8 (patch) | |
| tree | 90e62c82146470a4c4931801a36f66de0bc92318 | |
| parent | 5148954eed7550dcac587fce0214b470442efa0d (diff) | |
| download | lua-681297187ec45268e872b26753c441586c12bdd8.tar.gz lua-681297187ec45268e872b26753c441586c12bdd8.tar.bz2 lua-681297187ec45268e872b26753c441586c12bdd8.zip | |
Bug: yielding in '__close' mess up number of returns
Yielding in a __close metamethod called when returning vararg results
changes the top and so messes up the number of returned values.
| -rw-r--r-- | lstate.h | 2 | ||||
| -rw-r--r-- | lvm.c | 12 | ||||
| -rw-r--r-- | testes/locals.lua | 59 |
3 files changed, 71 insertions, 2 deletions
| @@ -165,7 +165,7 @@ typedef struct stringtable { | |||
| 165 | ** - field 'nyield' is used only while a function is "doing" an | 165 | ** - field 'nyield' is used only while a function is "doing" an |
| 166 | ** yield (from the yield until the next resume); | 166 | ** yield (from the yield until the next resume); |
| 167 | ** - field 'nres' is used only while closing tbc variables when | 167 | ** - field 'nres' is used only while closing tbc variables when |
| 168 | ** returning from a C function; | 168 | ** returning from a function; |
| 169 | ** - field 'transferinfo' is used only during call/returnhooks, | 169 | ** - field 'transferinfo' is used only during call/returnhooks, |
| 170 | ** before the function starts or after it ends. | 170 | ** before the function starts or after it ends. |
| 171 | */ | 171 | */ |
| @@ -847,10 +847,19 @@ void luaV_finishOp (lua_State *L) { | |||
| 847 | luaV_concat(L, total); /* concat them (may yield again) */ | 847 | luaV_concat(L, total); /* concat them (may yield again) */ |
| 848 | break; | 848 | break; |
| 849 | } | 849 | } |
| 850 | case OP_CLOSE: case OP_RETURN: { /* yielded closing variables */ | 850 | case OP_CLOSE: { /* yielded closing variables */ |
| 851 | ci->u.l.savedpc--; /* repeat instruction to close other vars. */ | 851 | ci->u.l.savedpc--; /* repeat instruction to close other vars. */ |
| 852 | break; | 852 | break; |
| 853 | } | 853 | } |
| 854 | case OP_RETURN: { /* yielded closing variables */ | ||
| 855 | StkId ra = base + GETARG_A(inst); | ||
| 856 | /* adjust top to signal correct number of returns, in case the | ||
| 857 | return is "up to top" ('isIT') */ | ||
| 858 | L->top = ra + ci->u2.nres; | ||
| 859 | /* repeat instruction to close other vars. and complete the return */ | ||
| 860 | ci->u.l.savedpc--; | ||
| 861 | break; | ||
| 862 | } | ||
| 854 | default: { | 863 | default: { |
| 855 | /* only these other opcodes can yield */ | 864 | /* only these other opcodes can yield */ |
| 856 | lua_assert(op == OP_TFORCALL || op == OP_CALL || | 865 | lua_assert(op == OP_TFORCALL || op == OP_CALL || |
| @@ -1672,6 +1681,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { | |||
| 1672 | n = cast_int(L->top - ra); /* get what is available */ | 1681 | n = cast_int(L->top - ra); /* get what is available */ |
| 1673 | savepc(ci); | 1682 | savepc(ci); |
| 1674 | if (TESTARG_k(i)) { /* may there be open upvalues? */ | 1683 | if (TESTARG_k(i)) { /* may there be open upvalues? */ |
| 1684 | ci->u2.nres = n; /* save number of returns */ | ||
| 1675 | if (L->top < ci->top) | 1685 | if (L->top < ci->top) |
| 1676 | L->top = ci->top; | 1686 | L->top = ci->top; |
| 1677 | luaF_close(L, base, CLOSEKTOP, 1); | 1687 | luaF_close(L, base, CLOSEKTOP, 1); |
diff --git a/testes/locals.lua b/testes/locals.lua index 6aad5d25..6151f64d 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
| @@ -814,6 +814,65 @@ end | |||
| 814 | 814 | ||
| 815 | 815 | ||
| 816 | do | 816 | do |
| 817 | -- yielding inside closing metamethods while returning | ||
| 818 | -- (bug in 5.4.3) | ||
| 819 | |||
| 820 | local extrares -- result from extra yield (if any) | ||
| 821 | |||
| 822 | local function check (body, extra, ...) | ||
| 823 | local t = table.pack(...) -- expected returns | ||
| 824 | local co = coroutine.wrap(body) | ||
| 825 | if extra then | ||
| 826 | extrares = co() -- runs until first (extra) yield | ||
| 827 | end | ||
| 828 | local res = table.pack(co()) -- runs until yield inside '__close' | ||
| 829 | assert(res.n == 2 and res[2] == nil) | ||
| 830 | local res2 = table.pack(co()) -- runs until end of function | ||
| 831 | assert(res2.n == t.n) | ||
| 832 | for i = 1, #t do | ||
| 833 | if t[i] == "x" then | ||
| 834 | assert(res2[i] == res[1]) -- value that was closed | ||
| 835 | else | ||
| 836 | assert(res2[i] == t[i]) | ||
| 837 | end | ||
| 838 | end | ||
| 839 | end | ||
| 840 | |||
| 841 | local function foo () | ||
| 842 | local x <close> = func2close(coroutine.yield) | ||
| 843 | local extra <close> = func2close(function (self) | ||
| 844 | assert(self == extrares) | ||
| 845 | coroutine.yield(100) | ||
| 846 | end) | ||
| 847 | extrares = extra | ||
| 848 | return table.unpack{10, x, 30} | ||
| 849 | end | ||
| 850 | check(foo, true, 10, "x", 30) | ||
| 851 | assert(extrares == 100) | ||
| 852 | |||
| 853 | local function foo () | ||
| 854 | local x <close> = func2close(coroutine.yield) | ||
| 855 | return | ||
| 856 | end | ||
| 857 | check(foo, false) | ||
| 858 | |||
| 859 | local function foo () | ||
| 860 | local x <close> = func2close(coroutine.yield) | ||
| 861 | local y, z = 20, 30 | ||
| 862 | return x | ||
| 863 | end | ||
| 864 | check(foo, false, "x") | ||
| 865 | |||
| 866 | local function foo () | ||
| 867 | local x <close> = func2close(coroutine.yield) | ||
| 868 | local extra <close> = func2close(coroutine.yield) | ||
| 869 | return table.unpack({}, 1, 100) -- 100 nils | ||
| 870 | end | ||
| 871 | check(foo, true, table.unpack({}, 1, 100)) | ||
| 872 | |||
| 873 | end | ||
| 874 | |||
| 875 | do | ||
| 817 | -- yielding inside closing metamethods after an error | 876 | -- yielding inside closing metamethods after an error |
| 818 | 877 | ||
| 819 | local co = coroutine.wrap(function () | 878 | local co = coroutine.wrap(function () |
