PopupMenu crashing issues

Hi there,

I am having an issue with using a PopupMenu to load presets from disk. The summary of the issue is as follows:

  • on the forum I’ve read that when using PopupMenu you should use the showAsync method if you are working inside a plugin (as I am).
  • calling showAsync works the first time and the preset state is loaded after the user selects from the popup menu.
  • the second time the menu is launched, the plugin crashes.
  • changing the code to use show makes it so the preset loading works all the time.

I’ve read that show() is inappropriate in this use case but it works where showAsync does not. What is going on here? Can someone help me figure out the cause of the crash? Also can anyone help my cartoon of the threading model here?

The relevant code is below. The PresetManager::Instance().loadPreset(number, processor) line calls the setStateInformation() method on the processor. If it is needed for reference, let me know and I will include that class as well.

Thanks!

class PresetDisplayButtonListener : public juce::Button::Listener {
    public:
        PresetDisplayButtonListener(TelegraphUIContentComponent& uiComp, TelegraphAudioProcessor& proc)
        :ui(uiComp)
        ,processor(proc)
        ,menu(std::make_unique<juce::PopupMenu>())
        {
            ui.getPresetDisplay()->addListener(this);
            ui.getPresetDisplay()->setButtonText(PresetFileManager::Instance().getCurrentPresetName());
        }
        void buttonClicked(Button* button) override {

            menu->clear();

            menu->addItem (0, "SELECT PRESET", false, false);
            menu->addSeparator();
            for(size_t prog_idx=0; prog_idx<static_cast<size_t>(PresetFileManager::Instance().getNumPresets()); prog_idx++){
                auto presetName = PresetFileManager::Instance().getPresetName(prog_idx);
                bool isCurrentPreset = presetName == PresetFileManager::Instance().getCurrentPresetName();
                menu->addItem (prog_idx+1, PresetFileManager::Instance().getPresetName(prog_idx), true, isCurrentPreset);
            }
            
            /*THIS CODE CRASHES ON SECOND POPUP OPEN*/
            // menu->showMenuAsync(
            //     juce::PopupMenu::Options()
            //         .withTargetComponent(button)
            //         .withMinimumWidth(button->getBounds().getWidth()), 
            //     popupCallback
            // );

            /* THIS CODE WORKS ??? */
            int result = menu->show();
            if(result>0){
                PresetFileManager::Instance().loadPreset(result-1, processor);
                ui.getPresetDisplay()->setButtonText(PresetFileManager::Instance().getCurrentPresetName());
            }

        }

    private:
        TelegraphUIContentComponent& ui;
        TelegraphAudioProcessor& processor;
        std::unique_ptr<juce::PopupMenu> menu;
        std::function<void (int)> popupCallback = std::function<void (int)>([&](int result){
            if(result>0){
                PresetFileManager::Instance().loadPreset(result-1, processor);
                ui.getPresetDisplay()->setButtonText(PresetFileManager::Instance().getCurrentPresetName());
                
            }
            
        });
};

What is the call stack when it crashes?

Running in the debugger should show you exactly which line of code is causing it, it’s likely something that has gone out of scope.

1 Like

Thanks for the reply @asimilon!

My current theory is that File IO is happening asynchronously from this listener callback and when the callback asks for the name of the current preset file, some action has happened on the file and we are left with a dangling pointer for the file. Does that make sense? How would you interpret the results?

Here is the result of me debugging the plugin host process:

Thread 1 "BitwigPluginHos" received signal SIGSEGV, Segmentation fault.
juce::CharPointer_UTF8::isEmpty (this=0x7ffcc84e9a20)
    at /home/xinniw/Development/synth/telegraph/libs/juce-6.0.7/juce/modules/juce_core/text/juce_CharPointer_UTF8.h:73
73          bool isEmpty() const noexcept                { return *data == 0; }
(gdb) backtrace 10
#0  juce::CharPointer_UTF8::isEmpty (this=0x7ffcc84e9a20)
    at /home/xinniw/Development/synth/telegraph/libs/juce-6.0.7/juce/modules/juce_core/text/juce_CharPointer_UTF8.h:73
#1  0x00007f7a8e24da65 in juce::String::lastIndexOfChar (this=0x7ffcc84e9a50, character=46 L'.')
    at /home/xinniw/Development/synth/telegraph/libs/juce-6.0.7/juce/modules/juce_core/text/juce_String.cpp:913
#2  0x00007f7a8e2300fa in juce::File::getFileNameWithoutExtension (this=0x7ffcc84e9a50)
    at /home/xinniw/Development/synth/telegraph/libs/juce-6.0.7/juce/modules/juce_core/files/juce_File.cpp:374
#3  0x00007f7a8e480299 in PresetFileManager::getCurrentPresetName (
    this=0x7f7a8e7c9440 <PresetFileManager::Instance()::instance>)
    at /home/xinniw/Development/synth/telegraph/src/telegraph-juce-shell/PresetFileManager.h:86
#4  0x00007f7a8e480787 in PresetDisplayButtonListener::popupCallback::{lambda(int)#1}::operator()(int) const (
    __closure=0xc87938, result=2)

For anyone else using PopupMenu or following along, note that I had to fix a few things to get debugging to work:
Recompiling with debug flags causes the following jassert to throw in PopupMenu:

void PopupMenu::addItem (Item newItem)
{
    // An ID of 0 is used as a return value to indicate that the user
    // didn't pick anything, so you shouldn't use it as the ID for an item..
    jassert (newItem.itemID != 0
              || newItem.isSeparator || newItem.isSectionHeader
              || newItem.subMenu != nullptr);

    items.add (std::move (newItem));
}

This is reasonable as I was inserting at element 0 which apparently I should not be doing. :slightly_smiling_face: I replaced that line in my original code with a call to addSectionHeader which is what I wanted to be doing in the first place if I had read the docs more carefully. :man_facepalming:

Thanks for your help,
David

I’m not particularly au fait with Linux debugging, but seems to me this:

is the start of where things go wrong in your code, I think it means line 86 of PresetFileManger.h. Something involving a juce::File, can’t suggest anything helpful beyond that without knowing exactly what that line is trying to do.

@asimilon You are correct! I was trying to hold on to a File reference that was managed by another JUCE object. It had gone out of scope.

I still don’t know why the synchronous version worked but it was likely just masking the real problem.

Thanks for your help!

David

1 Like