lua-users home
lua-l archive

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


I have discovered that I can access a userdata from Lua after its
finalizer has run -- is this intended?  A test program at the bottom
of this email can demonstrate this on both Lua 5.2.1 and Lua 5.1.4.

I have always written __gc metamethods thinking that the value was
guaranteed to be inaccessible after __gc finishes.  The examples from
PIL (first edition) seem to assume this also, since they dereference
(without guards) pointers that are freed in __gc.  [0]

Looking at the reference manual, this is not explicitly promised, so
maybe it's just the responsibility of the __gc method to mark the object
destroyed, and for all other functions that operate on the userdata to
check that it is not already finalized?

As a related issue, it also appears that a value in a weak table is not
necessarily removed immediately after its finalizer is invoked.  This is
also demonstrated in the attached program.

I can work around all of this, of course, I was just wondering whether
this behavior is as intended, and if so if it can be documented more
explicitly (and the code examples in PIL fixed to indicate this).

Thanks,
Josh

[0] See dir_iter(): http://www.lua.org/pil/29.1.html  (Sorry I
couldn't also check the 2nd edition, I can't find my copy at the
moment).

--

#include "lauxlib.h"
#include <stdio.h>

#if LUA_VERSION_NUM == 501
#define setfuncs(L, l) luaL_register(L, NULL, l)
#define luaL_setmetatable(L, name) \
  luaL_getmetatable(L, name); \
  lua_setmetatable(L, -2)
#elif LUA_VERSION_NUM == 502
#define setfuncs(L, l) luaL_setfuncs(L, l, 0)
#endif

static const char *mytype = "mytype";

static int newobj(lua_State *L) {
  void *ud = lua_newuserdata(L, 0);
  printf("new userdata=%p\n", ud);
  luaL_setmetatable(L, mytype);
  return 1;
}

static int call(lua_State *L) {
  printf("__call userdata=%p\n", luaL_checkudata(L, 1, mytype));
  return 0;
}

static int gc(lua_State *L) {
  printf("__gc userdata=%p\n", luaL_checkudata(L, 1, mytype));
  return 0;
}

static const struct luaL_Reg mm[] = {
  {"__gc", gc},
  {"__call", call},
  {NULL, NULL}
};

int luaopen_ext(lua_State *L) {
  luaL_newmetatable(L, mytype);
  setfuncs(L, mm);
  lua_pushcfunction(L, &newobj);
  return 1;
}

--

local ext = require "ext"

if _VERSION >= 'Lua 5.2' then
  function defer(fn)
    setmetatable({}, { __gc = fn })
  end
else
  function defer(fn)
    getmetatable(newproxy(true)).__gc = fn
  end
end

local x
local y = {}
setmetatable(y, {__mode = "v"})
defer(function()
  x()
  y[1]()
end)
x = ext()
y[1] = ext()

--

Output:

new userdata=0x634c88
new userdata=0x634cb8
__gc userdata=0x634cb8
__gc userdata=0x634c88
__call userdata=0x634c88
__call userdata=0x634cb8