Potential bug with String::isEmptyString() on Linux/gcc

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.

Thanks for reporting. I think this is likely due to a problem with the compiler flags used by the Projucer. I’m surprised we haven’t heard more reports of this problem!

There’s a potential fix on develop:

1 Like