dark-panda / ffi-geos

ffi-geos is an implementation of the GEOS Ruby bindings in Ruby via FFI.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Objects can get owned more than once, resulting in segfaults

dazuma opened this issue · comments

0.0.1.beta2

It is possible to cause GEOS to think an object is owned by multiple entities, with the result that multiple entities eventually try to dispose the object, resulting in segmentation faults.

Example:

cs = ::Geos::CoordinateSequence.new(1, 2)
cs.set_x(0, 1)
cs.set_y(0, 2)
p = ::Geos::Utils.create_point(cs)
c1 = ::Geos::Utils.create_geometry_collection(p)
# Now c1 thinks it owns p. However...
c2 = ::Geos::Utils.create_geometry_collection(p)
# Now c2 also thinks it owns p. Both will eventually try to dispose the object, resulting in a segfault

Another way to trigger a segfault is to take the result of getting an "nth" geometry from a collection (which will be owned by that collection) and attempt to pass it to the constructor of another collection.

It may be necessary to check the status of ptr.autorelease (which should indicate that the object is owned by another entity) and prevent such objects from being given to others for ownership, either by raising exceptions or by automatically cloning. RGeo does the latter internally. However, either is probably acceptable for ffi-geos.

Oh, btw, I am running this on GEOS 3.2.2 (not 3.3.0) if that matters. I don't think it would, but in the interest of clarity...

commented

I think that just auto-cloning all the time in these situations would be okay. I've written a patch to do that and it passes some unit tests based on your example code without segfaulting. I think it would be acceptable just to go ahead and always clone rather than checking in on autorelease, but we can play with it a bit.

commented

Possible fix pushed in 5d043b3.

I've tested this fix with both the latest GEOS trunk as well as 3.2.2 and the segfaults are gone, so I think you should be good at least based on your example code. Other segfaults may exist elsewhere, of course, but that's another matter I guess, eh?

As I understand the patch, now we're auto-cloning every single time rather than only when needed. In other words, in

cs = ::Geos::CoordinateSequence.new(1, 2)
cs.set_x(0, 1)
cs.set_y(0, 2)
p1 = ::Geos::Utils.create_point(cs)
p2 = ::Geos::Utils.create_point(cs)

Both p1 and p2 contain clones of cs: there are now three copies of the coordinate sequence running around: the one in p1, the one in p2, and the original cs.

It seems that this is an extra cloning that we often (indeed, probably usually) don't need to do. If we only created p1, we would technically not need to clone at all. So I'm kind of ambivalent about this. Would it work better if it auto-cloned only if the pointer's autorelease is already set to false (which would indicate that another object has already claimed ownership)?

commented

I don't think autorelease works quite like that by the looks of things. autorelease on its own is always set to false for AutoPointers, although it delegates the value set to Releaser#autorelease= but calling autorelease? on an AutoPointer in ffi 1.0.8 always returns false, as autorelease? isn't delegated in a similar fashion. That being the case, we can't get to the value of autorelease without using stuff like instance_variable_get, so I'm sure how safe this would be.

The autorelease? method itself was only added in 1.0.8 and doesn't exist in previous versions, so not delegating to the releaser is an oversight or something, so I'll ask on the ffi list if that's the case. At the moment, cloning everything is just the easiest way to prevent segfaults, but if we can make things cleaner by relying on something like autorelease?, then that would be cool. To that end, I've written a patch that is a bit more selective about when it clones, but it relies upon using instance_variable_get in a way I'm not thrilled about. In the case of JRuby there doesn't appear to be an @autorelease instance variable set in the Releaser at all -- in fact, in the case of JRuby the internals are totally different and there are no Releaser classes at all, and @Releaser therefore isn't even set in AutoPointer.

Anyways, the patch I have at the moment works -- fewer clones are performed, but it's hackish with the use of instance_variable_get. It also gracefully ignores JRuby and makes always makes clones at the moment since I have no idea how to get the same effect in JRuby.

I see. I didn't realize there wasn't a reliable way to determine the autorelease status. Then maybe you're right: it's safer just to auto-clone every time.

Actually, now that I think more about this, it probably is best just to auto-clone and be done with it. I just remembered why I had RGeo auto-clone every time: it can't predict what a user will do with an object and how long the user will want to keep it once it is obtained. An example: get the "nth" geometry of a collection, and then let the collection object get GC'd. The "nth" geometry object is now invalid because its pointer was owned by the collection and has been disposed out from under it. Better to clone the result of GEOSGetGeometryN_r after it has been received. (I suppose you could alternatively have the child keep a reference to its owner, but I'm not completely convinced that solution is immune from memory leaks.)

commented

Now that we're cloning things consistently, we should be good here.