[1.1b2 BUG] Heap corruptions when exposing SimObjectPtr fields to script - LOGGED
by Manoel Neto · in Torque 3D Professional · 04/16/2010 (11:20 am) · 12 replies
I'm re-purposing this thread with the description of this issue in 1.1b2, and an updated fix.
In 1.1b2, the TypeSimObjectPtr field type is gone, and in its place is the TYPEID<> system, which is much improved in that it assigns unique type ids for each script-exposed class, making it easier to expose pointers to such objects as fields.
However, they will not work as one would expect if the member variable is a SimObjectPtr, instead of a plain pointer to the class type: the field system stores void pointers to the exposed data, and will cast them to pointers. The SimObjectPtr is a completely different beast: it contains a pointer to a Reference object, which in turn contains a pointer to the object. When a SimObjectPtr is assigned an object, it registers with that object so it's reference can be cleared when the object is deleted. So, casting the raw memory address of a SimObjectPtr<T> into a pointer to T and writing to it will cause memory corruption, which will lead to crashes in odd places.
This problem existed in previous versions, but was much more subtle because SimObjectPtr stored a pointer to the object directly and could be written to and read from as a raw pointer (things would only blow up when deleting the reference). Back then I came up with a specific field type for SimObjectPtr members, which did the correct type casting. Here's the fix that works for 1.1b2:
In console/consoleTypes.h, add this:
In console/consoleTypes.cpp, add this:
In console/engineStructs.h add this: (only for beta 2)
In console/engineStructs.cpp add this: (only for beta 2)
Done, all you need is to use TypeSimObjectSafePtr instead of TYPEID<SomeClass>() in beta 2 or TypeSimObjectPtr in beta 1 and earlier when exposing a SimObjectPtr field (don't use TypeSimObjectSafePtr for raw pointers!).
And here are the members I found which are SimObjectPtr types but are exposed using TYPEID<> or other unsuitable types in beta2:
In 1.1b2, the TypeSimObjectPtr field type is gone, and in its place is the TYPEID<> system, which is much improved in that it assigns unique type ids for each script-exposed class, making it easier to expose pointers to such objects as fields.
However, they will not work as one would expect if the member variable is a SimObjectPtr, instead of a plain pointer to the class type: the field system stores void pointers to the exposed data, and will cast them to pointers. The SimObjectPtr is a completely different beast: it contains a pointer to a Reference object, which in turn contains a pointer to the object. When a SimObjectPtr is assigned an object, it registers with that object so it's reference can be cleared when the object is deleted. So, casting the raw memory address of a SimObjectPtr<T> into a pointer to T and writing to it will cause memory corruption, which will lead to crashes in odd places.
This problem existed in previous versions, but was much more subtle because SimObjectPtr stored a pointer to the object directly and could be written to and read from as a raw pointer (things would only blow up when deleting the reference). Back then I came up with a specific field type for SimObjectPtr members, which did the correct type casting. Here's the fix that works for 1.1b2:
In console/consoleTypes.h, add this:
DefineConsoleType( TypeSimObjectSafePtr, SimObjectPtr<SimObject> )
In console/consoleTypes.cpp, add this:
ConsoleType( SimObjectPointer, TypeSimObjectSafePtr, SimObjectPtr<SimObject> )
ImplementConsoleTypeCasters( TypeSimObjectSafePtr, SimObjectPtr<SimObject> ) //beta2
ConsoleSetType( TypeSimObjectSafePtr )
{
if(argc == 1)
{
SimObjectPtr<SimObject> *obj = (SimObjectPtr<SimObject>*)dptr;
*obj = Sim::findObject(argv[0]);
}
else
Con::printf("(TypeSimObjectSafePtr) Cannot set multiple args to a single S32.");
}
ConsoleGetType( TypeSimObjectSafePtr )
{
SimObjectPtr<SimObject> *obj = (SimObjectPtr<SimObject>*)dptr;
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%s", *obj ? (*obj)->getName() ? (*obj)->getName() : (*obj)->getIdString() : "");
return returnBuffer;
}In console/engineStructs.h add this: (only for beta 2)
class SimObject; template< typename T > class SimObjectPtr; DECLARE_STRUCT( SimObjectPtr<SimObject> );
In console/engineStructs.cpp add this: (only for beta 2)
IMPLEMENT_STRUCT( SimObjectPtr<SimObject>, SimObjectPointer,, "" ) END_IMPLEMENT_STRUCT;
Done, all you need is to use TypeSimObjectSafePtr instead of TYPEID<SomeClass>() in beta 2 or TypeSimObjectPtr in beta 1 and earlier when exposing a SimObjectPtr field (don't use TypeSimObjectSafePtr for raw pointers!).
And here are the members I found which are SimObjectPtr types but are exposed using TYPEID<> or other unsuitable types in beta2:
RenderFormatToken::mCopyPostEffect RenderFormatToken::mResolvePostEffect ForcedMaterialMeshMgr::mOverrideMaterial RenderPassStateBin::mStateToken GuiControlProfile::mSoundButtonDown GuiControlProfile::mSoundButtonOver SFXSource::mDescription;
About the author
Recent Threads
#2
However, does it properly handle exposing SimObjectPtrs instead of raw pointers? The only class in found that exposes SimObjectPtr is the PostEffect, so it's worth a look.
08/21/2010 (2:02 pm)
I see that TypeSimObjectPtr is gone in beta2, replaced by a much better system for exposing "pointer to some class" fields.However, does it properly handle exposing SimObjectPtrs instead of raw pointers? The only class in found that exposes SimObjectPtr is the PostEffect, so it's worth a look.
#3
We need to come up with a better way of handling this.
08/22/2010 (7:20 pm)
Alright, I can confirm that exposing SimObjectPtr fields using TYPEID<> is still completely unsafe and will cause crashes when deleting the objects. The SimObjectPtr<T> is cast to T* and written on, completely bypassing the registration mechanism.We need to come up with a better way of handling this.
#4
As of 1.1 beta2, SimObjectPtr<T> are *NOT* interchangeable with T pointers! The pointer is stored deeper inside now, and exposing SimObjectPtr<T> using TYPEID<T> will read/write from/to the wrong memory location!
08/22/2010 (7:45 pm)
Actually, while debugging the numerous crashes I'm having right now, I have to change my assertion:As of 1.1 beta2, SimObjectPtr<T> are *NOT* interchangeable with T pointers! The pointer is stored deeper inside now, and exposing SimObjectPtr<T> using TYPEID<T> will read/write from/to the wrong memory location!
#5
BTW, I did a search for SimObjectPtrs currently exposed as fields. These are most likely to trash the heap as soon as used via script:
RenderFormatToken:
SimObjectPtr<PostEffect> mCopyPostEffect;
SimObjectPtr<PostEffect> mResolvePostEffect;
ForcedMaterialMeshMgr:
SimObjectPtr<Material> mOverrideMaterial;
RenderPassStateBin:
SimObjectPtr< RenderPassStateToken > mStateToken;
08/22/2010 (8:22 pm)
I think SimObjectPtr can be modified so it can be used in fields in the same way SimObjectRef is. I'll try doing it myself.BTW, I did a search for SimObjectPtrs currently exposed as fields. These are most likely to trash the heap as soon as used via script:
RenderFormatToken:
SimObjectPtr<PostEffect> mCopyPostEffect;
SimObjectPtr<PostEffect> mResolvePostEffect;
ForcedMaterialMeshMgr:
SimObjectPtr<Material> mOverrideMaterial;
RenderPassStateBin:
SimObjectPtr< RenderPassStateToken > mStateToken;
#6
GuiControlProfile:
SimObjectPtr< SFXTrack > mSoundButtonDown;
SimObjectPtr< SFXTrack > mSoundButtonOver;
SFXSource:
SimObjectPtr< SFXDescription > mDescription;
There may be more. I managed to get my old TypeSimObjectSafePtr working in beta2, and it seems to avoid the heap corruptions.
08/23/2010 (12:02 am)
Seems all random crashes I get are caused by this bug, which causes heap corruption. Just found some more SimObjectPtr fields, this time exposed via other types:GuiControlProfile:
SimObjectPtr< SFXTrack > mSoundButtonDown;
SimObjectPtr< SFXTrack > mSoundButtonOver;
SFXSource:
SimObjectPtr< SFXDescription > mDescription;
There may be more. I managed to get my old TypeSimObjectSafePtr working in beta2, and it seems to avoid the heap corruptions.
#7
08/24/2010 (7:37 pm)
I put the beta2 fix in the OP.
#8
What I mean is how rare is the issue.
08/24/2010 (7:56 pm)
Is this something I should implement if I am not having problems or should I wait for beta3 ?What I mean is how rare is the issue.
#9
08/24/2010 (8:11 pm)
If you don't use RenderFormatToken (HDR) and has no sounds in your buttons, you shouldn't have problems. Otherwise you might experience random crashes and whatnot.
#10
With the old SimObjectPtr, reading them like SimObject* was no problem. However, writing them as SimObject* was problem since the change notifications would not be updated and thus lead to crashes.
So, the use of TYPEID is correct here and a separate console type is not needed and will only complicate matters with the inspectors, engine documentation, etc.
The correct thing to do is to use protected getters and setters on all fields that use SimObjectPtr as their representation. RenderFormatToken::mCopyPostEffect already does that which is why it is not doing anything wrong.
However, as you found, a couple of fields (like the profile sound fields) don't do it correctly. These must be updated to use protected getters and setters.
08/24/2010 (10:49 pm)
Well, the problems here are due to a hack being used previously. Before b2, SimObjectPtrs were representation-compatible with SimObject*. Now they are not. However, just because they were representation-compatible didn't mean they could be used interchangeably.With the old SimObjectPtr, reading them like SimObject* was no problem. However, writing them as SimObject* was problem since the change notifications would not be updated and thus lead to crashes.
So, the use of TYPEID is correct here and a separate console type is not needed and will only complicate matters with the inspectors, engine documentation, etc.
The correct thing to do is to use protected getters and setters on all fields that use SimObjectPtr as their representation. RenderFormatToken::mCopyPostEffect already does that which is why it is not doing anything wrong.
However, as you found, a couple of fields (like the profile sound fields) don't do it correctly. These must be updated to use protected getters and setters.
#11
I completely missed the protected getter/setter in RenderFormatToken.
While the protected getter/setter solves the problem, it's not obvious and one can easily fall into it if uninformed. I've been thinking of ways of making addField() a bit safer and avoid the explicit call to Offset() (which is the true trap). However, I admit my own project makes much heavier use of SimObjectPtr fields than usual on T3D, since I have lots of objects cross-referencing each other which would be a nightmare to handle without smart pointers.
08/25/2010 (3:03 am)
Yeah, the old SimObjectPtr behavior had me pulling hairs for an entire day trying to find out why our game would randomly crash during exit. The new implementation makes stuff blow up right when trying to read the field for the first time, which made it much easier to pinpoint the offending fields.I completely missed the protected getter/setter in RenderFormatToken.
While the protected getter/setter solves the problem, it's not obvious and one can easily fall into it if uninformed. I've been thinking of ways of making addField() a bit safer and avoid the explicit call to Offset() (which is the true trap). However, I admit my own project makes much heavier use of SimObjectPtr fields than usual on T3D, since I have lots of objects cross-referencing each other which would be a nightmare to handle without smart pointers.
#12
Totally agree. It's an easy trap as it only becomes apparent when your application misbehaves.
The best thing is to package it all into one single macro that moves all the typechecking and stuff into the internals. This is what's happening with the new engine export stuff but there's nothing like that for the console system ATM.
08/25/2010 (3:22 am)
Quote:While the protected getter/setter solves the problem, it's not obvious and one can easily fall into it if uninformed.
Totally agree. It's an easy trap as it only becomes apparent when your application misbehaves.
Quote:I've been thinking of ways of making addField() a bit safer and avoid the explicit call to Offset() (which is the true trap).
The best thing is to package it all into one single macro that moves all the typechecking and stuff into the internals. This is what's happening with the new engine export stuff but there's nothing like that for the console system ATM.
Associate Scott Burns
GG Alumni