From c5db5e6356ccbc888a53b74aaf32b7b87f472052 Mon Sep 17 00:00:00 2001 From: defiantnerd <97224712+defiantnerd@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:12:43 +0200 Subject: [PATCH 1/2] VST3: a null context-menu target is the global context, not a crash clap_plugin_context_menu::populate documents target == nullptr as the global context, so a plugin that passes it is conforming. The wrapper dereferenced it unconditionally while deciding which menu to create, which takes the host down. Treat it as the global context, which is what it means. An unrecognised kind still creates no menu, as before. --- src/wrapasvst3.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index 4d0d1112..b18b3a5d 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -2099,11 +2099,14 @@ bool ClapAsVst3::context_menu_populate(const clap_context_menu_target_t *target, if (!builder->supports(builder, CLAP_CONTEXT_MENU_ITEM_END_SUBMENU)) return false; // CLAP_CONTEXT_MENU_ITEM_TITLE is not used by VST3 - if (target->kind == CLAP_CONTEXT_MENU_TARGET_KIND_GLOBAL) + // A null target is the global context - see the documentation of + // clap_plugin_context_menu::populate in clap/ext/context-menu.h - so it must + // not be dereferenced. An unrecognised kind still creates no menu at all. + if (target == nullptr || target->kind == CLAP_CONTEXT_MENU_TARGET_KIND_GLOBAL) { this->vst3ContextMenu = componentHandler3->createContextMenu(this->_wrappedview, nullptr); } - if (target->kind == CLAP_CONTEXT_MENU_TARGET_KIND_PARAM) + else if (target->kind == CLAP_CONTEXT_MENU_TARGET_KIND_PARAM) { vst3ContextMenuParamID = target->id; vst3ContextMenu = componentHandler3->createContextMenu(_wrappedview, &vst3ContextMenuParamID); From f79bfa650d8c9fa4c117f2c76bec5515669b1d95 Mon Sep 17 00:00:00 2001 From: defiantnerd <97224712+defiantnerd@users.noreply.github.com> Date: Sun, 13 Sep 2026 10:12:54 +0200 Subject: [PATCH 2/2] VST3: mask the context-menu parameter id like every other one createParameter() publishes a parameter as `info->id & 0x7FFFFFFF`, but createContextMenu() was handed the raw clap_id. For any parameter whose id has the top bit set the host is asked for a menu for an id it never saw, so it cannot offer the parameter's own entries. The mask is not a convention: pluginterfaces/vst/vsttypes.h documents ParamID as "value in range [0, 0x7FFFFFFF]" and names the bound kMaxParamId. Every other clap_id -> ParamID conversion in the wrapper already applies it - param_clear, the queued editvalue, and the three in detail/vst3/process.cpp - so this call was the only one out of step. --- src/wrapasvst3.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index b18b3a5d..d1b2a742 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -2108,7 +2108,10 @@ bool ClapAsVst3::context_menu_populate(const clap_context_menu_target_t *target, } else if (target->kind == CLAP_CONTEXT_MENU_TARGET_KIND_PARAM) { - vst3ContextMenuParamID = target->id; + // Parameters are published to the host with the top bit cleared (see + // createParameter() in detail/vst3/parameter.cpp), so that - and not the + // raw clap_id - is the id the host can resolve back to a parameter. + vst3ContextMenuParamID = target->id & 0x7FFFFFFF; vst3ContextMenu = componentHandler3->createContextMenu(_wrappedview, &vst3ContextMenuParamID); } if (vst3ContextMenu)