Skip to content

guiRequestResize is called without a canUseGui() guard — segfault on any host that does not implement clap_host_gui #181

Description

@lylepmills

EditorWrapperComponent::resizeHostWindow() calls host.guiRequestResize()
unconditionally:

void resizeHostWindow()
{
if (editor != nullptr)
{
auto editorBounds = getSizeToContainChild().withPosition(0, 0);
{
const juce::ScopedValueSetter<bool> resizingParentSetter(resizingParent, true);
host.guiRequestResize((uint32_t)editorBounds.getWidth(),
(uint32_t)editorBounds.getHeight());
}
setBounds(editorBounds.withPosition(0, 0));
}
}

clap_host_gui is optional, and clap-helpers only asserts the predicate
before dereferencing:

template <MisbehaviourHandler h, CheckingLevel l>
bool HostProxy<h, l>::guiRequestResize(uint32_t width, uint32_t height) const noexcept {
   assert(canUseGui());
   ensureMainThread("gui.request_resize");
   return _hostGui->request_resize(_host, width, height);
}

assert is compiled out under NDEBUG, so in a release build a host without
clap_host_gui gets a null dereference rather than a no-op. This is exactly the
shape of #46 (paramsRescan() / clap_host_params), which is why every other
optional host extension in the wrapper is already guarded — canUseParams,
canUseState, canUseLatency, canUseTimerSupport, canUsePosixFdSupport,
canUseVoiceInfo, canUseNoteName, canUseRemoteControls, canUsePresetLoad,
canUseContextMenu. guiRequestResize appears to be the only one missed.

Symptom

EXC_BAD_ACCESS (SIGSEGV), KERN_INVALID_ADDRESS at 0x0000000000000008
  ClapJuceWrapper::guiSetScale(double)

The faulting address is 8 because request_resize is the second member of
clap_host_gui.

The backtrace is misleading: everything inlines into one frame, so it names
guiSetScale and reads like a bug in the editor wrapper. It is not —
editorWrapper and editorWrapper->editor are both non-null, and both are
already null-checked. The null pointer is the host's. The real path is:

guiSetScale -> EditorWrapperComponent::setEditorScaleFactor
            -> resizeHostWindow -> HostProxy::guiRequestResize

clap_plugin_gui::set_scale is not special — it is just the earliest call that
reaches resizeHostWindow(). childBoundsChanged() reaches the same line
whenever an editor adjusts its own bounds, so an editor that self-resizes
crashes the same way on such a host, with no set_scale involved.

Reproduction

A minimal CLAP host whose get_extension returns nullptr for everything
(legal — every CLAP extension is optional): create the plugin, gui->create,
then gui->set_scale(plugin, 1.0). Segfaults. Ordering is not a factor —
calling it after set_parent and show crashes too, just later.

Verified in both directions, on three separate release binaries (a JUCE 9
WebView-editor synth, and the same synth with a plain JUCE editor built against
JUCE 9 and JUCE 8 — all three crash identically):

  • adding a clap_host_gui to the host makes the crash disappear with the
    wrapper unpatched, and the editor then attaches and sizes correctly;
  • guarding the call makes it disappear with the host unchanged.

Real DAWs implement clap_host_gui — it is how a plugin asks to be resized —
so REAPER and Ableton Live never reach it. That is why this has gone unnoticed;
it is also why it is invisible to CI, which builds an effect and runs
clap-info without ever instantiating a window.

Fix

Guard the host call only. setBounds() should stay unconditional: a host that
declines, or never offers, a resize does not make the plugin's own view stale.

{
    const juce::ScopedValueSetter<bool> resizingParentSetter(resizingParent, true);
    if (host.canUseGui())
        host.guiRequestResize((uint32_t)editorBounds.getWidth(),
                              (uint32_t)editorBounds.getHeight());
}

setBounds(editorBounds.withPosition(0, 0));

Inert for any host implementing clap_host_gui — i.e. for every host that could
previously reach this line without crashing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions