steinbergmedia / vst3sdk

VST 3 Plug-In SDK
Other
1.57k stars 162 forks source link

pluginfactory_constexpr.h produces wrong factory.classInfos() on GCC 10.2.1/Raspberry Pi OS. #93

Closed rerdavies closed 1 year ago

rerdavies commented 1 year ago

Description

All VST3 plugins that declare plugin factories using pluginfactory_constexpr.h end up using the class declarations from the first-loaded VST3 plugin.

In the vst3sdk, the mda-vst3.vst3 and adelay.vst3 plugins both use pluginfactory_constexpr.h.

The problem is most likely a subtle defect in how static members of template classes are initialized are shared between .so's with non-GLOBAL linkage. There is some reason to believe that it a problem with aarch64 linux (which doesn't support all ELF linkage types). But it could be a defect in any of GCC, Rasberry Pi OS, or the GCC runtime. However, if it's a problem with Raspberry Pi OS, it's almost certainly a problem on all Debian-derived AARCH64 Linuxen.

Steps to repro:

Install, compile and build vst3sdk

(code fragment given below).

1) load the adelay.vst3 plugin. 2) unload the adelay.vst3 plugin. (optional Unloading doesn't make a difference) 3) Load the mda-vst3vst3 plugin, and obtain the class factory. 4) Call factory..classInfos().

Expected result:

a ClassInfosstructure containing the ClassInfodata for 68 mda-vst3.vst3 plugin classes.

Actual result:

a ClassInfos with 68 entries, containing the three ClassInfo entries for adlay.vst3, and 65 garbage entries.

Proposed fix:

The following fix has been tested successfully on GCC 10.2.1/Raspbian.

Force GCC to declare FactoryData with hidden linkage (non-exported).

plugin.sdk/source/main/pluginfactory_constexpr.h:

<
namespace Steinberg {
>
#if __GNUC__ > 4
// prevent template-class static initializers from being shared on GCC 10.1/aarch64
#define HIDDEN_CLASS  __attribute__ ((visibility ("hidden")))
#else
#define HIDDEN_CLASS  
#endif

namespace Steinberg {
------------------
<
#define BEGIN_FACTORY_DEF(company, url, email, noClasses)                                     \
    struct FactoryData                                                                        \
>
#define BEGIN_FACTORY_DEF(company, url, email, noClasses)                                     \
    struct HIDDEN_CLASS FactoryData                                                                        \
--------------------

Code sample to reproduce

On Raspberry PI os, build vst3sdk using VS Code with CMake support plugins installed.

Select the VSCode toolchain to be "GCC 10.2.1 aarch66-linux-gnu" (default on Raspberry Pi OS)

You'll have to sort out linkage yourself. Add a GCC include to the root of vst3sdk.

#include <string>
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/hosting/plugprovider.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivstunits.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/hosting/processdata.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"

#include "public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h"
#include "public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h"

using namespace std;
using namespace VST3::Hosting;
using namespace Steinberg;
using namespace Steinberg::Vst;

PluginFactory::ClassInfos GetVst3ClassInfos(const std::string& pluginPath)
{
    std::string error;
    Module::Ptr module = Module::create(pluginPath.c_str(), error);

    if (!module)
    {
        assert(false && "Vst3 load failed.");
    }
    const PluginFactory &factory = module->getFactory();

    return factory.classInfos();
}

static std::string HomePath() { return getenv("HOME"); }

void BugReportTest() { auto _ = GetVst3ClassInfos(HomePath() + "/.vst3/adelay.vst3"); PluginFactory::ClassInfos classInfos = GetVst3ClassInfos(HomePath() + "/.vst3/mda-vst3.vst3");

// right size.
assert(classInfos.size() == 68); // succeeds

// wrong classInfo (from adelay.vst3)
assert(classInfos[0].name() == "mda Ambience"); // actual result "ADelay"

}

Platform info:

Raspberry Pi OS Linux raspberrypi 5.15.32-v8+ #1538 SMP PREEMPT Thu Mar 31 19:40:39 BST 2022 aarch64 GNU/Linux gcc version 10.2.1 20210110 (Debian 10.2.1-6)

Built from vst3sdk 3.7.5 .zip package.using Visual Studio Code/CMake build.

Debug Notes

The bug has occurs by the time ModuleEntry() is called. I'm unable to get GDB to debug through the .so initialization code.

Instances of Steinberg::PluginFactory<FactoryData> factory have different addresses. FactoryData:classInfo does not appear to share the same address across plugin instances (array<>::size() is correct for both plugins). I'd have to guess that data used to perform constexprinitialization of FactoryData::classInfos ends up getting incorrectly shared between .so instances even though dlopen() flags do NOT specify global linkage.

Moving the declaration of static Steinberg::PluginFactory<FactoryData> factory; outside of the function does not correct the problem. The defect appears to be triggered by constexpr initialization regardless of the scope of the variable.

Prepending #define FactoryData FactoryData21341 before the #include of pluginfactory_constexpr.h does correct the problem. Apparently the issue is related to a lexical collision of a leaked declaration of Steinberg::PluginFactory<FactoryData>::FactoryData::classInfo::some_static_intitializer_method_or_data_ declaration between plugins.

scheffle commented 1 year ago

Thanks for the detailed report. We will make a fix for it in the next SDK update.

scheffle commented 1 year ago

Fixed with v3.7.6_build_18