Lua 错误

以下是自 4.0 版本以来 每个 Lua 版本 中发现的所有错误的列表。

每个条目都包含一个展示错误的最小示例,以及(如果可用)补丁或解决方案。较大的修复程序出现在 Lua 的 GitHub 仓库中,该仓库不定期同步。

除非另有说明,每个 Lua 版本都修复了先前版本中列出的所有错误。在新版本中发现的一些错误可能存在于旧版本中。

如果您想报告错误,请先阅读此内容

Lua 5.5.0

尚未报告任何错误。

Lua 5.4.8 1 · 2 · 3

  1. 全弱表中的新元表可能会欺骗垃圾回收器。
    由 jy l 于 2025 年 6 月 12 日报告。至少从 5.3.0 版本就存在。已在 github 中修复。

    示例

    -- (needs test library)
    collectgarbage"incremental"
    T.gcstate("propagate")
    local t = setmetatable({}, {__mode = "kv"})
    T.gcstate("atomic")
    setmetatable(t, {__mode = "kv"})
    T.gcstate("pause")
    print(getmetatable(t).__mode)
    

    补丁

    lgc.c:
    @@ -553,8 +553,12 @@ static lu_mem traversetable (global_State *g, Table *h) {
           traverseweakvalue(g, h);
         else if (!weakvalue)  /* strong values? */
           traverseephemeron(g, h, 0);
    -    else  /* all weak */
    -      linkgclist(h, g->allweak);  /* nothing to traverse now */
    +    else {  /* all weak */
    +      if (g->gcstate == GCSpropagate)
    +        linkgclist(h, g->grayagain);  /* must visit again its metatable */
    +      else
    +        linkgclist(h, g->allweak);  /* must clear collected entries */
    +    }
       }
       else  /* not weak */
         traversestrongtable(g, h);
    
  2. 当没有脚本时,Lua 在给定选项 '--' 时会引发错误。
    由 Jure Bagić 于 2025 年 7 月 4 日报告。自 5.4.6 版本就存在。已在 github 中修复。

    示例

    $ lua -i --
    Lua 5.4.8  Copyright (C) 1994-2025 Lua.org, PUC-Rio
    lua: not enough memory
    

    补丁

    --- a/lua.c
    +++ b/lua.c
    @@ -302,7 +302,8 @@ static int collectargs (char **argv, int *first) {
           case '-':  /* '--' */
             if (argv[i][2] != '\0')  /* extra characters after '--'? */
               return has_error;  /* invalid option */
    -        *first = i + 1;
    +        /* if there is a script name, it comes after '--' */
    +        *first = (argv[i + 1] != NULL) ? i + 1 : 0;
             return args;
           case '\0':  /* '-' */
             return args;  /* script "name" is '-' */
    
  3. 带 nil 的构造函数在解析期间可能会导致计数器溢出。
    由 Marco Schöpl 于 2025 年 8 月 26 日报告。至少从 5.4 版本就存在。已在 github 中修复。

    补丁

    --- a/lparser.c
    +++ b/lparser.c
    @@ -940,6 +940,8 @@ static void constructor (LexState *ls, expdesc *t) {
         if (ls->t.token == '}') break;
         closelistfield(fs, &cc);
         field(ls, &cc);
    +    checklimit(fs, cc.tostore + cc.na + cc.nh, INT_MAX/2,
    +               "items in a constructor");
       } while (testnext(ls, ',') || testnext(ls, ';'));
       check_match(ls, '}', '{', line);
       lastlistfield(fs, &cc);
    

Lua 5.4.7 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8

  1. 在 REPL 中,多行输入被文件末尾中断时,错误消息不正确。
    由 Eric Oertel 于 2024 年 7 月 4 日报告。自 5.3 版本就存在。已在 github 中修复。

    示例

    -- In the REPL, enter something incomplete (e.g., "a.<newline>"),
    -- and then end-of-file (control-D on Linux) at the next prompt.
    > a.
    >> (error message not a string)
    
  2. 带有比较的索引的代码生成不正确。
    由 toxamin 于 2024 年 8 月 3 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    $ lua
    > _ENV[1 < 2]
    stdin:1: attempt to index a number value
    

    补丁

    lcode.c:
    @@ -985,7 +985,7 @@
     ** or it is a constant.
     */
     void luaK_exp2val (FuncState *fs, expdesc *e) {
    -  if (hasjumps(e))
    +  if (e->k == VJMP || hasjumps(e))
         luaK_exp2anyreg(fs, e);
       else
         luaK_dischargevars(fs, e);
    
  3. 16 位系统中局部变量的限制不正确。
    由 Marcus Lund 于 2024 年 9 月 7 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    local x = 10

    补丁

    lparser.c:
    @@ -198,7 +198,7 @@ static int new_localvar (LexState *ls, TString *name) {
       checklimit(fs, dyd->actvar.n + 1 - fs->firstlocal,
                      MAXVARS, "local variables");
       luaM_growvector(L, dyd->actvar.arr, dyd->actvar.n + 1,
    -                  dyd->actvar.size, Vardesc, USHRT_MAX, "local variables");
    +                  dyd->actvar.size, Vardesc, SHRT_MAX, "local variables");
       var = &dyd->actvar.arr[dyd->actvar.n++];
       var->vd.kind = VDKREG;  /* default */
       var->vd.name = name;
    
  4. 恐慌函数中缺少错误状态。
    由 Alex Grund 于 2025 年 1 月 4 日报告。自 5.4.4 版本就存在。

    示例

    Calling 'lua_status' inside a panic function always returns 0.
    
  5. 在表更新期间,紧急垃圾回收可能会收集元表的 __newindex(如果元表是弱表)。
    由 Xmilia Hermit 于 2025 年 3 月 9 日报告。至少从 5.4.1 版本就存在。已在 github 中修复。

    示例

    -- we need the test library to force a memory-allocation error
    local mt = setmetatable({__newindex={}}, {__mode='v'})
    local t = setmetatable({}, mt)
    
    T.allocfailnext()
    
    for i=1, 10 do t[i] = 1 end
    
  6. 'luaD_seterrorobj' 不应该引发错误,因为它是在未保护的情况下调用的。
    由 Xmilia Hermit 于 2025 年 3 月 10 日报告。自 5.1 版本就存在。已在 github 中修复。
  7. 当关闭线程时,消息处理程序可能会被关闭变量覆盖。
    由 Xmilia Hermit 于 2025 年 3 月 10 日报告。已在 github 中修复。

    示例

    local c = coroutine.create(function()
        local a <close> = setmetatable({}, {__close = function ()
          local x = 134   -- will overwrite message handler
          error(x)
        end})
        -- yields coroutine but leaves a new message handler for it,
        -- that would be used when closing the coroutine (except that
        -- it will be overwritten)
        xpcall(coroutine.yield, function() return "XXX" end)
    end)
    
    assert(coroutine.resume(c))    -- start coroutine
    local st, msg = coroutine.close(c)
    assert(not st and msg == 134)
    
  8. 使用字符串 "_ENV" 的字段时,错误消息不正确。
    由 Xmilia Hermit 于 2025 年 3 月 11 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    -- error message comes with "global 'u'"
    print(("_ENV").u + 1)
    

Lua 5.4.6 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11

  1. 'l_strcmp' 中读取溢出。
    由 Xmilia Hermit 于 2023 年 6 月 9 日报告。至少从 5.0 版本就存在。已在 github 中修复。

    示例

    -- You must have the locale 'km_KH.UTF-8' installed
    assert(os.setlocale("km_KH.UTF-8"))
    local A = "\u{17A4}"
    local B = "\u{17A2}\u{17B6}"
    print(string.rep(B, 100000) .. "\0" <= string.rep(A, 100000) .. "\0")
    
  2. 当计数钩子让出时,调用钩子可能会被调用两次。
    由 G.k Ray 于 2023 年 7 月 20 日报告。至少从 5.4.0 版本就存在。已在 github 中修复。

    示例

    /* The following program prints "Call hook: Lua" twice, while it
       should print that only once.  */
    
    #include <stdio.h>
    #include "lua.h"
    #include "lauxlib.h"
    
    static void hook (lua_State *L, lua_Debug *ar) {
      if (ar->event == LUA_HOOKCALL) {  /* call hook? */
        lua_getinfo(L, "S", ar);
        printf("Call hook: %s\n", ar->what);
      }
      else  /* count hook */
        lua_yield(L, 0);
    }
    
    int main (void) {
      lua_State *L = luaL_newstate();
      lua_State *l = lua_newthread(L);
      luaL_loadstring(l, "(function(x) end)()");
      lua_sethook(l, hook, LUA_MASKCOUNT | LUA_MASKCALL, 1);
      int nres;
      while (lua_resume(l, NULL, 0, &nres) == LUA_YIELD) { }
      lua_close(L);
    }
    
  3. 函数调用的行号不正确。
    由 Thadeu de Paula 于 2023 年 8 月 20 日报告。自 5.2 版本就存在。已在 github 中修复。

    示例

    -- error should be at line 3
    foo
    (
    0
    )
    --> lua: temp:2: attempt to call a nil value (global 'foo')
    
  4. 'getobjname' 中的递归可能会导致栈溢出。
    由 Sergey Bronnikov 于 2023 年 10 月 30 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    -- This code takes forever to run, and eventually it crashes
    local a = {"local d = 1; d = d * _G"}
    for i = 2, 1e7 do a[i] = "._G" end
    a = table.concat(a)
    a = load(a)
    print(a())
    
  5. 字段名为 '_ENV' 的错误消息不正确。
    由 Roberto 于 2023 年 10 月 31 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    a = {_ENV = {}}
    print(a._ENV.x + 1)
    --> attempt to perform arithmetic on a nil value (global 'x')
    
  6. 字符串连接中的缓冲区溢出。
    由 Sergey Bronnikov 于 2023 年 12 月 16 日报告。自 5.3.2 版本就存在。已在 github 中修复。

    示例

    -- segfault in 32-bit machines
    local a = 'Name'
    for b = -1000, 0 do
      a = a .. '____________' .. a .. '____________' .. a .. a .. '____________' 
    end
    
  7. 在钩子中让出会在错误的指令处停止函数。
    由 无名字 于 2023 年 11 月 22 日报告。至少从 5.3.0 版本就存在。已在 github 中修复。

    示例

    *** yieldhook.c:
    #include "lua.h"
    #include "lauxlib.h"
    #include "string.h"
    
    static void hookf (lua_State *L, lua_Debug *ar) {
        if (ar->currentline == 6)
          lua_yield(L, 0);
    }
    
    static int db_sethook (lua_State *L) {
      lua_sethook(L, hookf, LUA_MASKLINE, 0);
      return 0;
    }
    
    int luaopen_yieldhook(lua_State *L) {
          static luaL_Reg l[] = {
                  { "sethook", db_sethook },
                  { NULL, NULL },
          };
          luaL_checkversion(L);
          luaL_newlib(L, l);
      return 1;
    }
    
    *** test.lua:
    local yieldhook = require 'yieldhook'
    local function test()
        local a1 = 1
        local a2 = 2
        local a3 = 3
        local a4 = 4
        return a1 + a2 + a3 + a4
    end
    
    local function run()
        yieldhook.sethook(coroutine.running())
        return test()
    end
    
    local co = coroutine.create(run)
    coroutine.resume(co)
    local name
    local i = 1
    for i = 1, 20 do
        local name, value =  debug.getlocal(co, 0, i)
        if name == nil then break end
        i = i + 1
        print(name, value)
    end
    
    result:
    a1      1
    a2      2
    --a3    3   is lost
    
  8. 获取没有调试信息的变长参数函数的活动行信息时崩溃。
    由 Hisham 于 2024 年 5 月 10 日报告。自 5.4.4 版本就存在。已在 github 中修复。

    示例

    sample = 'print("hello")'
    stripped = load(string.dump(load(sample), true))
    print(debug.getinfo(stripped, "L").activelines)
    
  9. 棘手的 _PROMPT 可能会触发未定义的行为。
    由 Soni 于 2024 年 6 月 4 日报告。自 5.3.0 版本就存在。已在 github 中修复。

    示例

    -- In the REPL. The "(null)" is generated by printf being called with
    -- a NULL string; that is undefined behavior in ISO C.
    > _PROMPT=setmetatable({}, {__tostring=error})
    (null)
    
  10. 重叠的赋值。
    由 Michal Domonkos 于 2024 年 6 月 4 日报告。自 5.2 版本就存在。已在 github 中修复。

    示例

    -- The code violates ISO C rule for assignments, but we don't know
    -- of any compiler where that is an issue.
    
  11. luaL_traceback 可能需要超过 5 个栈槽。
    由 Edoardo Lolletti 于 2024 年 5 月 30 日报告。自 5.2 版本就存在。已在 github 中修复。

Lua 5.4.5 1

  1. 'lua_resetthread' 的签名更改破坏了 ABI。
    由 Andrew Gierth 于 2023 年 4 月 29 日报告。已在 5.4.6 版本中修复。已在 github 中修复。

Lua 5.4.4 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11

  1. lua.c 假设 argv 至少有一个元素。
    由 DoubleF 于 2022 年 1 月 27 日报告。至少从 5.0 版本就存在。已在 github 中修复。
  2. 当 _ENV 是 <const> 时,Lua 可能会生成错误的代码。
    由 Кныжов Никита 于 2022 年 2 月 3 日报告。自 5.4.2 版本就存在。已在 github 中修复。

    示例

    -- Lua compiled with assertions on
    local _ENV <const> = 0
    X=0
    
  3. 位操作中常量的代码生成不正确。
    由 bmcq 于 2022 年 3 月 30 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    local _ = {"1","2","3","4","5","6","7","8","9","10","11","12",
    "13","14","15","16","17","18","19","20","21","22","23","24","25","26",
    "27","28","29","30","31","32","33","34","35","36","37","38",
    "39","40","41","42","43","44","45","46","47","48","49","50",
    "51","52","53","54","55","56","57","58","59","60","61","62","63","64",
    "65","66","67","68","69","70","71","72","73","74","75","76","77","78",
    "79","80","81","82","83","84","85","86","87","88","89","90","91","92",
    "93","94","95","96","97","98","99","100","101","102","103","104",
    "105","106","107","108","109","110","111","112","113","114",
    "115","116","117","118","119","120","121","122","123","124",
    "125","126","127","128","129","130","131","132","133","134",
    "135","136","137","138","139","140","141","142","143","144",
    "145","146","147","148","149","150","151","152","153","154",
    "155","156","157","158","159","160","161","162","163","164",
    "165","166","167","168","169","170","171","172","173","174",
    "175","176","177","178","179","180","181","182","183","184",
    "185","186","187","188","189","190","191","192","193","194",
    "195","196","197","198","199","200","201","202","203","204",
    "205","206","207","208","209","210","211","212","213","214",
    "215","216","217","218","219","220","221","222","223","224",
    "225","226","227","228","229","230","231","232","233","234",
    "235","236","237","238","239","240","241","242","243","244",
    "245","246","247","248","249","250","251","252","253","254",
    "255","256", }
    -- should print 3, but prints 2
    print (1 | (2 or 3))
    
  4. 在处理错误时 C 栈溢出,导致 Lua 栈溢出。
    由 Jinwei Dong 于 2022 年 5 月 13 日报告。自 5.4.2 版本就存在。已在 github 中修复。

    示例

    print(
        xpcall((0),
            function(...)
                local f
                if d[print(print(print(print(t[...]))))] then
                end
            end
        )
    )
    
  5. 'lua_settop' 可能会使用被 'luaF_close' 失效的栈指针。
    由 jun louis 于 2022 年 5 月 23 日报告。自 5.4.3 版本就存在。已在 github 中修复。

    示例

    -- (needs test library)
    
    -- reduce stack size
    collectgarbage(); collectgarbage(); collectgarbage()
    
    -- force a stack reallocation
    local function loop (n)
      if n < 400 then loop(n + 1) end
    end
    
    local o = setmetatable({}, {__close = function () loop(0) end})
    
    local script = [[toclose 2; settop 1; return 1]]
    
    assert(T.testC(script, o) == script)
    
  6. 'break' 可能无法在 'for' 循环中正确关闭变量。
    由 Xmilia Hermit 于 2022 年 5 月 18 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    local closed = false
    
    local o1 = setmetatable({}, {__close=function() closed = true end})
    
    local function test()
        for k, v in next, {}, nil, o1 do
          local function f() return k end   -- just to use 'k'
          break
        end
      assert(closed)   -- should be closed here
    end
    
    test()
    
  7. 在分代模式下,完整收集后,垃圾回收器未为下一个周期设置正确的目标。
    由 最萌 小汐 于 2022 年 7 月 15 日报告。自 5.4.3 版本就存在。已在 github 中修复。
  8. 'utf8.codes' 在遇到虚假延续字节时不会引发错误。
    由 Christian Ludwig 于 2022 年 9 月 18 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    -- the following code should raise an error
    for pos, cp in utf8.codes('in\xbfvalid') do
      print(pos, cp)
    end
    
  9. 'coroutine.close' 深度嵌套会导致 C 栈溢出。
    由 Xmilia Hermit 于 2022 年 10 月 13 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    -- Depending on the size of your C stack, you may need to increase the
    -- limit of the loop.
    local coro
    for i = 1, 1e5 do
      local previous = coro
      coro = coroutine.create(function()
        local cc <close> = setmetatable({}, {__close=function()
          if previous then
            assert(coroutine.close(previous))
          end
        end})
        coroutine.yield()
      end)
      assert(coroutine.resume(coro))
    end
    print(coroutine.close(coro))
    
  10. 算术错误中的错误消息行号不正确。
    由 Yongheng Chen 于 2023 年 2 月 5 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    local a = 0
    local b = 1
    print(b // a)
    --> lua: temp:1: attempt to divide by zero
    
  11. 加载损坏的二进制文件可能会导致段错误。
    由 Maik Betka 于 2023 年 3 月 15 日报告。自 5.2 版本就存在。已在 github 中修复。

    示例

    -- Binary files have different formats in different versions. This
    -- example is specific for Lua 5.4.
    local code =
     "\x1b\x4c\x75\x61\x54\x00\x19\x93\x0d\x0a\x1a\x0a\x04\x08\x08\x78\x56\z
      \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x77\x40\x00\x86\x40\z
      \x74\x65\x6d\x70\x81\x81\x01\x00\x02\x82\x48\x00\x02\x00\xc7\x00\x01\z
      \x00\x80\x80\x80\x82\x00\x00\x80\x81\x82\x78\x80\x82\x81\x86\x40\x74\z
      \x65\x6d\x70"
    
    assert(load(code))
    

Lua 5.4.3 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 11 · 10 · 11 · 12

  1. "for" 循环中要关闭的变量没有避免尾调用。
    由 Xmilia Hermit 于 2021 年 3 月 31 日报告。自 5.4.3 版本就存在。已在 github 中修复。

    示例

    local function iter ()
      return function () return true end, 0, 0,
             setmetatable({}, {__close = print})
    end
    
    local function tail() print("tail") end
    
    local function foo ()
      for k in iter() do return tail() end
    end
    
    foo()
    
  2. C99 单行注释与 C89 不兼容。
    由 Olexa Bilaniuk 于 2021 年 4 月 9 日报告。自 5.4.3 版本就存在。已在 github 中修复。

    补丁

    lvm.c:
    @@ -1156,8 +1156,10 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
         Instruction i;  /* instruction being executed */
         StkId ra;  /* instruction's A register */
         vmfetch();
    -// low-level line tracing for debugging Lua
    -// printf("line: %d\n", luaG_getfuncline(cl->p, pcRel(pc, cl->p)));
    +    #if 0
    +      /* low-level line tracing for debugging Lua */
    +      printf("line: %d\n", luaG_getfuncline(cl->p, pcRel(pc, cl->p)));
    +    #endif
         lua_assert(base == ci->func + 1);
         lua_assert(base <= L->top && L->top < L->stack_last);
         /* invalidate top for instructions not expecting it */
    
  3. 在返回变长参数结果时调用的 __close 元方法中让出,会使返回的值混乱。
    由 Xmilia Hermit 于 2021 年 4 月 7 日报告。自 5.4.3 版本就存在。已在 github 中修复。

    示例

    -- The final 'print' should print nothing, as the 'print' inside 'test'
    -- returns nothing.
    local function test()
        local x <close> = setmetatable({}, {
            __close = coroutine.yield
        })
        return print("Return")
    end
    
    local c = coroutine.wrap(test)
    c()          -- runs until '__close'
    print(c())   -- runs until end
    
    

    补丁

    lvm.c:
    @@ -847,10 +847,19 @@ void luaV_finishOp (lua_State *L) {
           luaV_concat(L, total);  /* concat them (may yield again) */
           break;
         }
    -    case OP_CLOSE:  case OP_RETURN: {  /* yielded closing variables */
    +    case OP_CLOSE: {  /* yielded closing variables */
           ci->u.l.savedpc--;  /* repeat instruction to close other vars. */
           break;
         }
    +    case OP_RETURN: {  /* yielded closing variables */
    +      StkId ra = base + GETARG_A(inst);
    +      /* correct top to signal correct number of returns (in case the
    +         return is "in top" */
    +      L->top = ra + ci->u2.nres;
    +      /* repeat instruction to close other vars. and complete the return */
    +      ci->u.l.savedpc--;
    +      break;
    +    }
         default: {
           /* only these other opcodes can yield */
           lua_assert(op == OP_TFORCALL || op == OP_CALL ||
    @@ -1672,6 +1681,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
               n = cast_int(L->top - ra);  /* get what is available */
             savepc(ci);
             if (TESTARG_k(i)) {  /* may there be open upvalues? */
    +          ci->u2.nres = n;  /* save number of returns */
               if (L->top < ci->top)
                 L->top = ci->top;
               luaF_close(L, base, CLOSEKTOP, 1);
    
  4. 函数语句语法不检查名称是否为常量。
    由 Halalaluyafail3 于 2021 年 6 月 16 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    local x<const> = {}
    function x() end    -- should raise an error
    print(x)
    
  5. 'luaL_tolstring' 可能会被负索引混淆。
    由 Xmilia Hermit 于 2021 年 7 月 22 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    -- (must be run in interactive mode)
    -- Both prints should show the same result
    > debug.debug()
    lua_debug> x = setmetatable({}, {__name="TABLE"})
    lua_debug> print(x)
    lua_debug> error(x)
    

    补丁

    lauxlib.c:
    @@ -881,6 +881,7 @@ LUALIB_API lua_Integer luaL_len (lua_State *L, int idx) {
     
     
     LUALIB_API const char *luaL_tolstring (lua_State *L, int idx, size_t *len) {
    +  idx = lua_absindex(L,idx);
       if (luaL_callmeta(L, idx, "__tostring")) {  /* metafield? */
         if (!lua_isstring(L, -1))
           luaL_error(L, "'__tostring' must return a string");
    
  6. 宏 'luaV_shiftr' 中的否定可能会溢出。
    由 user 673679 于 2021 年 7 月 22 日报告。自 5.3-alpha 版本就存在。已在 github 中修复。

    示例

    -- Lua compiled in gcc with option '-fsanitize=undefined'
    print(1 >> math.mininteger)
    

    补丁

    lvm.c:
    @@ -766,7 +766,7 @@ lua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) {
     /*
     ** Shift left operation. (Shift right just negates 'y'.)
     */
    -#define luaV_shiftr(x,y)       luaV_shiftl(x,-(y))
    +#define luaV_shiftr(x,y)       luaV_shiftl(x,intop(-, 0, y))
     
     lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) {
       if (y < 0) {  /* shift right? */
    
  7. 'coroutine.resume' 在受保护的错误后继续执行时,不会增加 C 调用计数器。
    由 Jihoi Kim 于 2021 年 10 月 29 日报告。自 5.4.2 版本就存在。已在 github 中修复。

    示例

    local function func()
      pcall(1)
      coroutine.wrap(func)()
    end
    func()
    
  8. 重置期间关闭变量时,协程状态不正确。
    由 MinSeok Kang 于 2021 年 10 月 30 日报告。自 5.4.1 版本就存在。已在 github 中修复。

    示例

    -- Must be run with assertions on, valgrind, or sanitizer
    co = coroutine.wrap(
      function()
        local x <close> = setmetatable(
          {}, {__close = function() pcall(co) end}
        )
        error()
      end
    )
    co()
    
  9. 关闭状态时 Lua 栈仍处于活动状态。
    由 Kang woosun 于 2021 年 11 月 30 日报告。自 5.4.3 版本就存在。已在 github 中修复。

    示例

    -- The following chunk may segfault
    function v (...)
      return os.exit(0, true)
    end
    
    local x <close> = setmetatable({}, {__close = error})
    
    v()
    
  10. 终结器不应调用垃圾回收器。
    由 김지회 于 2021 年 11 月 29 日报告。自 5.4.0 版本就存在。已在 github 中修复。

    示例

    function func1 ()
      local f = setmetatable({}, {__gc = function () collectgarbage("step") end})
      collectgarbage("step" , 1)
    end
    
    
    for i = 0,1000 do
      setmetatable({}, {__gc = func1})
    end
    
  11. 终结器可能会在无效栈中被调用。
    由 Minseok Kang 于 2021 年 12 月 6 日报告。自 5.4.3 版本就存在。已在 github 中修复。

    示例

    local x = {}; for i=1, 2000 do x[i] = i end
    
    local function f()  end
    
    local function g() return f(table.unpack(x)) end
    
    collectgarbage("step")
    setmetatable({}, {__gc = 1})
    
    g()
    
  12. 调用 os.exit 的终结器可能会破坏终结顺序。
    由 Xmilia Hermit 于 2021 年 12 月 21 日报告。自 5.2 版本就存在。已在 github 中修复。

    示例

    -- The following code illustrates the problem. If finalizer 3 calls
    -- a function from a dynamically loaded C module, the C module
    -- will be closed by the time the function is called, generating
    -- a segfault.
    
    -- should be called last
    print("creating 1")
    setmetatable({}, {__gc = function () print(1) end})
    
    print("creating 2")
    setmetatable({}, {__gc = function ()
      print("2")
      print("creating 3")
      setmetatable({}, {__gc = function () print(3) end})
      os.exit(1, true)
    end})
    
    

Lua 5.4.2 1 · 2 · 3 · 4

  1. 'table.sort' 不适用于偏序。
    由 Egor Skriptunof 于 2021 年 1 月 4 日报告。自 5.3 版本就存在。已在 github 中修复。

    示例

    nan = 0/0
    t = {nan, nan, 20, 10}
    table.sort(t)
    print(table.concat(t, ", "))
      --> -nan, 20, -nan, 10
    

    补丁:手册具有欺骗性。排序函数定义偏序是必要而非充分条件。

  2. 'debug.getinfo' 的参数 'what' 不能以 '>' 开头。
    由 Xmilia Hermit 于 2021 年 2 月 1 日报告。自 5.1 版本就存在。已在 github 中修复。

    示例

    -- with Lua compiled with option LUA_USE_APICHECK
    debug.getinfo(0, ">")
    

    补丁

    ldblib.c:
    @@ -152,6 +152,7 @@ static int db_getinfo (lua_State *L) {
       lua_State *L1 = getthread(L, &arg);
       const char *options = luaL_optstring(L, arg+2, "flnSrtu");
       checkstack(L, L1, 3);
    +  luaL_argcheck(L, options[0] != '>', arg + 2, "invalid option '>'");
       if (lua_isfunction(L, arg + 1)) {  /* info about a function? */
         options = lua_pushfstring(L, ">%s", options);  /* add '>' to 'options' */
         lua_pushvalue(L, arg + 1);  /* move function to 'L1' stack */
    
  3. 'string.concat' 中的错误消息使用错误的格式。
    由 no-n 和 Andrew Gierth 于 2021 年 2 月 14 日报告。自 5.3.0 版本就存在。已在 github 中修复。

    示例

    -- the following call gives an error message with a wrong index
    table.concat({}, "", math.maxinteger, math.maxinteger)
    

    补丁

    ltablib.c:
    @@ -146,7 +146,7 @@ static int tmove (lua_State *L) {
     static void addfield (lua_State *L, luaL_Buffer *b, lua_Integer i) {
       lua_geti(L, 1, i);
       if (!lua_isstring(L, -1))
    -    luaL_error(L, "invalid value (%s) at index %d in table for 'concat'",
    +    luaL_error(L, "invalid value (%s) at index %I in table for 'concat'",
                       luaL_typename(L, -1), i);
       luaL_addvalue(b);
     }
    
  4. 'isinstack' 错误地认为我们可以解决未定义的行为。
    由 Yongheng Chen 于 2021 年 2 月 21 日报告。自 5.3.0 版本就存在。已在 github 中修复。

    示例:在平面内存架构中,此错误可能不会导致故障。我们可以通过使用 gcc 选项 '-fsanitize=pointer-subtract'(以及使其工作所需的一切)编译 Lua 并运行以下代码来强制它发生

    print(setmetatable({}, {__index = 4}).x)
    

Lua 5.4.1 1

  1. 遍历期间从表中删除的键可能不被 'next' 接受。
    由 Xmilia Hermit 于 2020 年 10 月 11 日报告。自 5.4.0 版本就存在。已在 5.4.2 版本中修复。已在 github 中修复。

    示例

    t = {}
    t["no" .. "ref1"] = 1
    t["no" .. "ref2"] = 2
    
    for k, v in pairs(t) do
        t[k] = nil
        print(k, v)
        collectgarbage("collect")
    end
    

Lua 5.4.0 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11 · 12 · 13

  1. 旧的终结对象可能不被垃圾回收器访问。
    由 Yongheng Chen 于 2020 年 7 月 6 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    -- run this code with some memory checker, such as valgrind
    -- or gcc's option -fsanitize=address
    local A = {}
    A[1] = false     -- create an old anchor for an object
    
    -- obj finalizer
    local function gcf (obj)
      A[1] = obj     -- anchor object
      obj = nil      -- remove it from the stack
      collectgarbage("step", 0)   -- do a young collection
      print(getmetatable(A[1]).x)   -- metatable was collected!
    end
    
    collectgarbage()   -- make A old
    local obj = {}     -- create a new object
    collectgarbage("step", 0)   -- make it a survival
    setmetatable(obj, {__gc = gcf})   -- create its metatable
    obj = nil   -- clear object
    collectgarbage("step", 0)   -- will call obj's finalizer
    

    补丁

    lgc.c:
    @@ -1140,7 +1140,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
     static void youngcollection (lua_State *L, global_State *g) {
       GCObject **psurvival;  /* to point to first non-dead survival object */
       lua_assert(g->gcstate == GCSpropagate);
    -  markold(g, g->survival, g->reallyold);
    +  markold(g, g->allgc, g->reallyold);
       markold(g, g->finobj, g->finobjrold);
       atomic(L);
     
    
  2. 进入协程时栈限制的计算不正确。
    由 Yongheng Chen 于 2020 年 7 月 6 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    local lim = 1000
    local function stack (n)
      if n > 0 then return stack(n - 1) + 1
      else coroutine.wrap(function ()
             stack(lim)
           end)()
      end
    end
    
    print(xpcall(stack, function () return "ok" end, lim))
    

    补丁

    ldo.c:
    @@ -674,7 +674,7 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
       if (from == NULL)
         L->nCcalls = CSTACKTHREAD;
       else  /* correct 'nCcalls' for this thread */
    -    L->nCcalls = getCcalls(from) + from->nci - L->nci - CSTACKCF;
    +    L->nCcalls = getCcalls(from) - L->nci - CSTACKCF;
       if (L->nCcalls <= CSTACKERR)
         return resume_error(L, "C stack overflow", nargs);
       luai_userstateresume(L, nargs);
    
  3. 在加载函数上值时处理错误,紧急收集可能会导致段错误。
    由 Roberto 于 2020 年 6 月 30 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    -- must compile Lua with option -DHARDMEMTESTS, to force
    -- emergency collections
    local s = string.dump(function ()
      local x, y, z
      return function () return x + y + z end
    end)
    
    for i = 1, #s - 1 do
      assert(not load(string.sub(s, 1, i)))
    end
    

    补丁

    lundump.c:
    @@ -205,8 +205,9 @@ static void loadUpvalues (LoadState *S, Proto *f) {
       n = loadInt(S);
       f->upvalues = luaM_newvectorchecked(S->L, n, Upvaldesc);
       f->sizeupvalues = n;
    -  for (i = 0; i < n; i++) {
    +  for (i = 0; i < n; i++)
         f->upvalues[i].name = NULL;
    +  for (i = 0; i < n; i++) {
         f->upvalues[i].instack = loadByte(S);
         f->upvalues[i].idx = loadByte(S);
         f->upvalues[i].kind = loadByte(S);
    
  4. 'checkstackp' 可以运行垃圾回收步骤并销毁预分配的 CallInfo。
    由 Yongheng Chen 于 2020 年 7 月 6 日报告。已在 5.4.1 版本中修复。已在 github 中修复。

    示例:请参阅 https://lua-users.lua.ac.cn/lists/lua-l/2020-07/msg00053.html

    补丁

    ldo.c:
    @@ -466,13 +466,13 @@ void luaD_call (lua_State *L, StkId func, int nresults) {
           f = fvalue(s2v(func));
          Cfunc: {
           int n;  /* number of returns */
    -      CallInfo *ci = next_ci(L);
    +      CallInfo *ci;
           checkstackp(L, LUA_MINSTACK, func);  /* ensure minimum stack size */
    +      L->ci = ci = next_ci(L);
           ci->nresults = nresults;
           ci->callstatus = CIST_C;
           ci->top = L->top + LUA_MINSTACK;
           ci->func = func;
    -      L->ci = ci;
           lua_assert(ci->top <= L->stack_last);
           if (L->hookmask & LUA_MASKCALL) {
             int narg = cast_int(L->top - func) - 1;
    @@ -486,18 +486,18 @@ void luaD_call (lua_State *L, StkId func, int nresults) {
           break;
         }
         case LUA_VLCL: {  /* Lua function */
    -      CallInfo *ci = next_ci(L);
    +      CallInfo *ci;
           Proto *p = clLvalue(s2v(func))->p;
           int narg = cast_int(L->top - func) - 1;  /* number of real arguments */
           int nfixparams = p->numparams;
           int fsize = p->maxstacksize;  /* frame size */
           checkstackp(L, fsize, func);
    +      L->ci = ci = next_ci(L);
           ci->nresults = nresults;
           ci->u.l.savedpc = p->code;  /* starting point */
           ci->callstatus = 0;
           ci->top = func + 1 + fsize;
           ci->func = func;
    -      L->ci = ci;
           for (; narg < nfixparams; narg++)
             setnilvalue(s2v(L->top++));  /* complete missing arguments */
           lua_assert(ci->top <= L->stack_last);
    
  5. 调整栈大小后垃圾回收可能会再次缩小栈。
    由 Yongheng Chen 于 2020 年 7 月 6 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例:请参阅 https://lua-users.lua.ac.cn/lists/lua-l/2020-07/msg00054.html

    补丁

    ldo.h:
    @@ -44,7 +44,7 @@
     
     /* macro to check stack size and GC */
     #define checkstackGC(L,fsize)  \
    -       luaD_checkstackaux(L, (fsize), (void)0, luaC_checkGC(L))
    +       luaD_checkstackaux(L, (fsize), luaC_checkGC(L), (void)0)
     
     
     /* type of protected functions, to be ran by 'runprotected' */
    
  6. 终结器中的错误需要有效的 'pc' 才能生成错误消息。
    由 Rui Zhong 于 2020 年 7 月 6 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例:https://lua-users.lua.ac.cn/lists/lua-l/2020-07/msg00078.html

    补丁

    lvm.c:
    @@ -1104,7 +1104,7 @@ void luaV_finishOp (lua_State *L) {
     
     
     #define checkGC(L,c)  \
    -	{ luaC_condGC(L, L->top = (c),  /* limit of live values */ \
    +	{ luaC_condGC(L, (savepc(L), L->top = (c)), \
                              updatetrap(ci)); \
                luai_threadyield(L); }
     
    @@ -1792,8 +1792,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
             vmbreak;
           }
           vmcase(OP_VARARGPREP) {
    -        luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p);
    -        updatetrap(ci);
    +        ProtectNT(luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p));
             if (trap) {
               luaD_hookcall(L, ci);
               L->oldpc = pc + 1;  /* next opcode will be seen as a "new" line */
    
  7. 'popen' 在使用无效模式调用时可能会崩溃。
    由 Viacheslav Usov 于 2020 年 7 月 6 日报告。至少从 5.1 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例:(依赖于系统)

    补丁

    liolib.c:
    @@ -279,6 +279,8 @@ static int io_popen (lua_State *L) {
       const char *filename = luaL_checkstring(L, 1);
       const char *mode = luaL_optstring(L, 2, "r");
       LStream *p = newprefile(L);
    +  luaL_argcheck(L, ((mode[0] == 'r' || mode[0] == 'w') && mode[1] == '\0'),
    +                   2, "invalid mode");
       p->f = l_popen(L, filename, mode);
       p->closef = &io_pclose;
       return (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
    
  8. 返回函数时,字段 'L->oldpc' 不总是更新。
    由 Yongheng Chen 于 2020 年 7 月 9 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    -- run this code under valgrind. (Error depends on details of dynamic
    -- addresses.)
    function foo ()
      local f = load[[io.write('+');
                      for i = 1, 10000 do local a = {}; debug.sethook(nil) end
                      io.write'-']]
    
      local u = setmetatable({},
        {__gc = assert(load[[debug.sethook(print, "l");
                      error('err');
                      ]])})
    
      u = nil
      f()
    end
    
    for i = 1, 200 do
      foo()
    end
    

    补丁

    lgc.c:
    @@ -856,6 +856,8 @@ static void GCTM (lua_State *L) {
         if (unlikely(status != LUA_OK)) {  /* error while running __gc? */
           luaE_warnerror(L, "__gc metamethod");
           L->top--;  /* pops error object */
    +      if (isLua(L->ci))
    +        L->oldpc = L->ci->u.l.savedpc;  /* update 'oldpc' */
         }
       }
     }
    
  9. C 栈溢出(再次)。
    由 Yongheng Chen 于 2020 年 7 月 15 日报告。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    function errfunc ()
      return 10 + xpcall(nil, errfunc)
    end
    
    coroutine.resume(coroutine.create(function() xpcall(nil, errfunc) end))
    
  10. 即使在分代模式下,屏障在清除阶段也不能激活。
    由 Yongheng Chen 于 2020 年 7 月 15 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    -- The following chunk, under a memory checker like valgrind,
    -- produces a memory access violation.
    local old = {10}
    collectgarbage()   -- make 'old' old
    local co = coroutine.create(
      function ()
        local x = nil
        local f = function ()
                    return x[1]
                  end
        x = coroutine.yield(f)
        coroutine.yield()
      end
    )
    local _, f = coroutine.resume(co)     -- create closure over x in thread
    collectgarbage("step", 0)   -- make upvalue a survival
    old[1] = {"hello"}    -- 'old' go to grayagain as 'touched1'
    coroutine.resume(co, {123})     -- its value will be new
    co = nil
    -- next minor collection hits the barrier between upvalue and its
    -- conent while sweeping the thread. This will mix the lists 'gray'
    -- and 'grayagain' and will remove 'old' from 'grayagain'.
    collectgarbage("step", 0)
    assert(f() == 123 and old[1][1] == "hello")   -- still ok
    collectgarbage("step", 0)   -- run the collector once more
    -- now, as 'old' was not in 'grayagain', 'old[1]' was deleted
    assert(f() == 123 and old[1][1] == "hello")
    
  11. getlocal/setlocal 中的否定溢出。
    由 Yongheng Chen 于 2020 年 7 月 24 日报告。自 5.2 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    print(debug.getlocal(1, 2^31))
  12. 在 stripped 函数的行钩子中访问调试信息。
    由 Yongheng Chen 于 2020 年 7 月 24 日报告。自 5.4.0 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    local function foo ()
       local a = 1
       local b = 2
       local c = 3
    end
    
    local s = load(string.dump(foo, true))
    local line
    debug.sethook(function (e, l) line = l end, "l"); s(); debug.sethook(nil)
    print(line)
    
  13. 加载二进制文件时,长字符串可能在其内容被读取时被回收。
    由 Payo Nel 于 2020 年 8 月 15 日报告。自 5.3 版本就存在。已在 5.4.1 版本中修复。已在 github 中修复。

    示例

    -- run this code under some memory checker
    local function myload (s)
      return load(function ()
        if s == "" then return nil
        else
          local c = string.sub(s, 1, 1)
          s = string.sub(s, 2)
          collectgarbage()
          return c
        end
      end)
    end
    
    local y = string.dump(function ()
      return '01234567890123456789012345678901234567890123456789'
    end)
    y = myload(y)
    assert(y() == '01234567890123456789012345678901234567890123456789')
    

Lua 5.3.6

这是 Lua 5.3 的最后一个版本。此后报告的错误很可能已在 Lua 5.4 中修复。

Lua 5.3.5 1 · 2 · 3 · 4

  1. FreeBSD 配置中两次定义 'LUA_USE_READLINE'。
    由 Russell Haley 于 2018 年 7 月 13 日报告。

    补丁

    src/Makefile:
    @@ -104,2 +104,2 @@
     freebsd:
    -	$(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX -DLUA_USE_READLINE -I/usr/include/edit" SYSLIBS="-Wl,-E -ledit" CC="cc"
    +	$(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX -I/usr/include/edit" SYSLIBS="-Wl,-E -ledit" CC="cc"
    
  2. Makefile 中未更新修订号。
    由 milly 于 2018 年 8 月 8 日报告。

    补丁

    Makefile:
    @@ -49 +49 @@
    -R= $V.4
    +R= $V.5
    
  3. 带有大量 '=' 的长括号会使某些内部缓冲区算术溢出。
    由 Marco 于 2018 年 12 月 12 日报告。自 5.1 版本就存在。

    示例

    local eqs = string.rep("=", 0x3ffffffe)
    local code = "return [" .. eqs .. "[a]" .. eqs .. "]"
    print(#assert(load(code))())
    
  4. 将一个上值与其自身连接可能会导致使用后释放崩溃。
    由 Fady Othman 于 2019 年 1 月 10 日报告。自 5.3 版本就存在。

    示例

    -- the next code may crash the machine
    f=load(function() end)
    interesting={}
    interesting[0]=string.rep("A",512)
    debug.upvaluejoin(f,1,f,1)
    

    补丁

    lapi.c:
    @@ -1289,6 +1289,8 @@
    LUA_API void lua_upvaluejoin (lua_State *L, int fidx1, int n1,
       LClosure *f1;
       UpVal **up1 = getupvalref(L, fidx1, n1, &f1);
       UpVal **up2 = getupvalref(L, fidx2, n2, NULL);
    +  if (*up1 == *up2)
    +    return;
       luaC_upvdeccount(L, *up1);
       *up1 = *up2;
       (*up1)->refcount++;
    

Lua 5.3.4 1 · 2 · 3 · 4 · 5 · 6 · 7

  1. 'goto' 后跟 'if' 内部标签的代码生成不正确。
    由 云风 于 2017 年 4 月 13 日报告。自 5.2 版本就存在。

    示例

    -- should print 32323232..., but prints only '3'
    if true then
      goto LBL
      ::loop::
      print(2)
      ::LBL::
      print(3)
      goto loop
    end
    

    补丁

    lparser.c:
    @@ -1392,7 +1392,7 @@
         luaK_goiffalse(ls->fs, &v);  /* will jump to label if condition is true */
         enterblock(fs, &bl, 0);  /* must enter block before 'goto' */
         gotostat(ls, v.t);  /* handle goto/break */
    -    skipnoopstat(ls);  /* skip other no-op statements */
    +    while (testnext(ls, ';')) {}  /* skip semicolons */
         if (block_follow(ls, 0)) {  /* 'goto' is the entire block? */
           leaveblock(fs);
           return;  /* and that is it */
    
  2. Lua 在构建超过 2^30 个元素的序列时崩溃。
    由 Viacheslav Usov 于 2017 年 5 月 11 日报告。

    示例

    -- crashes if machine has enough memory
    local t = {}
    for i = 1, 0x7fffffff do
      t[i] = i
    end
    

    补丁

    ltable.c:
    @@ -223,7 +223,9 @@
       unsigned int na = 0;  /* number of elements to go to array part */
       unsigned int optimal = 0;  /* optimal size for array part */
       /* loop while keys can fill more than half of total size */
    -  for (i = 0, twotoi = 1; *pna > twotoi / 2; i++, twotoi *= 2) {
    +  for (i = 0, twotoi = 1;
    +       twotoi > 0 && *pna > twotoi / 2;
    +       i++, twotoi *= 2) {
         if (nums[i] > 0) {
           a += nums[i];
           if (a > twotoi/2) {  /* more than half elements present? */
    
  3. 表长度计算对于大于 2^31 个元素的序列溢出。
    由 Viacheslav Usov 于 2017 年 5 月 12 日报告。

    示例

    -- on a machine with enough memory
    local t = {}
    for i = 1, 2147483681 do
      t[i] = i
    end
    print(#t)
    

    补丁

    ltable.h:
    @@ -56,3 +56,3 @@
     LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key);
    -LUAI_FUNC int luaH_getn (Table *t);
    +LUAI_FUNC lua_Unsigned luaH_getn (Table *t);
     
    ltable.c:
    @@ -614,4 +614,4 @@
     
    -static int unbound_search (Table *t, unsigned int j) {
    -  unsigned int i = j;  /* i is zero or a present index */
    +static lua_Unsigned unbound_search (Table *t, lua_Unsigned j) {
    +  lua_Unsigned i = j;  /* i is zero or a present index */
       j++;
    @@ -620,3 +620,3 @@
         i = j;
    -    if (j > cast(unsigned int, MAX_INT)/2) {  /* overflow? */
    +    if (j > l_castS2U(LUA_MAXINTEGER) / 2) {  /* overflow? */
           /* table was built with bad purposes: resort to linear search */
    @@ -630,3 +630,3 @@
       while (j - i > 1) {
    -    unsigned int m = (i+j)/2;
    +    lua_Unsigned m = (i+j)/2;
         if (ttisnil(luaH_getint(t, m))) j = m;
    @@ -642,3 +642,3 @@
     */
    -int luaH_getn (Table *t) {
    +lua_Unsigned luaH_getn (Table *t) {
       unsigned int j = t->sizearray;
    
  4. Lua 在创建错误消息时不检查垃圾回收器。
    由 Viacheslav Usov 于 2017 年 7 月 6 日报告。自 5.3.2 版本就存在。

    示例

    function test()
      bob.joe.larry = 23
    end
    
    -- memory will grow steadly
    for i = 1, math.huge do
      pcall(test)
      if i % 100000 == 0 then
        io.write(collectgarbage'count'*1024, "\n")
      end
    end
    

    补丁

    ldebug.c:
    @@ -653,6 +653,7 @@
       CallInfo *ci = L->ci;
       const char *msg;
       va_list argp;
    +  luaC_checkGC(L);  /* error message uses memory */
       va_start(argp, fmt);
       msg = luaO_pushvfstring(L, fmt, argp);  /* format message */
       va_end(argp);
    
  5. 带 nil 值的死键可以保留在弱表中。
    由 云风 Cloud Wu 于 2017 年 8 月 15 日报告。自 5.2 版本就存在。

    示例

    -- The following chunk, under a memory checker like valgrind, produces a memory access violation.
    
    local a = setmetatable({}, {__mode = 'kv'})
    
    a['ABCDEFGHIJKLMNOPQRSTUVWXYZ' .. 'abcdefghijklmnopqrstuvwxyz'] = {}
    a[next(a)] = nil
    collectgarbage()
    print(a['BCDEFGHIJKLMNOPQRSTUVWXYZ' .. 'abcdefghijklmnopqrstuvwxyz'])
    

    补丁

    lgc.c:
    @@ -643,8 +643,9 @@
         for (n = gnode(h, 0); n < limit; n++) {
           if (!ttisnil(gval(n)) && (iscleared(g, gkey(n)))) {
             setnilvalue(gval(n));  /* remove value ... */
    -        removeentry(n);  /* and remove entry from table */
           }
    +      if (ttisnil(gval(n)))  /* is entry empty? */
    +        removeentry(n);  /* remove entry from table */
         }
       }
     }
    
  6. n 为零时,lua_pushcclosure 不应调用垃圾回收器。
    由 Andrew Gierth 于 2017 年 12 月 5 日报告。自 5.3.3 版本就存在。

    补丁

    lapi.c:
    @@ -533,6 +533,7 @@
       lua_lock(L);
       if (n == 0) {
         setfvalue(L->top, fn);
    +    api_incr_top(L);
       }
       else {
         CClosure *cl;
    @@ -546,9 +547,9 @@
           /* does not need barrier because closure is white */
         }
         setclCvalue(L, L->top, cl);
    +    api_incr_top(L);
    +    luaC_checkGC(L);
       }
    -  api_incr_top(L);
    -  luaC_checkGC(L);
       lua_unlock(L);
     }
    
  7. 调整表大小时的内存分配错误可能会使其处于不一致状态。
    由 Roberto 于 2017 年 12 月 8 日报告。自 5.0 版本就存在。

    示例

    local a = {x = 1, y = 1, z = 1}
    a[1] = 10   -- goes to the hash part (which has 4 slots)
    print(a[1])   --> 10
    
    -- assume that the 2nd memory allocation from now fails
    pcall(rawset, a, 2, 20)   -- forces a rehash
    
    -- a[1] now exists both in the array part (because the array part
    -- grew) and in the hash part (because the allocation of the hash
    -- part failed, keeping it as it was).
    -- This makes the following traversal goes forever...
    for k,v in pairs(a) do print(k,v) end
    

    补丁

    ltable.c:
    @@ -332,17 +332,34 @@
     }
     
     
    +typedef struct {
    +  Table *t;
    +  unsigned int nhsize;
    +} AuxsetnodeT;
    +
    +
    +static void auxsetnode (lua_State *L, void *ud) {
    +  AuxsetnodeT *asn = cast(AuxsetnodeT *, ud);
    +  setnodevector(L, asn->t, asn->nhsize);
    +}
    +
    +
     void luaH_resize (lua_State *L, Table *t, unsigned int nasize,
                                               unsigned int nhsize) {
       unsigned int i;
       int j;
    +  AuxsetnodeT asn;
       unsigned int oldasize = t->sizearray;
       int oldhsize = allocsizenode(t);
       Node *nold = t->node;  /* save old hash ... */
       if (nasize > oldasize)  /* array part must grow? */
         setarrayvector(L, t, nasize);
       /* create new hash part with appropriate size */
    -  setnodevector(L, t, nhsize);
    +  asn.t = t; asn.nhsize = nhsize;
    +  if (luaD_rawrunprotected(L, auxsetnode, &asn) != LUA_OK) {  /* mem. error? */
    +    setarrayvector(L, t, oldasize);  /* array back to its original size */
    +    luaD_throw(L, LUA_ERRMEM);  /* rethrow memory error */
    +  }
       if (nasize < oldasize) {  /* array part must shrink? */
         t->sizearray = nasize;
         /* re-insert elements from vanishing slice */
    

Lua 5.3.3 1 · 2 · 3 · 4

  1. 'for' 循环中带有四个或更多表达式的表达式列表可能会使解释器崩溃。
    由 Marco Schöpl 于 2016 年 6 月 17 日报告。自 5.2 版本就存在。已在 5.3.4 版本中修复。

    示例

    -- the next loop will probably crash the interpreter
    repeat until load "for _ in _,_,_,_ do local function _() end"
    

    补丁

    lparser.c:
    @@ -323,6 +323,8 @@
           luaK_nil(fs, reg, extra);
         }
       }
    +  if (nexps > nvars)
    +    ls->fs->freereg -= nexps - nvars;  /* remove extra values */
     }
     
     
    @@ -1160,11 +1162,8 @@
         int nexps;
         checknext(ls, '=');
         nexps = explist(ls, &e);
    -    if (nexps != nvars) {
    +    if (nexps != nvars)
           adjust_assign(ls, nvars, nexps, &e);
    -      if (nexps > nvars)
    -        ls->fs->freereg -= nexps - nvars;  /* remove extra values */
    -    }
         else {
           luaK_setoneret(ls->fs, &e);  /* close last expression */
           luaK_storevar(ls->fs, &lh->v, &e);
    
  2. 检查 os.date 的格式可能会读取超出格式字符串。
    由 Nagaev Boris 于 2016 年 7 月 10 日报告。自 5.3.3 版本就存在。已在 5.3.4 版本中修复。

    示例:此错误似乎不会在常规编译器中发生。它需要一个“拦截器” 'memcmp' 函数,该函数在发现差异后继续读取内存。

    补丁

    loslib.c:
    263c263,264
    <   for (option = LUA_STRFTIMEOPTIONS; *option != '\0'; option += oplen) {
    ---
    >   int convlen = (int)strlen(conv);
    >   for (option = LUA_STRFTIMEOPTIONS; *option != '\0' && oplen <= convlen; option += oplen) {
    
  3. Lua 可能会在具有太多常量的函数中生成错误的代码。
    由 Marco Schöpl 于 2016 年 7 月 17 日报告。自 5.3.3 版本就存在。已在 5.3.4 版本中修复。

    示例:请参阅 https://lua-users.lua.ac.cn/lists/lua-l/2016-07/msg00303.html

    补丁

    lcode.c:
    @@ -1018,8 +1018,8 @@
     */
     static void codebinexpval (FuncState *fs, OpCode op,
                                expdesc *e1, expdesc *e2, int line) {
    -  int rk1 = luaK_exp2RK(fs, e1);  /* both operands are "RK" */
    -  int rk2 = luaK_exp2RK(fs, e2);
    +  int rk2 = luaK_exp2RK(fs, e2);  /* both operands are "RK" */
    +  int rk1 = luaK_exp2RK(fs, e1);
       freeexps(fs, e1, e2);
       e1->u.info = luaK_codeABC(fs, op, 0, rk1, rk2);  /* generate opcode */
       e1->k = VRELOCABLE;  /* all those operations are relocatable */
    
  4. 当协程试图恢复非挂起的协程时,它可能会在检测到错误之前造成一些混乱(并破坏 C 断言)。
    由 Marco Schöpl 于 2016 年 7 月 20 日报告。已在 5.3.4 版本中修复。

    示例

    -- with C assertions on
    A = coroutine.running()
    B = coroutine.create(function() coroutine.resume(A) end)
    coroutine.resume(B)
    -- or
    A = coroutine.wrap(function() pcall(A, _) end)
    A()
    

Lua 5.3.2 1 · 2 · 3

  1. 元表在 __newindex 中有自引用时,可能会访问其自身的已释放字段。
    由 actboy168 于 2016 年 1 月 1 日报告。自 5.3.2 版本就存在。已在 5.3.3 版本中修复。

    示例

    local mt = {}
    mt.__newindex = mt
    local t = setmetatable({}, mt)
    t[1] = 1     -- will segfault on some machines
    

    补丁

    lvm.c:
    @@ -190,18 +190,19 @@
       for (loop = 0; loop < MAXTAGLOOP; loop++) {
         const TValue *tm;
         if (oldval != NULL) {
    -      lua_assert(ttistable(t) && ttisnil(oldval));
    +      Table *h = hvalue(t);  /* save 't' table */
    +      lua_assert(ttisnil(oldval));
           /* must check the metamethod */
    -      if ((tm = fasttm(L, hvalue(t)->metatable, TM_NEWINDEX)) == NULL &&
    +      if ((tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL &&
              /* no metamethod; is there a previous entry in the table? */
              (oldval != luaO_nilobject ||
              /* no previous entry; must create one. (The next test is
                 always true; we only need the assignment.) */
    -         (oldval = luaH_newkey(L, hvalue(t), key), 1))) {
    +         (oldval = luaH_newkey(L, h, key), 1))) {
             /* no metamethod and (now) there is an entry with given key */
             setobj2t(L, cast(TValue *, oldval), val);
    -        invalidateTMcache(hvalue(t));
    -        luaC_barrierback(L, hvalue(t), val);
    +        invalidateTMcache(h);
    +        luaC_barrierback(L, h, val);
             return;
           }
           /* else will try the metamethod */
    
  2. 局部定义之间的标签可能会混淆它们的初始化。
    由 Karel Tuma 于 2016 年 3 月 1 日报告。自 5.2 版本就存在。已在 5.3.3 版本中修复。

    示例

    do
      local k = 0
      local x
      ::foo::
      local y       -- should be reset to nil after goto, but it is not
      assert(not y)
      y = true
      k = k + 1
      if k < 2 then goto foo end
    end
    

    补丁

    lparser.c:
    @@ -1226,7 +1226,7 @@
       checkrepeated(fs, ll, label);  /* check for repeated labels */
       checknext(ls, TK_DBCOLON);  /* skip double colon */
       /* create new entry for this label */
    -  l = newlabelentry(ls, ll, label, line, fs->pc);
    +  l = newlabelentry(ls, ll, label, line, luaK_getlabel(fs));
       skipnoopstat(ls);  /* skip other no-op statements */
       if (block_follow(ls, 0)) {  /* label is last no-op statement in the block? */
         /* assume that locals are already out of scope */
    
  3. gmatch 迭代器在从创建它的协程以外的协程调用时失败。
    由 Nagaev Boris 于 2016 年 3 月 18 日报告。自 5.3.2 版本就存在。已在 5.3.3 版本中修复。

    示例

    local f = string.gmatch("1 2 3 4 5", "%d+")
    print(f())     --> 1
    co = coroutine.wrap(f)
    print(co())    --> ??? (should be 2)
    

    补丁

    lstrlib.c:
    @@ -688,6 +688,7 @@
     static int gmatch_aux (lua_State *L) {
       GMatchState *gm = (GMatchState *)lua_touserdata(L, lua_upvalueindex(3));
       const char *src;
    +  gm->ms.L = L;
       for (src = gm->src; src <= gm->ms.src_end; src++) {
         const char *e;
         reprepstate(&gm->ms);
    

Lua 5.3.1 1

  1. io.lines 不检查选项的最大数量。
    由 Patrick Donnell 于 2015 年 7 月 10 日报告。自 5.3.0 版本就存在。已在 5.3.2 版本中修复。

    示例

    -- can crash in some machines
    t ={}; for i = 1, 253 do t[i] = 1 end
    io.lines("someexistingfile", table.unpack(t))()
    

    补丁

    liolib.c:
    @@ -318,8 +318,15 @@
     static int io_readline (lua_State *L);
     
     
    +/*
    +** maximum number of arguments to 'f:lines'/'io.lines' (it + 3 must fit
    +** in the limit for upvalues of a closure)
    +*/
    +#define MAXARGLINE	250
    +
     static void aux_lines (lua_State *L, int toclose) {
       int n = lua_gettop(L) - 1;  /* number of arguments to read */
    +  luaL_argcheck(L, n <= MAXARGLINE, MAXARGLINE + 2, "too many arguments");
       lua_pushinteger(L, n);  /* number of arguments to read */
       lua_pushboolean(L, toclose);  /* close/not close file when finished */
       lua_rotate(L, 2, 2);  /* move 'n' and 'toclose' to their positions */
    

Lua 5.3.0 1 · 2 · 3 · 4

  1. string.format("%f") 可能会导致缓冲区溢出(仅当 'lua_Number' 是 long double 时!)。
    由 Roberto 于 2015 年 1 月 13 日报告。自 5.3 版本就存在。已在 5.3.1 版本中修复。

    示例

    string.format("%.99f", 1e4000)    -- when floats are long double
    

    补丁

    lstrlib.c:
    @@ -800,3 +800,4 @@
     /* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */
    -#define MAX_ITEM	512
    +#define MAX_ITEM  \
    +  (sizeof(lua_Number) <= 4 ? 150 : sizeof(lua_Number) <= 8 ? 450 : 5050)
     
    
  2. 在钩子中挂起的协程上使用 debug.getlocal 可能会使解释器崩溃。
    由 云风 于 2015 年 2 月 11 日报告。自 5.2 版本就存在。已在 5.3.1 版本中修复。

    示例:请参阅 https://lua-users.lua.ac.cn/lists/lua-l/2015-02/msg00146.html

    补丁

    ldebug.c:
    @@ -49,4 +49,14 @@
     
     
    +static void swapextra (lua_State *L) {
    +  if (L->status == LUA_YIELD) {
    +    CallInfo *ci = L->ci;  /* get function that yielded */
    +    StkId temp = ci->func;  /* exchange its 'func' and 'extra' values */
    +    ci->func = restorestack(L, ci->extra);
    +    ci->extra = savestack(L, temp);
    +  }
    +}
    +
    +
     /*
     ** this function can be called asynchronous (e.g. during a signal)
    @@ -145,4 +155,5 @@
       const char *name;
       lua_lock(L);
    +  swapextra(L);
       if (ar == NULL) {  /* information about non-active function? */
         if (!isLfunction(L->top - 1))  /* not a Lua function? */
    @@ -159,4 +170,5 @@
         }
       }
    +  swapextra(L);
       lua_unlock(L);
       return name;
    @@ -166,10 +178,13 @@
     LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) {
       StkId pos = 0;  /* to avoid warnings */
    -  const char *name = findlocal(L, ar->i_ci, n, &pos);
    +  const char *name;
       lua_lock(L);
    +  swapextra(L);
    +  name = findlocal(L, ar->i_ci, n, &pos);
       if (name) {
         setobjs2s(L, pos, L->top - 1);
         L->top--;  /* pop value */
       }
    +  swapextra(L);
       lua_unlock(L);
       return name;
    @@ -271,4 +286,5 @@
       StkId func;
       lua_lock(L);
    +  swapextra(L);
       if (*what == '>') {
         ci = NULL;
    @@ -289,4 +305,5 @@
         api_incr_top(L);
       }
    +  swapextra(L);
       if (strchr(what, 'L'))
         collectvalidlines(L, cl);
    
  3. 挂起的 __le 元方法可能会给出错误的结果。
    由 Eric Zhong 于 2015 年 4 月 7 日报告。自 5.2 版本就存在。已在 5.3.1 版本中修复。

    示例

    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)
    

    补丁

    lstate.h:
    @@ -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:
    @@ -292,9 +292,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 */
    +  }
     }
     
     
    @@ -553,11 +558,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 */
    
  4. 函数返回时,返回钩子可能看不到活动局部变量的正确值。
    由 Philipp Janda 和 Peng Yi 于 2015 年 5 月 19 日报告。自 5.0 版本就存在。已在 5.3.1 版本中修复。

    示例:请参阅 https://lua-users.lua.ac.cn/lists/lua-l/2015-05/msg00376.html

Lua 5.2.4

这是 Lua 5.2 的最后一个版本。此后报告的错误很可能已在 Lua 5.3 中修复。

Lua 5.2.3 1 · 2 · 3

  1. 编译器可以在 table.unpack 中优化掉溢出检查。
    由 Paige DePol 于 2014 年 3 月 30 日报告。自 5.1 版本就存在。已在 5.3.0 和 5.2.4 版本中修复。

    示例

    unpack({}, 0, 2^31 - 1) -- crashes on some platforms with some compiler options
    

    补丁

    ltablib.c:
    @@ -134,13 +135,14 @@
     
     
     static int unpack (lua_State *L) {
    -  int i, e, n;
    +  int i, e;
    +  unsigned int n;
       luaL_checktype(L, 1, LUA_TTABLE);
       i = luaL_optint(L, 2, 1);
       e = luaL_opt(L, luaL_checkint, 3, luaL_len(L, 1));
       if (i > e) return 0;  /* empty range */
    -  n = e - i + 1;  /* number of elements */
    -  if (n <= 0 || !lua_checkstack(L, n))  /* n <= 0 means arith. overflow */
    +  n = (unsigned int)e - (unsigned int)i;  /* number of elements minus 1 */
    +  if (n > (INT_MAX - 10) || !lua_checkstack(L, ++n))
         return luaL_error(L, "too many results to unpack");
       lua_rawgeti(L, 1, i);  /* push arg[i] (avoiding overflow problems) */
       while (i++ < e)  /* push arg[i + 1...e] */
    
  2. 弱引用表可能会错误地收集具有强键的条目。
    由 Jörg Richter 于 2014 年 8 月 22 日报告。自 5.2.0 版本就存在。已在 5.3.0 和 5.2.4 版本中修复。

    示例:此错误非常难以重现,因为它取决于增量收集器和程序之间事件的特定交错。

    补丁

    lgc.c:
    @@ -403,7 +403,7 @@
           reallymarkobject(g, gcvalue(gval(n)));  /* mark it now */
         }
       }
    -  if (prop)
    +  if (g->gcstate != GCSatomic || prop)
         linktable(h, &g->ephemeron);  /* have to propagate again */
       else if (hasclears)  /* does table have white keys? */
         linktable(h, &g->allweak);  /* may have to clean white keys */
    
  3. 行数过多的块可能会使 Lua 崩溃。
    由 Roberto 于 2014 年 11 月 14 日报告。至少从 5.1 版本就存在。已在 5.3.0 和 5.2.4 版本中修复。

    示例:此错误的原因是使用了未初始化的变量,因此无法可靠重现。

    local s = string.rep("\n", 2^24)
    print(load(function () return s end))
    

    补丁

    llex.c:
    @@ -153,5 +153,5 @@
         next(ls);  /* skip `\n\r' or `\r\n' */
       if (++ls->linenumber >= MAX_INT)
    -    luaX_syntaxerror(ls, "chunk has too many lines");
    +    lexerror(ls, "chunk has too many lines", 0);
     }
     
    

Lua 5.2.2 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8

  1. 具有许多固定参数但调用时参数很少的变长参数函数中栈溢出。
    由 云风 于 2013 年 4 月 17 日报告。自 5.1 版本就存在。已在 5.2.3 版本中修复。

    示例

    function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10,
               p11, p12, p13, p14, p15, p16, p17, p18, p19, p20,
               p21, p22, p23, p24, p25, p26, p27, p28, p29, p30,
               p31, p32, p33, p34, p35, p36, p37, p38, p39, p40,
               p41, p42, p43, p44, p45, p46, p48, p49, p50, ...)
      local a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14
    end
    
    f()   -- crashes on some machines
    

    补丁

    ldo.c:
    @@ -324,7 +324,7 @@
         case LUA_TLCL: {  /* Lua function: prepare its call */
           StkId base;
           Proto *p = clLvalue(func)->p;
    -      luaD_checkstack(L, p->maxstacksize);
    +      luaD_checkstack(L, p->maxstacksize + p->numparams);
           func = restorestack(L, funcr);
           n = cast_int(L->top - func) - 1;  /* number of real arguments */
           for (; n < p->numparams; n++)
    
  2. 垃圾回收器在递归循环中触发次数过多。
    由 Roberto 于 2013 年 4 月 25 日报告。自 5.2.2 版本就存在。已在 5.2.3 版本中修复。

    示例

    function f() f() end
    f()   -- it takes too long before a "stack overflow" error
    

    补丁

    lgc.c:
    @@ -495,2 +495,3 @@
     static lu_mem traversestack (global_State *g, lua_State *th) {
    +  int n = 0;
       StkId o = th->stack;
    @@ -505,3 +506,9 @@
       }
    -  return sizeof(lua_State) + sizeof(TValue) * th->stacksize;
    +  else {  /* count call infos to compute size */
    +    CallInfo *ci;
    +    for (ci = &th->base_ci; ci != th->ci; ci = ci->next)
    +      n++;
    +  }
    +  return sizeof(lua_State) + sizeof(TValue) * th->stacksize +
    +         sizeof(CallInfo) * n;
     }
    
  3. 报告连接错误时断言错误(仅在 Lua 以调试模式编译时才显示)。
    由 Roberto 于 2013 年 5 月 5 日报告。自 5.2.0 版本就存在。已在 5.2.3 版本中修复。

    示例

    -- only with Lua compiled in debug mode
    print({} .. 2)
    

    补丁

    ldebug.c:
    @@ -519,5 +519,5 @@
     l_noret luaG_concaterror (lua_State *L, StkId p1, StkId p2) {
       if (ttisstring(p1) || ttisnumber(p1)) p1 = p2;
    -  lua_assert(!ttisstring(p1) && !ttisnumber(p2));
    +  lua_assert(!ttisstring(p1) && !ttisnumber(p1));
       luaG_typeerror(L, p1, "concatenate");
     }
    
  4. 某些短路表达式中的错误消息不正确。
    由 Egor Skriptunoff 于 2013 年 5 月 10 日报告。自 5.0 版本就存在。已在 5.2.3 版本中修复。

    示例

    a,b,c = true,true,true
    (a and b or c)('', '')
    --> stdin:1: attempt to call a boolean value (global 'c')
    --  it should be global 'b' instead of 'c'
    

    补丁

    ldebug.c:
    @@ -327,12 +327,20 @@
     }
     
     
    +static int filterpc (int pc, int jmptarget) {
    +  if (pc < jmptarget)  /* is code conditional (inside a jump)? */
    +    return -1;  /* cannot know who sets that register */
    +  else return pc;  /* current position sets that register */
    +}
    +
    +
     /*
     ** try to find last instruction before 'lastpc' that modified register 'reg'
     */
     static int findsetreg (Proto *p, int lastpc, int reg) {
       int pc;
       int setreg = -1;  /* keep last instruction that changed 'reg' */
    +  int jmptarget = 0;  /* any code before this address is conditional */
       for (pc = 0; pc < lastpc; pc++) {
         Instruction i = p->code[pc];
         OpCode op = GET_OPCODE(i);
    @@ -341,33 +349,38 @@
           case OP_LOADNIL: {
             int b = GETARG_B(i);
             if (a <= reg && reg <= a + b)  /* set registers from 'a' to 'a+b' */
    -          setreg = pc;
    +          setreg = filterpc(pc, jmptarget);
             break;
           }
           case OP_TFORCALL: {
    -        if (reg >= a + 2) setreg = pc;  /* affect all regs above its base */
    +        if (reg >= a + 2)  /* affect all regs above its base */
    +          setreg = filterpc(pc, jmptarget);
             break;
           }
           case OP_CALL:
           case OP_TAILCALL: {
    -        if (reg >= a) setreg = pc;  /* affect all registers above base */
    +        if (reg >= a)  /* affect all registers above base */
    +          setreg = filterpc(pc, jmptarget);
             break;
           }
           case OP_JMP: {
             int b = GETARG_sBx(i);
             int dest = pc + 1 + b;
             /* jump is forward and do not skip `lastpc'? */
    -        if (pc < dest && dest <= lastpc)
    -          pc += b;  /* do the jump */
    +        if (pc < dest && dest <= lastpc) {
    +          if (dest > jmptarget)
    +            jmptarget = dest;  /* update 'jmptarget' */
    +        }
             break;
           }
           case OP_TEST: {
    -        if (reg == a) setreg = pc;  /* jumped code can change 'a' */
    +        if (reg == a)  /* jumped code can change 'a' */
    +          setreg = filterpc(pc, jmptarget);
             break;
           }
           default:
             if (testAMode(op) && reg == a)  /* any instruction that set A */
    -          setreg = pc;
    +          setreg = filterpc(pc, jmptarget);
             break;
         }
       }
    
  5. luac 列表在长字符串上卡住。
    由 Ashwin Hirschi 于 2013 年 7 月 3 日报告。自 5.2.1 版本就存在。已在 5.2.3 版本中修复。

    示例

    -- When you call 'luac -l' over this chunk, it chokes the output
    s="Lorem ipsum dolor sit amet, consectetur, "
    

    补丁

    luac.c:
    @@ -251,7 +251,7 @@
     static void PrintConstant(const Proto* f, int i)
     {
      const TValue* o=&f->k[i];
    - switch (ttype(o))
    + switch (ttypenv(o))
      {
       case LUA_TNIL:
            printf("nil");
    
  6. 解析器期间,垃圾回收器可能会收集仍在使用的长字符串。
    由 Roberto 于 2013 年 8 月 30 日报告。自 5.2.0 版本就存在。已在 5.2.3 版本中修复。

    示例:此错误非常难以发生(和重现),因为它取决于垃圾回收器在解析带有长(超过 40 个字符)标识符的源代码时以非常特定的方式运行。

    补丁

    ltable.h:
    @@ -18,4 +18,8 @@
     #define invalidateTMcache(t)	((t)->flags = 0)
     
    +/* returns the key, given the value of a table entry */
    +#define keyfromval(v) \
    +  (gkey(cast(Node *, cast(char *, (v)) - offsetof(Node, i_val))))
    +
     
     LUAI_FUNC const TValue *luaH_getint (Table *t, int key);
    
    llex.c:
    @@ -134,4 +134,7 @@
         luaC_checkGC(L);
       }
    +  else {  /* string already present */
    +    ts = rawtsvalue(keyfromval(o));  /* re-use value previously stored */
    +  }
       L->top--;  /* remove string from stack */
       return ts;
    
  7. luai_userstateclose 的调用应仅在调用 __gc 方法之后进行。
    由 Jean-Luc Jumpertz 于 2013 年 9 月 2 日报告。已在 5.2.3 版本中修复。

    补丁

    lstate.c:
    @@ -194,2 +194,4 @@
       g->gcrunning = 1;  /* allow gc */
    +  g->version = lua_version(NULL);
    +  luai_userstateopen(L);
     }
    @@ -224,2 +226,4 @@
       luaC_freeallobjects(L);  /* collect all objects */
    +  if (g->version)  /* closing a fully built state? */
    +    luai_userstateclose(L);
       luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size);
    @@ -289,3 +293,3 @@
       g->panic = NULL;
    -  g->version = lua_version(NULL);
    +  g->version = NULL;
       g->gcstate = GCSpause;
    @@ -308,4 +312,2 @@
       }
    -  else
    -    luai_userstateopen(L);
       return L;
    @@ -317,3 +319,2 @@
       lua_lock(L);
    -  luai_userstateclose(L);
       close_state(L);
    
  8. 恢复正在运行的协程会使其不可让出。
    由 Florian Nücke 于 2013 年 10 月 28 日报告。自 5.2.0 版本就存在。已在 5.2.3 版本中修复。

    示例

    -- should print 'true'
    print(coroutine.resume(coroutine.create(function()
      coroutine.resume(coroutine.running())
      coroutine.yield()
    end)))
    

    补丁

    ldo.c:
    @@ -536,2 +536,3 @@
       int status;
    +  int oldnny = L->nny;  /* save 'nny' */
       lua_lock(L);
    @@ -557,3 +558,3 @@
       }
    -  L->nny = 1;  /* do not allow yields */
    +  L->nny = oldnny;  /* restore 'nny' */
       L->nCcalls--;
    

Lua 5.2.1 1 · 2 · 3 · 4

  1. 由于递归,某些模式可能会使 C 栈溢出。
    由 Tim Starling 于 2012 年 7 月 8 日报告。自 2.5 版本就存在。已在 5.2.2 版本中修复。

    示例

    print(string.find(string.rep("a", 2^20), string.rep(".?", 2^20)))
    
  2. 在协程内部时,pcall 可能不会恢复之前的错误函数。
    由 Alexander Gavrilov 于 2012 年 6 月 12 日报告。自 5.2.0 版本就存在。已在 5.2.2 版本中修复。

    示例

    function errfunc(x)
      return 'errfunc'
    end
    
    function test(do_yield)
      print(do_yield and "yielding" or "not yielding")
      pcall(function() -- this pcall sets errfunc back to none
        if do_yield then
          coroutine.yield() -- stops errfunc from being restored
        end
      end)
      error('fail!')
    end
    
    coro = coroutine.wrap(function()
      print(xpcall(test, errfunc, false))
      print(xpcall(test, errfunc, true))
      print(xpcall(test, errfunc, false))
    end)
    
    coro()
    --> not yielding
    --> false	errfunc
    --> yielding
    coro()
    --> false	temp:12: fail!       <<<< should be 'errfunc' too
    --> not yielding
    --> false	errfunc
    

    补丁

    ldo.c:
    @@ -403,7 +403,11 @@
       int n;
       lua_assert(ci->u.c.k != NULL);  /* must have a continuation */
       lua_assert(L->nny == 0);
    -  /* finish 'lua_callk' */
    +  if (ci->callstatus & CIST_YPCALL) {  /* was inside a pcall? */
    +    ci->callstatus &= ~CIST_YPCALL;  /* finish 'lua_pcall' */
    +    L->errfunc = ci->u.c.old_errfunc;
    +  }
    +  /* finish 'lua_callk'/'lua_pcall' */
       adjustresults(L, ci->nresults);
       /* call continuation function */
       if (!(ci->callstatus & CIST_STAT))  /* no call status? */
    
  3. 函数调用中的垃圾回收器检查未覆盖所有路径。
    由 Roberto 于 2012 年 8 月 15 日报告。自 5.2.1 版本就存在。已在 5.2.2 版本中修复。

    示例:请参阅 https://lua-users.lua.ac.cn/lists/lua-l/2012-08/msg00149.html

    补丁

    ldo.c:
    @@ -311,6 +311,7 @@
           ci->top = L->top + LUA_MINSTACK;
           lua_assert(ci->top <= L->stack_last);
           ci->callstatus = 0;
    +      luaC_checkGC(L);  /* stack grow uses memory */
           if (L->hookmask & LUA_MASKCALL)
             luaD_hook(L, LUA_HOOKCALL, -1);
           lua_unlock(L);
    @@ -338,6 +339,7 @@
           ci->u.l.savedpc = p->code;  /* starting point */
           ci->callstatus = CIST_LUA;
           L->top = ci->top;
    +      luaC_checkGC(L);  /* stack grow uses memory */
           if (L->hookmask & LUA_MASKCALL)
             callhook(L, ci);
           return 0;
    @@ -393,7 +395,6 @@
         luaV_execute(L);  /* call it */
       if (!allowyield) L->nny--;
       L->nCcalls--;
    -  luaC_checkGC(L);
     }
    
  4. loadloadfile 在为没有上值的二进制块提供环境时返回错误结果。
    由 Vladimir Strakh 于 2012 年 11 月 28 日报告。自 5.2.0 版本就存在。已在 5.2.2 版本中修复。

    示例

    f = load(string.dump(function () return 1 end), nil, "b", {})
    print(type(f))   --> table	(should be function)
    

    补丁

    lbaselib.c:
    @@ -244,5 +244,11 @@
     
    -static int load_aux (lua_State *L, int status) {
    -  if (status == LUA_OK)
    +static int load_aux (lua_State *L, int status, int envidx) {
    +  if (status == LUA_OK) {
    +    if (envidx != 0) {  /* 'env' parameter? */
    +      lua_pushvalue(L, envidx);  /* environment for loaded function */
    +      if (!lua_setupvalue(L, -2, 1))  /* set it as 1st upvalue */
    +        lua_pop(L, 1);  /* remove 'env' if not used by previous call */
    +    }
         return 1;
    +  }
       else {
    @@ -258,9 +264,5 @@
       const char *mode = luaL_optstring(L, 2, NULL);
    -  int env = !lua_isnone(L, 3);  /* 'env' parameter? */
    +  int env = (!lua_isnone(L, 3) ? 3 : 0);  /* 'env' index or 0 if no 'env' */
       int status = luaL_loadfilex(L, fname, mode);
    -  if (status == LUA_OK && env) {  /* 'env' parameter? */
    -    lua_pushvalue(L, 3);
    -    lua_setupvalue(L, -2, 1);  /* set it as 1st upvalue of loaded chunk */
    -  }
    -  return load_aux(L, status);
    +  return load_aux(L, status, env);
     }
    @@ -309,5 +311,5 @@
       size_t l;
    -  int top = lua_gettop(L);
       const char *s = lua_tolstring(L, 1, &l);
       const char *mode = luaL_optstring(L, 3, "bt");
    +  int env = (!lua_isnone(L, 4) ? 4 : 0);  /* 'env' index or 0 if no 'env' */
       if (s != NULL) {  /* loading a string? */
    @@ -322,7 +324,3 @@
       }
    -  if (status == LUA_OK && top >= 4) {  /* is there an 'env' argument */
    -    lua_pushvalue(L, 4);  /* environment for loaded function */
    -    lua_setupvalue(L, -2, 1);  /* set it as 1st upvalue */
    -  }
    -  return load_aux(L, status);
    +  return load_aux(L, status, env);
     }
    

Lua 5.2.0 1 · 2 · 3 · 4 · 5 · 6

  1. 为协程创建 Lua 钩子时内存囤积。
    由 Arseny Vakhrushev 于 2012 年 1 月 16 日报告。自 5.1 版本就存在。已在 5.2.1 版本中修复。

    示例

    collectgarbage(); print(collectgarbage'count' * 1024)
    
    for i = 1, 100 do
      local co = coroutine.create(function () end)
      local x = {}
      for j=1,1000 do x[j] = j end
      debug.sethook(co, function () return x end, 'l')
    end
    
    collectgarbage(); print(collectgarbage'count' * 1024)
    -- value should back to near the original level
    

    补丁

    ldblib.c:
    @@ -253,14 +253,15 @@
     }
     
     
    -#define gethooktable(L)	luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY);
    +#define gethooktable(L)	luaL_getsubtable(L, LUA_REGISTRYINDEX, HOOKKEY)
     
     
     static void hookf (lua_State *L, lua_Debug *ar) {
       static const char *const hooknames[] =
         {"call", "return", "line", "count", "tail call"};
       gethooktable(L);
    -  lua_rawgetp(L, -1, L);
    +  lua_pushthread(L);
    +  lua_rawget(L, -2);
       if (lua_isfunction(L, -1)) {
         lua_pushstring(L, hooknames[(int)ar->event]);
         if (ar->currentline >= 0)
    @@ -306,10 +307,15 @@
         count = luaL_optint(L, arg+3, 0);
         func = hookf; mask = makemask(smask, count);
       }
    -  gethooktable(L);
    +  if (gethooktable(L) == 0) {  /* creating hook table? */
    +    lua_pushstring(L, "k");
    +    lua_setfield(L, -2, "__mode");  /** hooktable.__mode = "k" */
    +    lua_pushvalue(L, -1);
    +    lua_setmetatable(L, -2);  /* setmetatable(hooktable) = hooktable */
    +  }
    +  lua_pushthread(L1); lua_xmove(L1, L, 1);
       lua_pushvalue(L, arg+1);
    -  lua_rawsetp(L, -2, L1);  /* set new hook */
    -  lua_pop(L, 1);  /* remove hook table */
    +  lua_rawset(L, -3);  /* set new hook */
       lua_sethook(L1, func, mask, count);  /* set hooks */
       return 0;
     }
    @@ -325,7 +331,8 @@
         lua_pushliteral(L, "external hook");
       else {
         gethooktable(L);
    -    lua_rawgetp(L, -1, L1);   /* get hook */
    +    lua_pushthread(L1); lua_xmove(L1, L, 1);
    +    lua_rawget(L, -2);   /* get hook */
         lua_remove(L, -2);  /* remove hook table */
       }
       lua_pushstring(L, unmakemask(mask, buff));
    
  2. 词法分析器在某些算术运算符和十六进制数的组合中会混淆。
    由 Alexandra Barros 于 2012 年 1 月 17 日报告。自 5.2.0 版本就存在。已在 5.2.1 版本中修复。

    示例

    print(0xE+1)
    

    补丁

    llex.c:
    @@ -223,12 +223,19 @@
    
     /* LUA_NUMBER */
     static void read_numeral (LexState *ls, SemInfo *seminfo) {
    +  const char *expo = "Ee";
    +  int first = ls->current;
       lua_assert(lisdigit(ls->current));
    -  do {
    -    save_and_next(ls);
    -    if (check_next(ls, "EePp"))  /* exponent part? */
    +  save_and_next(ls);
    +  if (first == '0' && check_next(ls, "Xx"))  /* hexadecimal? */
    +    expo = "Pp";
    +  for (;;) {
    +    if (check_next(ls, expo))  /* exponent part? */
           check_next(ls, "+-");  /* optional exponent sign */
    -  } while (lislalnum(ls->current) || ls->current == '.');
    +    if (lisxdigit(ls->current) || ls->current == '.')
    +      save_and_next(ls);
    +    else  break;
    +  }
       save(ls, '\0');
       buffreplace(ls, '.', ls->decpoint);  /* follow locale for decimal point */
       if (!buff2d(ls->buff, &seminfo->r))  /* format error? */
    
  3. 终结器可能会在动态库卸载后调用其中的函数。
    由 Josh Haberman 于 2012 年 4 月 8 日报告。自 5.1 版本就存在。已在 5.2.1 版本中修复。

    示例

    local u = setmetatable({}, {__gc = function () foo() end})
    local m = require 'mod'   -- 'mod' may be any dynamic library written in C
    foo = m.foo     -- 'foo' may be any function from 'mod'
    -- end program; it crashes
    

    补丁

    loadlib.c:
    95c95
    < #define LIBPREFIX	"LOADLIB: "
    ---
    > #define CLIBS		"_CLIBS"
    251,266c251,256
    < 
    < static void **ll_register (lua_State *L, const char *path) {
    <   void **plib;
    <   lua_pushfstring(L, "%s%s", LIBPREFIX, path);
    <   lua_gettable(L, LUA_REGISTRYINDEX);  /* check library in registry? */
    <   if (!lua_isnil(L, -1))  /* is there an entry? */
    <     plib = (void **)lua_touserdata(L, -1);
    <   else {  /* no entry yet; create one */
    <     lua_pop(L, 1);  /* remove result from gettable */
    <     plib = (void **)lua_newuserdata(L, sizeof(const void *));
    <     *plib = NULL;
    <     luaL_setmetatable(L, "_LOADLIB");
    <     lua_pushfstring(L, "%s%s", LIBPREFIX, path);
    <     lua_pushvalue(L, -2);
    <     lua_settable(L, LUA_REGISTRYINDEX);
    <   }
    ---
    > static void *ll_checkclib (lua_State *L, const char *path) {
    >   void *plib;
    >   lua_getfield(L, LUA_REGISTRYINDEX, CLIBS);
    >   lua_getfield(L, -1, path);
    >   plib = lua_touserdata(L, -1);  /* plib = CLIBS[path] */
    >   lua_pop(L, 2);  /* pop CLIBS table and 'plib' */
    270a261,270
    > static void ll_addtoclib (lua_State *L, const char *path, void *plib) {
    >   lua_getfield(L, LUA_REGISTRYINDEX, CLIBS);
    >   lua_pushlightuserdata(L, plib);
    >   lua_pushvalue(L, -1);
    >   lua_setfield(L, -3, path);  /* CLIBS[path] = plib */
    >   lua_rawseti(L, -2, luaL_len(L, -2) + 1);  /* CLIBS[#CLIBS + 1] = plib */
    >   lua_pop(L, 1);  /* pop CLIBS table */
    > }
    > 
    > 
    272,273c272,273
    < ** __gc tag method: calls library's `ll_unloadlib' function with the lib
    < ** handle
    ---
    > ** __gc tag method for CLIBS table: calls 'll_unloadlib' for all lib
    > ** handles in list CLIBS
    276,278c276,281
    <   void **lib = (void **)luaL_checkudata(L, 1, "_LOADLIB");
    <   if (*lib) ll_unloadlib(*lib);
    <   *lib = NULL;  /* mark library as closed */
    ---
    >   int n = luaL_len(L, 1);
    >   for (; n >= 1; n--) {  /* for each handle, in reverse order */
    >     lua_rawgeti(L, 1, n);  /* get handle CLIBS[n] */
    >     ll_unloadlib(lua_touserdata(L, -1));
    >     lua_pop(L, 1);  /* pop handle */
    >   }
    284,286c287,292
    <   void **reg = ll_register(L, path);
    <   if (*reg == NULL) *reg = ll_load(L, path, *sym == '*');
    <   if (*reg == NULL) return ERRLIB;  /* unable to load library */
    ---
    >   void *reg = ll_checkclib(L, path);  /* check loaded C libraries */
    >   if (reg == NULL) {  /* must load library? */
    >     reg = ll_load(L, path, *sym == '*');
    >     if (reg == NULL) return ERRLIB;  /* unable to load library */
    >     ll_addtoclib(L, path, reg);
    >   }
    292c298
    <     lua_CFunction f = ll_sym(L, *reg, sym);
    ---
    >     lua_CFunction f = ll_sym(L, reg, sym);
    675,676c681,683
    <   /* create new type _LOADLIB */
    <   luaL_newmetatable(L, "_LOADLIB");
    ---
    >   /* create table CLIBS to keep track of loaded C libraries */
    >   luaL_getsubtable(L, LUA_REGISTRYINDEX, CLIBS);
    >   lua_createtable(L, 0, 1);  /* metatable for CLIBS */
    678a686
    >   lua_setmetatable(L, -2);
    
  4. 协程中 nCcalls 的处理不正确。
    由 Alexander Gavrilov 于 2012 年 4 月 18 日报告。自 5.2.0 版本就存在。已在 5.2.1 版本中修复。

    示例

    coroutine.wrap(function()
      print(pcall(pcall,pcall,pcall,pcall,pcall,error,3))
    end)()
    

    补丁

    ldo.c:
    @@ -402,8 +402,6 @@
       int n;
       lua_assert(ci->u.c.k != NULL);  /* must have a continuation */
       lua_assert(L->nny == 0);
    -  /* finish 'luaD_call' */
    -  L->nCcalls--;
       /* finish 'lua_callk' */
       adjustresults(L, ci->nresults);
       /* call continuation function */
    @@ -513,7 +511,6 @@
             api_checknelems(L, n);
             firstArg = L->top - n;  /* yield results come from continuation */
           }
    -      L->nCcalls--;  /* finish 'luaD_call' */
           luaD_poscall(L, firstArg);  /* finish 'luaD_precall' */
         }
         unroll(L, NULL);
    
  5. 内部 Lua 值可能会通过调试 API 泄露。
    由 Dan Tull 于 2012 年 4 月 20 日报告。自 5.1 版本就存在。已在 5.2.1 版本中修复。

    示例

    local firsttime = true
    local function foo ()
      if firsttime then
        firsttime = false
        return "a = 1" 
      else
        for i = 1, 10 do
          print(debug.getlocal(2, i))
        end
      end
    end
    
    print(load(foo))   -- prints some lines and then crashes
    
  6. 从调试钩子让出时出现问题。
    由 Erik Cassel 于 2012 年 6 月 5 日报告。自 5.2.0 版本就存在。已在 5.2.1 版本中修复。

    示例:在 C 中,设置一个简单地让出的行钩子,然后调用任何 Lua 函数。您将得到一个无限循环的让出。

Lua 5.1.5 1 · 2

这是 Lua 5.1 的最后一个版本。此后报告的错误很可能已在 Lua 5.2 中修复。

  1. src/Makefile 中的注释不可移植。
    由 Lorenzo Donati 于 2012 年 3 月 9 日报告。自 5.1.5 版本就存在。已在 5.2.0 版本中修复。

    示例:此问题在某些 mingw 系统上编译 Lua 时发生;它似乎不影响其他系统。

    补丁

    src/Makefile:
    @@ -50,3 +50,3 @@
     $(LUA_A): $(CORE_O) $(LIB_O)
    -	$(AR) $@ $(CORE_O) $(LIB_O)	# DLL needs all object files
    +	$(AR) $@ $(CORE_O) $(LIB_O)
     	$(RANLIB) $@
    
  2. 加载文件时,Lua 可能会在读取器函数返回输入结束之后再次调用它。
    由 Chris Howie 于 2013 年 6 月 5 日报告。自 5.1 版本就存在。已在 5.2.0 版本中修复。

    示例

    load(function () print("called"); return nil end)
    --> called
    --> called             (should be called only once!)
    

    补丁

    lzio.h:
    @@ -59,6 +59,7 @@
       lua_Reader reader;
       void* data;			/* additional data */
       lua_State *L;			/* Lua state (for reader) */
    +  int eoz;			/* true if reader has no more data */
     };
    
    
    lzio.c:
    @@ -22,10 +22,14 @@
       size_t size;
       lua_State *L = z->L;
       const char *buff;
    +  if (z->eoz) return EOZ;
       lua_unlock(L);
       buff = z->reader(L, z->data, &size);
       lua_lock(L);
    -  if (buff == NULL || size == 0) return EOZ;
    +  if (buff == NULL || size == 0) {
    +    z->eoz = 1;  /* avoid calling reader function next time */
    +    return EOZ;
    +  }
       z->n = size - 1;
       z->p = buff;
       return char2int(*(z->p++));
    @@ -51,6 +55,7 @@
       z->data = data;
       z->n = 0;
       z->p = NULL;
    +  z->eoz = 0;
     }
    

Lua 5.1.4 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11

  1. 恶意制作的预编译代码可能会使 Lua 崩溃。
    由 Peter Cawley 于 2008 年 9 月 1 日报告。

    解决方案:为避免运行来自不受信任来源的预编译代码,如果流中的第一个字节是转义字符(十进制 27),则引发错误。

  2. 巧妙使用变长参数可能会创建返回过多参数并使 C 函数栈溢出的函数。
    由 Patrick Donnelly 于 2008 年 12 月 10 日报告。已在 5.1.5 版本中修复。

    示例

    function lunpack(i, ...)
      if i == 0 then
        return ...
      else
        return lunpack(i-1, 1, ...)
      end
    end
    
    现在,如果 C 调用带有巨大 n 的 lunpack(n),它的栈中可能会有过多值并混淆其栈索引。
  3. 某些特定布尔表达式的代码生成不正确。(另请参见 9
    由 Brian Kelley 于 2009 年 4 月 15 日报告。自 5.0 版本就存在。已在 5.1.5 版本中修复。

    示例

    print(((1 or false) and true) or false)   --> 1, but should be 'true'
    

    补丁:(部分解决方案;另请参见 9

    lcode.c:
    @@ -544,15 +544,18 @@
           pc = NO_JUMP;  /* always true; do nothing */
           break;
         }
    -    case VFALSE: {
    -      pc = luaK_jump(fs);  /* always jump */
    -      break;
    -    }
         case VJMP: {
           invertjump(fs, e);
           pc = e->u.s.info;
           break;
         }
    +    case VFALSE: {
    +      if (!hasjumps(e)) {
    +        pc = luaK_jump(fs);  /* always jump */
    +        break;
    +      }
    +      /* else go through */
    +    }
         default: {
           pc = jumponcond(fs, e, 0);
           break;
    @@ -572,14 +575,17 @@
           pc = NO_JUMP;  /* always false; do nothing */
           break;
         }
    -    case VTRUE: {
    -      pc = luaK_jump(fs);  /* always jump */
    -      break;
    -    }
         case VJMP: {
           pc = e->u.s.info;
           break;
         }
    +    case VTRUE: {
    +      if (!hasjumps(e)) {
    +        pc = luaK_jump(fs);  /* always jump */
    +        break;
    +      }
    +      /* else go through */
    +    }
         default: {
           pc = jumponcond(fs, e, 1);
           break;
    
  4. luaV_settable 可能会使对表的引用失效并尝试重新使用它。
    由 Mark Feldman 于 2009 年 6 月 27 日报告。自 5.0 版本就存在。已在 5.1.5 版本中修复。

    示例

    grandparent = {}
    grandparent.__newindex = function(s,_,_) print(s) end
    
    parent = {}
    parent.__newindex = parent
    setmetatable(parent, grandparent)
    
    child = setmetatable({}, parent)
    child.foo = 10      --> (crash on some machines)
    

    补丁

    lvm.c:
    @@ -133,6 +133,7 @@
     
     void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
       int loop;
    +  TValue temp;
       for (loop = 0; loop < MAXTAGLOOP; loop++) {
         const TValue *tm;
         if (ttistable(t)) {  /* `t' is a table? */
    @@ -152,7 +153,9 @@
           callTM(L, tm, t, key, val);
           return;
         }
    -    t = tm;  /* else repeat with `tm' */ 
    +    /* else repeat with `tm' */
    +    setobj(L, &temp, tm);  /* avoid pointing inside table (may rehash) */
    +    t = &temp;
       }
       luaG_runerror(L, "loop in settable");
     }
    
  5. debug.getfenv 不检查它是否有参数。
    由 Patrick Donnelly 于 2009 年 7 月 30 日报告。自 5.1 版本就存在。已在 5.1.5 版本中修复。

    示例

    debug.getfenv()   -- should raise an error
    

    补丁

    ldblib.c:
    @@ -45,6 +45,7 @@
     
     
     static int db_getfenv (lua_State *L) {
    +  luaL_checkany(L, 1);
       lua_getfenv(L, 1);
       return 1;
     }
    
  6. 垃圾回收器在解析期间可能会卡住,并避免正确调整字符串表的大小,使其列表增长过多并降低性能。
    由 Sean Conner 于 2009 年 11 月 10 日报告。自 5.1 版本就存在。已在 5.1.5 版本中修复。

    示例:请参阅 https://lua-users.lua.ac.cn/lists/lua-l/2009-11/msg00463.html

    补丁

    llex.c:
    @@ -118,8 +118,10 @@
       lua_State *L = ls->L;
       TString *ts = luaS_newlstr(L, str, l);
       TValue *o = luaH_setstr(L, ls->fs->h, ts);  /* entry for `str' */
    -  if (ttisnil(o))
    +  if (ttisnil(o)) {
         setbvalue(o, 1);  /* make sure `str' will not be collected */
    +    luaC_checkGC(L);
    +  }
       return ts;
     }
     
    
  7. 当缺少参数且格式字符串过长时,string.format 可能会将缓冲区作为参数。
    由 Roberto 于 2010 年 4 月 12 日报告。自 5.0 版本就存在。已在 5.1.5 版本中修复。

    示例

    x = string.rep("x", 10000) .. "%d"
    print(string.format(x))    -- gives wrong error message
    

    补丁

    lstrlib.c:
    @@ -754,6 +754,7 @@
     
     
     static int str_format (lua_State *L) {
    +  int top = lua_gettop(L);
       int arg = 1;
       size_t sfl;
       const char *strfrmt = luaL_checklstring(L, arg, &sfl);
    @@ -768,7 +769,8 @@
         else { /* format item */
           char form[MAX_FORMAT];  /* to store the format (`%...') */
           char buff[MAX_ITEM];  /* to store the formatted item */
    -      arg++;
    +      if (++arg > top)
    +        luaL_argerror(L, arg, "no value");
           strfrmt = scanformat(L, strfrmt, form);
           switch (*strfrmt++) {
             case 'c': {
    
  8. 如果第二次读取失败,io.read("*n","*n") 可能会返回垃圾值。
    由 Roberto 于 2010 年 4 月 12 日报告。自 5.0 版本就存在。已在 5.1.5 版本中修复。

    示例

    print(io.read("*n", "*n"))   --<< enter "10   hi"
    --> file (0x884420)	nil
    

    补丁

    liolib.c:
    @@ -276,7 +276,10 @@
         lua_pushnumber(L, d);
         return 1;
       }
    -  else return 0;  /* read fails */
    +  else {
    +    lua_pushnil(L);  /* "result" to be removed */
    +    return 0;  /* read fails */
    +  }
     }
     
     
    
  9. 某些特定布尔表达式的代码生成不正确。
    由 Thierry Van Elsuwe 于 2011 年 1 月 20 日报告。自 5.0 版本就存在。已在 5.1.5 版本中修复。

    示例

    print((('hi' or true) and true) or true)
    --> hi     (should be true)
    print(((nil and nil) or false) and true)
    --> nil    (should be false)
    

    补丁:(在 3 中的补丁之后应用)

    lcode.c:
    @@ -549,13 +549,6 @@
           pc = e->u.s.info;
           break;
         }
    -    case VFALSE: {
    -      if (!hasjumps(e)) {
    -        pc = luaK_jump(fs);  /* always jump */
    -        break;
    -      }
    -      /* else go through */
    -    }
         default: {
           pc = jumponcond(fs, e, 0);
           break;
    @@ -579,13 +572,6 @@
           pc = e->u.s.info;
           break;
         }
    -    case VTRUE: {
    -      if (!hasjumps(e)) {
    -        pc = luaK_jump(fs);  /* always jump */
    -        break;
    -      }
    -      /* else go through */
    -    }
         default: {
           pc = jumponcond(fs, e, 1);
           break;
    
  10. 如果元表是其自身的元表,则新索引元方法可能不起作用。
    由 Cuero Bugot 于 2011 年 8 月 9 日报告。自 5.1 版本就存在。已在 5.1.5 版本中修复。

    示例

    meta={}
    setmetatable(meta, meta)
    meta.__newindex = function(t, key, value) print("set") end
    o = setmetatable({}, meta)
    o.x = 10    -- should print 'set'
    

    补丁

    lvm.c:
    @@ -142,6 +142,7 @@
           if (!ttisnil(oldval) ||  /* result is no nil? */
               (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
             setobj2t(L, oldval, val);
    +        h->flags = 0;
             luaC_barriert(L, h, val);
             return;
           }
    
  11. 解析器在构建原型时可能会收集它。
    由 Ingo van Lil 于 2011 年 10 月 13 日报告。自 5.1.4 版本就存在(由补丁 5.1.4-6 引起)。已在 5.1.5 版本中修复。

    补丁

    lparser.c:
    @@ -374,9 +374,9 @@
       lua_assert(luaG_checkcode(f));
       lua_assert(fs->bl == NULL);
       ls->fs = fs->prev;
    -  L->top -= 2;  /* remove table and prototype from the stack */
       /* last token read was anchored in defunct function; must reanchor it */
       if (fs) anchor_token(ls);
    +  L->top -= 2;  /* remove table and prototype from the stack */
     }
     
     
    

Lua 5.1.3 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11 · 12

  1. LUAI_MAXCSTACK 必须小于 -LUA_REGISTRYINDEX
    由 Patrick Donnelly 于 2008 年 2 月 11 日报告。自 5.1.3 版本就存在。已在 5.1.4 版本中修复。

    示例

    j = 1e4
    co = coroutine.create(function()
           t = {}
           for i = 1, j do t[i] = i end
           return unpack(t)
    end)
    print(coroutine.resume(co))
    

    补丁

    luaconf.h:
    443c443,444
    < ** functions to consume unlimited stack space.
    ---
    > ** functions to consume unlimited stack space. (must be smaller than
    > ** -LUA_REGISTRYINDEX)
    445,446c446
    < #define LUAI_MCS_AUX  ((int)(INT_MAX / (4*sizeof(LUA_NUMBER))))
    < #define LUAI_MAXCSTACK        (LUAI_MCS_AUX > SHRT_MAX ? SHRT_MAX : LUAI_MCS_AUX)
    ---
    > #define LUAI_MAXCSTACK        8000
    
  2. coroutine.resume 在不确保栈大小的情况下压入元素。
    于 2008 年 2 月 11 日报告。自 5.0 版本就存在。已在 5.1.4 版本中修复。

    示例:此错误无法在没有内部断言的情况下检测。

    补丁

    lbaselib.c:
    @@ -526,7 +526,7 @@
       status = lua_resume(co, narg);
       if (status == 0 || status == LUA_YIELD) {
         int nres = lua_gettop(co);
    -    if (!lua_checkstack(L, nres))
    +    if (!lua_checkstack(L, nres + 1))
           luaL_error(L, "too many results to resume");
         lua_xmove(co, L, nres);  /* move yielded values */
         return nres;
    
  3. lua_checkstack 对于大 'size' 可能会有算术溢出。
    由 Patrick Donnelly 于 2008 年 2 月 12 日报告。自 5.0 版本就存在。已在 5.1.4 版本中修复。

    示例

    print(unpack({1,2,3}, 0, 2^31-3))
    

    补丁

    lapi.c:
    @@ -93,15 +93,14 @@
     
     
     LUA_API int lua_checkstack (lua_State *L, int size) {
    -  int res;
    +  int res = 1;
       lua_lock(L);
    -  if ((L->top - L->base + size) > LUAI_MAXCSTACK)
    +  if (size > LUAI_MAXCSTACK || (L->top - L->base + size) > LUAI_MAXCSTACK)
         res = 0;  /* stack overflow */
    -  else {
    +  else if (size > 0) {
         luaD_checkstack(L, size);
         if (L->ci->top < L->top + size)
           L->ci->top = L->top + size;
    -    res = 1;
       }
       lua_unlock(L);
       return res;
    
  4. 带有最大索引的 unpack 可能会因算术溢出而崩溃。
    由 Patrick Donnelly 于 2008 年 2 月 12 日报告。自 5.1 版本就存在。已在 5.1.4 版本中修复。

    示例

    print(unpack({1,2,3}, 2^31-1, 2^31-1))
    

    补丁

    lbaselib.c:
    @@ -344,10 +344,12 @@
       luaL_checktype(L, 1, LUA_TTABLE);
       i = luaL_optint(L, 2, 1);
       e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1));
    +  if (i > e) return 0;  /* empty range */
       n = e - i + 1;  /* number of elements */
    -  if (n <= 0) return 0;  /* empty range */
    -  luaL_checkstack(L, n, "table too big to unpack");
    -  for (; i<=e; i++)  /* push arg[i...e] */
    +  if (n <= 0 || !lua_checkstack(L, n))  /* n <= 0 means arith. overflow */
    +    return luaL_error(L, "too many results to unpack");
    +  lua_rawgeti(L, 1, i);  /* push arg[i] (avoiding overflow problems) */
    +  while (i++ < e)  /* push arg[i + 1...e] */
         lua_rawgeti(L, 1, i);
       return n;
     }
    
  5. 恶意制作的预编译代码可能会使 Lua 崩溃。
    由 Peter Cawley 于 2008 年 3 月 24 日报告。自 5.0 版本就存在。已在 5.1.4 版本中修复。

    示例

    a = string.dump(function()return;end)
    a = a:gsub(string.char(30,37,122,128), string.char(34,0,0), 1)
    loadstring(a)()
    

    补丁

    ldebug.c:
    @@ -275,12 +275,12 @@
     
     static int precheck (const Proto *pt) {
       check(pt->maxstacksize <= MAXSTACK);
    -  lua_assert(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize);
    -  lua_assert(!(pt->is_vararg & VARARG_NEEDSARG) ||
    +  check(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize);
    +  check(!(pt->is_vararg & VARARG_NEEDSARG) ||
                   (pt->is_vararg & VARARG_HASARG));
       check(pt->sizeupvalues <= pt->nups);
       check(pt->sizelineinfo == pt->sizecode || pt->sizelineinfo == 0);
    -  check(GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN);
    +  check(pt->sizecode > 0 && GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN);
       return 1;
     }
     
    @@ -363,7 +363,11 @@
         }
         switch (op) {
           case OP_LOADBOOL: {
    -        check(c == 0 || pc+2 < pt->sizecode);  /* check its jump */
    +        if (c == 1) {  /* does it jump? */
    +          check(pc+2 < pt->sizecode);  /* check its jump */
    +          check(GET_OPCODE(pt->code[pc+1]) != OP_SETLIST ||
    +                GETARG_C(pt->code[pc+1]) != 0);
    +        }
             break;
           }
           case OP_LOADNIL: {
    @@ -428,7 +432,10 @@
           }
           case OP_SETLIST: {
             if (b > 0) checkreg(pt, a + b);
    -        if (c == 0) pc++;
    +        if (c == 0) {
    +          pc++;
    +          check(pc < pt->sizecode - 1);
    +        }
             break;
           }
           case OP_CLOSURE: {
    
  6. 恶意制作的预编译代码可能会使 C 栈溢出。
    由 Greg Falcon 于 2008 年 3 月 25 日报告。自 5.0 版本就存在。已在 5.1.4 版本中修复。

    示例

    function crash(depth)
      local init = '\27\76\117\97\81\0\1\4\4\4\8\0\7\0\0\0\61\115\116' ..
                   '\100\105\110\0\1\0\0\0\1\0\0\0\0\0\0\2\2\0\0\0\36' ..
                   '\0\0\0\30\0\128\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0' ..
                   '\1\0\0\0\0\0\0\2'
      local mid = '\1\0\0\0\30\0\128\0\0\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0\0'
      local fin = '\0\0\0\0\0\0\0\2\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\2\0' ..
                  '\0\0\97\0\1\0\0\0\1\0\0\0\0\0\0\0'
      local lch = '\2\0\0\0\36\0\0\0\30\0\128\0\0\0\0\0\1\0\0\0\0\0\0' ..
                  '\0\1\0\0\0\1\0\0\0\0\0\0\2'
      local rch = '\0\0\0\0\0\0\0\2\0\0\0\1\0\0\0\1\0\0\0\1\0\0\0\2\0' ..
                  '\0\0\97\0\1\0\0\0\1'
      for i=1,depth do lch,rch = lch..lch,rch..rch end
      loadstring(init .. lch .. mid .. rch .. fin)
    end
    for i=1,25 do print(i); crash(i) end
    

    补丁

    lundump.c:
    @@ -161,7 +160,9 @@
     
     static Proto* LoadFunction(LoadState* S, TString* p)
     {
    - Proto* f=luaF_newproto(S->L);
    + Proto* f;
    + if (++S->L->nCcalls > LUAI_MAXCCALLS) error(S,"code too deep");
    + f=luaF_newproto(S->L);
      setptvalue2s(S->L,S->L->top,f); incr_top(S->L);
      f->source=LoadString(S); if (f->source==NULL) f->source=p;
      f->linedefined=LoadInt(S);
    @@ -175,6 +176,7 @@
      LoadDebug(S,f);
      IF (!luaG_checkcode(f), "bad code");
      S->L->top--;
    + S->L->nCcalls--;
      return f;
     }
    
  7. 代码验证器可能会拒绝(恶意制作的)正确代码。
    由 Greg Falcon 于 2008 年 3 月 26 日报告。自 5.0 版本就存在。已在 5.1.4 版本中修复。

    示例

    z={}
    for i=1,27290 do z[i]='1,' end
    z = 'if 1+1==2 then local a={' .. table.concat(z) .. '} end'
    func = loadstring(z)
    print(loadstring(string.dump(func)))
    

    补丁

    ldebug.c:
    @@ -346,9 +346,18 @@
               int dest = pc+1+b;
               check(0 <= dest && dest < pt->sizecode);
               if (dest > 0) {
    -            /* cannot jump to a setlist count */
    -            Instruction d = pt->code[dest-1];
    -            check(!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0));
    +            int j;
    +            /* check that it does not jump to a setlist count; this
    +               is tricky, because the count from a previous setlist may
    +               have the same value of an invalid setlist; so, we must
    +               go all the way back to the first of them (if any) */
    +            for (j = 0; j < dest; j++) {
    +              Instruction d = pt->code[dest-1-j];
    +              if (!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0)) break;
    +            }
    +            /* if 'j' is even, previous value is not a setlist (even if
    +               it looks like one) */
    +            check((j&1) == 0);
               }
             }
             break;
    
  8. 恶意制作的预编译代码可以将无效的布尔值注入 Lua 代码。
    由 Greg Falcon 于 2008 年 3 月 27 日报告。自 5.0 版本就存在。已在 5.1.4 版本中修复。

    示例

    maybe = string.dump(function() return ({[true]=true})[true] end)
    maybe = maybe:gsub('\1\1','\1\2')
    maybe = loadstring(maybe)()
    assert(type(maybe) == "boolean" and maybe ~= true and maybe ~= false)
    

    补丁

    lundump.c:
    @@ -115,7 +115,7 @@
            setnilvalue(o);
            break;
        case LUA_TBOOLEAN:
    -       setbvalue(o,LoadChar(S));
    +       setbvalue(o,LoadChar(S)!=0);
            break;
        case LUA_TNUMBER:
            setnvalue(o,LoadNumber(S));
    
  9. string.byte 在某些超出范围的负索引中会混淆。
    由 Mike Pall 于 2008 年 6 月 3 日报告。自 5.1 版本就存在。已在 5.1.4 版本中修复。

    示例

    print(string.byte("abc", -5))   --> 97   98   99   (should print nothing)
    

    补丁

    lstrlib.c:
    @@ -35,7 +35,8 @@
     
     static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) {
       /* relative string position: negative means back from end */
    -  return (pos>=0) ? pos : (ptrdiff_t)len+pos+1;
    +  if (pos < 0) pos += (ptrdiff_t)len + 1;
    +  return (pos >= 0) ? pos : 0;
     }
     
     
    
  10. 用户请求的垃圾回收步骤可能会永远循环。
    由 Makoto Hamanaka 于 2008 年 7 月 1 日报告。自 5.1 版本就存在。已在 5.1.4 版本中修复。

    示例

    collectgarbage("setpause", 100) -- small value
    collectgarbage("setstepmul", 2000) -- large value
    collectgarbage("step",0)
    

    补丁

    lapi.c:
    @@ -929,10 +929,13 @@
             g->GCthreshold = g->totalbytes - a;
           else
             g->GCthreshold = 0;
    -      while (g->GCthreshold <= g->totalbytes)
    +      while (g->GCthreshold <= g->totalbytes) {
             luaC_step(L);
    -      if (g->gcstate == GCSpause)  /* end of cycle? */
    -        res = 1;  /* signal it */
    +        if (g->gcstate == GCSpause) {  /* end of cycle? */
    +          res = 1;  /* signal it */
    +          break;
    +        }
    +      }
           break;
         }
         case LUA_GCSETPAUSE: {
    
  11. module 可能会更改 C 函数的环境。
    由 Peter Cawley 于 2008 年 7 月 16 日报告。自 5.1 版本就存在。已在 5.1.4 版本中修复。

    示例

    pcall(module, "xuxu")
    assert(debug.getfenv(pcall) == xuxu)
    

    补丁

    loadlib.c:
    @@ -506,8 +506,11 @@
     
     static void setfenv (lua_State *L) {
       lua_Debug ar;
    -  lua_getstack(L, 1, &ar);
    -  lua_getinfo(L, "f", &ar);
    +  if (lua_getstack(L, 1, &ar) == 0 ||
    +      lua_getinfo(L, "f", &ar) == 0 ||  /* get calling function */
    +      lua_iscfunction(L, -1))
    +    luaL_error(L, "function " LUA_QL("module")
    +                  " not called from a Lua function");
       lua_pushvalue(L, -2);
       lua_setfenv(L, -2);
       lua_pop(L, 1);
    
  12. 内部宏 svalue 不正确。
    由 Martijn van Buul 于 2008 年 8 月 4 日报告。自 5.1 版本就存在。已在 5.1.4 版本中修复。

    示例

    /* in luaconf.h */
    #define LUAI_USER_ALIGNMENT_T   union { char b[32]; }
    

    补丁

    lobject.h:
    @@ -210,3 +210,3 @@
     #define getstr(ts)	cast(const char *, (ts) + 1)
    -#define svalue(o)       getstr(tsvalue(o))
    +#define svalue(o)       getstr(rawtsvalue(o))
     
    

Lua 5.1.2 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11 · 12 · 13

  1. Lua 可能会关闭标准文件,然后 C 可能会使用这些文件。
    由 David Manura 于 2007 年 4 月 17 日报告。已在 5.1.3 版本中修复。
  2. -nil-true-false 生成的代码不正确。
    由 David Manura 和 Rici Lake 于 2007 年 4 月 29 日报告。自 5.1 版本就存在。已在 5.1.3 版本中修复。

    示例

    print(-nil)
    

    补丁

    lcode.c:
    @@ -699,7 +699,7 @@
       e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0;
       switch (op) {
         case OPR_MINUS: {
    -      if (e->k == VK)
    +      if (!isnumeral(e))
             luaK_exp2anyreg(fs, e);  /* cannot operate on non-numeric constants */
           codearith(fs, OP_UNM, e, &e2);
           break;
    
  3. 计数钩子可能在未设置的情况下被调用。
    由 Mike Pall 于 2007 年 5 月报告。已在 5.1.3 版本中修复。

    补丁

    lvm.c:
    @@ -61,11 +61,9 @@
       lu_byte mask = L->hookmask;
       const Instruction *oldpc = L->savedpc;
       L->savedpc = pc;
    -  if (mask > LUA_MASKLINE) {  /* instruction-hook set? */
    -    if (L->hookcount == 0) {
    -      resethookcount(L);
    -      luaD_callhook(L, LUA_HOOKCOUNT, -1);
    -    }
    +  if ((mask & LUA_MASKCOUNT) && L->hookcount == 0) {
    +    resethookcount(L);
    +    luaD_callhook(L, LUA_HOOKCOUNT, -1);
       }
       if (mask & LUA_MASKLINE) {
         Proto *p = ci_func(L->ci)->l.p;
    
  4. 递归协程可能会使 C 栈溢出。

    示例

    a = function(a) coroutine.wrap(a)(a) end
    a(a)
    

    补丁:'nCcalls' 计数器应该由所有线程共享。(也就是说,它应该在 'global_State' 结构中声明,而不是在 'lua_State' 中声明。)

  5. 某些连接中的错误消息不正确。
    由 Alex Davies 于 2007 年 5 月报告。自 5.1.2 版本就存在。已在 5.1.3 版本中修复。

    示例

    a = nil; a = (1)..a
    

    补丁

    ldebug.c:
    @@ -563,8 +563,8 @@
    
    
     void luaG_concaterror (lua_State *L, StkId p1, StkId p2) {
    -  if (ttisstring(p1)) p1 = p2;
    -  lua_assert(!ttisstring(p1));
    +  if (ttisstring(p1) || ttisnumber(p1)) p1 = p2;
    +  lua_assert(!ttisstring(p1) && !ttisnumber(p1));
       luaG_typeerror(L, p1, "concatenate");
     }
    
    
  6. 非常小的数字在哈希函数中都会冲突。(这只会导致性能问题;行为是正确的。)
    于 2007 年 4 月 18 日报告。自 Lua 5.0 版本就存在。已在 5.1.3 版本中修复。

    补丁

    ltable.c:
    87,88c87,88
    <   n += 1;  /* normalize number (avoid -0) */
    <   lua_assert(sizeof(a) <= sizeof(n));
    ---
    >   if (luai_numeq(n, 0))  /* avoid problems with -0 */
    >     return gnode(t, 0);
    
  7. 赋值中变量过多可能会导致 C 栈溢出。
    由 Mike Pall 于 2007 年 7 月 31 日报告。自 5.0 版本就存在。已在 5.1.3 版本中修复。

    示例

    $ ulimit -s 1024       # Reduce C stack to 1MB for quicker results
    $ lua -e 'local s = "a,"; for i=1,18 do s = s..s end print(loadstring("local a;"..s.."a=nil", ""))'
    

    补丁

    lparser.c:
    @@ -938,6 +938,8 @@
         primaryexp(ls, &nv.v);
         if (nv.v.k == VLOCAL)
           check_conflict(ls, lh, &nv.v);
    +    luaY_checklimit(ls->fs, nvars, LUAI_MAXCCALLS - ls->L->nCcalls,
    +                    "variable names");
         assignment(ls, &nv, nvars+1);
       }
       else {  /* assignment -> `=' explist1 */
    
  8. 通过 '-l' 选项加载的模块中的错误不显示回溯。
    由 David Manura 于 2007 年 8 月 25 日报告。自 5.1 版本就存在。已在 5.1.3 版本中修复。

    示例

    lua -ltemp    (assuming temp.lua has an error)
    

    补丁

    lua.c:
    @@ -144,7 +144,7 @@
     static int dolibrary (lua_State *L, const char *name) {
       lua_getglobal(L, "require");
       lua_pushstring(L, name);
    -  return report(L, lua_pcall(L, 1, 0, 0));
    +  return report(L, docall(L, 1, 1));
     }
    
  9. gsub 在没有第三个参数且主题很长时,错误调用可能会失控。
    由 Florian Berger 于 2007 年 10 月 26 日报告。自 5.1 版本就存在。已在 5.1.3 版本中修复。

    示例

    x = string.rep('a', 10000) .. string.rep('b', 10000)
    print(#string.gsub(x, 'b'))
    

    补丁

    lstrlib.c:
    @@ -631,6 +631,2 @@
         }
    -    default: {
    -      luaL_argerror(L, 3, "string/function/table expected"); 
    -      return;
    -    }
       }
    @@ -650,2 +646,3 @@
       const char *p = luaL_checkstring(L, 2);
    +  int  tr = lua_type(L, 3);
       int max_s = luaL_optint(L, 4, srcl+1);
    @@ -655,2 +652,5 @@
       luaL_Buffer b;
    +  luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING ||
    +                   tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3,
    +                      "string/function/table expected");
       luaL_buffinit(L, &b);
    
  10. 当给定超出范围的索引时,table.remove 会删除表的最后一个元素。
    由 Patrick Donnelly 于 2007 年 11 月 13 日报告。至少从 5.0 版本就存在。已在 5.1.3 版本中修复。

    示例

    a = {1,2,3}
    table.remove(a, 4)
    print(a[3])   --> nil   (should be 3)
    

    补丁

    ltablib.c:
    @@ -118,7 +118,8 @@
     static int tremove (lua_State *L) {
       int e = aux_getn(L, 1);
       int pos = luaL_optint(L, 2, e);
    -  if (e == 0) return 0;  /* table is `empty' */
    +  if (!(1 <= pos && pos <= e))  /* position is outside bounds? */
    +   return 0;  /* nothing to remove */
       luaL_setn(L, 1, e - 1);  /* t.n = n-1 */
       lua_rawgeti(L, 1, pos);  /* result = t[pos] */
       for ( ;pos&lt;e; pos++) {
    
  11. 如果对无效对象调用,lua_setfenv 可能会崩溃。
    由 Mike Pall 于 2007 年 11 月 28 日报告。自 5.1 版本就存在。已在 5.1.3 版本中修复。

    示例

    > debug.setfenv(3, {})
    

    补丁

    lapi.c:
    @@ -749,7 +749,7 @@
           res = 0;
           break;
       }
    -  luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1));
    +  if (res) luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1));
       L->top--;
       lua_unlock(L);
       return res;
    
  12. 当“消息”是协程时,独立解释器显示不正确的错误消息。
    由 Patrick Donnelly 于 2007 年 12 月 17 日报告。自 5.1 版本就存在。已在 5.1.3 版本中修复。

    示例

    > error(coroutine.create(function() end))
    

    补丁

    lua.c:
    @@ -74,6 +74,8 @@
     
     
     static int traceback (lua_State *L) {
    +  if (!lua_isstring(L, 1))  /* 'message' not a string? */
    +    return 1;  /* keep it intact */
       lua_getfield(L, LUA_GLOBALSINDEX, "debug");
       if (!lua_istable(L, -1)) {
         lua_pop(L, 1);
    
    
  13. debug.sethook/gethook 可能会使线程栈溢出。
    由 Ivko Stanilov 于 2008 年 1 月 4 日报告。自 5.1 版本就存在。已在 5.1.3 版本中修复。

    示例

    a = coroutine.create(function() yield() end)
    coroutine.resume(a)
    debug.sethook(a)      -- may overflow the stack of 'a'
    

    补丁

    ldblib.c:
    @@ -268,12 +268,11 @@
         count = luaL_optint(L, arg+3, 0);
         func = hookf; mask = makemask(smask, count);
       }
    -  gethooktable(L1);
    -  lua_pushlightuserdata(L1, L1);
    +  gethooktable(L);
    +  lua_pushlightuserdata(L, L1);
       lua_pushvalue(L, arg+1);
    -  lua_xmove(L, L1, 1);
    -  lua_rawset(L1, -3);  /* set new hook */
    -  lua_pop(L1, 1);  /* remove hook table */
    +  lua_rawset(L, -3);  /* set new hook */
    +  lua_pop(L, 1);  /* remove hook table */
       lua_sethook(L1, func, mask, count);  /* set hooks */
       return 0;
     }
    @@ -288,11 +287,10 @@
       if (hook != NULL && hook != hookf)  /* external hook? */
         lua_pushliteral(L, "external hook");
       else {
    -    gethooktable(L1);
    -    lua_pushlightuserdata(L1, L1);
    -    lua_rawget(L1, -2);   /* get hook */
    -    lua_remove(L1, -2);  /* remove hook table */
    -    lua_xmove(L1, L, 1);
    +    gethooktable(L);
    +    lua_pushlightuserdata(L, L1);
    +    lua_rawget(L, -2);   /* get hook */
    +    lua_remove(L, -2);  /* remove hook table */
       }
       lua_pushstring(L, unmakemask(mask, buff));
       lua_pushinteger(L, lua_gethookcount(L1));
    

Lua 5.1.1 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9

  1. 列表构造函数有错误的限制。
    由 Norman Ramsey 于 2006 年 6 月 5 日报告。自 Lua 5.1 版本就存在。已在 5.1.2 版本中修复。

    示例

    a = {}
    a[1] = "x={1"
    for i = 2, 2^20 do
      a[i] = 1
    end
    a[#a + 1] = "}"
    s = table.concat(a, ",")
    assert(loadstring(s))()
    print(#x)
    

    补丁

    lparser.c:
    @@ -489,7 +489,7 @@
    
     static void listfield (LexState *ls, struct ConsControl *cc) {
       expr(ls, &cc->v);
    -  luaY_checklimit(ls->fs, cc->na, MAXARG_Bx, "items in a constructor");
    +  luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor");
       cc->na++;
       cc->tostore++;
     }
    
  2. 在某些涉及闭包的情况下,错误消息不正确。
    由 Shmuel Zeigerman 于 2006 年 7 月 8 日报告。自 Lua 5.1 版本就存在。已在 5.1.2 版本中修复。

    示例

    local Var
    local function main()
      NoSuchName (function() Var=0 end)
    end
    main()
    
    错误消息是 attempt to call upvalue 'Var' (a nil value)。它应该是 attempt to call global 'NoSuchName' (a nil value)

    补丁

    ldebug.c:
    @@ -435,14 +435,16 @@
             break;
           }
           case OP_CLOSURE: {
    -        int nup;
    +        int nup, j;
             check(b < pt->sizep);
             nup = pt->p[b]->nups;
             check(pc + nup < pt->sizecode);
    -        for (; nup>0; nup--) {
    -          OpCode op1 = GET_OPCODE(pt->code[pc+nup]);
    +        for (j = 1; j <= nup; j++) {
    +          OpCode op1 = GET_OPCODE(pt->code[pc + j]);
               check(op1 == OP_GETUPVAL || op1 == OP_MOVE);
             }
    +        if (reg != NO_REG)  /* tracing? */
    +          pc += nup;  /* do not 'execute' these pseudo-instructions */
             break;
           }
           case OP_VARARG: {
    
  3. string.format("%") 可能会读取超出字符串。
    由 Roberto 于 2006 年 9 月报告。至少从 5.0 版本就存在。已在 5.1.2 版本中修复。

    示例

    print(string.format("%"))
    

    补丁

    lstrlib.c:
    @@ -723,7 +723,7 @@
    
     static const char *scanformat (lua_State *L, const char *strfrmt, char *form) {
       const char *p = strfrmt;
    -  while (strchr(FLAGS, *p)) p++;  /* skip flags */
    +  while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++;  /* skip flags */
       if ((size_t)(p - strfrmt) >= sizeof(FLAGS))
         luaL_error(L, "invalid format (repeated flags)");
       if (isdigit(uchar(*p))) p++;  /* skip width */
    
  4. 当结果为空字符串时,os.date 会抛出错误。
    由 Nick Gammon 于 2006 年 9 月 15 日报告。自 4.0 版本就存在。已在 5.1.2 版本中修复。

    示例

    print(os.date(""))
    

    补丁

    loslib.c:
    @@ -148,7 +148,18 @@
       else {
    -    char b[256];
    -    if (strftime(b, sizeof(b), s, stm))
    -      lua_pushstring(L, b);
    -    else
    -      return luaL_error(L, LUA_QL("date") " format too long");
    +    char cc[3];
    +    luaL_Buffer b;
    +    cc[0] = '%'; cc[2] = '\0';
    +    luaL_buffinit(L, &b);
    +    for (; *s; s++) {
    +      if (*s != '%' || *(s + 1) == '\0')  /* no conversion specifier? */
    +        luaL_addchar(&b, *s);
    +      else {
    +        size_t reslen;
    +        char buff[200];  /* should be big enough for any conversion result */
    +        cc[1] = *(++s);
    +        reslen = strftime(buff, sizeof(buff), cc, stm);
    +        luaL_addlstring(&b, buff, reslen);
    +      }
    +    }
    +    luaL_pushresult(&b);
       }
    
  5. setfenv 接受无效的第一个参数。
    由 Doug Rogers 于 2007 年 2 月 8 日报告。自 5.0 版本就存在。已在 5.1.2 版本中修复。

    示例

    setfenv(nil, {})   -- should throw an error
    

    补丁

    lbaselib.c:
    @@ -116,3 +116,3 @@
    
    -static void getfunc (lua_State *L) {
    +static void getfunc (lua_State *L, int opt) {
       if (lua_isfunction(L, 1)) lua_pushvalue(L, 1);
    @@ -120,3 +120,3 @@
         lua_Debug ar;
    -    int level = luaL_optint(L, 1, 1);
    +    int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1);
         luaL_argcheck(L, level >= 0, 1, "level must be non-negative");
    @@ -133,3 +133,3 @@
     static int luaB_getfenv (lua_State *L) {
    -  getfunc(L);
    +  getfunc(L, 1);
       if (lua_iscfunction(L, -1))  /* is a C function? */
    @@ -144,3 +144,3 @@
       luaL_checktype(L, 2, LUA_TTABLE);
    -  getfunc(L);
    +  getfunc(L, 0);
       lua_pushvalue(L, 2);
    
  6. 在某些特定场景下,算术表达式的代码生成不正确。
    由 Thierry Grellier 于 2007 年 1 月 19 日报告。自 5.1 版本就存在。已在 5.1.2 版本中修复。

    示例

    -- use a large number of names (almost 256)
    v1=1; v2=1; v3=1; v4=1; v5=1; v6=1; v7=1; v8=1; v9=1;
    v10=1; v11=1; v12=1; v13=1; v14=1; v15=1; v16=1; v17=1;
    v18=1; v19=1; v20=1; v21=1; v22=1; v23=1; v24=1; v25=1;
    v26=1; v27=1; v28=1; v29=1; v30=1; v31=1; v32=1; v33=1;
    v34=1; v35=1; v36=1; v37=1; v38=1; v39=1; v40=1; v41=1;
    v42=1; v43=1; v44=1; v45=1; v46=1; v47=1; v48=1; v49=1;
    v50=1; v51=1; v52=1; v53=1; v54=1; v55=1; v56=1; v57=1;
    v58=1; v59=1; v60=1; v61=1; v62=1; v63=1; v64=1; v65=1;
    v66=1; v67=1; v68=1; v69=1; v70=1; v71=1; v72=1; v73=1;
    v74=1; v75=1; v76=1; v77=1; v78=1; v79=1; v80=1; v81=1;
    v82=1; v83=1; v84=1; v85=1; v86=1; v87=1; v88=1; v89=1;
    v90=1; v91=1; v92=1; v93=1; v94=1; v95=1; v96=1; v97=1;
    v98=1; v99=1; v100=1; v101=1; v102=1; v103=1; v104=1; v105=1;
    v106=1; v107=1; v108=1; v109=1; v110=1; v111=1; v112=1; v113=1;
    v114=1; v115=1; v116=1; v117=1; v118=1; v119=1; v120=1; v121=1;
    v122=1; v123=1; v124=1; v125=1; v126=1; v127=1; v128=1; v129=1;
    v130=1; v131=1; v132=1; v133=1; v134=1; v135=1; v136=1; v137=1;
    v138=1; v139=1; v140=1; v141=1; v142=1; v143=1; v144=1; v145=1;
    v146=1; v147=1; v148=1; v149=1; v150=1; v151=1; v152=1; v153=1;
    v154=1; v155=1; v156=1; v157=1; v158=1; v159=1; v160=1; v161=1;
    v162=1; v163=1; v164=1; v165=1; v166=1; v167=1; v168=1; v169=1;
    v170=1; v171=1; v172=1; v173=1; v174=1; v175=1; v176=1; v177=1;
    v178=1; v179=1; v180=1; v181=1; v182=1; v183=1; v184=1; v185=1;
    v186=1; v187=1; v188=1; v189=1; v190=1; v191=1; v192=1; v193=1;
    v194=1; v195=1; v196=1; v197=1; v198=1; v199=1; v200=1; v201=1;
    v202=1; v203=1; v204=1; v205=1; v206=1; v207=1; v208=1; v209=1;
    v210=1; v211=1; v212=1; v213=1; v214=1; v215=1; v216=1; v217=1;
    v218=1; v219=1; v220=1; v221=1; v222=1; v223=1; v224=1; v225=1;
    v226=1; v227=1; v228=1; v229=1; v230=1; v231=1; v232=1; v233=1;
    v234=1; v235=1; v236=1; v237=1; v238=1; v239=1; v240=1; v241=1;
    v242=1; v243=1; v244=1; v245=1; v246=1; v247=1; v248=1; v249=1;
    v250=1;
    v251={k1 = 1};
    v252=1;
    print(2 * v251.k1, v251.k1 * 2);   -- 2 2, OK
    v253=1;
    print(2 * v251.k1, v251.k1 * 2);   -- 1 2, ???
    

    补丁

    lcode.c:
    @@ -657,10 +657,16 @@
       if (constfolding(op, e1, e2))
         return;
       else {
    -    int o1 = luaK_exp2RK(fs, e1);
         int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0;
    -    freeexp(fs, e2);
    -    freeexp(fs, e1);
    +    int o1 = luaK_exp2RK(fs, e1);
    +    if (o1 > o2) {
    +      freeexp(fs, e1);
    +      freeexp(fs, e2);
    +    }
    +    else {
    +      freeexp(fs, e2);
    +      freeexp(fs, e1);
    +    }
         e1->u.s.info = luaK_codeABC(fs, op, 0, o1, o2);
         e1->k = VRELOCABLE;
       }
    @@ -718,10 +724,15 @@
           luaK_exp2nextreg(fs, v);  /* operand must be on the `stack' */
           break;
         }
    -    default: {
    +    case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV:
    +    case OPR_MOD: case OPR_POW: {
           if (!isnumeral(v)) luaK_exp2RK(fs, v);
           break;
         }
    +    default: {
    +      luaK_exp2RK(fs, v);
    +      break;
    +    }
       }
     }
    
  7. 将 nil 赋值给参数可能会被优化掉。
    由 Thomas Lauer 于 2007 年 3 月 21 日报告。自 5.1 版本就存在。已在 5.1.2 版本中修复。

    示例

    function f (a)
      a=nil
      return a
    end
    
    print(f("test"))
    

    补丁

    lcode.c:
    @@ -35,16 +35,20 @@
     void luaK_nil (FuncState *fs, int from, int n) {
       Instruction *previous;
       if (fs->pc > fs->lasttarget) {  /* no jumps to current position? */
    -    if (fs->pc == 0)  /* function start? */
    -      return;  /* positions are already clean */
    -    previous = &fs->f->code[fs->pc-1];
    -    if (GET_OPCODE(*previous) == OP_LOADNIL) {
    -      int pfrom = GETARG_A(*previous);
    -      int pto = GETARG_B(*previous);
    -      if (pfrom <= from && from <= pto+1) {  /* can connect both? */
    -        if (from+n-1 > pto)
    -          SETARG_B(*previous, from+n-1);
    -        return;
    +    if (fs->pc == 0) {  /* function start? */
    +      if (from >= fs->nactvar)
    +        return;  /* positions are already clean */
    +    }
    +    else {
    +      previous = &fs->f->code[fs->pc-1];
    +      if (GET_OPCODE(*previous) == OP_LOADNIL) {
    +        int pfrom = GETARG_A(*previous);
    +        int pto = GETARG_B(*previous);
    +        if (pfrom <= from && from <= pto+1) {  /* can connect both? */
    +          if (from+n-1 > pto)
    +            SETARG_B(*previous, from+n-1);
    +          return;
    +        }
           }
         }
       }
    
  8. 连接元方法将数字转换为字符串。
    由 Paul Winwood 于 2006 年 12 月 24 日报告。自 5.0 版本就存在。已在 5.1.2 版本中修复。

    示例

    a = {}
    setmetatable(a, {__concat = function (a,b) print(type(a), type(b)) end})
    a = 4 .. a
    

    补丁

    lvm.c:
    @@ -281,10 +281,12 @@
       do {
         StkId top = L->base + last + 1;
         int n = 2;  /* number of elements handled in this pass (at least 2) */
    -    if (!tostring(L, top-2) || !tostring(L, top-1)) {
    +    if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) {
           if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT))
             luaG_concaterror(L, top-2, top-1);
    -    } else if (tsvalue(top-1)->len > 0) {  /* if len=0, do nothing */
    +    } else if (tsvalue(top-1)->len == 0)  /* second op is empty? */
    +      (void)tostring(L, top - 2);  /* result is first op (as string) */
    +    else {
           /* at least two string values; get as many as possible */
           size_t tl = tsvalue(top-1)->len;
           char *buffer;
    
  9. loadlib.c 是一个库,不应访问 Lua 内部(通过 lobject.h)。
    由 Jérôme Vuarand 于 2007 年 3 月 25 日报告。至少从 5.0 版本就存在。已在 5.1.2 版本中修复。

    示例:此错误对外部行为没有影响。

    补丁:在 loadlib.c 中,将所有出现的 luaO_pushfstring 更改为 lua_pushfstring

Lua 5.1 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10

  1. 在 16 位机器上,以及/或右操作数是数字常量的表达式可能会导致奇怪的值。
    由 Andreas Stenius 于 2006 年 3 月 15 日报告。已在 5.1.1 版本中修复。

    示例

    print(false or 0)   -- on 16-bit machines
    

    补丁

    lcode.c:
    @@ -731,17 +731,15 @@
         case OPR_AND: {
           lua_assert(e1->t == NO_JUMP);  /* list must be closed */
           luaK_dischargevars(fs, e2);
    -      luaK_concat(fs, &e1->f, e2->f);
    -      e1->k = e2->k; e1->u.s.info = e2->u.s.info;
    -      e1->u.s.aux = e2->u.s.aux; e1->t = e2->t;
    +      luaK_concat(fs, &e2->f, e1->f);
    +      *e1 = *e2;
           break;
         }
         case OPR_OR: {
           lua_assert(e1->f == NO_JUMP);  /* list must be closed */
           luaK_dischargevars(fs, e2);
    -      luaK_concat(fs, &e1->t, e2->t);
    -      e1->k = e2->k; e1->u.s.info = e2->u.s.info;
    -      e1->u.s.aux = e2->u.s.aux; e1->f = e2->f;
    +      luaK_concat(fs, &e2->t, e1->t);
    +      *e1 = *e2;
           break;
         }
    
  2. luaL_checkudata 可能会产生错误的错误消息。
    由 Greg Falcon 于 2006 年 3 月 21 日报告。已在 5.1.1 版本中修复。

    示例

    getmetatable(io.stdin).__gc()
    --> bad argument #1 to '__gc' (FILE* expected, got table)
    

    补丁

    lauxlib.c:
    @@ -123,11 +123,17 @@
    
     LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) {
       void *p = lua_touserdata(L, ud);
    -  lua_getfield(L, LUA_REGISTRYINDEX, tname);  /* get correct metatable */
    -  if (p == NULL || !lua_getmetatable(L, ud) || !lua_rawequal(L, -1, -2))
    -    luaL_typerror(L, ud, tname);
    -  lua_pop(L, 2);  /* remove both metatables */
    -  return p;
    +  if (p != NULL) {  /* value is a userdata? */
    +    if (lua_getmetatable(L, ud)) {  /* does it have a metatable? */
    +      lua_getfield(L, LUA_REGISTRYINDEX, tname);  /* get correct metatable */
    +      if (lua_rawequal(L, -1, -2)) {  /* does it have the correct mt? */
    +        lua_pop(L, 2);  /* remove both metatables */
    +        return p;
    +      }
    +    }
    +  }
    +  luaL_typerror(L, ud, tname);  /* else error */
    +  return NULL;  /* to avoid warnings */
     }
    
  3. 同时使用 Lua 和 DirectX 的 Windows 应用程序可能会出现异常行为。这不是 Lua 中的错误! 问题在于 DirectX 违反了 Lua 所依赖的 ABI。

    补丁:最简单的解决方案是使用带有 D3DCREATE_FPU_PRESERVE 标志的 DirectX。否则,您可以将 luaconf.h 中 lua_number2int 的定义更改为以下内容

    #define lua_number2int(i,d)   __asm fld d   __asm fistp i
    
  4. string.formatE 中的选项 '%q' 未正确处理 '\r'。
    由 FleetCommand 于 2006 年 4 月 1 日报告。已在 5.1.1 版本中修复。

    示例

    local s = "a string with \r and \n and \r\n and \n\r"
    local c = string.format("return %q", s)
    assert(assert(loadstring(c))() == s)
    

    补丁

    lstrlib.c:
    @@ -703,6 +703,10 @@
             luaL_addchar(b, *s);
             break;
           }
    +      case '\r': {
    +        luaL_addlstring(b, "\\r", 2);
    +        break;
    +      }
           case '\0': {
             luaL_addlstring(b, "\\000", 4);
             break;
    
  5. luaL_dofileluaL_dostring 应返回块返回的所有值。
    由 mos 于 2006 年 4 月 11 日报告。已在 5.1.1 版本中修复。

    补丁

    lauxlib.h:
    @@ -108,9 +108,11 @@
    
     #define luaL_typename(L,i)     lua_typename(L, lua_type(L,(i)))
    
    -#define luaL_dofile(L, fn)     (luaL_loadfile(L, fn) || lua_pcall(L, 0, 0, 0))
    +#define luaL_dofile(L, fn) \
    +       (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
    
    -#define luaL_dostring(L, s)    (luaL_loadstring(L, s) || lua_pcall(L, 0, 0, 0))
    +#define luaL_dostring(L, s) \
    +       (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
    
     #define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
    
  6. 垃圾回收器对终结器补偿不足。
    由 Roberto 于 2006 年 5 月报告。已在 5.1.1 版本中修复。

    补丁

    lgc.c:
    @@ -322,4 +322,6 @@
    
    -static void propagateall (global_State *g) {
    -  while (g->gray) propagatemark(g);
    +static size_t propagateall (global_State *g) {
    +  size_t m = 0;
    +  while (g->gray) m += propagatemark(g);
    +  return m;
     }
    @@ -542,3 +544,3 @@
       marktmu(g);  /* mark `preserved' userdata */
    -  propagateall(g);  /* remark, to propagate `preserveness' */
    +  udsize += propagateall(g);  /* remark, to propagate `preserveness' */
       cleartable(g->weak);  /* remove collected objects from weak tables */
    @@ -592,2 +594,4 @@
             GCTM(L);
    +        if (g->estimate > GCFINALIZECOST)
    +          g->estimate -= GCFINALIZECOST;
    
  7. 调试钩子与协程混合时可能会出错。
    由 Ivko Stanilov 于 2006 年 6 月 3 日报告。已在 5.1.1 版本中修复。

    示例

    co = coroutine.create(function () coroutine.yield() end)
    debug.sethook(co, function() end, "lr")
    coroutine.resume(co)
    coroutine.resume(co)
    

    补丁

    ldo.c:
    @@ -389,6 +389,7 @@
           return;
       }
       else {  /* resuming from previous yield */
    +    L->status = 0;
         if (!f_isLua(ci)) {  /* `common' yield? */
           /* finish interrupted execution of `OP_CALL' */
           lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||
    @@ -399,7 +400,6 @@
         else  /* yielded inside a hook: just continue its execution */
           L->base = L->ci->base;
       }
    -  L->status = 0;
       luaV_execute(L, cast_int(L->ci - L->base_ci));
     }
    
  8. 列表构造函数有错误的限制。
  9. 在某些涉及闭包的情况下,错误消息不正确。
  10. 在某些特定场景下,算术表达式的代码生成不正确。

Lua 5.0.3

这是 Lua 5.0 的最后一个版本。此后报告的错误很可能已在 Lua 5.1 中修复。

Lua 5.0.2 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8

  1. 字符串连接可能会导致算术溢出,从而导致缓冲区溢出。
    由 Rici Lake 于 2004 年 5 月 20 日报告。已在 5.1 和 5.0.3 版本中修复。

    示例

    longs = string.rep("\0", 2^25)
    function catter(i)
        return assert(loadstring(
          string.format("return function(a) return a%s end",
                         string.rep("..a", i-1))))()
    end
    rep129 = catter(129)
    rep129(longs)
    

    补丁

    lvm.c:
    @@ -321,15 +321,15 @@
             luaG_concaterror(L, top-2, top-1);
         } else if (tsvalue(top-1)->tsv.len > 0) {  /* if len=0, do nothing */
           /* at least two string values; get as many as possible */
    -      lu_mem tl = cast(lu_mem, tsvalue(top-1)->tsv.len) +
    -                  cast(lu_mem, tsvalue(top-2)->tsv.len);
    +      size_t tl = tsvalue(top-1)->tsv.len;
           char *buffer;
           int i;
    -      while (n < total && tostring(L, top-n-1)) {  /* collect total length */
    -        tl += tsvalue(top-n-1)->tsv.len;
    -        n++;
    +      /* collect total length */
    +      for (n = 1; n < total && tostring(L, top-n-1); n++) {
    +        size_t l = tsvalue(top-n-1)->tsv.len;
    +        if (l >= MAX_SIZET - tl) luaG_runerror(L, "string length overflow");
    +        tl += l;
           }
    -      if (tl > MAX_SIZET) luaG_runerror(L, "string size overflow");
           buffer = luaZ_openspace(L, &G(L)->buff, tl);
           tl = 0;
           for (i=n; i>0; i--) {  /* concat all strings */
    
  2. lua_getupvaluelua_setupvalue 不检查索引是否过小。
    由 Mike Pall 于 2004 年 6 月 6 日报告。已在 5.1 和 5.0.3 版本中修复。

    示例

    debug.getupvalue(function() end, 0)
    

    补丁

    lapi.c
    941c941
    <     if (n > f->c.nupvalues) return NULL;
    ---
    >     if (!(1 <= n && n <= f->c.nupvalues)) return NULL;
    947c947
    <     if (n > p->sizeupvalues) return NULL;
    ---
    >     if (!(1 <= n && n <= p->sizeupvalues)) return NULL;
    
  3. 挂起线程的开放上值中持有的值可能会被错误地回收。
    由 Spencer Schumann 于 2004 年 12 月 31 日报告。已在 5.1 和 5.0.3 版本中修复。

    示例

    local thread_id = 0
    local threads = {}
    
    function fn(thread)
        thread_id = thread_id + 1
        threads[thread_id] = function()
                                 thread = nil
                             end
        coroutine.yield()
    end
    
    while true do
        local thread = coroutine.create(fn)
        coroutine.resume(thread, thread)
    end
    

    补丁

    lgc.c:
    221,224c221,222
    <       if (!u->marked) {
    <         markobject(st, &u->value);
    <         u->marked = 1;
    <       }
    ---
    >       markobject(st, u->v);
    >       u->marked = 1;
    
  4. rawsetrawget 不忽略额外的参数。
    由 Romulo Bahiense 于 2005 年 3 月 11 日报告。已在 5.1 和 5.0.3 版本中修复。

    示例

    a = {}
    rawset(a, 1, 2, 3)
    print(a[1], a[2])    -- should be 2 and nil
    

    补丁

    lbaselib.c:
    175a176
    >   lua_settop(L, 2);
    183a185
    >   lua_settop(L, 3);
    
  5. 经历一次回收后存活的弱表永远不会被回收。
    由 Chromix 于 2006 年 1 月 2 日报告。已在 5.1 和 5.0.3 版本中修复。

    示例

    a = {}
    print(gcinfo())
    for i = 1, 10000 do
      a[i] = setmetatable({}, {__mode = "v"})
    end
    collectgarbage()
    a = nil
    collectgarbage()
    print(gcinfo())
    

    补丁

    lgc.c
    @@ -366,7 +366,7 @@
       GCObject *curr;
       int count = 0;  /* number of collected items */
       while ((curr = *p) != NULL) {
    -    if (curr->gch.marked > limit) {
    +    if ((curr->gch.marked & ~(KEYWEAK | VALUEWEAK)) > limit) {
           unmark(curr);
           p = &curr->gch.next;
         }
    
  6. 垃圾回收器对终结器补偿不足。
  7. 某些“not not”表达式可能不会产生布尔值。
    由 Aaron Brown 于 2005 年 6 月 30 日报告。自 4.0 版本就存在。已在 5.0.3 版本中修复。

    示例

    -- should print false, but prints nil
    print(not not (nil and 4))
    
  8. 在某些机器上,关闭“管道文件”(使用 io.popen 创建)可能会使 Lua 崩溃。
    由 Mike Pall 于 2005 年 5 月 23 日报告。自 5.0 版本就存在。已在 5.0.3 版本中修复。

    示例

    f = io.popen("ls")
    f:close()
    

Lua 5.0 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · 10 · 11 · 12 · 13 · 14 · 15 · 16 · 17

  1. lua_closethread 仅存在于手册中。
    由 Nguyen Binh 于 2003 年 4 月 28 日报告。已在 5.0.2 版本中修复。

    解决方案:手册错误:线程受垃圾回收控制。

  2. 尝试恢复正在运行的协程会使 Lua 崩溃。
    由 Alex Bilyk 于 2003 年 5 月 9 日报告。已在 5.0.2 版本中修复。

    示例

    function co_func (current_co)
       coroutine.resume(co)
    end
    co = coroutine.create(co_func)
    coroutine.resume(co)
    coroutine.resume(co)     --> crash
    

    补丁

    ldo.c:
    325,326c325
    <     if (nargs >= L->top - L->base)
    <       luaG_runerror(L, "cannot resume dead coroutine");
    ---
    >     lua_assert(nargs < L->top - L->base);
    329c328,329
    <   else if (ci->state & CI_YIELD) {  /* inside a yield? */
    ---
    >   else {  /* inside a yield */
    >     lua_assert(ci->state & CI_YIELD);
    344,345d343
    <   else
    <     luaG_runerror(L, "cannot resume non-suspended coroutine");
    351a350,358
    > static int resume_error (lua_State *L, const char *msg) {
    >   L->top = L->ci->base;
    >   setsvalue2s(L->top, luaS_new(L, msg));
    >   incr_top(L);
    >   lua_unlock(L);
    >   return LUA_ERRRUN;
    > }
    >
    >
    355a363,368
    >   if (L->ci == L->base_ci) {
    >     if (nargs >= L->top - L->base)
    >       return resume_error(L, "cannot resume dead coroutine");
    >   }
    >   else if (!(L->ci->state & CI_YIELD))  /* not inside a yield? */
    >     return resume_error(L, "cannot resume non-suspended coroutine");
    
  3. file:close 不能在没有文件的情况下调用(会导致崩溃)。
    由 Tuomo Valkonen 于 2003 年 5 月 27 日报告。已在 5.0.2 版本中修复。

    示例

    > io.stdin.close()    -- correct call should be io.stdin:close()
    

    补丁

    liolib.c:
    161c161
    <   if (lua_isnone(L, 1)) {
    ---
    >   if (lua_isnone(L, 1) && lua_type(L, lua_upvalueindex(1)) == LUA_TTABLE) {
    
  4. C 函数的栈可能大于当前顶部。
    由 Alex Bilyk 于 2003 年 6 月 9 日报告。已在 5.0.2 版本中修复。

    示例:必须使用 lua.c 中的更改和定义的 lua_assert 重新编译 Lua

    lua.c:
    381a382
    >   lua_checkstack(l, 1000);
    

    补丁

    lgc.c:
    247c247
    <     if (!(ci->state & CI_C) && lim < ci->top)
    ---
    >     if (lim < ci->top)
    
  5. 当协程挂起时,'pc' 地址失效。
    由 Nick Trout 于 2003 年 7 月 7 日报告。已在 5.0.2 版本中修复。

    示例

    function g(x)
        coroutine.yield(x)
    end
    
    function f (i)
      debug.sethook(print, "l")
      for j=1,1000 do
        g(i+j)
      end
    end
    
    co = coroutine.wrap(f)
    co(10)
    pcall(co)
    pcall(co)
    

    补丁

    lvm.c:
    402d401
    <   L->ci->u.l.pc = &pc;
    405a405
    >   L->ci->u.l.pc = &pc;
    676,678c676
    <           lua_assert(ci->u.l.pc == &pc &&
    <                      ttisfunction(ci->base - 1) &&
    <                      (ci->state & CI_SAVEDPC));
    ---
    >           lua_assert(ttisfunction(ci->base - 1) && (ci->state & CI_SAVEDPC));
    
  6. 要回收的用户数据仍计入新的垃圾回收阈值,从而增加内存消耗。
    由 Roberto 于 2003 年 7 月 25 日报告。已在 5.0.2 版本中修复。

    示例

    a = newproxy(true)
    getmetatable(a).__gc = function () end
    for i=1,10000000 do
      newproxy(a)
      if math.mod(i, 10000) == 0 then print(gcinfo()) end
    end
    

    补丁

    lgc.h:
    18c18
    < void luaC_separateudata (lua_State *L);
    ---
    > size_t luaC_separateudata (lua_State *L);
    
    lgc.c:
    113c113,114
    < void luaC_separateudata (lua_State *L) {
    ---
    > size_t luaC_separateudata (lua_State *L) {
    >   size_t deadmem = 0;
    127a129
    >       deadmem += sizeudata(gcotou(curr)->uv.len);
    136a139
    >   return deadmem;
    390c393
    < static void checkSizes (lua_State *L) {
    ---
    > static void checkSizes (lua_State *L, size_t deadmem) {
    400c403
    <   G(L)->GCthreshold = 2*G(L)->nblocks;  /* new threshold */
    ---
    >   G(L)->GCthreshold = 2*G(L)->nblocks - deadmem;  /* new threshold */
    454c457,458
    < static void mark (lua_State *L) {
    ---
    > static size_t mark (lua_State *L) {
    >   size_t deadmem;
    467c471
    <   luaC_separateudata(L);  /* separate userdata to be preserved */
    ---
    >   deadmem = luaC_separateudata(L);  /* separate userdata to be preserved */
    475a480
    >   return deadmem;
    480c485
    <   mark(L);
    ---
    >   size_t deadmem = mark(L);
    482c487
    <   checkSizes(L);
    ---
    >   checkSizes(L, deadmem);
    
  7. IBM AS400 (OS400) 的 sizeof(void *)==16,并且 '%p' 在 'printf' 中可能会生成多达 60 个字符,导致 tostring 中的缓冲区溢出。
    由 David Burgess 于 2003 年 8 月 25 日报告。已在 5.0.2 版本中修复。

    示例

    print{}  -- on an AS400 machine
    

    补丁

    liolib.c:
    178c178
    <   char buff[32];
    ---
    >   char buff[128];
    
    lbaselib.c:
    327c327
    <   char buff[64];
    ---
    >   char buff[128];
    
  8. 语法 local function 不会增加栈大小。
    由 Rici Lake 于 2003 年 9 月 26 日报告。已在 5.0.2 版本中修复。

    示例

    -- must run this with precompiled code
    local a,b,c
    local function d () end
    

    补丁

    lparser.c:
    1143a1144
    >   FuncState *fs = ls->fs;
    1145c1146,1147
    <   init_exp(&v, VLOCAL, ls->fs->freereg++);
    ---
    >   init_exp(&v, VLOCAL, fs->freereg);
    >   luaK_reserveregs(fs, 1);
    1148c1150,1152
    <   luaK_storevar(ls->fs, &v, &b);
    ---
    >   luaK_storevar(fs, &v, &b);
    >   /* debug information will only see the variable after this point! */
    >   getlocvar(fs, fs->nactvar - 1).startpc = fs->pc;
    
  9. 计数钩子可能在未设置的情况下被调用。
    由 Andreas Stenius 于 2003 年 10 月 6 日报告。已在 5.0.2 版本中修复。

    示例
    按如下设置您的钩子。在未设置计数钩子的情况下使用正计数是很奇怪的,但没有错。

    lua_sethook(L, my_hook, LUA_MASKLINE | LUA_MASKRET, 1);
    

    补丁

    lvm.c:
    69c69
    <   if (mask > LUA_MASKLINE) {  /* instruction-hook set? */
    ---
    >   if (mask & LUA_MASKCOUNT) {  /* instruction-hook set? */
    
  10. dofile 在没有参数调用时吃掉一个返回值。
    由 Frederico Abraham 于 2004 年 1 月 15 日报告。已在 5.0.2 版本中修复。

    示例

    a,b = dofile()   --< here you enter `return 1,2,3 <eof>'
    print(a,b)   --> 2   3   (should be 1 and 2)
    

    补丁

    lbaselib.c:
    313a314
    >   int n = lua_gettop(L);
    317c318
    <   return lua_gettop(L) - 1;
    ---
    >   return lua_gettop(L) - n;
    
  11. 字符串连接可能会导致算术溢出,从而导致缓冲区溢出。
  12. lua_getupvaluelua_setupvalue 不检查索引是否过小。
  13. rawsetrawget 不忽略额外的参数。
  14. 经历一次回收后存活的弱表永远不会被回收。
  15. 垃圾回收器对终结器补偿不足。
  16. 某些“not not”表达式可能不会产生布尔值。
  17. 在某些机器上,关闭“管道文件”(使用 io.popen 创建)可能会使 Lua 崩溃。

Lua 4.0

Lua 4.0 中的大多数错误已在 Lua 4.0.1 及更高版本中修复。

  1. 解析器不接受 'return' 后面的 ';'。
    由 lhf 于 2000 年 11 月 29 日报告。已在 4.0.1 版本中修复。
  2. 当 'read' 失败时,它必须返回 nil(而不是不返回任何值)。
    由 Carlos Cassino 于 2000 年 12 月 22 日报告。已在 5.0 版本中修复。
  3. lua_pushuserdata(L,NULL) 不起作用。
    由 Edgar Toernig 于 2001 年 2 月 1 日报告。已在 4.0.1 版本中修复。
  4. while 1 dostring[[print('hello\n')]] end 从不回收内存。
    由 Andrew Paton 于 2001 年 2 月 2 日报告。已在 4.0.1 版本中修复。
  5. C 中的 ESC(开始预编译代码)是 \33,而不是 \27。
    由 Edgar Toernig 于 2001 年 2 月 6 日报告。已在 4.0.1 版本中修复。
  6. '%a' 的错误消息给出了错误的行号。
    由 Leonardo Constantino 于 2001 年 7 月 10 日报告。已在 4.0.1 版本中修复。
  7. rawget/rawset 获得额外参数时崩溃。
    由 Eric Mauger 于 2001 年 12 月 21 日报告。已在 4.0.1 版本中修复。
  8. 行钩子获得错误的 'ar'。
    由 Daniel Sinclair 于 2002 年 6 月 19 日报告。已在 4.0.1 版本中修复。
  9. 'protectedparser' 可能会运行垃圾回收器,然后回收 'filename'(在 'parse_file' 中)。
    由 Alex Bilyk 于 2002 年 6 月 19 日报告。已在 4.0.1 版本中修复。
  10. ULONG_MAX>>10 可能不适合 int
    由 Jeff Petkau 于 2002 年 11 月 21 日报告。已在 5.0 版本中修复。