Skip to content

VST: get/set state on main thread#96

Open
RomanPudashkin wants to merge 4 commits into
musescore:mainfrom
RomanPudashkin:vst_get_set_state_on_main_thread
Open

VST: get/set state on main thread#96
RomanPudashkin wants to merge 4 commits into
musescore:mainfrom
RomanPudashkin:vst_get_set_state_on_main_thread

Conversation

@RomanPudashkin

Copy link
Copy Markdown
Contributor

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

VstComponentHandler and VstAdvancedHandler gain an atomic notification-suppression mechanism: new setSuppressNotify/suppressNotify methods gate performEdit, endEdit, setDirty, and finishGroupEdit notifications. VstPluginInstance is refactored to enforce main-thread execution on syncControllerToComponentState, rescanParams, and a new setPluginConfig method. setPluginConfig replaces the previous m_updatingState flag approach by wrapping state restore in setSuppressNotify(true/false). The destructor sets m_isLoaded = false early, accessors now guard on both m_isLoaded and m_pluginProvider, and m_mutex, m_updatingState, ClassInfo m_classInfo, isAbleForInput, and isValid are removed.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete relative to the template; it references an external PR but lacks required checklist items, issue reference, motivation explanation, and other template sections. Fill in the complete template including issue reference (Resolves: #NNNNN), description/motivation, all checklist items, and build configuration details.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change—ensuring state/config operations run on the main thread as required by VST specifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/vst/internal/vstplugininstance.cpp`:
- Around line 42-50: The stateBufferFromString function casts the strSize
parameter from size_t to Steinberg::int32 without validating that the value fits
within the int32 range, which could cause silent truncation on 64-bit systems.
Before the buffer.write call, add a bounds check to ensure strSize does not
exceed INT32_MAX, and either return early or throw an error if the size is
invalid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b6063472-0d2e-40b2-bf2f-1c4df10d2651

📥 Commits

Reviewing files that changed from the base of the PR and between 5a48cf5 and d782e78.

📒 Files selected for processing (4)
  • framework/vst/internal/vstcomponenthandler.cpp
  • framework/vst/internal/vstcomponenthandler.h
  • framework/vst/internal/vstplugininstance.cpp
  • framework/vst/internal/vstplugininstance.h

Comment on lines +42 to +50
static void stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize)
{
if (strSize == 0) {
return;
}

buffer.setSize(0);
buffer.write(strData, static_cast<Steinberg::int32>(strSize), nullptr);
buffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify all state-buffer writes that narrow size_t to Steinberg::int32.
# Expected: Any cast to Steinberg::int32 for stream byte counts is guarded by an upper-bound check.

rg -n -C4 'static_cast<Steinberg::int32>\(|\.write\(.*Steinberg::int32|stateBufferFromString' --iglob '*.cpp' --iglob '*.h'

Repository: musescore/muse_framework

Length of output: 3745


🏁 Script executed:

#!/bin/bash
# Check the Steinberg type definition and VST API expectations
rg -n 'int32|IBStream' --type h framework/ | head -30

Repository: musescore/muse_framework

Length of output: 3215


🏁 Script executed:

#!/bin/bash
# Look for any existing size validations on state before stateBufferFromString calls
rg -B10 'stateBufferFromString' framework/vst/internal/vstplugininstance.cpp | grep -i 'size\|max\|limit\|check'

Repository: musescore/muse_framework

Length of output: 305


🏁 Script executed:

#!/bin/bash
# Check if there are size limits defined elsewhere in VST plugin code
rg -n 'MAX.*SIZE|SIZE.*MAX|MAX.*BUFFER|numeric_limits' framework/vst/

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check Steinberg type definitions in the codebase
find framework -name "*.h" -type f | xargs grep -l "Steinberg.*int32\|namespace Steinberg" | head -5

Repository: musescore/muse_framework

Length of output: 157


🏁 Script executed:

#!/bin/bash
# Look for Steinberg header includes to understand the type
rg -n 'include.*Steinberg|Steinberg::int32' framework/vst/internal/vstplugininstance.cpp | head -10

Repository: musescore/muse_framework

Length of output: 147


🏁 Script executed:

#!/bin/bash
# Check the complete context around the write() method calls to see full signature
cat -n framework/vst/internal/vstplugininstance.cpp | sed -n '42,51p'

Repository: musescore/muse_framework

Length of output: 446


🏁 Script executed:

#!/bin/bash
# Verify if VstMemoryStream or IBStream has any documented size limits
rg -n 'class.*VstMemoryStream|class.*IBStream' framework/vst/ -A10 | head -40

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check the full context of both stateBufferFromString call sites
cat -n framework/vst/internal/vstplugininstance.cpp | sed -n '280,305p'

Repository: musescore/muse_framework

Length of output: 1368


🏁 Script executed:

#!/bin/bash
# Check where componentState and controllerState come from - are they from config maps?
rg -B20 'componentState.*config.end\|controllerState.*config.end' framework/vst/internal/vstplugininstance.cpp | head -50

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Look for any deserialization or config loading that might produce oversized state
rg -n 'readConfig|loadState|setState' framework/vst/internal/vstplugininstance.cpp | head -20

Repository: musescore/muse_framework

Length of output: 224


🏁 Script executed:

#!/bin/bash
# Check if there are any size limits or validations on state blobs elsewhere
rg -n 'maxSize|MAX_SIZE|max.*state|state.*max' framework/vst/ -i

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Verify what std::string::size() typically returns and whether it could exceed INT32_MAX
# Also check if there's any pruning/truncation of config states before loading
rg -B5 'second.*size\(\)' framework/vst/internal/vstplugininstance.cpp | head -30

Repository: musescore/muse_framework

Length of output: 645


🏁 Script executed:

#!/bin/bash
# Check the exception handling - does it catch narrowing exceptions?
# Also look for std::length_error or std::out_of_range in the codebase
rg -n 'std::length_error|std::out_of_range|numeric_limits' framework/vst/

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Verify the actual type signature of Steinberg::int32 and its limits
rg -n 'typedef.*int32|using.*int32' framework/ --type h | grep -i steinberg | head -10

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check if there's any platform-specific definition of Steinberg types
find framework -name "*.h" -type f -exec grep -l "Steinberg" {} \; | xargs grep -l "int32" | head -5 | xargs cat | grep -A3 -B3 "int32" | head -30

Repository: musescore/muse_framework

Length of output: 1051


🏁 Script executed:

#!/bin/bash
# Look for any VST SDK includes that might define these types
rg -n 'include' framework/vst/internal/vstplugininstance.cpp | head -15

Repository: musescore/muse_framework

Length of output: 237


🏁 Script executed:

#!/bin/bash
# Check what config sources these state strings come from
rg -B30 'componentState.*config.end' framework/vst/internal/vstplugininstance.cpp | grep -A30 'config' | head -40

Repository: musescore/muse_framework

Length of output: 1055


🏁 Script executed:

#!/bin/bash
# Look at the function signature of the applyConfig call to understand config origin
rg -n 'void.*applyConfig|applyConfig.*{' framework/vst/internal/vstplugininstance.cpp -A5 | head -20

Repository: musescore/muse_framework

Length of output: 196


🏁 Script executed:

#!/bin/bash
# Check if state blobs have any size limits documented or enforced earlier
rg -n 'size.*limit|limit.*size|MAX.*BYTE|BYTE.*MAX' framework/vst/ -i

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Let's verify the exact type definitions by checking the Steinberg SDK headers
# First, find where Steinberg types are defined/included
find framework -name "*.h" -type f | xargs grep -l "namespace Steinberg" | head -3

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check the audio::AudioUnitConfig type to understand config structure
rg -n 'using AudioUnitConfig|class AudioUnitConfig|struct AudioUnitConfig' framework/ --type h -A5

Repository: musescore/muse_framework

Length of output: 531


🏁 Script executed:

#!/bin/bash
# Verify the actual behavior: check if std::string can hold data larger than INT32_MAX in practice
# and whether there are any safeguards in config serialization
rg -n 'AudioUnitConfig|saveConfig|loadConfig' framework/vst/ | head -20

Repository: musescore/muse_framework

Length of output: 1650


Add bounds validation before narrowing state buffer size to Steinberg::int32.

Line 49 casts serialized state size from size_t to Steinberg::int32 without checking the upper bound. A std::string can exceed INT32_MAX on 64-bit systems; silent narrowing would truncate the state blob and corrupt plugin state restoration. Add a bounds check before casting.

Proposed fix
+#include <limits>
+#include <stdexcept>
+
 static void stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize)
 {
+    buffer.setSize(0);
+
     if (strSize == 0) {
         return;
     }
 
-    buffer.setSize(0);
+    if (strSize > static_cast<size_t>(std::numeric_limits<Steinberg::int32>::max())) {
+        throw std::length_error("VST state buffer is too large");
+    }
+
     buffer.write(strData, static_cast<Steinberg::int32>(strSize), nullptr);
     buffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static void stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize)
{
if (strSize == 0) {
return;
}
buffer.setSize(0);
buffer.write(strData, static_cast<Steinberg::int32>(strSize), nullptr);
buffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr);
`#include` <limits>
`#include` <stdexcept>
static void stateBufferFromString(VstMemoryStream& buffer, char* strData, const size_t strSize)
{
buffer.setSize(0);
if (strSize == 0) {
return;
}
if (strSize > static_cast<size_t>(std::numeric_limits<Steinberg::int32>::max())) {
throw std::length_error("VST state buffer is too large");
}
buffer.write(strData, static_cast<Steinberg::int32>(strSize), nullptr);
buffer.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr);
}
🧰 Tools
🪛 Cppcheck (2.21.0)

[style] 45-45: The function 'modularity_isGlobalInterface' is never used.

(unusedFunction)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/vst/internal/vstplugininstance.cpp` around lines 42 - 50, The
stateBufferFromString function casts the strSize parameter from size_t to
Steinberg::int32 without validating that the value fits within the int32 range,
which could cause silent truncation on 64-bit systems. Before the buffer.write
call, add a bounds check to ensure strSize does not exceed INT32_MAX, and either
return early or throw an error if the size is invalid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant