Unset object's metatable in __gc

Run the following code:

  do
      local d
      local f = function() d.visible = true end
      if _VERSION >= "Lua 5.2" then
          setmetatable({}, { __gc = f })
      else
          getmetatable(newproxy(true)).__gc = f
      end
      d = drawin({})
  end
  collectgarbage("collect")

Awesome will segfault.

The reason for this is that after the above code ran, all variables in it are
unreferenced and will be garbage-collected at the next sweep phase. Lua runs
garbage collectors in the inverse order that their corresponding objects were
"marked" which means for the above code that the drawin's garbage collector will
run before function f runs. So the code will access the drawin after its
destructor already ran. Obviously, awesome's C code does not expect nor
correctly deal with this situation and was dereferencing a NULL pointer.

To fix this, this commit "unsets" the metatable of a userdata object when it is
being garbage collected. Since the type of a userdata is inferred via its
metatable, the object will no longer be accepted by luaA_toudata().

For the above code this will result in an unhelpful error message saying that
something tried to index a userdata, but userdata cannot be indexed. At least we
no longer crash and the traceback of the error will hopefully point at some __gc
metamethod which should be enough of a hint to figure out the problem.

Thanks-to: http://blog.reverberate.org/2014/06/beware-of-lua-finalizers-in-c-modules.html
Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2015-06-20 12:21:52 +02:00
parent 4bea4554fb
commit 1408e8b952
1 changed files with 5 additions and 0 deletions

View File

@ -179,6 +179,11 @@ luaA_class_gc(lua_State *L)
for(; class; class = class->parent)
if(class->collector)
class->collector(item);
/* Unset its metatable so that e.g. luaA_toudata() will no longer accept
* this object. This is needed since other __gc methods can still use this.
*/
lua_newtable(L);
lua_setmetatable(L, 1);
return 0;
}