diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2025-01-28 11:45:45 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2025-01-28 11:45:45 -0300 |
commit | 39a14ea7d7b14172595c61619e8f35c2614b2606 (patch) | |
tree | 863ea56e6014de9e1284f2e47ac1fbd4b69339ed | |
parent | c4e7cdb541d89142056927ebdfd8f97017d38f45 (diff) | |
download | lua-39a14ea7d7b14172595c61619e8f35c2614b2606.tar.gz lua-39a14ea7d7b14172595c61619e8f35c2614b2606.tar.bz2 lua-39a14ea7d7b14172595c61619e8f35c2614b2606.zip |
CallInfo bit CIST_CLSRET broken in two
Since commit f407b3c4a, it was being used for two distinct (and
incompatible) meanings:
A: Function has TBC variables (now bit CIST_TBC)
B: Interpreter is closing TBC variables (original bit CIST_CLSRET)
B implies A, but A does not imply B.
-rw-r--r-- | lapi.c | 6 | ||||
-rw-r--r-- | ldo.c | 12 | ||||
-rw-r--r-- | lstate.h | 4 | ||||
-rw-r--r-- | testes/coroutine.lua | 37 |
4 files changed, 44 insertions, 15 deletions
@@ -195,7 +195,7 @@ LUA_API void lua_settop (lua_State *L, int idx) { | |||
195 | } | 195 | } |
196 | newtop = L->top.p + diff; | 196 | newtop = L->top.p + diff; |
197 | if (diff < 0 && L->tbclist.p >= newtop) { | 197 | if (diff < 0 && L->tbclist.p >= newtop) { |
198 | lua_assert(ci->callstatus & CIST_CLSRET); | 198 | lua_assert(ci->callstatus & CIST_TBC); |
199 | newtop = luaF_close(L, newtop, CLOSEKTOP, 0); | 199 | newtop = luaF_close(L, newtop, CLOSEKTOP, 0); |
200 | } | 200 | } |
201 | L->top.p = newtop; /* correct top only after closing any upvalue */ | 201 | L->top.p = newtop; /* correct top only after closing any upvalue */ |
@@ -207,7 +207,7 @@ LUA_API void lua_closeslot (lua_State *L, int idx) { | |||
207 | StkId level; | 207 | StkId level; |
208 | lua_lock(L); | 208 | lua_lock(L); |
209 | level = index2stack(L, idx); | 209 | level = index2stack(L, idx); |
210 | api_check(L, (L->ci->callstatus & CIST_CLSRET) && L->tbclist.p == level, | 210 | api_check(L, (L->ci->callstatus & CIST_TBC) && (L->tbclist.p == level), |
211 | "no variable to close at given level"); | 211 | "no variable to close at given level"); |
212 | level = luaF_close(L, level, CLOSEKTOP, 0); | 212 | level = luaF_close(L, level, CLOSEKTOP, 0); |
213 | setnilvalue(s2v(level)); | 213 | setnilvalue(s2v(level)); |
@@ -1280,7 +1280,7 @@ LUA_API void lua_toclose (lua_State *L, int idx) { | |||
1280 | o = index2stack(L, idx); | 1280 | o = index2stack(L, idx); |
1281 | api_check(L, L->tbclist.p < o, "given index below or equal a marked one"); | 1281 | api_check(L, L->tbclist.p < o, "given index below or equal a marked one"); |
1282 | luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ | 1282 | luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ |
1283 | L->ci->callstatus |= CIST_CLSRET; /* mark that function has TBC slots */ | 1283 | L->ci->callstatus |= CIST_TBC; /* mark that function has TBC slots */ |
1284 | lua_unlock(L); | 1284 | lua_unlock(L); |
1285 | } | 1285 | } |
1286 | 1286 | ||
@@ -505,7 +505,7 @@ l_sinline void genmoveresults (lua_State *L, StkId res, int nres, | |||
505 | ** Given 'nres' results at 'firstResult', move 'fwanted-1' of them | 505 | ** Given 'nres' results at 'firstResult', move 'fwanted-1' of them |
506 | ** to 'res'. Handle most typical cases (zero results for commands, | 506 | ** to 'res'. Handle most typical cases (zero results for commands, |
507 | ** one result for expressions, multiple results for tail calls/single | 507 | ** one result for expressions, multiple results for tail calls/single |
508 | ** parameters) separated. The flag CIST_CLSRET in 'fwanted', if set, | 508 | ** parameters) separated. The flag CIST_TBC in 'fwanted', if set, |
509 | ** forces the swicth to go to the default case. | 509 | ** forces the swicth to go to the default case. |
510 | */ | 510 | */ |
511 | l_sinline void moveresults (lua_State *L, StkId res, int nres, | 511 | l_sinline void moveresults (lua_State *L, StkId res, int nres, |
@@ -526,8 +526,9 @@ l_sinline void moveresults (lua_State *L, StkId res, int nres, | |||
526 | break; | 526 | break; |
527 | default: { /* two/more results and/or to-be-closed variables */ | 527 | default: { /* two/more results and/or to-be-closed variables */ |
528 | int wanted = get_nresults(fwanted); | 528 | int wanted = get_nresults(fwanted); |
529 | if (fwanted & CIST_CLSRET) { /* to-be-closed variables? */ | 529 | if (fwanted & CIST_TBC) { /* to-be-closed variables? */ |
530 | L->ci->u2.nres = nres; | 530 | L->ci->u2.nres = nres; |
531 | L->ci->callstatus |= CIST_CLSRET; /* in case of yields */ | ||
531 | res = luaF_close(L, res, CLOSEKTOP, 1); | 532 | res = luaF_close(L, res, CLOSEKTOP, 1); |
532 | L->ci->callstatus &= ~CIST_CLSRET; | 533 | L->ci->callstatus &= ~CIST_CLSRET; |
533 | if (L->hookmask) { /* if needed, call hook after '__close's */ | 534 | if (L->hookmask) { /* if needed, call hook after '__close's */ |
@@ -552,8 +553,8 @@ l_sinline void moveresults (lua_State *L, StkId res, int nres, | |||
552 | ** that. | 553 | ** that. |
553 | */ | 554 | */ |
554 | void luaD_poscall (lua_State *L, CallInfo *ci, int nres) { | 555 | void luaD_poscall (lua_State *L, CallInfo *ci, int nres) { |
555 | l_uint32 fwanted = ci->callstatus & (CIST_CLSRET | CIST_NRESULTS); | 556 | l_uint32 fwanted = ci->callstatus & (CIST_TBC | CIST_NRESULTS); |
556 | if (l_unlikely(L->hookmask) && !(fwanted & CIST_CLSRET)) | 557 | if (l_unlikely(L->hookmask) && !(fwanted & CIST_TBC)) |
557 | rethook(L, ci, nres); | 558 | rethook(L, ci, nres); |
558 | /* move results to proper place */ | 559 | /* move results to proper place */ |
559 | moveresults(L, ci->func.p, nres, fwanted); | 560 | moveresults(L, ci->func.p, nres, fwanted); |
@@ -785,7 +786,8 @@ static int finishpcallk (lua_State *L, CallInfo *ci) { | |||
785 | */ | 786 | */ |
786 | static void finishCcall (lua_State *L, CallInfo *ci) { | 787 | static void finishCcall (lua_State *L, CallInfo *ci) { |
787 | int n; /* actual number of results from C function */ | 788 | int n; /* actual number of results from C function */ |
788 | if (ci->callstatus & CIST_CLSRET) { /* was returning? */ | 789 | if (ci->callstatus & CIST_CLSRET) { /* was closing TBC variable? */ |
790 | lua_assert(ci->callstatus & CIST_TBC); | ||
789 | n = ci->u2.nres; /* just redo 'luaD_poscall' */ | 791 | n = ci->u2.nres; /* just redo 'luaD_poscall' */ |
790 | /* don't need to reset CIST_CLSRET, as it will be set again anyway */ | 792 | /* don't need to reset CIST_CLSRET, as it will be set again anyway */ |
791 | } | 793 | } |
@@ -235,8 +235,10 @@ struct CallInfo { | |||
235 | #define CIST_FRESH cast(l_uint32, CIST_C << 1) | 235 | #define CIST_FRESH cast(l_uint32, CIST_C << 1) |
236 | /* function is closing tbc variables */ | 236 | /* function is closing tbc variables */ |
237 | #define CIST_CLSRET (CIST_FRESH << 1) | 237 | #define CIST_CLSRET (CIST_FRESH << 1) |
238 | /* function has tbc variables to close */ | ||
239 | #define CIST_TBC (CIST_CLSRET << 1) | ||
238 | /* original value of 'allowhook' */ | 240 | /* original value of 'allowhook' */ |
239 | #define CIST_OAH (CIST_CLSRET << 1) | 241 | #define CIST_OAH (CIST_TBC << 1) |
240 | /* call is running a debug hook */ | 242 | /* call is running a debug hook */ |
241 | #define CIST_HOOKED (CIST_OAH << 1) | 243 | #define CIST_HOOKED (CIST_OAH << 1) |
242 | /* doing a yieldable protected call */ | 244 | /* doing a yieldable protected call */ |
diff --git a/testes/coroutine.lua b/testes/coroutine.lua index c1252ab8..78b9bdca 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua | |||
@@ -515,7 +515,7 @@ else | |||
515 | print "testing yields inside hooks" | 515 | print "testing yields inside hooks" |
516 | 516 | ||
517 | local turn | 517 | local turn |
518 | 518 | ||
519 | local function fact (t, x) | 519 | local function fact (t, x) |
520 | assert(turn == t) | 520 | assert(turn == t) |
521 | if x == 0 then return 1 | 521 | if x == 0 then return 1 |
@@ -642,7 +642,7 @@ else | |||
642 | 642 | ||
643 | 643 | ||
644 | print "testing coroutine API" | 644 | print "testing coroutine API" |
645 | 645 | ||
646 | -- reusing a thread | 646 | -- reusing a thread |
647 | assert(T.testC([[ | 647 | assert(T.testC([[ |
648 | newthread # create thread | 648 | newthread # create thread |
@@ -920,7 +920,7 @@ do -- a few more tests for comparison operators | |||
920 | until res ~= 10 | 920 | until res ~= 10 |
921 | return res | 921 | return res |
922 | end | 922 | end |
923 | 923 | ||
924 | local function test () | 924 | local function test () |
925 | local a1 = setmetatable({x=1}, mt1) | 925 | local a1 = setmetatable({x=1}, mt1) |
926 | local a2 = setmetatable({x=2}, mt2) | 926 | local a2 = setmetatable({x=2}, mt2) |
@@ -932,7 +932,7 @@ do -- a few more tests for comparison operators | |||
932 | assert(2 >= a2) | 932 | assert(2 >= a2) |
933 | return true | 933 | return true |
934 | end | 934 | end |
935 | 935 | ||
936 | run(test) | 936 | run(test) |
937 | 937 | ||
938 | end | 938 | end |
@@ -1037,6 +1037,31 @@ f = T.makeCfunc([[ | |||
1037 | return * | 1037 | return * |
1038 | ]], 23, "huu") | 1038 | ]], 23, "huu") |
1039 | 1039 | ||
1040 | |||
1041 | do -- testing bug introduced in commit f407b3c4a | ||
1042 | local X = false -- flag "to be closed" | ||
1043 | local coro = coroutine.wrap(T.testC) | ||
1044 | -- runs it until 'pcallk' (that yields) | ||
1045 | -- 4th argument (at index 4): object to be closed | ||
1046 | local res1, res2 = coro( | ||
1047 | [[ | ||
1048 | toclose 3 # this could break the next 'pcallk' | ||
1049 | pushvalue 2 # push function 'yield' to call it | ||
1050 | pushint 22; pushint 33 # arguments to yield | ||
1051 | # calls 'yield' (2 args; 2 results; continuation function at index 4) | ||
1052 | pcallk 2 2 4 | ||
1053 | invalid command (should not arrive here) | ||
1054 | ]], -- 1st argument (at index 1): code; | ||
1055 | coroutine.yield, -- (at index 2): function to be called | ||
1056 | func2close(function () X = true end), -- (index 3): TBC slot | ||
1057 | "pushint 43; return 3" -- (index 4): code for continuation function | ||
1058 | ) | ||
1059 | |||
1060 | assert(res1 == 22 and res2 == 33 and not X) | ||
1061 | local res1, res2, res3 = coro(34, "hi") -- runs continuation function | ||
1062 | assert(res1 == 34 and res2 == "hi" and res3 == 43 and X) | ||
1063 | end | ||
1064 | |||
1040 | x = coroutine.wrap(f) | 1065 | x = coroutine.wrap(f) |
1041 | assert(x() == 102) | 1066 | assert(x() == 102) |
1042 | eqtab({x()}, {23, "huu"}) | 1067 | eqtab({x()}, {23, "huu"}) |
@@ -1094,11 +1119,11 @@ co = coroutine.wrap(function (...) return | |||
1094 | cannot be here! | 1119 | cannot be here! |
1095 | ]], | 1120 | ]], |
1096 | [[ # 1st continuation | 1121 | [[ # 1st continuation |
1097 | yieldk 0 3 | 1122 | yieldk 0 3 |
1098 | cannot be here! | 1123 | cannot be here! |
1099 | ]], | 1124 | ]], |
1100 | [[ # 2nd continuation | 1125 | [[ # 2nd continuation |
1101 | yieldk 0 4 | 1126 | yieldk 0 4 |
1102 | cannot be here! | 1127 | cannot be here! |
1103 | ]], | 1128 | ]], |
1104 | [[ # 3th continuation | 1129 | [[ # 3th continuation |