Why statement return error in current Lua(Codea)?

原创
2016/05/27 23:45
阅读数 146

Why statement "table.insert(touches, touch.id, touch)" return error in current Lua(Codea)?

Introduction

When I tried to run some Codea code on the new version, I found that some code about touch always return error. First the error is float without integer representation, after modified it with math.floor, the error changed to position out of bounds. It made me confuse(I had a post in the forum [SOLVED] Should touch.id be a int or a float?). I tried to look for the reason, below is the whole record.

Test code

The code can run on the old version Codea which using the lua-5.1, and the current Codea is using lua-5.3, so I guess the bug is because of the different version of lua. For the easy testing, I do the debug on my Raspberry Pi which have two versions of lua. the test code is below:

Test Code 1

touches = {}
touch={id=100}
table.insert(touches, math.floor(touch.id), touch)

return error in Lua-5.3.2:

pi@rpi /opt/software/lua-5.3.2 $ lua
Lua 5.3.2  Copyright (C) 1994-2015 Lua.org, PUC-Rio
>
> touches = {}
> touch={id=100}
> table.insert(touches, math.floor(touch.id), touch)
stdin:1: bad argument #2 to 'insert' (position out of bounds)
stack traceback:
        [C]: in function 'table.insert'
        stdin:1: in main chunk
        [C]: in ?
> 

return success in Lua-5.1.5:

pi@rpi /opt/software $ lua5.1
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
>
> touches = {}
> touch={id=100}
> table.insert(touches, math.floor(touch.id), touch)
> 

Test Code 2

return error in Lua-5.3.2:

kano@kano ~ $ lua
Lua 5.3.2  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> my={}
> table.insert(my,123,12)
stdin:1: bad argument #2 to 'insert' (position out of bounds)
stack traceback:
	[C]: in function 'table.insert'
	stdin:1: in main chunk
	[C]: in ?
> table.insert(my,1,12)
> table.insert(my,2,12)
> table.insert(my,4,12)
stdin:1: bad argument #2 to 'insert' (position out of bounds)
stack traceback:
	[C]: in function 'table.insert'
	stdin:1: in main chunk
	[C]: in ?
> my[123]=123
> #my
2
> unpack(my)
stdin:1: attempt to call a nil value (global 'unpack')
stack traceback:
	stdin:1: in main chunk
	[C]: in ?
> table.unpack(my)
12	12
> for k,v in pairs(my) do print(k,v) end
1	12
2	12
123	123
> 

return success in Lua-5.1.5:

pi@rpi /opt/software/lua-5.3.2/src $ lua
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> my={}
> table.insert(my,123,12)
> #my
stdin:1: unexpected symbol near '#'
> table.length(my)
stdin:1: attempt to call field 'length' (a nil value)
stack traceback:
        stdin:1: in main chunk
        [C]: ?
> table.len(my)
stdin:1: attempt to call field 'len' (a nil value)
stack traceback:
        stdin:1: in main chunk
        [C]: ?
> table.insert(my,1,12)
> table.insert(my,2,12)
> table.insert(my,4,12)
> my[123]=123
> print(#my)
4
> table.unpack(my)
stdin:1: attempt to call field 'unpack' (a nil value)
stack traceback:
        stdin:1: in main chunk
        [C]: ?
> for k,v in pairs(my) do print(k,v) end
1       12
2       12
4       12
123     123
> 

Conclusion

It is easy to see:

  • Use table.insert on a empty table must start with position 1
  • 123 only be treat with the hash key in my, not array index.
  • The table length will not include the elements which stored with hash.

Analyze the lua source code

Use the command git grep -n "error info", we can find the relational function source code:

pi@rpi /opt/software/lua-5.3.2 $ git grep -n "position out of bounds"
src/ltablib.c:90:      luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds");
src/ltablib.c:110:    luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds");
pi@rpi /opt/software/lua-5.3.2 $ 

The result is clear, we know that the code is in file src/ltablib.c, line 90 and 110, the code is below:

 79 static int tinsert (lua_State *L) {
 80   lua_Integer e = aux_getn(L, 1, TAB_RW) + 1;  /* first empty element */
 81   lua_Integer pos;  /* where to insert new element */
 82   switch (lua_gettop(L)) {
 83     case 2: {  /* called with only 2 arguments */
 84       pos = e;  /* insert new element at the end */
 85       break;
 86     }
 87     case 3: {
 88       lua_Integer i;
 89       pos = luaL_checkinteger(L, 2);  /* 2nd argument is the position */
 90       luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds");
 91       for (i = e; i > pos; i--) {  /* move up elements */
 92         lua_geti(L, 1, i - 1);
 93         lua_seti(L, 1, i);  /* t[i] = t[i - 1] */
 94       }
 95       break;
 96     }
 97     default: {
 98       return luaL_error(L, "wrong number of arguments to 'insert'");
 99     }
100   }
101   lua_seti(L, 1, pos);  /* t[pos] = v */
102   return 0;
103 }
104 
105 
106 static int tremove (lua_State *L) {
107   lua_Integer size = aux_getn(L, 1, TAB_RW);
108   lua_Integer pos = luaL_optinteger(L, 2, size);
109   if (pos != size)  /* validate 'pos' if given */
110     luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds");
111   lua_geti(L, 1, pos);  /* result = t[pos] */
112   for ( ; pos < size; pos++) {
113     lua_geti(L, 1, pos + 1);
114     lua_seti(L, 1, pos);  /* t[pos] = t[pos + 1] */
115   }
116   lua_pushnil(L);
117   lua_seti(L, 1, pos);  /* t[pos] = nil */
118   return 1;
119 }

We can see the check 1 <= pos && pos <= e in function tinsert and the check 1 <= pos && pos <= size + 1 in function tremove. And we can find the define of e and size:

lua_Integer e = aux_getn(L, 1, TAB_RW) + 1; /* first empty element */

The relational code:

 27 #define TAB_R   1           /* read */
 28 #define TAB_W   2           /* write */
 29 #define TAB_L   4           /* length */
 30 #define TAB_RW  (TAB_R | TAB_W)     /* read/write */
 31 
 32 
 33 #define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n))
 34 
 35 
 36 static int checkfield (lua_State *L, const char *key, int n) {
 37   lua_pushstring(L, key);
 38   return (lua_rawget(L, -n) != LUA_TNIL);
 39 }
 40 
 41 
 42 /*
 43 ** Check that 'arg' either is a table or can behave like one (that is,
 44 ** has a metatable with the required metamethods)
 45 */
 46 static void checktab (lua_State *L, int arg, int what) {
 47   if (lua_type(L, arg) != LUA_TTABLE) {  /* is it not a table? */
 48     int n = 1;  /* number of elements to pop */
 49     if (lua_getmetatable(L, arg) &&  /* must have metatable */
 50         (!(what & TAB_R) || checkfield(L, "__index", ++n)) &&
 51         (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) &&
 52         (!(what & TAB_L) || checkfield(L, "__len", ++n))) {
 53       lua_pop(L, n);  /* pop metatable and tested metamethods */
 54     }
 55     else
 56       luaL_argerror(L, arg, "table expected");  /* force an error */
 57   }
 58 }

Now we know the meaning: check the insert position, if it is bigger than the current size of the table, then return error with info "position out of bounds".

In version 5.1, we can insert in any position:

pi@rpi /opt/software $ lua5.1
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> touches = {}
> touch={id=100}
> table.insert(touches, 1000000, touch)
> 

Let us see the source code in version 5.1:

 90 static int tinsert (lua_State *L) {
 91   int e = aux_getn(L, 1) + 1;  /* first empty element */
 92   int pos;  /* where to insert new element */
 93   switch (lua_gettop(L)) {
 94     case 2: {  /* called with only 2 arguments */
 95       pos = e;  /* insert new element at the end */
 96       break;
 97     }
 98     case 3: {
 99       int i;
100       pos = luaL_checkint(L, 2);  /* 2nd argument is the position */
101       if (pos > e) e = pos;  /* `grow' array if necessary */
102       for (i = e; i > pos; i--) {  /* move up elements */
103         lua_rawgeti(L, 1, i-1);
104         lua_rawseti(L, 1, i);  /* t[i] = t[i-1] */
105       }
106       break;
107     }
108     default: {
109       return luaL_error(L, "wrong number of arguments to " LUA_QL("insert"));
110     }
111   }
112   luaL_setn(L, 1, e);  /* new size */
113   lua_rawseti(L, 1, pos);  /* t[pos] = v */
114   return 0;
115 }
116 
117 
118 static int tremove (lua_State *L) {
119   int e = aux_getn(L, 1);
120   int pos = luaL_optint(L, 2, e);
121   if (!(1 <= pos && pos <= e))  /* position is outside bounds? */
122    return 0;  /* nothing to remove */
123   luaL_setn(L, 1, e - 1);  /* t.n = n-1 */
124   lua_rawgeti(L, 1, pos);  /* result = t[pos] */
125   for ( ;pos<e; pos++) {
126     lua_rawgeti(L, 1, pos+1);
127     lua_rawseti(L, 1, pos);  /* t[pos] = t[pos+1] */
128   }
129   lua_pushnil(L);
130   lua_rawseti(L, 1, e);  /* t[e] = nil */
131   return 1;
132 }

We can see that, it is a different dealing with the insert position argument.

if (pos > e) e = pos;  /* `grow' array if necessary */

If the pos is bigger than the current max position(the e), then expand the table with the pos, It is a "grow" array, but it need alloc more memery. Maybe for the optimizing reason, in 5.3 this is not allowed, then the old code get error.

More details of source code

如果想了解更清楚, 可以在源代码里搜索一下函数(或者宏) luaL_argcheck:

pi@rpi /opt/software/lua-5.3.2 $ git grep -n "luaL_argcheck"
...
src/lauxlib.h:114:#define luaL_argcheck(L, cond,arg,extramsg)   \
...

看样子是个宏, 打开 src/lauxlib.h, 查到如下宏定义:

114 #define luaL_argcheck(L, cond,arg,extramsg) \
115         ((void)((cond) || luaL_argerror(L, (arg), (extramsg))))

发现又调用了一个 luaL_argerror, 先在本文件里查一下, 发现有函数声明:

38 LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg);

那么函数定义应该在 src/lauxlib.c 中, 再用 git grep -n 搜一把, 如下:

pi@rpi /opt/software/lua-5.3.2 $ git grep -n "luaL_argerror"
...
src/lauxlib.c:164:LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) {
...

很好, 打开看看具体代码:

 164 LUALIB_API int luaL_argerror (lua_State *L, int arg, const char *extramsg) {
 165   lua_Debug ar;
 166   if (!lua_getstack(L, 0, &ar))  /* no stack frame? */
 167     return luaL_error(L, "bad argument #%d (%s)", arg, extramsg);
 168   lua_getinfo(L, "n", &ar);
 169   if (strcmp(ar.namewhat, "method") == 0) {
 170     arg--;  /* do not count 'self' */
 171     if (arg == 0)  /* error is in the self argument itself? */
 172       return luaL_error(L, "calling '%s' on bad self (%s)",
 173                            ar.name, extramsg);
 174   }
 175   if (ar.name == NULL)
 176     ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?";
 177   return luaL_error(L, "bad argument #%d to '%s' (%s)",
 178                         arg, ar.name, extramsg);
 179 }

看得出来, 我们的试验代码触发了最后一条判断语句:

 ...
 175   if (ar.name == NULL)
 176     ar.name = (pushglobalfuncname(L, &ar)) ? lua_tostring(L, -1) : "?";
 177   return luaL_error(L, "bad argument #%d to '%s' (%s)",
 178                         arg, ar.name, extramsg);
 ...

--The End

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部