Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VST3 SDK: Problematic construction of std::string member in ClassInfo object causes program to crash

The Problem:

I'm trying to use the VST Hosting utilities included in the SDK to load a plugin. The code is as shown:

#include "vst/v3/Vst3CommonIncludes.h"

int main()
{
    std::string vst3_module_path = R"(C:\Program Files\Common Files\VST3\Kontakt.vst3)";
    std::string error;
    std::shared_ptr<Module> vst_module = Module::create(vst3_module_path, error);
    std::vector<ClassInfo> class_infos = vst_module->getFactory().classInfos();;

    assert(error.empty());
    assert(class_infos.size());

    ClassInfo plugin_info = class_infos[0]; //Crash
    //... load the plugin and do more things
    return 0;
}

where Vst3CommonIncludes.h just includes all the VST SDK headers from pluginterfaces/vst and public.sdk/source/vst 8

In my case, the SDK comes in source and cmake files to build them into a static library. So my code and SDK code share the same compiler.

VST SDK sources from Steinberg Media


Investigation Done:

My investigation showed that PluginFactory::classInfos() returned corrupted data, trying to assign from them causes std::bad_alloc since the size of std::string is not valid.

Definition of PluginFactory::classInfos() in VST SDK:

PluginFactory::ClassInfos PluginFactory::classInfos () const noexcept
{
    auto count = classCount ();
    Optional<FactoryInfo> factoryInfo;
    ClassInfos classes;
    classes.reserve (count);
    auto f3 = Steinberg::FUnknownPtr<Steinberg::IPluginFactory3> (factory);
    auto f2 = Steinberg::FUnknownPtr<Steinberg::IPluginFactory2> (factory);
    Steinberg::PClassInfo ci;
    Steinberg::PClassInfo2 ci2;
    Steinberg::PClassInfoW ci3;
    for (uint32_t i = 0; i < count; ++i)
    {
        if (f3 && f3->getClassInfoUnicode (i, &ci3) == Steinberg::kResultTrue)
//------------Unexpected behaviour here--------------------
            classes.emplace_back (ci3);                //--
//---------------------------------------------------------
        else if (f2 && f2->getClassInfo2 (i, &ci2) == Steinberg::kResultTrue)
            classes.emplace_back (ci2);
        else if (factory->getClassInfo (i, &ci) == Steinberg::kResultTrue)
            classes.emplace_back (ci);
        auto& classInfo = classes.back ();
        if (classInfo.vendor ().empty ())
        {
            if (!factoryInfo)
                factoryInfo = Optional<FactoryInfo> (info ());
            classInfo.get ().vendor = factoryInfo->vendor ();
        }
    }
    return classes;
}

After the in-place construction of the new ClassInfo element, ClassInfo::data::category and other std::string members (name, vendor, etc.) reads <NULL> in debugger.

Stepping into the constructor of std::string, I've found the this pointer during construction of data.category is NOT equal to &data.category, and was offset by 4 bytes.

&data.category = 0x 0000 009b 546f ed14
this (std::string constructor scope) = 0x 0000 009b 546f ed18
//Actual address varies but the offset remains the same

Thus the string object became corrupted and later crashes the program.

Additional Information:


std::string related experiment:

Also, experimenting with ClassInfo and its string members, I ran into this:

ClassInfo ci;
ci.get().category = "testCategory";                  //OK
const_cast<string&>(ci.category()) = "testCategory"; //Crash, Access violation at 0xFFFFFFFFFFFFFFFF

I think it's highly related to the problem, but I couldn't come up with an explanation.

C++ standard consistency:

I also added

#if __cplusplus != 201703L
#error
#endif

to every relevant file, so I'm sure they share the same STL implementation, the problem will still occur.

Reproducing Attempt:

I hoped to recreate a minimal scenario where VST SDK is not included, and with my own MimicClassInfo that resembles the structure of original ClassInfo in some aspects. The problem does not occur.


Compiler and SDK info:

MSVC 14.37.32822 , using C++17 standard. VST SDK 3.7.8 build 34 (2023-05-15)

like image 818
Yuwei Xu Avatar asked Sep 06 '25 07:09

Yuwei Xu


1 Answers

Another reason of such behaviour is different compiler flags (especially macros) used to build the C++ files.

For example if some class X conditionally (based on some macro) defines one more additional data member, that affects the data members layout. If you compile two C++ files one with the macro and another one without that macro, your application may crash if those two C++ translation units work on the same object of class X.

So your C++ code can be syntactically correct, but because of different compiler flags, linker may build buggy app.

like image 81
Dmytro Ovdiienko Avatar answered Sep 07 '25 19:09

Dmytro Ovdiienko