diff options
| author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-01-18 11:40:45 -0300 |
|---|---|---|
| committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2021-01-18 11:40:45 -0300 |
| commit | d0f34d91373fa265d4445e456e4a10ce206c1559 (patch) | |
| tree | 0389b97b7844634144158df06040ea89c63fda46 | |
| parent | 825ac8eca8e384d6ad2538b5670088c31e08a9d7 (diff) | |
| download | lua-d0f34d91373fa265d4445e456e4a10ce206c1559.tar.gz lua-d0f34d91373fa265d4445e456e4a10ce206c1559.tar.bz2 lua-d0f34d91373fa265d4445e456e4a10ce206c1559.zip | |
Allow yields in '__close' metamethods ater errors
Completes commit b07fc10e91a. '__close' metamethods can yield even
when they are being called due to an error. '__close' metamethods from
C functions are still not allowed to yield.
| -rw-r--r-- | ldo.c | 127 | ||||
| -rw-r--r-- | lstate.h | 22 | ||||
| -rw-r--r-- | testes/locals.lua | 48 |
3 files changed, 126 insertions, 71 deletions
| @@ -565,25 +565,64 @@ void luaD_callnoyield (lua_State *L, StkId func, int nResults) { | |||
| 565 | 565 | ||
| 566 | 566 | ||
| 567 | /* | 567 | /* |
| 568 | ** Completes the execution of an interrupted C function, calling its | 568 | ** Finish the job of 'lua_pcallk' after it was interrupted by an yield. |
| 569 | ** continuation function. | 569 | ** (The caller, 'finishCcall', does the final call to 'adjustresults'.) |
| 570 | ** The main job is to complete the 'luaD_pcall' called by 'lua_pcallk'. | ||
| 571 | ** If a '__close' method yields here, eventually control will be back | ||
| 572 | ** to 'finishCcall' (when that '__close' method finally returns) and | ||
| 573 | ** 'finishpcallk' will run again and close any still pending '__close' | ||
| 574 | ** methods. Similarly, if a '__close' method errs, 'precover' calls | ||
| 575 | ** 'unroll' which calls ''finishCcall' and we are back here again, to | ||
| 576 | ** close any pending '__close' methods. | ||
| 577 | ** Note that, up to the call to 'luaF_close', the corresponding | ||
| 578 | ** 'CallInfo' is not modified, so that this repeated run works like the | ||
| 579 | ** first one (except that it has at least one less '__close' to do). In | ||
| 580 | ** particular, field CIST_RECST preserves the error status across these | ||
| 581 | ** multiple runs, changing only if there is a new error. | ||
| 570 | */ | 582 | */ |
| 571 | static void finishCcall (lua_State *L, int status) { | 583 | static int finishpcallk (lua_State *L, CallInfo *ci) { |
| 572 | CallInfo *ci = L->ci; | 584 | int status = getcistrecst(ci); /* get original status */ |
| 585 | if (status == LUA_OK) /* no error? */ | ||
| 586 | status = LUA_YIELD; /* was interrupted by an yield */ | ||
| 587 | else { /* error */ | ||
| 588 | StkId func = restorestack(L, ci->u2.funcidx); | ||
| 589 | L->allowhook = getoah(ci->callstatus); /* restore 'allowhook' */ | ||
| 590 | luaF_close(L, func, status, 1); /* can yield or raise an error */ | ||
| 591 | func = restorestack(L, ci->u2.funcidx); /* stack may be moved */ | ||
| 592 | luaD_seterrorobj(L, status, func); | ||
| 593 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ | ||
| 594 | setcistrecst(ci, LUA_OK); /* clear original status */ | ||
| 595 | } | ||
| 596 | ci->callstatus &= ~CIST_YPCALL; | ||
| 597 | L->errfunc = ci->u.c.old_errfunc; | ||
| 598 | /* if it is here, there were errors or yields; unlike 'lua_pcallk', | ||
| 599 | do not change status */ | ||
| 600 | return status; | ||
| 601 | } | ||
| 602 | |||
| 603 | |||
| 604 | /* | ||
| 605 | ** Completes the execution of a C function interrupted by an yield. | ||
| 606 | ** The interruption must have happened while the function was | ||
| 607 | ** executing 'lua_callk' or 'lua_pcallk'. In the second case, the | ||
| 608 | ** call to 'finishpcallk' finishes the interrupted execution of | ||
| 609 | ** 'lua_pcallk'. After that, it calls the continuation of the | ||
| 610 | ** interrupted function and finally it completes the job of the | ||
| 611 | ** 'luaD_call' that called the function. | ||
| 612 | ** In the call to 'adjustresults', we do not know the number of | ||
| 613 | ** results of the function called by 'lua_callk'/'lua_pcallk', | ||
| 614 | ** so we are conservative and use LUA_MULTRET (always adjust). | ||
| 615 | */ | ||
| 616 | static void finishCcall (lua_State *L, CallInfo *ci) { | ||
| 573 | int n; | 617 | int n; |
| 618 | int status = LUA_YIELD; /* default if there were no errors */ | ||
| 574 | /* must have a continuation and must be able to call it */ | 619 | /* must have a continuation and must be able to call it */ |
| 575 | lua_assert(ci->u.c.k != NULL && yieldable(L)); | 620 | lua_assert(ci->u.c.k != NULL && yieldable(L)); |
| 576 | /* error status can only happen in a protected call */ | 621 | if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */ |
| 577 | lua_assert((ci->callstatus & CIST_YPCALL) || status == LUA_YIELD); | 622 | status = finishpcallk(L, ci); /* finish it */ |
| 578 | if (ci->callstatus & CIST_YPCALL) { /* was inside a pcall? */ | 623 | adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */ |
| 579 | ci->callstatus &= ~CIST_YPCALL; /* continuation is also inside it */ | ||
| 580 | L->errfunc = ci->u.c.old_errfunc; /* with the same error function */ | ||
| 581 | } | ||
| 582 | /* finish 'lua_callk'/'lua_pcall'; CIST_YPCALL and 'errfunc' already | ||
| 583 | handled */ | ||
| 584 | adjustresults(L, ci->nresults); | ||
| 585 | lua_unlock(L); | 624 | lua_unlock(L); |
| 586 | n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation function */ | 625 | n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */ |
| 587 | lua_lock(L); | 626 | lua_lock(L); |
| 588 | api_checknelems(L, n); | 627 | api_checknelems(L, n); |
| 589 | luaD_poscall(L, ci, n); /* finish 'luaD_call' */ | 628 | luaD_poscall(L, ci, n); /* finish 'luaD_call' */ |
| @@ -600,7 +639,7 @@ static void unroll (lua_State *L, void *ud) { | |||
| 600 | UNUSED(ud); | 639 | UNUSED(ud); |
| 601 | while ((ci = L->ci) != &L->base_ci) { /* something in the stack */ | 640 | while ((ci = L->ci) != &L->base_ci) { /* something in the stack */ |
| 602 | if (!isLua(ci)) /* C function? */ | 641 | if (!isLua(ci)) /* C function? */ |
| 603 | finishCcall(L, LUA_YIELD); /* complete its execution */ | 642 | finishCcall(L, ci); /* complete its execution */ |
| 604 | else { /* Lua function */ | 643 | else { /* Lua function */ |
| 605 | luaV_finishOp(L); /* finish interrupted instruction */ | 644 | luaV_finishOp(L); /* finish interrupted instruction */ |
| 606 | luaV_execute(L, ci); /* execute down to higher C 'boundary' */ | 645 | luaV_execute(L, ci); /* execute down to higher C 'boundary' */ |
| @@ -624,40 +663,6 @@ static CallInfo *findpcall (lua_State *L) { | |||
| 624 | 663 | ||
| 625 | 664 | ||
| 626 | /* | 665 | /* |
| 627 | ** Auxiliary structure to call 'recover' in protected mode. | ||
| 628 | */ | ||
| 629 | struct RecoverS { | ||
| 630 | int status; | ||
| 631 | CallInfo *ci; | ||
| 632 | }; | ||
| 633 | |||
| 634 | |||
| 635 | /* | ||
| 636 | ** Recovers from an error in a coroutine: completes the execution of the | ||
| 637 | ** interrupted 'luaD_pcall', completes the interrupted C function which | ||
| 638 | ** called 'lua_pcallk', and continues running the coroutine. If there is | ||
| 639 | ** an error in 'luaF_close', this function will be called again and the | ||
| 640 | ** coroutine will continue from where it left. | ||
| 641 | */ | ||
| 642 | static void recover (lua_State *L, void *ud) { | ||
| 643 | struct RecoverS *r = cast(struct RecoverS *, ud); | ||
| 644 | int status = r->status; | ||
| 645 | CallInfo *ci = r->ci; /* recover point */ | ||
| 646 | StkId func = restorestack(L, ci->u2.funcidx); | ||
| 647 | /* "finish" luaD_pcall */ | ||
| 648 | L->ci = ci; | ||
| 649 | L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */ | ||
| 650 | luaF_close(L, func, status, 0); /* may change the stack */ | ||
| 651 | func = restorestack(L, ci->u2.funcidx); | ||
| 652 | luaD_seterrorobj(L, status, func); | ||
| 653 | luaD_shrinkstack(L); /* restore stack size in case of overflow */ | ||
| 654 | L->errfunc = ci->u.c.old_errfunc; | ||
| 655 | finishCcall(L, status); /* finish 'lua_pcallk' callee */ | ||
| 656 | unroll(L, NULL); /* continue running the coroutine */ | ||
| 657 | } | ||
| 658 | |||
| 659 | |||
| 660 | /* | ||
| 661 | ** Signal an error in the call to 'lua_resume', not in the execution | 666 | ** Signal an error in the call to 'lua_resume', not in the execution |
| 662 | ** of the coroutine itself. (Such errors should not be handled by any | 667 | ** of the coroutine itself. (Such errors should not be handled by any |
| 663 | ** coroutine error handler and should not kill the coroutine.) | 668 | ** coroutine error handler and should not kill the coroutine.) |
| @@ -705,19 +710,21 @@ static void resume (lua_State *L, void *ud) { | |||
| 705 | 710 | ||
| 706 | 711 | ||
| 707 | /* | 712 | /* |
| 708 | ** Calls 'recover' in protected mode, repeating while there are | 713 | ** Unrolls a coroutine in protected mode while there are recoverable |
| 709 | ** recoverable errors, that is, errors inside a protected call. (Any | 714 | ** errors, that is, errors inside a protected call. (Any error |
| 710 | ** error interrupts 'recover', and this loop protects it again so it | 715 | ** interrupts 'unroll', and this loop protects it again so it can |
| 711 | ** can continue.) Stops with a normal end (status == LUA_OK), an yield | 716 | ** continue.) Stops with a normal end (status == LUA_OK), an yield |
| 712 | ** (status == LUA_YIELD), or an unprotected error ('findpcall' doesn't | 717 | ** (status == LUA_YIELD), or an unprotected error ('findpcall' doesn't |
| 713 | ** find a recover point). | 718 | ** find a recover point). |
| 714 | */ | 719 | */ |
| 715 | static int p_recover (lua_State *L, int status) { | 720 | static int precover (lua_State *L, int status) { |
| 716 | struct RecoverS r; | 721 | CallInfo *ci; |
| 717 | r.status = status; | 722 | while (errorstatus(status) && (ci = findpcall(L)) != NULL) { |
| 718 | while (errorstatus(status) && (r.ci = findpcall(L)) != NULL) | 723 | L->ci = ci; /* go down to recovery functions */ |
| 719 | r.status = luaD_rawrunprotected(L, recover, &r); | 724 | setcistrecst(ci, status); /* status to finish 'pcall' */ |
| 720 | return r.status; | 725 | status = luaD_rawrunprotected(L, unroll, NULL); |
| 726 | } | ||
| 727 | return status; | ||
| 721 | } | 728 | } |
| 722 | 729 | ||
| 723 | 730 | ||
| @@ -738,7 +745,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs, | |||
| 738 | api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); | 745 | api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); |
| 739 | status = luaD_rawrunprotected(L, resume, &nargs); | 746 | status = luaD_rawrunprotected(L, resume, &nargs); |
| 740 | /* continue running after recoverable errors */ | 747 | /* continue running after recoverable errors */ |
| 741 | status = p_recover(L, status); | 748 | status = precover(L, status); |
| 742 | if (likely(!errorstatus(status))) | 749 | if (likely(!errorstatus(status))) |
| 743 | lua_assert(status == L->status); /* normal end or yield */ | 750 | lua_assert(status == L->status); /* normal end or yield */ |
| 744 | else { /* unrecoverable error */ | 751 | else { /* unrecoverable error */ |
| @@ -191,17 +191,33 @@ typedef struct CallInfo { | |||
| 191 | */ | 191 | */ |
| 192 | #define CIST_OAH (1<<0) /* original value of 'allowhook' */ | 192 | #define CIST_OAH (1<<0) /* original value of 'allowhook' */ |
| 193 | #define CIST_C (1<<1) /* call is running a C function */ | 193 | #define CIST_C (1<<1) /* call is running a C function */ |
| 194 | #define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ | 194 | #define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */ |
| 195 | #define CIST_HOOKED (1<<3) /* call is running a debug hook */ | 195 | #define CIST_HOOKED (1<<3) /* call is running a debug hook */ |
| 196 | #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ | 196 | #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ |
| 197 | #define CIST_TAIL (1<<5) /* call was tail called */ | 197 | #define CIST_TAIL (1<<5) /* call was tail called */ |
| 198 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ | 198 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ |
| 199 | #define CIST_FIN (1<<7) /* call is running a finalizer */ | 199 | #define CIST_FIN (1<<7) /* call is running a finalizer */ |
| 200 | #define CIST_TRAN (1<<8) /* 'ci' has transfer information */ | 200 | #define CIST_TRAN (1<<8) /* 'ci' has transfer information */ |
| 201 | /* Bits 9-11 are used for CIST_RECST (see below) */ | ||
| 202 | #define CIST_RECST 9 | ||
| 201 | #if defined(LUA_COMPAT_LT_LE) | 203 | #if defined(LUA_COMPAT_LT_LE) |
| 202 | #define CIST_LEQ (1<<9) /* using __lt for __le */ | 204 | #define CIST_LEQ (1<<12) /* using __lt for __le */ |
| 203 | #endif | 205 | #endif |
| 204 | 206 | ||
| 207 | |||
| 208 | /* | ||
| 209 | ** Field CIST_RECST stores the "recover status", used to keep the error | ||
| 210 | ** status while closing to-be-closed variables in coroutines, so that | ||
| 211 | ** Lua can correctly resume after an yield from a __close method called | ||
| 212 | ** because of an error. (Three bits are enough for error status.) | ||
| 213 | */ | ||
| 214 | #define getcistrecst(ci) (((ci)->callstatus >> CIST_RECST) & 7) | ||
| 215 | #define setcistrecst(ci,st) \ | ||
| 216 | check_exp(((st) & 7) == (st), /* status must fit in three bits */ \ | ||
| 217 | ((ci)->callstatus = ((ci)->callstatus & ~(7 << CIST_RECST)) \ | ||
| 218 | | ((st) << CIST_RECST))) | ||
| 219 | |||
| 220 | |||
| 205 | /* active function is a Lua function */ | 221 | /* active function is a Lua function */ |
| 206 | #define isLua(ci) (!((ci)->callstatus & CIST_C)) | 222 | #define isLua(ci) (!((ci)->callstatus & CIST_C)) |
| 207 | 223 | ||
diff --git a/testes/locals.lua b/testes/locals.lua index c9c93ccf..8506195e 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
| @@ -697,34 +697,66 @@ end | |||
| 697 | 697 | ||
| 698 | 698 | ||
| 699 | do | 699 | do |
| 700 | -- yielding inside closing metamethods after an error: | 700 | -- yielding inside closing metamethods after an error |
| 701 | -- not yet implemented; raises an error | ||
| 702 | 701 | ||
| 703 | local co = coroutine.wrap(function () | 702 | local co = coroutine.wrap(function () |
| 704 | 703 | ||
| 705 | local function foo (err) | 704 | local function foo (err) |
| 706 | 705 | ||
| 706 | local z <close> = func2close(function(_, msg) | ||
| 707 | assert(msg == nil or msg == err + 20) | ||
| 708 | coroutine.yield("z") | ||
| 709 | return 100, 200 | ||
| 710 | end) | ||
| 711 | |||
| 712 | local y <close> = func2close(function(_, msg) | ||
| 713 | -- still gets the original error (if any) | ||
| 714 | assert(msg == err or (msg == nil and err == 1)) | ||
| 715 | coroutine.yield("y") | ||
| 716 | if err then error(err + 20) end -- creates or changes the error | ||
| 717 | end) | ||
| 718 | |||
| 707 | local x <close> = func2close(function(_, msg) | 719 | local x <close> = func2close(function(_, msg) |
| 708 | assert(msg == err) | 720 | assert(msg == err or (msg == nil and err == 1)) |
| 709 | coroutine.yield("x") | 721 | coroutine.yield("x") |
| 710 | return 100, 200 | 722 | return 100, 200 |
| 711 | end) | 723 | end) |
| 712 | 724 | ||
| 713 | if err then error(err) else return 10, 20 end | 725 | if err == 10 then error(err) else return 10, 20 end |
| 714 | end | 726 | end |
| 715 | 727 | ||
| 716 | coroutine.yield(pcall(foo, nil)) -- no error | 728 | coroutine.yield(pcall(foo, nil)) -- no error |
| 729 | coroutine.yield(pcall(foo, 1)) -- error in __close | ||
| 717 | return pcall(foo, 10) -- 'foo' will raise an error | 730 | return pcall(foo, 10) -- 'foo' will raise an error |
| 718 | end) | 731 | end) |
| 719 | 732 | ||
| 720 | local a, b = co() | 733 | local a, b = co() -- first foo: no error |
| 721 | assert(a == "x" and b == nil) -- yields inside 'x'; Ok | 734 | assert(a == "x" and b == nil) -- yields inside 'x'; Ok |
| 722 | 735 | a, b = co() | |
| 736 | assert(a == "y" and b == nil) -- yields inside 'y'; Ok | ||
| 737 | a, b = co() | ||
| 738 | assert(a == "z" and b == nil) -- yields inside 'z'; Ok | ||
| 723 | local a, b, c = co() | 739 | local a, b, c = co() |
| 724 | assert(a and b == 10 and c == 20) -- returns from 'pcall(foo, nil)' | 740 | assert(a and b == 10 and c == 20) -- returns from 'pcall(foo, nil)' |
| 725 | 741 | ||
| 726 | local st, msg = co() -- error yielding after an error | 742 | local a, b = co() -- second foo: error in __close |
| 727 | assert(not st and string.find(msg, "attempt to yield")) | 743 | assert(a == "x" and b == nil) -- yields inside 'x'; Ok |
| 744 | a, b = co() | ||
| 745 | assert(a == "y" and b == nil) -- yields inside 'y'; Ok | ||
| 746 | a, b = co() | ||
| 747 | assert(a == "z" and b == nil) -- yields inside 'z'; Ok | ||
| 748 | local st, msg = co() -- reports the error in 'y' | ||
| 749 | assert(not st and msg == 21) | ||
| 750 | |||
| 751 | local a, b = co() -- third foo: error in function body | ||
| 752 | assert(a == "x" and b == nil) -- yields inside 'x'; Ok | ||
| 753 | a, b = co() | ||
| 754 | assert(a == "y" and b == nil) -- yields inside 'y'; Ok | ||
| 755 | a, b = co() | ||
| 756 | assert(a == "z" and b == nil) -- yields inside 'z'; Ok | ||
| 757 | local st, msg = co() -- gets final error | ||
| 758 | assert(not st and msg == 10 + 20) | ||
| 759 | |||
| 728 | end | 760 | end |
| 729 | 761 | ||
| 730 | 762 | ||
