jrouwe / JoltPhysics

A multi core friendly rigid body physics and collision detection library. Written in C++. Suitable for games and VR applications. Used by Horizon Forbidden West.
MIT License
6.59k stars 429 forks source link

Should DecoratedShapeSettings allow for raw pointers in its constructor? #1036

Closed jdumas closed 6 months ago

jdumas commented 6 months ago

Consider the following modified HelloWorld.cpp example:

diff --git i/Source/HelloWorld.cpp w/Source/HelloWorld.cpp
index 72b6b55..eea0ccb 100644
--- i/Source/HelloWorld.cpp
+++ w/Source/HelloWorld.cpp
@@ -15,6 +15,7 @@
 #include <Jolt/Physics/PhysicsSystem.h>
 #include <Jolt/Physics/Collision/Shape/BoxShape.h>
 #include <Jolt/Physics/Collision/Shape/SphereShape.h>
+#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
 #include <Jolt/Physics/Body/BodyActivationListener.h>

@@ -294,8 +295,11 @@ int main(int argc, char** argv)
    // Note that for simple shapes (like boxes) you can also directly construct a BoxShape.
    BoxShapeSettings floor_shape_settings(Vec3(100.0f, 1.0f, 100.0f));

+   // Transform the shape
+   RotatedTranslatedShapeSettings transformed_shape_settings(Vec3::sZero(), Quat::sIdentity(), &floor_shape_settings);
+
    // Create the shape
-   ShapeSettings::ShapeResult floor_shape_result = floor_shape_settings.Create();
+   ShapeSettings::ShapeResult floor_shape_result = transformed_shape_settings.Create();
    ShapeRefC floor_shape = floor_shape_result.Get(); // We don't expect an error here, but you can check floor_shape_result for HasError() / GetError()

    // Create the settings for the body itself. Note that here you can also set other properties like the restitution / friction.

Running the program in Debug mode exits with the following:

HelloWorld(78064,0x1ec29bac0) malloc: *** error for object 0x16b86cb30: pointer being freed was not allocated
HelloWorld(78064,0x1ec29bac0) malloc: *** set a breakpoint in malloc_error_break to debug
fish: Job 1, './HelloWorld' terminated by signal SIGABRT (Abort)

I think the issue comes from the parent DecoratedShapeSettings object that takes a raw pointer as input: https://github.com/jrouwe/JoltPhysics/blob/4deaf12b3c12d5890d0cc0d55234fef54d63436b/Jolt/Physics/Collision/Shape/DecoratedShape.h#L20-L24

The function signature suggests that it is safe to pass a raw pointer to a stack-allocated BoxShapeSettings for example. But because the object becomes refcounted, destroying the RotatedTranslatedShapeSettings will attempt to destroy the previously stack-allocated BoxShapeSettings.

I don't know if this design is intentional. I would argue that the DecoratedShapeSettings should take as argument a RefConst<> to prevent this kind of situation. At the very least the HelloWorld.cpp example suggests that it's ok to stack-allocate shape setting but this can lead to this kind of tricky situation. Let me know what are your thoughts on this. Thanks!

jrouwe commented 6 months ago

If you allocate a refcounted object on the stack and take a reference, you need to call object.SetEmbedded() to signal the reference counting system that you're responsible for cleaning it up.

So:

BoxShapeSettings floor_shape_settings(Vec3(100.0f, 1.0f, 100.0f));

should be

BoxShapeSettings floor_shape_settings(Vec3(100.0f, 1.0f, 100.0f));
floor_shape_settings.SetEmbedded();

in this case. In the original HelloWorld example, nothing takes a ref to the BoxShapeSettings object so it's fine in that case to skip the call.

jdumas commented 6 months ago

I see, thanks for the tip! It might be worth mentioning this use-case in the HelloWorld example maybe? I feel it's an easy mistake to make since the HelloWorld example make it look like it's ok to have those objects be stack-allocated without doing anything special.

Closing this issue since you've answer the initial question.

jrouwe commented 6 months ago

Done.