diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2015-04-10 14:56:25 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2015-04-10 14:56:25 -0300 |
commit | ae76c39712852d14514f9ef19ac332e961749d93 (patch) | |
tree | 33fbd2d0a5c375048eade2a14c0cb606a8c5bba4 | |
parent | 0d4a1f71db1400a21654fc46b7e93a27db7641ae (diff) | |
download | lua-ae76c39712852d14514f9ef19ac332e961749d93.tar.gz lua-ae76c39712852d14514f9ef19ac332e961749d93.tar.bz2 lua-ae76c39712852d14514f9ef19ac332e961749d93.zip |
Bug: suspended '__le' metamethod can give wrong result
-rw-r--r-- | bugs | 67 | ||||
-rw-r--r-- | lstate.h | 3 | ||||
-rw-r--r-- | lvm.c | 32 |
3 files changed, 90 insertions, 12 deletions
@@ -3376,6 +3376,73 @@ patch = [[ | |||
3376 | } | 3376 | } |
3377 | 3377 | ||
3378 | 3378 | ||
3379 | Bug{ | ||
3380 | what = [[suspended '__le' metamethod can give wrong result]], | ||
3381 | report = [[Eric Zhong, 2015/04/07]], | ||
3382 | since = [[5.2]], | ||
3383 | fix = nil, | ||
3384 | |||
3385 | example = [[ | ||
3386 | mt = {__le = function (a,b) coroutine.yield("yield"); return a.x <= b.x end} | ||
3387 | t1 = setmetatable({x=1}, mt) | ||
3388 | t2 = {x=2} | ||
3389 | co = coroutine.wrap(function (a,b) return t2 <= t1 end) | ||
3390 | co() | ||
3391 | print(co()) --> true (should be false) | ||
3392 | ]], | ||
3393 | |||
3394 | patch = [[ | ||
3395 | --- lstate.h 2015/03/04 13:31:21 2.120 | ||
3396 | +++ lstate.h 2015/04/08 16:30:40 | ||
3397 | @@ -94,6 +94,7 @@ | ||
3398 | #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ | ||
3399 | #define CIST_TAIL (1<<5) /* call was tail called */ | ||
3400 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ | ||
3401 | +#define CIST_LEQ (1<<7) /* using __lt for __le */ | ||
3402 | |||
3403 | #define isLua(ci) ((ci)->callstatus & CIST_LUA) | ||
3404 | |||
3405 | |||
3406 | --- lvm.c 2015/03/30 15:45:01 2.238 | ||
3407 | +++ lvm.c 2015/04/09 15:30:13 | ||
3408 | @@ -275,9 +275,14 @@ | ||
3409 | return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; | ||
3410 | else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* first try 'le' */ | ||
3411 | return res; | ||
3412 | - else if ((res = luaT_callorderTM(L, r, l, TM_LT)) < 0) /* else try 'lt' */ | ||
3413 | - luaG_ordererror(L, l, r); | ||
3414 | - return !res; | ||
3415 | + else { /* try 'lt': */ | ||
3416 | + L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ | ||
3417 | + res = luaT_callorderTM(L, r, l, TM_LT); | ||
3418 | + L->ci->callstatus ^= CIST_LEQ; /* clear mark */ | ||
3419 | + if (res < 0) | ||
3420 | + luaG_ordererror(L, l, r); | ||
3421 | + return !res; /* result is negated */ | ||
3422 | + } | ||
3423 | } | ||
3424 | |||
3425 | @@ -542,11 +547,11 @@ | ||
3426 | case OP_LE: case OP_LT: case OP_EQ: { | ||
3427 | int res = !l_isfalse(L->top - 1); | ||
3428 | L->top--; | ||
3429 | - /* metamethod should not be called when operand is K */ | ||
3430 | - lua_assert(!ISK(GETARG_B(inst))); | ||
3431 | - if (op == OP_LE && /* "<=" using "<" instead? */ | ||
3432 | - ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE))) | ||
3433 | - res = !res; /* invert result */ | ||
3434 | + if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ | ||
3435 | + lua_assert(op == OP_LE); | ||
3436 | + ci->callstatus ^= CIST_LEQ; /* clear mark */ | ||
3437 | + res = !res; /* negate result */ | ||
3438 | + } | ||
3439 | lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); | ||
3440 | if (res != GETARG_A(inst)) /* condition failed? */ | ||
3441 | ci->u.l.savedpc++; /* skip jump instruction */ | ||
3442 | ]] | ||
3443 | } | ||
3444 | |||
3445 | |||
3379 | --[=[ | 3446 | --[=[ |
3380 | Bug{ | 3447 | Bug{ |
3381 | what = [[ ]], | 3448 | what = [[ ]], |
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | ** $Id: lstate.h,v 2.119 2014/10/30 18:53:28 roberto Exp roberto $ | 2 | ** $Id: lstate.h,v 2.120 2015/03/04 13:31:21 roberto Exp roberto $ |
3 | ** Global State | 3 | ** Global State |
4 | ** See Copyright Notice in lua.h | 4 | ** See Copyright Notice in lua.h |
5 | */ | 5 | */ |
@@ -94,6 +94,7 @@ typedef struct CallInfo { | |||
94 | #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ | 94 | #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */ |
95 | #define CIST_TAIL (1<<5) /* call was tail called */ | 95 | #define CIST_TAIL (1<<5) /* call was tail called */ |
96 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ | 96 | #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */ |
97 | #define CIST_LEQ (1<<7) /* using __lt for __le */ | ||
97 | 98 | ||
98 | #define isLua(ci) ((ci)->callstatus & CIST_LUA) | 99 | #define isLua(ci) ((ci)->callstatus & CIST_LUA) |
99 | 100 | ||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | ** $Id: lvm.c,v 2.237 2015/03/07 19:30:16 roberto Exp roberto $ | 2 | ** $Id: lvm.c,v 2.238 2015/03/30 15:45:01 roberto Exp roberto $ |
3 | ** Lua virtual machine | 3 | ** Lua virtual machine |
4 | ** See Copyright Notice in lua.h | 4 | ** See Copyright Notice in lua.h |
5 | */ | 5 | */ |
@@ -262,7 +262,12 @@ int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { | |||
262 | 262 | ||
263 | 263 | ||
264 | /* | 264 | /* |
265 | ** Main operation less than or equal to; return 'l <= r'. | 265 | ** Main operation less than or equal to; return 'l <= r'. If it needs |
266 | ** a metamethod and there is no '__le', try '__lt', based on | ||
267 | ** l <= r iff !(r < l) (assuming a total order). If the metamethod | ||
268 | ** yields during this substitution, the continuation has to know | ||
269 | ** about it (to negate the result of r<l); bit CIST_LEQ in the call | ||
270 | ** status keeps that information. | ||
266 | */ | 271 | */ |
267 | int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { | 272 | int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { |
268 | int res; | 273 | int res; |
@@ -273,11 +278,16 @@ int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) { | |||
273 | return luai_numle(nl, nr); | 278 | return luai_numle(nl, nr); |
274 | else if (ttisstring(l) && ttisstring(r)) /* both are strings? */ | 279 | else if (ttisstring(l) && ttisstring(r)) /* both are strings? */ |
275 | return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; | 280 | return l_strcmp(tsvalue(l), tsvalue(r)) <= 0; |
276 | else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* first try 'le' */ | 281 | else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* try 'le' */ |
277 | return res; | 282 | return res; |
278 | else if ((res = luaT_callorderTM(L, r, l, TM_LT)) < 0) /* else try 'lt' */ | 283 | else { /* try 'lt': */ |
279 | luaG_ordererror(L, l, r); | 284 | L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */ |
280 | return !res; | 285 | res = luaT_callorderTM(L, r, l, TM_LT); |
286 | L->ci->callstatus ^= CIST_LEQ; /* clear mark */ | ||
287 | if (res < 0) | ||
288 | luaG_ordererror(L, l, r); | ||
289 | return !res; /* result is negated */ | ||
290 | } | ||
281 | } | 291 | } |
282 | 292 | ||
283 | 293 | ||
@@ -542,11 +552,11 @@ void luaV_finishOp (lua_State *L) { | |||
542 | case OP_LE: case OP_LT: case OP_EQ: { | 552 | case OP_LE: case OP_LT: case OP_EQ: { |
543 | int res = !l_isfalse(L->top - 1); | 553 | int res = !l_isfalse(L->top - 1); |
544 | L->top--; | 554 | L->top--; |
545 | /* metamethod should not be called when operand is K */ | 555 | if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */ |
546 | lua_assert(!ISK(GETARG_B(inst))); | 556 | lua_assert(op == OP_LE); |
547 | if (op == OP_LE && /* "<=" using "<" instead? */ | 557 | ci->callstatus ^= CIST_LEQ; /* clear mark */ |
548 | ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE))) | 558 | res = !res; /* negate result */ |
549 | res = !res; /* invert result */ | 559 | } |
550 | lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); | 560 | lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP); |
551 | if (res != GETARG_A(inst)) /* condition failed? */ | 561 | if (res != GETARG_A(inst)) /* condition failed? */ |
552 | ci->u.l.savedpc++; /* skip jump instruction */ | 562 | ci->u.l.savedpc++; /* skip jump instruction */ |