I’m porting some VST3 plug-ins to Linux, specifically for use on a Raspberry Pi 4. I’m using an Ubuntu Mate 22.04 Desktop image, and I ran into this issue. I’m using GCC as a compiler, and building with make, and building directly on the rpi. I’m using the JUCE 7.0.4 release.
Plug-ins build and run fine in general, but as soon as I had two instances, each of a different plug-in (for example in AudioPluginHost), it would crash with a segmentation fault.
Let’s call them plug-in A and plug-in B just for the sake of explanation. I can have as many instances of plug-in A as a I want, or as many instances of plug-in B, but as soon as a single instance of A or B is loaded at the same time (order does not matter), I would get the crash.
Debugging led me to this method in juce_String.cpp:
static void release (StringHolder* const b) noexcept
{
if (! isEmptyString (b))
if (--(b->refCount) == -1)
delete[] reinterpret_cast<char*> (b);
}
I noticed that when it would crash the argument b
always had an empty string value, and yet the call to String::isEmptyString()
was returning that it was not an empty string, and the call delete[]
would take place, and hence the segmentation fault would occur.
So, if we look at String::isEmptyString()
, it’s very simple:
static bool isEmptyString (StringHolder* other)
{
return other == &emptyString;
}
It looks like it is comparing the address of the pointer other
to the address of a predefined StringHolder
that holds the default empty string value, which is declared in juce_String.cpp like this:
constexpr StringHolder emptyString;
When loading plug-in A or B separately, the code works as expected, however, as soon as A and B are loaded together, in whichever plug-in is loaded second, the memory address for emptyString
is somehow different than other
holding an empty string, and so the call to:
static bool isEmptyString (StringHolder* other)
{
return other == &emptyString;
}
…fails because the address no longer matches, even though the other
argument is actually holding/pointing to an empty string value, hence String::release()
fails, and the delete[]
gets called erroneously, and then the crash.
I was able to reproduce it easily by creating two test plug-in projects using 7.0.4 Projucer, basically just the standard basic audio plug-in project (in this case only working with VST3).
The only thing I added in the constructor of both plug-ins’ PluginProcessor.cpp identically was this:
juce::var value = juce::var("");
auto valueStr = value.toString();
DBG(juce::String(ProjectInfo::projectName) + " value = " + valueStr);
So both plug-in projects are identical except with different plug-in codes and different plug-in/project names.
All I have to do is load them both into AudioPluginHost and I get the same crash.
I’m not sure if this has to do with the use of statics here or what, but it just seems super weird that loading a different plug-in would somehow get clobbered by the first plug-in, in terms of the address of emptyString
changing in a way that breaks the isEmptyString()
method.
Further, this is only crashing on the Raspberry Pi (I haven’t tested any other Linux target yet) - macOS and Windows don’t suffer from this issue at all.
Is this a known issue? I searched the forum and found some stuff related to this (maybe), but nothing that seemed to be this exact issue.
I wanted to see if anyone knew about this issue, and if it has been addressed already before filing a more formal bug report.
I was able to work around this issue by applying a pretty ugly hack:
static bool isEmptyString (StringHolder* other)
{
// HACK patch to prevent crash on Linux/rPI
if (other != nullptr && other->text[0] == '\0')
return true;
return other == &emptyString;
}
Any thoughts or insights are appreciated.