Wavelab 10 crash on quit inside VST3 wrapper

I have a customer who gets a crash on quitting Wavelab 10.0.30 on OS X if multiple vst3 instances of my plugin is inserted when doing a montage session. He contacted Steinberg about this and they blame my plugin for the problem (naturally). After a few attempts I managed to recreate the crash on my own machine using a debug build. Thankfully, Wavelab allows attaching the XCode debugger.

Then I switched to the Juce GainPlugin demo and without any changes to any code, the exact same crash happens. To reproduce, load two audio files into Wavelab, create a Montage, insert a juce-built vst3 plugin on both tracks. Then save the montage :). Now quit Wavelab and after a few second the crash happens (most of the time):

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   com.JUCE.GainPlugin           	0x000000015b8dfad8 juce::ComSmartPtr<Steinberg::Vst::IHostApplication>::~ComSmartPtr() + 40 (juce_VST3Common.h:401)
1   com.JUCE.GainPlugin           	0x000000015b8dfa35 juce::ComSmartPtr<Steinberg::Vst::IHostApplication>::~ComSmartPtr() + 21 (juce_VST3Common.h:401)
2   com.JUCE.GainPlugin           	0x000000015b8dfb5e juce::JucePluginFactory::~JucePluginFactory() + 94 (juce_VST3_Wrapper.cpp:2956)
3   com.JUCE.GainPlugin           	0x000000015b8df275 juce::JucePluginFactory::~JucePluginFactory() + 21 (juce_VST3_Wrapper.cpp:2956)
4   com.JUCE.GainPlugin           	0x000000015b8df299 juce::JucePluginFactory::~JucePluginFactory() + 25 (juce_VST3_Wrapper.cpp:2953)
5   com.JUCE.GainPlugin           	0x000000015b8de9f0 juce::JucePluginFactory::release() + 256 (juce_VST3_Wrapper.cpp:2976)
6   net.steinberg.WaveLab-Pro-10-0	0x000000010c8e9b1e 0x10af4e000 + 26852126
7   net.steinberg.WaveLab-Pro-10-0	0x000000010c8ec000 0x10af4e000 + 26861568
8   org.qt-project.QtCore         	0x000000010e159829 QHashData::free_helper(void (*)(QHashData::Node*)) + 73
9   net.steinberg.WaveLab-Pro-10-0	0x000000010c8eb90c 0x10af4e000 + 26859788
10  net.steinberg.WaveLab-Pro-10-0	0x000000010bc6a680 0x10af4e000 + 13747840
11  net.steinberg.WaveLab-Pro-10-0	0x000000010bc5fb64 0x10af4e000 + 13704036
12  net.steinberg.WaveLab-Pro-10-0	0x000000010cb14597 0x10af4e000 + 29123991
13  libdyld.dylib                 	0x00007fff7055c015 start + 1

My conclusion is that the problem is either in Juce or Wavelab - my feeling is that Wavelab is to blame as otherwise we’d see the same issue in more vst3 hosts. It’s also curious that this does not happen when inserting the same plugin on the master section.

Has anyone else seen that and maybe found a workaround?

I’m still looking into this using the unaltered GainPlugin example code. The crash happens when a member of the JucePluginFactory gets destroyed, namely

    ComSmartPtr<Vst::IHostApplication> host;

I don’t quite know how to go on debugging this. I tried stepping through the JucePluginFactory destruction in the XCode debugger and maybe the problem is related to getting two JucePluginFactory destructor calls in very quick succession (for two instances). I see the globalFactory static variable is already nullptr on the second call, but the ComSmartPtr<Vst::HostApplication> host gets destroyed again - which feels wrong and does crash. Should I really get two dtor calls? The combination of smartPointers and ref counted singletons surely is confusing.

And btw. the same crash happens when using two VST3 instances as master insert plugins. So it’s not limited to the montage section. All it seems to take is loading the same JUCE vst3 plugin twice and then quitting Wavelab.

I think I found the bug and it appears to be in the JUCE VST3 wrapper.

Unfortunately I am not very familiar with those COM interfaces, but I think the JUCE VST3 wrapper fails to increment the reference count on that host smartpointer. When Wavelab is shut down, the host gets detroyed and the non incremented smart pointer crashes when it is destroyed. Many hosts just don’t worry about proper cleanup when shutting down and maybe that’s why this crash only happens in Wavelab…?

Anyway here’s the trail of the problem.

in JucePluginFactory::setHostContext (FUnknown* context) this happens:

        host.loadFrom (context);

This calls the following method of ComSmartPtr<>:

bool loadFrom (Steinberg::FUnknown* o)
{
    *this = nullptr;
    return o != nullptr && o->queryInterface (ObjectType::iid, (void**) &source) == Steinberg::kResultOk;
} 

I think the issue comes from not incrementing the refCount in this loadFrom(). The smart-pointer gets cast to (void**)…! Therefore the queryInterface cannot increment the refCount, but the destruction of the factory will for sure decrement it and thus the crash happens as the host object can already be destroyed before the plugin. Adding an ->addRef() there solves the crash, but it could also be added also be added in setHostContext(). not sure what’s better as I don’t understand what loadFrom() is supposed to do in the first place. That’s a weird method name. Assigning to a smart-pointer using a cast to (void**) seems to be the real issue, but of course the loadFrom is used elsewhere in the code where maybe that’s desired behaviour?

Argh… now I realize QueryInterface is supposed to call AddRef before returning any object - not sure where this leaves things.

I looked at the vst3 wrapper code some more. First I found this:

//==============================================================================
// no leak detector here to prevent it firing on shutdown when running in hosts that
// don't release the factory object correctly...
JUCE_DECLARE_NON_COPYABLE (JucePluginFactory)

Obviously something goes wrong on shutdown in WaveLab. I then looked at the vst3 sdk sample code to see how the examples deal with that smart pointer to the host in their pluginfactory class.
Surprise… they don’t deal with it at all and just return kNotImplemented and then use nullptr as the context in the factory createInstance method - which is the only place that smart pointer is ever used. Based on the sample code I am not sure it is ever needed… doesn’t seem to be. I don’t understand why the JUCE VST3 wrapper needs to store it, based on the comment above it is clear that holding onto a smart pointer might be a bad choice if the class is expected to not be released properly. So here comes the fix. Just don’t keep it around in WaveLab. I have no idea whether there is a host that needs it, but WaveLab does dandy without it and all other hosts probably work just fine as well - otherwise they wouldn’t work with the VST3 SDK examples. Here is my diff so others can avoid the annoying debug session:

+++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp**
@@ -3072,6 +3072,10 @@ struct JucePluginFactory : public IPluginFactory3
tresult PLUGIN_API setHostContext (FUnknown* context) override
{
+   // WaveLab 10 on OS X (and maybe other WaveLabs): if the context object gets smart referenced here,
+   // a crash happens on shutdown in case multiple instances were active.
+   if (getHostType().isWavelab()) return kNotImplemented;
+
   host.loadFrom (context);

It would make my day if someone with more VST3 experience could explain to me why the setHostContext() even exists.

1 Like

Has this been addressed by JUCE? I believe we’re seeing the exact same crash.

@ygrabit do you know whose fault this crash is? Hard to tell on our end if it’s JUCE or Wavelab.

No my one-liner pull request never got merged - or even looked at :frowning: . I believe it is mainly a bug in WaveLab, but JUCE could easily work around it by not storing that pointer that is not needed anyway. I gave up on submitting PR after this…

Don’t merge such workarounds into JUCE, please! Those are always causing a lot of headaches here. :face_with_head_bandage:

I just debugged WaveLab and found the root of the crash. It is fixed and an update will hopefully be available soon.

Next time you can trigger me directly and I will see what I can do :grin:

2 Likes