当前位置: 首页 > 编程日记 > 正文

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

2019独角兽企业重金招聘Python工程师标准>>> 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 $ 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

相关文章:

【MATLAB】符号数学计算(六):符号函数的操作

一、复合函数的操作 compose(f,g)&#xff1a;返回复合函数f(g(y))&#xff0c;此处ff(x)&#xff0c;gg(y)&#xff1b;compose(f,g,x,z)&#xff1a;返回自变量是z的复合函数f(g(z)) >> syms x y >> fsym(xx^-1); >> gsym(sin(x)); >> h(1y^2); >…

java中如何应对读改写场景

前言 volatile可以确保数据及时刷新到主存&#xff0c;但是对于读改写场景还是无能为力 举个例子 public class ConcurrentHashMapExample {public static void main(String[] args) throws InterruptedException {Map<String, Long> ordersMap new ConcurrentHashMap&l…

Apache Hudi的写时复制和读时合并

Apache Hudi http://hudi.apache.org/ http://hudi.apache.org/docs/quick-start-guide.html Hudi是什么 Hudi将流处理带到大数据&#xff0c;提供新数据&#xff0c;同时比传统批处理效率高一个数量级。 Hudi可以帮助你构建高效的数据湖&#xff0c;解决一些最复杂的底层…

顶尖程序员不同于常人的 5 个区别

2019独角兽企业重金招聘Python工程师标准>>> 《The Effective Engineer》的作者在写书的过程中&#xff0c;为了了解那些顶级程序员和普通程序员的区别&#xff0c;采访了很多硅谷顶级科技公司的顶尖软件工程师。他发现这些给世界带来巨大影响的的工程师们至少有以下…

【MATLAB】符号数学计算(七):符号微积分、符号微分方程求解、符号代数方程求解

一、符号表达式的极限 limit(F,x,a)&#xff1a;求当时&#xff0c;符号表达式F的极限。limit(F,a)&#xff1a;符号表达式F采用默认自变量&#xff08;可由函数findsym求得&#xff09;&#xff0c;该函数求F的自变量趋于a时的极限值。limit(F)&#xff1a;符号表达式采用默认…

Qt运行时中文乱码的解决办法

QT5的解决办法&#xff0c;在类之前添加&#xff1a;   #pragma execution_character_set("utf-8")QT4解决办法&#xff1a; QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());转载于:https://www.cnblogs.com/bjxingch/articles/9992998.html

更换yum的源为阿里云或者网易

1.备份原本的yum源&#xff1a; #mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2.下载阿里云的yum源&#xff1a; CentOS6,CentOS7,CentOS8下对应的即可 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Ce…

socket编程:多路复用I/O服务端客户端之poll

一. 关于poll对于IO复用模型&#xff0c;其优点无疑是免去了对一个个IO事件就绪的等待&#xff0c;转而代之的是同时对多个IO数据的检测&#xff0c;当检测等待的事件中至少有一个就绪的时候&#xff0c;就会返回告诉用户进程“已经有数据准备好了&#xff0c;快看看是哪个赶紧…

【MATLAB】符号数学计算(八):符号分析可视化

一、funtool分析界面 在命令行窗口中输入&#xff1a; funtool 这里就说一下第四排&#xff1a; Insert&#xff1a;把当前激活窗的函数写入列表Cycle&#xff1a;依次循环显示fxlist中的函数Delete&#xff1a;从fxlist列表中删除激活窗的函数Reset&#xff1a;使计算器恢复…

java 根据实体对象生成 增删改的SQL语句 ModelToSQL

2019独角兽企业重金招聘Python工程师标准>>> java 根据实体对象生成 增删改的SQL语句 ModelToSQL 转载于:https://my.oschina.net/miaojiangmin/blog/2907010

深入浅出SpringBoot源码分析

Spring源码非常多,不要迷失在源码的汪洋大海里,抓住主要脉络,有需要再研究即可。 Bean的初始化 1.发现所有的bean ComponentScanAnnotationParser.parse()调用doScan()扫包 这里只是扫用户定义的bean,系统的自然不用扫 ClassPathBeanDefinitionScanner.doScan protected…

HBase基本知识

为什么80%的码农都做不了架构师&#xff1f;>>> 概述 HBase 特性&#xff1a; 强一致性读写: HBase 不是 "最终一致性(eventually consistent)" 数据存储. 这让它很适合高速计数聚合类任务。自动分片(Automatic sharding): HBase 表通过region分布在集群…

【编程题】猜年龄

题目标题: 猜年龄 美国数学家维纳(N.Wiener) 智力早熟&#xff0c;11岁就上了大学。他曾在1935~1936年应邀来中国清华大学讲学。 一次&#xff0c;他参加某个重要会议&#xff0c;年轻的脸孔引人注目。于是有人询问他的年龄&#xff0c;他回答说&#xff1a; “我年龄的立方是个…

XenServer和VMware vSphere技术比较

此次将Citrix XenServer7.1和VMware ESXi 6.5从技术角度进行比较&#xff0c;并从企业角度对企业关心的项进行比较。主要包含市场地位、服务器虚拟化底层稳定性、管理架构、兼容性上进行分析。 市场地位 VMware在虚拟化的地位类似于大型存储中的EMC、小型机中IBM、网络中的思科…

阿里巴巴开源的缓存框架JetCache创建缓存

官网:https://github.com/alibaba/jetcache/wiki/CacheAPI_CN ======================= 多层嵌套缓存无效的问题: https://github.com/alibaba/jetcache/issues/424 某个service的方法加缓存注解,然后引用同一个类的另一个加缓存注解service的方法,这样必须在类里面注入…

【Python】百度翻译的爬虫实现(前篇)

该程序只能实现中文到英文的翻译 import requestsimport jsonurl "http://fanyi.baidu.com/basetrans"query_str input("请输入要翻译成英文的内容&#xff1a;")data{ "query": query_str,"from": "zh","to"…

github每次推送都要输入用户名和密码

/****************************************************************************** github每次推送都要输入用户名和密码* 说明&#xff1a;* 今天开始使用github管理一些东西&#xff0c;但是每次提交都出现要输入用户名和密码&#xff0c;* 这简直让人…

ELASTIC SEARCH 性能调优

ELASTICSEARCH 性能调优建议 创建索引调优 1.在创建索引的使用使用批量的方式导入到ES。 2.使用多线程的方式导入数据库。 3.增加默认刷新时间。 默认的刷新时间是1秒钟&#xff0c;这样会产生太多小的SEGMENT&#xff0c;导致未来的合并压力&#xff0c;如果调整这个大小&…

Android开源中国客户端学习 (自定义View)左右滑动控件ScrollLayout

左右滑动的控件我们使用的也是非常多了,但是基本上都是使用的viewpager 等 android基础的控件,那么我们有么有考虑过查看他的源码进行定制呢?当然,如果你自我感觉非常好的话可以自己定制一个,osc的ScrollLayout就是自己定义的View 和Viewpager的区别还是不小的 代码不是很多不…

【Python】有道翻译的爬虫实现(前篇)

import requestsimport jsonurl "http://fanyi.youdao.com/translate_o?smartresultdict&smartresultrule"data {"i": "我喜欢学习", "from": "AUTO", "to": "AUTO", "smartresult":&q…

自动生成纯文本表格的工具

https://tableconvert.com/?outputtext 有时候需要写文档的时候生成这种纯文本表格&#xff0c;这个工具真的很方便&#xff0c;贴上数据就可以了。

《Java编程思想》笔记13.字符串

点击进入我的博客 字符串操作是计算机程序设计中最常见的行为 13.1 不可变String String底层是由char[]实现的&#xff0c;是不可变的。看起来会改变String的方法&#xff0c;实际上都是创建了一个新的String对象&#xff0c;任何指向它的引用都不可能改变它本身的值。 13.2 重…

【Python】有道翻译的爬虫实现(后篇)

前面说到&#xff0c;有道翻译和百度翻译不同 &#xff08;百度翻译是模拟iPhone手机&#xff0c;可能百度翻译用Pc端也会有类似的问题&#xff0c;有道翻译的User—Agent是Pc端&#xff09; 每一次的salt和sign都不一样&#xff0c;这是什么原因产生的呢&#xff1f; 一、每…

Redis客户端JetCache的单机版和集群版的配置

jetcache基础应用参考这个:阿里巴巴开源的缓存框架JetCache创建缓存 1.JetCache的jedis配置 https://github.com/alibaba/jetcache/wiki/Config_CN jedis配置:apollo版 jetcache.remote.default.type = redis jetcache.remote.default.keyConvertor = fastjson jetcache.r…

5 分钟一次理解 Spring IOC !

今天我们分析一下 spring 的 IOC&#xff0c;梳理一下 IOC 和 DI 的概念与原理。在网上看到开涛有篇文章写的不错&#xff0c;提取其中一部分精华内容并做一些解读。 1.1.IOC是什么&#xff1f; Ioc—Inversion of Control&#xff0c;即“控制反转”&#xff0c;不是什么技术&…

工作两年的编程感想

2019独角兽企业重金招聘Python工程师标准>>> 工作已有两年了&#xff0c;两年不长也不短了&#xff0c;程序员的辛酸苦乐也都体验了一些&#xff0c;故写此博客既为留念&#xff0c;也为接下来的两年留下一个参考点。 首先需要声明的是&#xff0c;本人的工作是Java…

微服务重构心得

现在都在做微服务,看起来就是做服务拆分比较简单,但是实际上真正重构起来又遇到许许多多的问题。 微服务重构常见问题 1.领域驱动模型的困扰 比如听到很多理论比如领域驱动,那么到底需要不需要学习或者使用领域驱动呢? 2.系统的复杂性 重构的时候发现系统之间调用非常…

【Python】百度翻译的爬虫实现(后篇)

这个程序可以实现中英文的自动识别然后进行翻译 看着程序就很好理解。 import requestsimport jsonclass Translation():翻译def __init__(self,content):self.content contentself.url "http://fanyi.baidu.com/basetrans"self.headers {"User-Agent"…

美团即时物流的分布式系统架构设计

背景 美团外卖已经发展了五年&#xff0c;即时物流探索也经历了3年多的时间&#xff0c;业务从零孵化到初具规模&#xff0c;在整个过程中积累了一些分布式高并发系统的建设经验。最主要的收获包括两点&#xff1a; 即时物流业务对故障和高延迟的容忍度极低&#xff0c;在业务复…

Intellij IDEA单元测试提示Test events were not received

Intellij IDEA单元测试时提示Test events were not received 也就是可以运行test方法&#xff0c;也提示成功&#xff0c;但是看不到具体的执行结果。 Intellij IDEA从2019.2.1版本开始&#xff0c;会将Gradle管理的项目的测试代码&#xff0c;默认使用Gradle来运行&#xff0…