diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-02-12 13:36:30 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-02-12 13:36:30 -0300 |
commit | bc970005ce2e258e29a5c315ea4e49f76a66586e (patch) | |
tree | 5575ca66aeddad2036df74f38fa2bed217c2801e | |
parent | f79ccdca9bbe9d486d91a44a4464b99ce38de0e2 (diff) | |
download | lua-bc970005ce2e258e29a5c315ea4e49f76a66586e.tar.gz lua-bc970005ce2e258e29a5c315ea4e49f76a66586e.tar.bz2 lua-bc970005ce2e258e29a5c315ea4e49f76a66586e.zip |
'__close' methods can yield in the return of a C function
When, inside a coroutine, a C function with to-be-closed slots return,
the corresponding metamethods can yield. ('__close' metamethods called
through 'lua_closeslot' still cannot yield, as there is no continuation
to go when resuming.)
-rw-r--r-- | lapi.h | 2 | ||||
-rw-r--r-- | ldo.c | 72 | ||||
-rw-r--r-- | lstate.h | 12 | ||||
-rw-r--r-- | manual/manual.of | 3 | ||||
-rw-r--r-- | testes/locals.lua | 76 |
5 files changed, 131 insertions, 34 deletions
@@ -42,6 +42,8 @@ | |||
42 | 42 | ||
43 | #define hastocloseCfunc(n) ((n) < LUA_MULTRET) | 43 | #define hastocloseCfunc(n) ((n) < LUA_MULTRET) |
44 | 44 | ||
45 | /* Map [-1, inf) (range of 'nresults') into (-inf, -2] */ | ||
45 | #define codeNresults(n) (-(n) - 3) | 46 | #define codeNresults(n) (-(n) - 3) |
47 | #define decodeNresults(n) (-(n) - 3) | ||
46 | 48 | ||
47 | #endif | 49 | #endif |
@@ -408,24 +408,27 @@ static void moveresults (lua_State *L, StkId res, int nres, int wanted) { | |||
408 | case LUA_MULTRET: | 408 | case LUA_MULTRET: |
409 | wanted = nres; /* we want all results */ | 409 | wanted = nres; /* we want all results */ |
410 | break; | 410 | break; |
411 | default: /* multiple results (or to-be-closed variables) */ | 411 | default: /* two/more results and/or to-be-closed variables */ |
412 | if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */ | 412 | if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */ |
413 | ptrdiff_t savedres = savestack(L, res); | 413 | ptrdiff_t savedres = savestack(L, res); |
414 | luaF_close(L, res, CLOSEKTOP, 0); /* may change the stack */ | 414 | L->ci->callstatus |= CIST_CLSRET; /* in case of yields */ |
415 | wanted = codeNresults(wanted); /* correct value */ | 415 | L->ci->u2.nres = nres; |
416 | if (wanted == LUA_MULTRET) | 416 | luaF_close(L, res, CLOSEKTOP, 1); |
417 | wanted = nres; | 417 | L->ci->callstatus &= ~CIST_CLSRET; |
418 | if (L->hookmask) /* if needed, call hook after '__close's */ | 418 | if (L->hookmask) /* if needed, call hook after '__close's */ |
419 | rethook(L, L->ci, nres); | 419 | rethook(L, L->ci, nres); |
420 | res = restorestack(L, savedres); /* close and hook can move stack */ | 420 | res = restorestack(L, savedres); /* close and hook can move stack */ |
421 | wanted = decodeNresults(wanted); | ||
422 | if (wanted == LUA_MULTRET) | ||
423 | wanted = nres; /* we want all results */ | ||
421 | } | 424 | } |
422 | break; | 425 | break; |
423 | } | 426 | } |
427 | /* generic case */ | ||
424 | firstresult = L->top - nres; /* index of first result */ | 428 | firstresult = L->top - nres; /* index of first result */ |
425 | /* move all results to correct place */ | 429 | if (nres > wanted) /* extra results? */ |
426 | if (nres > wanted) | 430 | nres = wanted; /* don't need them */ |
427 | nres = wanted; /* don't need more than that */ | 431 | for (i = 0; i < nres; i++) /* move all results to correct place */ |
428 | for (i = 0; i < nres; i++) | ||
429 | setobjs2s(L, res + i, firstresult + i); | 432 | setobjs2s(L, res + i, firstresult + i); |
430 | for (; i < wanted; i++) /* complete wanted number of results */ | 433 | for (; i < wanted; i++) /* complete wanted number of results */ |
431 | setnilvalue(s2v(res + i)); | 434 | setnilvalue(s2v(res + i)); |
@@ -445,6 +448,9 @@ void luaD_poscall (lua_State *L, CallInfo *ci, int nres) { | |||
445 | rethook(L, ci, nres); | 448 | rethook(L, ci, nres); |
446 | /* move results to proper place */ | 449 | /* move results to proper place */ |
447 | moveresults(L, ci->func, nres, wanted); | 450 | moveresults(L, ci->func, nres, wanted); |
451 | /* function cannot be in any of these cases when returning */ | ||
452 | lua_assert(!(ci->callstatus & | ||
453 | (CIST_HOOKED | CIST_YPCALL | CIST_FIN | CIST_TRAN | CIST_CLSRET))); | ||
448 | L->ci = ci->previous; /* back to caller (after closing variables) */ | 454 | L->ci = ci->previous; /* back to caller (after closing variables) */ |
449 | } | 455 | } |
450 | 456 | ||
@@ -615,28 +621,36 @@ static int finishpcallk (lua_State *L, CallInfo *ci) { | |||
615 | 621 | ||
616 | /* | 622 | /* |
617 | ** Completes the execution of a C function interrupted by an yield. | 623 | ** Completes the execution of a C function interrupted by an yield. |
618 | ** The interruption must have happened while the function was | 624 | ** The interruption must have happened while the function was either |
619 | ** executing 'lua_callk' or 'lua_pcallk'. In the second case, the | 625 | ** closing its tbc variables in 'moveresults' or executing |
620 | ** call to 'finishpcallk' finishes the interrupted execution of | 626 | ** 'lua_callk'/'lua_pcallk'. In the first case, it just redoes |
621 | ** 'lua_pcallk'. After that, it calls the continuation of the | 627 | ** 'luaD_poscall'. In the second case, the call to 'finishpcallk' |
622 | ** interrupted function and finally it completes the job of the | 628 | ** finishes the interrupted execution of 'lua_pcallk'. After that, it |
623 | ** 'luaD_call' that called the function. | 629 | ** calls the continuation of the interrupted function and finally it |
624 | ** In the call to 'adjustresults', we do not know the number of | 630 | ** completes the job of the 'luaD_call' that called the function. In |
625 | ** results of the function called by 'lua_callk'/'lua_pcallk', | 631 | ** the call to 'adjustresults', we do not know the number of results |
626 | ** so we are conservative and use LUA_MULTRET (always adjust). | 632 | ** of the function called by 'lua_callk'/'lua_pcallk', so we are |
633 | ** conservative and use LUA_MULTRET (always adjust). | ||
627 | */ | 634 | */ |
628 | static void finishCcall (lua_State *L, CallInfo *ci) { | 635 | static void finishCcall (lua_State *L, CallInfo *ci) { |
629 | int n; | 636 | int n; /* actual number of results from C function */ |
630 | int status = LUA_YIELD; /* default if there were no errors */ | 637 | if (ci->callstatus & CIST_CLSRET) { /* was returning? */ |
631 | /* must have a continuation and must be able to call it */ | 638 | lua_assert(hastocloseCfunc(ci->nresults)); |
632 | lua_assert(ci->u.c.k != NULL && yieldable(L)); | 639 | n = ci->u2.nres; /* just redo 'luaD_poscall' */ |
633 | if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */ | 640 | /* don't need to reset CIST_CLSRET, as it will be set again anyway */ |
634 | status = finishpcallk(L, ci); /* finish it */ | 641 | } |
635 | adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */ | 642 | else { |
636 | lua_unlock(L); | 643 | int status = LUA_YIELD; /* default if there were no errors */ |
637 | n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */ | 644 | /* must have a continuation and must be able to call it */ |
638 | lua_lock(L); | 645 | lua_assert(ci->u.c.k != NULL && yieldable(L)); |
639 | api_checknelems(L, n); | 646 | if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */ |
647 | status = finishpcallk(L, ci); /* finish it */ | ||
648 | adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */ | ||
649 | lua_unlock(L); | ||
650 | n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */ | ||
651 | lua_lock(L); | ||
652 | api_checknelems(L, n); | ||
653 | } | ||
640 | luaD_poscall(L, ci, n); /* finish 'luaD_call' */ | 654 | luaD_poscall(L, ci, n); /* finish 'luaD_call' */ |
641 | } | 655 | } |
642 | 656 | ||
@@ -164,6 +164,8 @@ typedef struct stringtable { | |||
164 | ** protected call; | 164 | ** protected call; |
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 | ||
168 | ** returning from a C function; | ||
167 | ** - field 'transferinfo' is used only during call/returnhooks, | 169 | ** - field 'transferinfo' is used only during call/returnhooks, |
168 | ** before the function starts or after it ends. | 170 | ** before the function starts or after it ends. |
169 | */ | 171 | */ |
@@ -186,6 +188,7 @@ typedef struct CallInfo { | |||
186 | union { | 188 | union { |
187 | int funcidx; /* called-function index */ | 189 | int funcidx; /* called-function index */ |
188 | int nyield; /* number of values yielded */ | 190 | int nyield; /* number of values yielded */ |
191 | int nres; /* number of values returned */ | ||
189 | struct { /* info about transferred values (for call/return hooks) */ | 192 | struct { /* info about transferred values (for call/return hooks) */ |
190 | unsigned short ftransfer; /* offset of first value transferred */ | 193 | unsigned short ftransfer; /* offset of first value transferred */ |
191 | unsigned short ntransfer; /* number of values transferred */ | 194 | unsigned short ntransfer; /* number of values transferred */ |
@@ -203,15 +206,16 @@ typedef struct CallInfo { | |||
203 | #define CIST_C (1<<1) /* call is running a C function */ | 206 | #define CIST_C (1<<1) /* call is running a C function */ |
204 | #define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ | 207 | #define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ |
205 | #define CIST_HOOKED (1<<3) /* call is running a debug hook */ | 208 | #define CIST_HOOKED (1<<3) /* call is running a debug hook */ |
206 | #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ | 209 | #define CIST_YPCALL (1<<4) /* doing a yieldable protected call */ |
207 | #define CIST_TAIL (1<<5) /* call was tail called */ | 210 | #define CIST_TAIL (1<<5) /* call was tail called */ |
208 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ | 211 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ |
209 | #define CIST_FIN (1<<7) /* call is running a finalizer */ | 212 | #define CIST_FIN (1<<7) /* call is running a finalizer */ |
210 | #define CIST_TRAN (1<<8) /* 'ci' has transfer information */ | 213 | #define CIST_TRAN (1<<8) /* 'ci' has transfer information */ |
211 | /* Bits 9-11 are used for CIST_RECST (see below) */ | 214 | #define CIST_CLSRET (1<<9) /* function is closing tbc variables */ |
212 | #define CIST_RECST 9 | 215 | /* Bits 10-12 are used for CIST_RECST (see below) */ |
216 | #define CIST_RECST 10 | ||
213 | #if defined(LUA_COMPAT_LT_LE) | 217 | #if defined(LUA_COMPAT_LT_LE) |
214 | #define CIST_LEQ (1<<12) /* using __lt for __le */ | 218 | #define CIST_LEQ (1<<13) /* using __lt for __le */ |
215 | #endif | 219 | #endif |
216 | 220 | ||
217 | 221 | ||
diff --git a/manual/manual.of b/manual/manual.of index 89069281..e7040b2b 100644 --- a/manual/manual.of +++ b/manual/manual.of | |||
@@ -3102,6 +3102,9 @@ Close the to-be-closed slot at the given index and set its value to @nil. | |||
3102 | The index must be the last index previously marked to be closed | 3102 | The index must be the last index previously marked to be closed |
3103 | @see{lua_toclose} that is still active (that is, not closed yet). | 3103 | @see{lua_toclose} that is still active (that is, not closed yet). |
3104 | 3104 | ||
3105 | A @Lid{__close} metamethod cannot yield | ||
3106 | when called through this function. | ||
3107 | |||
3105 | (Exceptionally, this function was introduced in release 5.4.3. | 3108 | (Exceptionally, this function was introduced in release 5.4.3. |
3106 | It is not present in previous 5.4 releases.) | 3109 | It is not present in previous 5.4 releases.) |
3107 | 3110 | ||
diff --git a/testes/locals.lua b/testes/locals.lua index 446ec13a..a93839db 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
@@ -707,7 +707,6 @@ if rawget(_G, "T") then | |||
707 | -- results are correct | 707 | -- results are correct |
708 | checktable(t, {10, 20}) | 708 | checktable(t, {10, 20}) |
709 | end | 709 | end |
710 | |||
711 | end | 710 | end |
712 | 711 | ||
713 | 712 | ||
@@ -930,6 +929,81 @@ assert(co == nil) -- eventually it will be collected | |||
930 | collectgarbage() | 929 | collectgarbage() |
931 | 930 | ||
932 | 931 | ||
932 | if rawget(_G, "T") then | ||
933 | print("to-be-closed variables x coroutines in C") | ||
934 | do | ||
935 | local token = 0 | ||
936 | local count = 0 | ||
937 | local f = T.makeCfunc[[ | ||
938 | toclose 1 | ||
939 | toclose 2 | ||
940 | return . | ||
941 | ]] | ||
942 | |||
943 | local obj = func2close(function (_, msg) | ||
944 | count = count + 1 | ||
945 | token = coroutine.yield(count, token) | ||
946 | end) | ||
947 | |||
948 | local co = coroutine.wrap(f) | ||
949 | local ct, res = co(obj, obj, 10, 20, 30, 3) -- will return 10, 20, 30 | ||
950 | -- initial token value, after closing 2nd obj | ||
951 | assert(ct == 1 and res == 0) | ||
952 | -- run until yield when closing 1st obj | ||
953 | ct, res = co(100) | ||
954 | assert(ct == 2 and res == 100) | ||
955 | res = {co(200)} -- run until end | ||
956 | assert(res[1] == 10 and res[2] == 20 and res[3] == 30 and res[4] == nil) | ||
957 | assert(token == 200) | ||
958 | end | ||
959 | |||
960 | do | ||
961 | local f = T.makeCfunc[[ | ||
962 | toclose 1 | ||
963 | return . | ||
964 | ]] | ||
965 | |||
966 | local obj = func2close(function () | ||
967 | local temp | ||
968 | local x <close> = func2close(function () | ||
969 | coroutine.yield(temp) | ||
970 | return 1,2,3 -- to be ignored | ||
971 | end) | ||
972 | temp = coroutine.yield("closing obj") | ||
973 | return 1,2,3 -- to be ignored | ||
974 | end) | ||
975 | |||
976 | local co = coroutine.wrap(f) | ||
977 | local res = co(obj, 10, 30, 1) -- will return only 30 | ||
978 | assert(res == "closing obj") | ||
979 | res = co("closing x") | ||
980 | assert(res == "closing x") | ||
981 | res = {co()} | ||
982 | assert(res[1] == 30 and res[2] == nil) | ||
983 | end | ||
984 | |||
985 | do | ||
986 | -- still cannot yield inside 'closeslot' | ||
987 | local f = T.makeCfunc[[ | ||
988 | toclose 1 | ||
989 | closeslot 1 | ||
990 | ]] | ||
991 | local obj = func2close(coroutine.yield) | ||
992 | local co = coroutine.create(f) | ||
993 | local st, msg = coroutine.resume(co, obj) | ||
994 | assert(not st and string.find(msg, "attempt to yield across")) | ||
995 | |||
996 | -- nor outside a coroutine | ||
997 | local f = T.makeCfunc[[ | ||
998 | toclose 1 | ||
999 | ]] | ||
1000 | local st, msg = pcall(f, obj) | ||
1001 | assert(not st and string.find(msg, "attempt to yield from outside")) | ||
1002 | end | ||
1003 | end | ||
1004 | |||
1005 | |||
1006 | |||
933 | -- to-be-closed variables in generic for loops | 1007 | -- to-be-closed variables in generic for loops |
934 | do | 1008 | do |
935 | local numopen = 0 | 1009 | local numopen = 0 |