2019独角兽企业重金招聘Python工程师标准>>>

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 $ 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 ?
>

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)
>

代码2

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
>

再看看 5.1 中的表现

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
>

结论

可以看出:

  • 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     else56       luaL_argerror(L, arg, "table expected");  /* force an error */57   }58 }

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

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

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)
>

那么我们看看 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<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 }

很显然, 在 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

Lua(Codea) 中 table.insert 越界错误原因分析相关推荐

  1. 段错误原因分析和查找

    转自:http://www.cnblogs.com/panfeng412/archive/2011/11/06/2237857.html 最近在Linux环境下做C语言项目,由于是在一个原有项目基础之 ...

  2. UG数控编程中刀过切的原因分析和解决对策

    UG数控编程中刀过切的原因分析和解决对策 对从事UG数控编程工作中经常容易犯错(撞刀过切)的原因进行分析和总结,并结合一线实际工作案例和情景总结出合理的解决对策方法,让行业新人或者学习者在工作上少犯错 ...

  3. “undefined reference to JNI_GetCreatedJavaVM”和“File format not recognized”错误原因分析...

    "undefined reference to JNI_GetCreatedJavaVM"和"File format not recognized"错误原因分析 ...

  4. Property 'X' not found on type entity.Customer错误原因分析

    错误原因分析: 实体类 entity.Customer的属性x一定要小写,规范规定 属性值的首字母必须小写. 另外需要在映射表上把小写字母属性值x 映射到数据库的表里的值大写X

  5. uni-app浏览器、iPhone手机显示轮播图,微信、支付宝小程序中不显示的错误原因及解决办法

    源码: index.vue: <template><view class="main"><swiper :indicator-dots="t ...

  6. MySQL · 案例分析 · RDS MySQL线上实例insert慢常见原因分析

    概述 insert慢是经常被问到的问题,笔者尝试在本文中对这个问题做一个分类梳理,列举的线上例子会做简化,希望对读者有所启发. 注意:因为阿里云MySQL线上实例还是以RDS 5.6为主体,本文的分析 ...

  7. 线上阿里云mysql慢_MySQL · 案例分析 · RDS MySQL线上实例insert慢常见原因分析-阿里云开发者社区...

    概述 insert慢是经常被问到的问题,笔者尝试在本文中对这个问题做一个分类梳理,列举的线上例子会做简化,希望对读者有所启发. 注意:因为阿里云MySQL线上实例还是以RDS 5.6为主体,本文的分析 ...

  8. Invalid project description.错误原因分析与解决方案

    今天使用 eclipse 创建 maven 项目的时候遇到了一个问题,经过多次搜索排查,最终找到了对应的解决方案. 发生 Invalid project description 错误的原因可能不止一种 ...

  9. Entity Framework 数据并发访问错误原因分析与系统架构优化

    本文主要记录近两天针对项目发生的数据访问问题的分析研究过程与系统架构优化,我喜欢说通俗的白话,高手轻拍 1. 发现问题 系统新模块上线后,使用频率较高,故在实际使用和后期的问题重现测试中,产生了一下系 ...

最新文章

  1. python怎么判断一个文件是否存在-python判断文件是否存在
  2. 基于Fixed定位的框选功能
  3. 信号量CSemaphore的使用
  4. 获得OnOK退出控制
  5. HTTP性能测试工具wrk安装及使用
  6. Java之消息摘要(MD5)
  7. python中的引用怎么理解_浅谈动态类型领域中 Python 的变量、对象以及引用
  8. 基于matlab的谐波处理及无功功率补偿源码,谐波抑制和无功功率补偿(第3版) pdf epub mobi txt 下载...
  9. 自动化专业是计算机相关专业吗,自动化专业属于什么学科门类
  10. 7月上热搜50次!周杰伦新专辑1天1.5亿!歌手新歌爆红的营销路径
  11. h5唤醒软键盘(数字键盘)
  12. 上传到服务器的网站打开是空白,网站上传服务器,首页打开空白的解决办法|74cms|骑士cms...
  13. 天基实业个人投资理财选择优质项目最为重要
  14. Explain是什么?Explain能干嘛?
  15. Python PyQt5
  16. 梯度下降及python实现
  17. python异常处理try判断整数_Python之异常处理
  18. antd select 等组件可搜索问题
  19. 如何跟领导说话,会让领导喜欢并器重你?
  20. LED背光驱动IC 支持32通道 PIN艾瓦特7039,7088

热门文章

  1. numpy数组切片:一维/二维/数组
  2. SVO(SVO: fast semi-direct monocular visual odometry)
  3. php函数serialize()与unserialize()
  4. 负载均衡环境中和如何设置Expires和Etag
  5. 何时使用margin和padding?
  6. .NET下正则表达式应用的四个示例
  7. ASP.net随机数应用实例
  8. 深入理解malloc和free
  9. 提高C++性能的编程技术笔记:虚函数、返回值优化+测试代码
  10. 手机如何看python代码_python如何绘制iPhone手机图案?(代码示例)