Releasing a node corrupting this ptr

Releasing a node corrupting this ptr
0
#1

Build the latest changes of my code on Windows - VS2017 I’m hitting an issue that I was sure wasn’t there before, and doesn’t manifest itself on OSX.

In the following example

panel_mainmenu is a cocos2d::Scene
bookmenu is essential a cocos2d:Node and is a child of panel_mainmenu

when the notification is triggered the sender is bookmenu.

I now remove this menu from its parent which is panel_mainmenu

The problem is, under windows, panel_mainmenu is ALSO released and in debug mode this becomes 0xdddddddd

How can the parent node be removed during a remove from parent. Surely the current scene is retained by the Director?

void panel_mainmenu::OnEndStory()
{
    bookmenu->setNotificationCallback ( [&](uinotificationinterface* sender, uieventargs* event) {
        
        uibookmenu* menu = static_cast<uibookmenu*>(sender);
        menu->removeFromParent();
        
        bookeventargs* event = static_cast<bookeventargs*>(event);
        if ( event != nullptr ) { // null == cancelled
            deleteStory( event->id );
        }
    });
}
#2

Anyone got any ideas?

#3

I thought I replied but maybe I didn’t. Can you show me where you use any retain(), autorelease() release() etc?

#4

So here is that function in full…

void panel_mainmenu::OnEndStory()
{
    auto bookmenu = uibookmenu::createWithStory( mr->stories->getStoriesInfo() );
    if ( bookmenu == nullptr )
        return;
    
    addChild(bookmenu,ZORDER_POPUP);

    bookmenu->setNotificationCallback ( [&](uinotificationinterface* s, uieventargs* e) {
        
        uibookmenu* menu = static_cast<uibookmenu*>(s);
        menu->removeFromParent();
		
        bookeventargs* event = static_cast<bookeventargs*>(e);
        if ( event != nullptr ) { // null == cancelled
            deleteStory( event->id );
        }
    });
}

And here is the create for uibookmenu

uibookmenu* uibookmenu::createWithStory( storyinfo_t* story )
{
    uibookmenu* node = new (std::nothrow) uibookmenu();
    if (node && node->initWithStory(story))
    {
        node->autorelease();
        return node;
    }
    CC_SAFE_DELETE(node);
    return nullptr;
}

And panel_mainmenu is a Scene and was initiated with… [less some ifs for checking different panels being shown and different possible transitions etc…]

    auto director = Director::getInstance();
  
    auto incoming = panel_mainmenu::create();

    director->replaceScene(TransitionCrossFade::create(defaultDuration, incoming); 
#5

Just throwing this out there, and I doubt this has anything to do with your issue, but the static_cast<> calls should be changed to dynamic_cast<>, since you’re casting from a base class type (uinotificationinterface) to a subclass, which you cannot guarantee will be correct, and given there is no type checking, you may end up with an invalid result.

If you were to cast from a subclass to a base class, then it would make sense to use a static_cast<>, since you know for certain that the cast is correct, and it would avoid unnecessary checks.

Now, with your actual issue, have you tried following the menu->removeFromParent() call, up to the point where it releases it from memory? Check the parent field of that menu node before the menu is released, specifically to see the value of the _referenceCount field. Add a conditional breakpoint at the start of the the Ref::release() method where it that checks for the pointer address of the panel_mainmenu and that the _referenceCount is equal to 1 (because if it’s 1 then as soon as that method is called it is deleted). It should trigger right at the point it is getting released, and you can trace it back via the callstack if this is the case to find the source of the problem.

#6

In this instance I can guaranty the casting, as the only thing calling that lambda notification is bookmenu. I could actually remove the cast and just use bookmenu directly by capturing it into the lambda.

this has a ref count of 2 before menu is removed. I stepped through but nothing was obvious. My issue is that if panel_menu is part of the current director then the only way it could be delete would be for it to be removed from the director. However removeFromParent wouldn’t do that surely? Even if the removefromparent meant the parent had no children the parent still shouldn’t get released during this call.

Remember it’s the child being released not the parent.

Also, I can only get this to go wrong on Windows, it’s fine on OSX.

#7

The removeFromParent() call on a child element should never remove the parent element, and looking over the code, it doesn’t even interact with the pointer to the parent.

I’m a little concerned that this may be a compiler bug, but I have never encountered such an error, besides mistakes I’ve made in my own code (same 0xDDDDDDDD pointer etc), and I’ve been using VS2017 since release (and now, VS2019). Any chance you could try VS2019 to see if you get the same result, since it uses an updated compiler (v142)? (big download… but it’s way more stable than VS2017, and a lot quicker too).

#8

I don’t fully understand how the issue is manifesting or where in your code, but I had a crash where I was popping a scene from director or something where a transition was still playing and things got weird, so I had to make sure the scene had completely finished the transition.

I haven’t personally seen any bugs with release/retain themselves since they’re pretty straightforward, but I have had bugs that manifested only on Android and not win32 because of compiler differences. You not seeing it on ios could just mean that compiler uses different values for unitinialized data somewhere, I dunno.

#9

Do you declare constructor and destructor:

    uibookmenu();
    ~uibookmenu();

in your class and implement them:

uibookmenu::uibookmenu() {};
uibookmenu::~uibookmenu() {};

same with all super and subclasses?

I noticed this as problematic if missing from a subclass of any cocos node.

( Also super OCD but PascalCase and camelCase used for classes and functions and variables respectively can make it easier to follow code :slight_smile: )

1 Like
#10

I created a new VM, installed a clean Windows10, Clean VS2019, Clean cocos, build the libs, rebuild the project… same result.

#11

Different people, different code styles… :slight_smile:

Anyway, I probably am not doing the constructors etc, so I will take a look at that.

1 Like
#12

So it appears to be Thread related. The removeFromParent needs to be on the UI thread under Windows but not un OSX. It’s not actually releasing the object but something in the lambda capture us getting corrupt. If I move removeFromParent to after the deleteStory block, it also works fine.

#13

Ah bingo. OSX might be calling the callback in the main thread. Would be hard for me to figure without knowing more about the setNotificationCallback callback or what invokes it…

When in another thread, just use:

cocos2d::Director::getInstance()->getScheduler()->performFunctionInCocosThread([=](){
        <do your stuff here>
    });

So for your case:

bookmenu->setNotificationCallback ( [&](uinotificationinterface* sender, uieventargs* event) {

    cocos2d::Director::getInstance()->getScheduler()->performFunctionInCocosThread([=](){
                uibookmenu* menu = static_cast<uibookmenu*>(sender);
                menu->removeFromParent();
            
                 bookeventargs* event = static_cast<bookeventargs*>(event);
                 if ( event != nullptr ) { // null == cancelled
                     deleteStory( event->id );
                 }
            });
#14

Cheers I already have it. I actually have a shortcut…
RUN_ON_UI_THREAD

1 Like