博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Lua(Codea) 中 table.insert 越界错误原因分析
阅读量:6500 次
发布时间:2019-06-24

本文共 9565 字,大约阅读时间需要 31 分钟。

hot3.png

Lua(Codea) 中 table.insert(touches, touch.id, touch) 越界错误原因分析

背景介绍

Codea 上运行其他人以前写的代码时, 发现某段处理 touch 事件的代码总是报错, 开始报浮点数没有整型的表示, 修改代码增加类型转换后, 又报越界错误.

试验代码

因为这些程序在之前版本的 Codea 可以正常运行(使用 lua-5.1), 所以我推测这个错误可能是 lua 版本差异引发的. 为方便定位问题, 从 iPad 转到 树莓派lua-5.3.2 环境进行试验(因为目前最新版本的 Codea 对应的 Lua 版本是 5.3), Codea 中的试验代码如下:

代码1

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

Lua-5.3.2 中报错, 运行信息如下:

pi@rpi /opt/software/lua-5.3.2 $ luaLua 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 ?>

Lua-5.1.5 中正常运行, 运行信息如下:

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

代码2

Lua-5.3.2 中报错, 运行信息如下:

kano@kano ~ $ luaLua 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> #my2> 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) end1	122	12123	123>

再看看 5.1 中的表现

pi@rpi /opt/software/lua-5.3.2/src $ luaLua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio> my={}> table.insert(my,123,12)> #mystdin: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) end1       122       124       12123     123>

结论

可以看出:

  • table.insert 时空表必须从 1 开始, 后面的索引要跟前一个保持连续.
  • 123 仅仅被当成 my 中哈希表的 key, 而不是数组索引.
  • 计算长度时没有把以哈希表方式存储的项目算进去

分析Lua源代码

开始怀疑可能是 touch.id 数字太大, 后来发现改用小数字也不行, 幸好 lua 提供了源代码, 用 git grep -n "报错信息"lua-5.3.2 的源代码中顺利找到对应的函数代码, 发现确实有一个条件判断, 查询结果如下:

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 $

查询结果很明确, 该错误信息可在源文件 src/ltablib.c 的第 90 行和第 110 行找到, 用 vi 打开该文件, 在 vi 命令模式下输入 :90, 即可跳转到第 90 行, 发现是一个 table.insert 函数, 第 110 行是一个 table.remove 函数, 代码如下:

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 }

读读代码, 发现这里的两个函数都用 luaL_argcheck 对参数做了检查, 如果合法则通过, 如果不合法则返回错误信息.

在函数 tinsert 中的合法条件是 1 <= pos && pos <= e, 那么 e 是多少呢? 继续看代码, 在函数最开始有定义, 还有注释:

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

表中的第一个空元素的位置索引(也就是最后一个位置+1).

接着看一下在函数 tremove 中的判断条件: 1 <= pos && pos <= size + 1, 其中的 size 也在函数最开始有定义, 跟函数 tinsert 中的 e 完全一样:

lua_Integer size = aux_getn(L, 1, TAB_RW);

相关的几个定义:

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 }

现在我们明白这个判断条件的意思了, 就是对第二个参数(插入位置索引/删除位置索引)进行判断, 如果它超出当前表的大小, 那么就返回错误.

这种表现明显跟我们以前版本的 lua 不一样, 以前(5.1)可以任意取一个位置索引进行插入, 比如这样:

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

那么我们看看 5.1 中这两个函数(tinsert/tremove)的源代码:

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

很显然, 在 5.1 中对位置索引的判断处理不太一样:

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

如果索引位置大于当前最大位置, 则把索引位置赋给当前最大位置, 相当于扩大了表, 这是一个可以动态"生长"的数组, 这样的话可能需要分配更多的无用空间. 也许出于优化考虑, 在 5.3 中不允许这么做了. 所以就让我们以前正常的代码出错了.

更多代码细节

如果想了解更清楚, 可以在源代码里搜索一下函数(或者宏) 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); ...

--结束

转载于:https://my.oschina.net/freeblues/blog/683117

你可能感兴趣的文章
ps切图教程 android,PS前端切图完整教程
查看>>
html显示服务器状态,显示服务器时间并一直显示(html代码)
查看>>
在线html代码优化,网站seo优化html代码方法
查看>>
HTML如何把输入框变成必填值,required输入框为必填项
查看>>
在html中哪一个不是链接的目标属性,HTML试题
查看>>
android otg 挂载流程,android USB OTG功能如何打开及实现
查看>>
html属性board,pin_board.html
查看>>
html定位有几种,POSITION定位有哪几种?各有什么特点?
查看>>
背锅侠逆袭之路
查看>>
演示:使用协议分析器取证IPv6的报文结构
查看>>
oracle 11gr2 rac中的4种IP解说
查看>>
为什么你找不到工作?
查看>>
20 个免费的 jQuery 的工具提示插件:
查看>>
只有在北方的中国帝国能力享受免费的商业课程:财富规划法与愿景
查看>>
汇编语言的应用
查看>>
device platform 相应的表
查看>>
php des 加密解密实例
查看>>
【Mac】Mac键盘实现Home, End, Page UP, Page DOWN
查看>>
实战使用Axure设计App,使用WebStorm开发(1) – 用Axure描述需求
查看>>
安德鲁斯----多媒体编程
查看>>