commit ae76c39712852d14514f9ef19ac332e961749d93
parent 0d4a1f71db1400a21654fc46b7e93a27db7641ae
Author: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date: Fri, 10 Apr 2015 14:56:00 -0300
Bug: suspended '__le' metamethod can give wrong result
Diffstat:
M | bugs | | | 67 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | lstate.h | | | 3 | ++- |
M | lvm.c | | | 32 | +++++++++++++++++++++----------- |
3 files changed, 90 insertions(+), 12 deletions(-)
diff --git a/bugs b/bugs
@@ -3376,6 +3376,73 @@ patch = [[
}
+Bug{
+what = [[suspended '__le' metamethod can give wrong result]],
+report = [[Eric Zhong, 2015/04/07]],
+since = [[5.2]],
+fix = nil,
+
+example = [[
+mt = {__le = function (a,b) coroutine.yield("yield"); return a.x <= b.x end}
+t1 = setmetatable({x=1}, mt)
+t2 = {x=2}
+co = coroutine.wrap(function (a,b) return t2 <= t1 end)
+co()
+print(co()) --> true (should be false)
+]],
+
+patch = [[
+--- lstate.h 2015/03/04 13:31:21 2.120
++++ lstate.h 2015/04/08 16:30:40
+@@ -94,6 +94,7 @@
+ #define CIST_YPCALL (1<<4) /* call is a yieldable protected call */
+ #define CIST_TAIL (1<<5) /* call was tail called */
+ #define CIST_HOOKYIELD (1<<6) /* last hook called yielded */
++#define CIST_LEQ (1<<7) /* using __lt for __le */
+
+ #define isLua(ci) ((ci)->callstatus & CIST_LUA)
+
+
+--- lvm.c 2015/03/30 15:45:01 2.238
++++ lvm.c 2015/04/09 15:30:13
+@@ -275,9 +275,14 @@
+ return l_strcmp(tsvalue(l), tsvalue(r)) <= 0;
+ else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* first try 'le' */
+ return res;
+- else if ((res = luaT_callorderTM(L, r, l, TM_LT)) < 0) /* else try 'lt' */
+- luaG_ordererror(L, l, r);
+- return !res;
++ else { /* try 'lt': */
++ L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */
++ res = luaT_callorderTM(L, r, l, TM_LT);
++ L->ci->callstatus ^= CIST_LEQ; /* clear mark */
++ if (res < 0)
++ luaG_ordererror(L, l, r);
++ return !res; /* result is negated */
++ }
+ }
+
+@@ -542,11 +547,11 @@
+ case OP_LE: case OP_LT: case OP_EQ: {
+ int res = !l_isfalse(L->top - 1);
+ L->top--;
+- /* metamethod should not be called when operand is K */
+- lua_assert(!ISK(GETARG_B(inst)));
+- if (op == OP_LE && /* "<=" using "<" instead? */
+- ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE)))
+- res = !res; /* invert result */
++ if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */
++ lua_assert(op == OP_LE);
++ ci->callstatus ^= CIST_LEQ; /* clear mark */
++ res = !res; /* negate result */
++ }
+ lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP);
+ if (res != GETARG_A(inst)) /* condition failed? */
+ ci->u.l.savedpc++; /* skip jump instruction */
+]]
+}
+
+
--[=[
Bug{
what = [[ ]],
diff --git a/lstate.h b/lstate.h
@@ -1,5 +1,5 @@
/*
-** $Id: lstate.h,v 2.119 2014/10/30 18:53:28 roberto Exp roberto $
+** $Id: lstate.h,v 2.120 2015/03/04 13:31:21 roberto Exp roberto $
** Global State
** See Copyright Notice in lua.h
*/
@@ -94,6 +94,7 @@ typedef struct CallInfo {
#define CIST_YPCALL (1<<4) /* call is a yieldable protected call */
#define CIST_TAIL (1<<5) /* call was tail called */
#define CIST_HOOKYIELD (1<<6) /* last hook called yielded */
+#define CIST_LEQ (1<<7) /* using __lt for __le */
#define isLua(ci) ((ci)->callstatus & CIST_LUA)
diff --git a/lvm.c b/lvm.c
@@ -1,5 +1,5 @@
/*
-** $Id: lvm.c,v 2.237 2015/03/07 19:30:16 roberto Exp roberto $
+** $Id: lvm.c,v 2.238 2015/03/30 15:45:01 roberto Exp roberto $
** Lua virtual machine
** See Copyright Notice in lua.h
*/
@@ -262,7 +262,12 @@ int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) {
/*
-** Main operation less than or equal to; return 'l <= r'.
+** Main operation less than or equal to; return 'l <= r'. If it needs
+** a metamethod and there is no '__le', try '__lt', based on
+** l <= r iff !(r < l) (assuming a total order). If the metamethod
+** yields during this substitution, the continuation has to know
+** about it (to negate the result of r<l); bit CIST_LEQ in the call
+** status keeps that information.
*/
int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) {
int res;
@@ -273,11 +278,16 @@ int luaV_lessequal (lua_State *L, const TValue *l, const TValue *r) {
return luai_numle(nl, nr);
else if (ttisstring(l) && ttisstring(r)) /* both are strings? */
return l_strcmp(tsvalue(l), tsvalue(r)) <= 0;
- else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* first try 'le' */
+ else if ((res = luaT_callorderTM(L, l, r, TM_LE)) >= 0) /* try 'le' */
return res;
- else if ((res = luaT_callorderTM(L, r, l, TM_LT)) < 0) /* else try 'lt' */
- luaG_ordererror(L, l, r);
- return !res;
+ else { /* try 'lt': */
+ L->ci->callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */
+ res = luaT_callorderTM(L, r, l, TM_LT);
+ L->ci->callstatus ^= CIST_LEQ; /* clear mark */
+ if (res < 0)
+ luaG_ordererror(L, l, r);
+ return !res; /* result is negated */
+ }
}
@@ -542,11 +552,11 @@ void luaV_finishOp (lua_State *L) {
case OP_LE: case OP_LT: case OP_EQ: {
int res = !l_isfalse(L->top - 1);
L->top--;
- /* metamethod should not be called when operand is K */
- lua_assert(!ISK(GETARG_B(inst)));
- if (op == OP_LE && /* "<=" using "<" instead? */
- ttisnil(luaT_gettmbyobj(L, base + GETARG_B(inst), TM_LE)))
- res = !res; /* invert result */
+ if (ci->callstatus & CIST_LEQ) { /* "<=" using "<" instead? */
+ lua_assert(op == OP_LE);
+ ci->callstatus ^= CIST_LEQ; /* clear mark */
+ res = !res; /* negate result */
+ }
lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_JMP);
if (res != GETARG_A(inst)) /* condition failed? */
ci->u.l.savedpc++; /* skip jump instruction */