lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I found a bug of lua 5.3.4 in our project, but it's hardly to make a simple pure lua minimal reproducible example.
 so I use a C program to explain it.

```C
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdlib.h>
#include <lstring.h>

static void *
l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
if (nsize == 0) {
printf("free %p\n", ptr);
free(ptr);
return NULL;
} else {
return realloc(ptr, nsize);
}
}

static int
lpointer(lua_State *L) {
const char * str = luaL_checkstring(L, 1);
const TString *ts = (const TString *)str - 1;
lua_pushlightuserdata(L, (void *)ts);
return 1;
}

const char *source = "\n\
local a = setmetatable({} , { __mode = 'kv' })\n\
a['ABCDEFGHIJKLMNOPQRSTUVWXYZ' .. 'abcdefghijklmnopqrstuvwxyz' ] = {}\n\
print(pointer((next(a))))\n\
a[next(a)] = nil\n\
collectgarbage 'collect'\n\
local key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' .. 'abcdefghijklmnopqrstuvwxyz'\n\
print(pointer(key))\n\
a[key] = {}\n\
print(pointer((next(a))))\n\
";

int main() {
lua_State *L = lua_newstate (l_alloc, NULL);
luaL_openlibs(L);
lua_pushcfunction(L, lpointer);
lua_setglobal(L, "pointer");
luaL_dostring(L, source);

return 0;
}
```

...
userdata: 00000000006fedd0     <-   TString 1: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
free 00000000006FAB50     <- GC
free 00000000006FA890
free 00000000006FE940
free 00000000006FE910
free 0000000000000000
free 00000000006FA650
free 00000000006FEDD0      <-  Tstring 1 is free
free 00000000006FEBC0
free 0000000000000000
free 00000000006FAA50
free 00000000006F9770
userdata: 00000000006f1eb0   <-   TString 2: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
userdata: 00000000006fedd0   <-   TString 3 is the TString 1, and it has already free.

-----

The string "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" is collected by collectgarbage , but the TString * remains in the table 'a' because the value is nil. When we set a new key with the same string , luaH_set use the old slot with TString has already free.

This patch can fix this bug, but I think there is a better way.

diff --git a/src/lgc.c b/src/lgc.c
index ba2c19e..2d06ac5 100644
--- a/src/lgc.c
+++ b/src/lgc.c
@@ -641,7 +641,7 @@ static void clearkeys (global_State *g, GCObject *l, GCObjec     Table *h = gco2t(l);
     Node *n, *limit = gnodelast(h);
     for (n = gnode(h, 0); n < limit; n++) {
-      if (!ttisnil(gval(n)) && (iscleared(g, gkey(n)))) {
+      if (iscleared(g, gkey(n))) {
         setnilvalue(gval(n));  /* remove value ... */
         removeentry(n);  /* and remove entry from table */
       }