diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2024-01-11 13:44:16 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2024-01-11 13:44:16 -0300 |
commit | e288c5a91883793d14ed9e9d93464f6ee0b08915 (patch) | |
tree | f563f9b4d218c9e35ff3e56eda068cfddb039651 | |
parent | 5853c37a83ec66ccb45094f9aeac23dfdbcde671 (diff) | |
download | lua-e288c5a91883793d14ed9e9d93464f6ee0b08915.tar.gz lua-e288c5a91883793d14ed9e9d93464f6ee0b08915.tar.bz2 lua-e288c5a91883793d14ed9e9d93464f6ee0b08915.zip |
Bug: Yielding in a hook stops in the wrong instruction
Yielding in a hook must decrease the program counter, because it already
counted an instruction that, in the end, was not executed. However,
that decrement should be done only when about to restart the thread.
Otherwise, inspecting the thread with the debug library shows it one
instruction behind of where it really is.
-rw-r--r-- | ldebug.c | 5 | ||||
-rw-r--r-- | ldo.c | 4 | ||||
-rw-r--r-- | testes/coroutine.lua | 8 |
3 files changed, 11 insertions, 6 deletions
@@ -925,12 +925,12 @@ int luaG_traceexec (lua_State *L, const Instruction *pc) { | |||
925 | } | 925 | } |
926 | pc++; /* reference is always next instruction */ | 926 | pc++; /* reference is always next instruction */ |
927 | ci->u.l.savedpc = pc; /* save 'pc' */ | 927 | ci->u.l.savedpc = pc; /* save 'pc' */ |
928 | counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT)); | 928 | counthook = (mask & LUA_MASKCOUNT) && (--L->hookcount == 0); |
929 | if (counthook) | 929 | if (counthook) |
930 | resethookcount(L); /* reset count */ | 930 | resethookcount(L); /* reset count */ |
931 | else if (!(mask & LUA_MASKLINE)) | 931 | else if (!(mask & LUA_MASKLINE)) |
932 | return 1; /* no line hook and count != 0; nothing to be done now */ | 932 | return 1; /* no line hook and count != 0; nothing to be done now */ |
933 | if (ci->callstatus & CIST_HOOKYIELD) { /* called hook last time? */ | 933 | if (ci->callstatus & CIST_HOOKYIELD) { /* hook yielded last time? */ |
934 | ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ | 934 | ci->callstatus &= ~CIST_HOOKYIELD; /* erase mark */ |
935 | return 1; /* do not call hook again (VM yielded, so it did not move) */ | 935 | return 1; /* do not call hook again (VM yielded, so it did not move) */ |
936 | } | 936 | } |
@@ -952,7 +952,6 @@ int luaG_traceexec (lua_State *L, const Instruction *pc) { | |||
952 | if (L->status == LUA_YIELD) { /* did hook yield? */ | 952 | if (L->status == LUA_YIELD) { /* did hook yield? */ |
953 | if (counthook) | 953 | if (counthook) |
954 | L->hookcount = 1; /* undo decrement to zero */ | 954 | L->hookcount = 1; /* undo decrement to zero */ |
955 | ci->u.l.savedpc--; /* undo increment (resume will increment it again) */ | ||
956 | ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ | 955 | ci->callstatus |= CIST_HOOKYIELD; /* mark that it yielded */ |
957 | luaD_throw(L, LUA_YIELD); | 956 | luaD_throw(L, LUA_YIELD); |
958 | } | 957 | } |
@@ -792,6 +792,10 @@ static void resume (lua_State *L, void *ud) { | |||
792 | lua_assert(L->status == LUA_YIELD); | 792 | lua_assert(L->status == LUA_YIELD); |
793 | L->status = LUA_OK; /* mark that it is running (again) */ | 793 | L->status = LUA_OK; /* mark that it is running (again) */ |
794 | if (isLua(ci)) { /* yielded inside a hook? */ | 794 | if (isLua(ci)) { /* yielded inside a hook? */ |
795 | /* undo increment made by 'luaG_traceexec': instruction was not | ||
796 | executed yet */ | ||
797 | lua_assert(ci->callstatus & CIST_HOOKYIELD); | ||
798 | ci->u.l.savedpc--; | ||
795 | L->top.p = firstArg; /* discard arguments */ | 799 | L->top.p = firstArg; /* discard arguments */ |
796 | luaV_execute(L, ci); /* just continue running Lua code */ | 800 | luaV_execute(L, ci); /* just continue running Lua code */ |
797 | } | 801 | } |
diff --git a/testes/coroutine.lua b/testes/coroutine.lua index de7e46fb..e566c86e 100644 --- a/testes/coroutine.lua +++ b/testes/coroutine.lua | |||
@@ -610,18 +610,20 @@ else | |||
610 | -- (bug in 5.2/5.3) | 610 | -- (bug in 5.2/5.3) |
611 | c = coroutine.create(function (a, ...) | 611 | c = coroutine.create(function (a, ...) |
612 | T.sethook("yield 0", "l") -- will yield on next two lines | 612 | T.sethook("yield 0", "l") -- will yield on next two lines |
613 | assert(a == 10) | 613 | local b = a |
614 | return ... | 614 | return ... |
615 | end) | 615 | end) |
616 | 616 | ||
617 | assert(coroutine.resume(c, 1, 2, 3)) -- start coroutine | 617 | assert(coroutine.resume(c, 1, 2, 3)) -- start coroutine |
618 | local n,v = debug.getlocal(c, 0, 1) -- check its local | 618 | local n,v = debug.getlocal(c, 0, 1) -- check its local |
619 | assert(n == "a" and v == 1) | 619 | assert(n == "a" and v == 1 and debug.getlocal(c, 0, 2) ~= "b") |
620 | assert(debug.setlocal(c, 0, 1, 10)) -- test 'setlocal' | 620 | assert(debug.setlocal(c, 0, 1, 10)) -- test 'setlocal' |
621 | local t = debug.getinfo(c, 0) -- test 'getinfo' | 621 | local t = debug.getinfo(c, 0) -- test 'getinfo' |
622 | assert(t.currentline == t.linedefined + 1) | 622 | assert(t.currentline == t.linedefined + 2) |
623 | assert(not debug.getinfo(c, 1)) -- no other level | 623 | assert(not debug.getinfo(c, 1)) -- no other level |
624 | assert(coroutine.resume(c)) -- run next line | 624 | assert(coroutine.resume(c)) -- run next line |
625 | local n,v = debug.getlocal(c, 0, 2) -- check next local | ||
626 | assert(n == "b" and v == 10) | ||
625 | v = {coroutine.resume(c)} -- finish coroutine | 627 | v = {coroutine.resume(c)} -- finish coroutine |
626 | assert(v[1] == true and v[2] == 2 and v[3] == 3 and v[4] == undef) | 628 | assert(v[1] == true and v[2] == 2 and v[3] == 3 and v[4] == undef) |
627 | assert(not coroutine.resume(c)) | 629 | assert(not coroutine.resume(c)) |