Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions librtt/Rtt_DisplayObjectExtensions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "Rtt_Lua.h"
#include "Rtt_LuaContext.h"
#include "Rtt_LuaLibPhysics.h"
#include "Rtt_LuaAux.h"
#include "Rtt_PhysicsWorld.h"
#include "Rtt_Runtime.h"

Expand All @@ -42,23 +43,34 @@ DisplayObjectExtensions::~DisplayObjectExtensions()
#ifdef Rtt_PHYSICS
if ( fBody )
{
GroupObject *parent = fOwner.GetParent();
if ( Rtt_VERIFY( parent ) )
// Invalidate UserdataWrappers for all joints attached to this body
// BEFORE clearing the body's UserData. When StepWorld later calls
// DestroyBody, Box2D will destroy these joints internally. Without
// pre-invalidation, the Lua GC could trigger PhysicsJoint::Finalizer
// on a dangling b2Joint pointer, causing use-after-free (SIGSEGV in
// b2Fixture::Destroy).
b2JointEdge *je = fBody->GetJointList();
while ( je )
{
fBody->SetUserData( NULL );

// Do NOT DestroyBody here. Instead, at end of StepWorld(), we lazily
// detect if the body's userdata is NULL. If it is, we know to destroy
// the body.
/*
Runtime *runtime = static_cast< Runtime* >( Rtt_AllocatorGetUserdata( parent->Allocator() ) );
b2World *world = runtime->GetWorld();
if ( Rtt_VERIFY( world ) )
b2Joint *joint = je->joint;
UserdataWrapper *wrapper = (UserdataWrapper *)joint->GetUserData();
if ( wrapper && UserdataWrapper::GetFinalizedValue() != wrapper )
{
world->DestroyBody( fBody );
wrapper->Invalidate();
}
*/
joint->SetUserData( NULL );
je = je->next;
}

// Always clear UserData to prevent StepWorld from dereferencing
// a freed DisplayObject. GetParent() returns NULL for objects
// with IsRenderedOffScreen (snapshot.group, canvas cache), which
// previously caused SetUserData(NULL) to be skipped.
fBody->SetUserData( NULL );

// Do NOT DestroyBody here. Instead, at end of StepWorld(), we lazily
// detect if the body's userdata is NULL. If it is, we know to destroy
// the body.
}
#endif // Rtt_PHYSICS
}
Expand Down
11 changes: 9 additions & 2 deletions librtt/Rtt_PhysicsJoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1428,8 +1428,15 @@ PhysicsJoint::Finalizer( lua_State *L )
b2Joint *joint = (b2Joint*)wrapper->Dereference();
if ( joint )
{
// Make sure joint no longer points to this wrapper that we're about to destroy
joint->SetUserData( NULL );
// Only clear userdata if the joint still points back to this wrapper.
// If it doesn't, the joint may have already been destroyed by
// DestroyBody/DestroyJoint (SayGoodbye already invalidated the wrapper),
// and the pointer could be dangling or reused memory.
void *userData = joint->GetUserData();
if ( userData == wrapper )
{
joint->SetUserData( NULL );
}
}

Rtt_DELETE( wrapper );
Expand Down
34 changes: 25 additions & 9 deletions librtt/Rtt_PhysicsWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,19 +181,19 @@ PhysicsWorld::StopWorld()
SetProperty( kIsWorldRunning, false );

fWorld->SetContactListener( NULL );
fWorld->SetDestructionListener( NULL );

// The b2World is about to destroy the block allocator that owns
// the memory for b2Body objects, so we have to pre-emptively
// iterate over bodies and detach from display object
const void *groundBodyUserdata = LuaLibPhysics::GetGroundBodyUserdata();

for ( b2Body *body = fWorld->GetBodyList(), *nextBody = NULL;
// Phase 1: Detach display objects from physics bodies.
// We do NOT call DestroyBody here — doing so during bulk shutdown
// causes use-after-free when DestroyBody(bodyA) calls DestroyJoint
// and modifies bodyB which may have already been freed, or when
// the block allocator reuses freed joint memory (zeroing the vtable).
for ( b2Body *body = fWorld->GetBodyList();
NULL != body;
body = nextBody )
body = body->GetNext() )
{
// Prefetch next body before we delete body
nextBody = body->GetNext();

if ( body->GetUserData() )
{
if ( body->GetUserData() != groundBodyUserdata )
Expand All @@ -202,10 +202,26 @@ PhysicsWorld::StopWorld()
o->RemoveExtensions();
}
}
}

fWorld->DestroyBody( body );
// Phase 2: Invalidate any remaining joint wrappers so that
// Lua GC finalizers won't access freed b2Joint memory later.
void *finalizedUserdata = UserdataWrapper::GetFinalizedValue();
for ( b2Joint *joint = fWorld->GetJointList();
NULL != joint;
joint = joint->GetNext() )
{
UserdataWrapper *wrapper = (UserdataWrapper *)joint->GetUserData();
if ( wrapper && finalizedUserdata != wrapper )
{
wrapper->Invalidate();
}
}

// Phase 3: Delete the world. ~b2World cleans up fixture shape
// allocations, and ~b2BlockAllocator bulk-frees all body/joint/
// fixture memory. This avoids the complex per-body DestroyBody →
// DestroyJoint chain that caused joint double-free crashes.
Rtt_DELETE( fWorld );
fWorld = NULL;

Expand Down