[BUG]ui::Button Can't delete it self!

I’am using 3.1 version.
cn:我使用的是3.1版本。

If you create a Button, and use the callback function to remove itself(using removeChild method), game will crash.
cn:如果你创建一个BUTTON,使用它的按键回调函数删除用(removeChild)它,程序会立刻崩溃。

There is the problem, (at UIWidget.cpp)
void Widget::releaseUpEvent()
{
    // is ok here, execute callback function
    // cn:这里没问题,执行回调函数
    if (_touchEventCallback) {
        _touchEventCallback(this, TouchEventType::ENDED);
    }
    
    // here is the monster!!! after the callback function executed removeChild,the widget will delete himself in Instantly! that means in here, “this” is a nullptr! but the event remain releaseUpEvent() to execute. program will crash here! how dose that happen? I create Button using “create”, according to documents, which means is autorelease.
    // cn:怪物就在这里!在执行了回调函数removeChild之后,Button会立刻删除自己,这意味着,执行到这里的时候this已经是一个野指针了。但按键的事件还没执行完毕。程序在这里崩溃!这怎么会发生?我用的是create函数创建的,不是会在垃圾回收器中回收吗?
    if (_touchEventListener && _touchEventSelector)
    {
        (_touchEventListener->*_touchEventSelector)(this,TOUCH_EVENT_ENDED);
    }
}

SolutionsA: using an extra “retain()” and “release()” manually elsewhere, so, the Button can’t release it self after removeChild.
cn:解决方案A:额外手动使用retain()和release(),让button不能删除自己。

SolutionsB: change source code like( replace two part order, but I’am a rookie, I don’t known it’s consequences):
cn:解决方案B:将源代码修改如下(交换两个的执行顺序,但我不知道这样会有什么样的后果):

void Widget::releaseUpEvent()
{
    if (_touchEventListener && _touchEventSelector)
    {
        (_touchEventListener->*_touchEventSelector)(this,TOUCH_EVENT_ENDED);
    }

if (_touchEventCallback) {
        _touchEventCallback(this, TouchEventType::ENDED);
    }
}

Did 3.2 version has the BUG? I hope have better plan.
cn:3.2修复这个BUG没有?有更好的方案吗?

1 Like

@T_Master yes 3.2 version has the same problem. I have found your post because of that. My case is a bit different, but with the same roots. I have a layer and in that layer a button. When button is pressed I call this:

Button* closeButton = Button::create("close_icon.png", "", "", TextureResType::PLIST);
closeButton->addTouchEventListener([this](Ref* pSender, Widget::TouchEventType type)
{
    if (type == Widget::TouchEventType::ENDED)
    {
        Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(CLOSE_POPUP_EVENT);
        this->removeFromParentAndCleanup(true);
    }
});

So it is actually a close button that removes the layer from its parent and destroys as it is an auto-release layer. If I retain the layer, the destructor is not being called, hence, crash does not occur.

Also I don’t understand your Solution A. Could you please explain it more. My button is autorelease object. What should I do? Do you mean to retain it, then release the layer somewhere else after removing it from its parent?

Also tried Solution B, works great. Don’t know what will happen thought :smile:

this is a long long long ago bug…and not ok

@ha397666 is there a bug report and a workaround for this problem?

@T_Master In my opinion it would be completely safe to exchange the places the 2 ifs. I don’t think _touchEventCallback and _touchEventSelector callbacks will be used in any place simultaneously. But is should be considered that it is not a solution. Once the call of _touchEventSelector will release the object the same will happen in

if (_touchEventCallback) {
    _touchEventCallback(this, TouchEventType::ENDED);
}

block. I have opened a bug (feedback?) in issue tracker: http://www2.cocos2d-x.org/feedbacks/103