What is the best practice for mixing Objective-C ARC with longjmp
?
I am using Lua as scripting language, and my platform exports custom library for scripts. Entry points do check arguments with luaL_checkinteger(L, 2)
(among others), which, in turn, may call luaL_typerror(L, 2, ...)
, that is implemented in Lua with setjmp
/longjmp
. As far as I know, ARC simply auto-generates retain
/release
code, but what happens if it longjmp
s out of scope? Will this code leak on mistyped arguments?
static int
set_tag(lua_State *L)
{
NSControl *control = (__bridge NSControl *)lua_topointer(L, 1);
[control setTag:(NSInteger)luaL_checkinteger(L, 2)]; // may longjmp!
return 0;
}
In the snippet above, control
will be temporarily retained by ARC, but with longjmp
s uncatchable nature, corresponding release call may never happen. On the other hand, all arguments may be checked before assigning to control
variable.
static int
set_tag(lua_State *L)
{
NSInteger tag = luaL_checkinteger(L, 2); // may longjmp!
NSControl *control = (__bridge NSControl *)lua_topointer(L, 1);
[control setTag:tag];
return 0;
}
Does it resolve [potential] leak above? Are there better ways to do this?
UPDATE: longjmp
only unwinds to Lua internals, and never crosses any system code, except for Lua source (which is aware), and my entry points (which I hope are aware).
I'm pretty sure that second snippet does right, but I need kind of formal proof.
LATE UPDATE:
LuaJIT implements dwarf2-compatible errors, so they are just like C++ exceptions. Pass -fobjc-arc-exceptions
compiler flag to arc-enabled sources with Lua code and any retained object will be released on any lua_error
. Nothing to worry about now! You are still not allowed to throw errors across Cocoa runtime, though.
I recall that original Lua may be compiled with exceptions too, but I'm not sure.
Doesn't really matter if ARC is in use or not; any
setjmp
/longjmp
that jumps over any frame of code from the system frameworks will yield undefined behavior (for the same reason that exceptions cannot be used for recoverable error handling).So, yes, that code might leak. Might because it depends on whether the compiler emits a
retain
/release
in that block and where. Might also because whether the compiler emitsretain
/release
will be impacted by the optimization level and, over time, the version of the compiler.That is helpful. As long as you structure your entry points such that they never intermingle system scope with Lua jumpable scopes, you should be OK. I would recommend turning off ARC in the source files where you have to manage this (and, of course, put the ObjC->Lua interface into a nicely encapsulated bit of implementation so the rest of your code can be ARC clean).
Consider, though, that there is non-obvious risk:
The above would cause lua to "jump over" system code. Same goes for
enumerateWithBlock:
, KVO, Notification handlers, etc...You're going to have to think very very carefully about every potential stack trigger by a call from Lua into your code. If that call triggers any kind of automated behavior on the part of the system that could then call Lua API that could trigger a
longjmp
, all bets are off.