CRASH: JUCE symbols exported by default in AAX causes symbol clashes with ProTools

tldr: Using CMake and the Ninja generator when building AAX plug-ins causes all of JUCE’s symbols to be exported causing subtle bugs due to symbol clashes with an internal ProTools plug-in which is also built with JUCE.

Long version:

We’ve recently been observing a strange issue with AAX plug-ins built by JUCE causing very subtle bugs in recent development versions of ProTools. For example, the code of one of my clients, crashes while loading in ProTools due to juce::emptyString being de-allocated - this should never happen as juce::emptyString is a global variable.

The root cause took a while to figure out: it turns out that there are two copies of JUCE in the process address space of ProTools: one from my client’s plug-in - let’s call this JUCE A - and the other inside one of ProTool’s internal AAX plug-ins which is also using JUCE - let’s call this JUCE B.

Our crash occurs because, in JUCE A &juce::var::VariantType::defaultToString is assigned to the juce::var::VariantType::toString function pointer at runtime. However, this resolves to the function juce::var::VariantType::defaultToString of JUCE B. The defaultToString returns an empty string which is simply a reference to the global variable juce::emptyString. In the above case, it will return a pointer to JUCE B’s copy of juce::emptyString.
On string de-allocation, JUCE compares the pointer to juce::emptyString and if it is equal, it will skip de-allocation, which is the correct behaviour, as juce::emptyString is a global. However, in the above crash, JUCE A now compares the pointer (coming from juce::emptyString of JUCE B) with juce::emptyString of JUCE A, which is not equal, and JUCE will try to de-allocate the string.

Note however, that the crash manifests itself in many different ways for different plug-ins. The above is just an example. The crash usually turns up in ValueTree or OpenGL related code as both make extensive use of function pointers which are resolved at runtime.

Of course, neither my client’s plug-in nor any ProTools internal plug-ins should be exporting JUCE symbols. In fact, the only thing that should be exported are the AAX entry points (and a single JUCE symbol related to accessibility).

We’ve narrowed down the bug to JUCE’s cmake support. If the AAX is built with CMake and ninja then the AAX will export all of JUCE’s symbols. This can easily be checked with the following command:

nm --defined-only --extern-only "MyAAXPlugin.aaxplugin/Contents/MacOS/MyAAXPlugin"| grep " T " | grep juce | c++filt 

If the above command lists a bunch of JUCE symbols (instead of a single accessibility JUCE symbol) then the AAX wasn’t linked correctly and you are likely going to run into this issue.

Interestingly, building the same AAX with CMake but using the XCode generator fixes the issue.

Any help would be appreciated! Once this issue is fixed we need to reach out to Avid so that they fix their internal plug-in. As no plug-in should be exporting JUCE symbols.

5 Likes

Hello, I’m the client :slight_smile: Thanks Fabian for reporting this. Eager to hear what other users and JUCE devs have to say about it. Do tell if I can be of any help.

1 Like

I’ve go this on my backlog, I will try and take a look as soon as possible.

1 Like

I’m on holiday atm but this was too weird to ignore!

In JUCEUtils.cmake, _juce_link_plugin_wrapper (called for each active plugin format) calls:

set_target_properties(${target_name} PROPERTIES
    VISIBILITY_INLINES_HIDDEN TRUE
    C_VISIBILITY_PRESET hidden
    CXX_VISIBILITY_PRESET hidden)

My understanding is that this hides all symbols that aren’t explicitly exported from the module. When I build the AudioPluginDemo_AAX target using Ninja on macOS and check it using the command you provided, I see only the AAX entry points exported. This is immediately after building (no PACE signing).

0000000000044afc T _ACFCanUnloadNow
0000000000044a7c T _ACFGetClassFactory
0000000000044c34 T _ACFGetSDKVersion
0000000000044a04 T _ACFRegisterComponent
0000000000044994 T _ACFRegisterPlugin
0000000000044bcc T _ACFShutdown
0000000000044b64 T _ACFStartup

Some things that might be worth checking:

  • Do you see the same result if you build one of the JUCE example plugins?
  • If you build in verbose mode (cmake --build <dir> --target <target> --verbose), do you see -fvisibility=hidden and -fvisibility-inlines-hidden in the compiler invocations? If these options are present, do they get switched off later in the compiler invocation? I’m wondering whether Ninja and Xcode are passing the compiler args in different orders.
  • Is the plugin’s CMake setup doing anything unusual, like building JUCE as a staticlib, or creating the plugin without using juce_add_plugin?

The point about Pro Tools plugins exporting JUCE symbols is interesting. If you’re not testing with the most recent version of Pro Tools, it might be worth checking if this has been fixed in the most recent release. If you’re still able to show that symbols are unnecessarily exposed in the most recent release, then your suggestion to reach out to Avid sounds like a good idea.

2 Likes