From 6f9d513cdf9287b5ede7b3a3be6761f6932ebb8a Mon Sep 17 00:00:00 2001 From: Pia Baltazar Date: Mon, 13 Jul 2026 03:20:59 -0400 Subject: [PATCH] Scenario: guard TimeSync execution component against use after cleanup cleanup() resets m_ossia_node while the component's destruction is deferred through the execution queue. Model edits arriving in that window (autotrigger toggles, trigger/expression changes, quantization changes, GUI trigger) would then dereference a null node in the exec lambda, or trip SCORE_ASSERT in updateTrigger(). Bail out early when m_ossia_node is null. --- .../Document/TimeSync/TimeSyncExecution.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/plugins/score-plugin-scenario/Scenario/Document/TimeSync/TimeSyncExecution.cpp b/src/plugins/score-plugin-scenario/Scenario/Document/TimeSync/TimeSyncExecution.cpp index 40829e71a1..e3de4b4f54 100644 --- a/src/plugins/score-plugin-scenario/Scenario/Document/TimeSync/TimeSyncExecution.cpp +++ b/src/plugins/score-plugin-scenario/Scenario/Document/TimeSync/TimeSyncExecution.cpp @@ -35,8 +35,13 @@ TimeSyncComponent::TimeSyncComponent( con(element, &Scenario::TimeSyncModel::activeChanged, this, &TimeSyncComponent::updateTrigger); - con(element, &Scenario::TimeSyncModel::autotriggerChanged, this, - [this](bool b) { in_exec([ts = m_ossia_node, b] { ts->set_autotrigger(b); }); }); + con(element, &Scenario::TimeSyncModel::autotriggerChanged, this, [this](bool b) { + // cleanup() resets m_ossia_node while destruction is deferred through the + // exec queue, so model edits can still arrive afterwards: ignore them. + if(!m_ossia_node) + return; + in_exec([ts = m_ossia_node, b] { ts->set_autotrigger(b); }); + }); con(element, &Scenario::TimeSyncModel::triggerChanged, this, &TimeSyncComponent::updateTrigger); @@ -173,7 +178,8 @@ void TimeSyncComponent::updateTrigger() start = m_score_node->isStartPoint(); } - SCORE_ASSERT(m_ossia_node); + if(!m_ossia_node) + return; in_exec([e = m_ossia_node, exp_ptr, autotrigger, start] { bool was_observing = e->is_observing_expression(); if(was_observing) @@ -191,6 +197,8 @@ void TimeSyncComponent::updateTrigger() void TimeSyncComponent::updateTriggerTime() { OSSIA_ENSURE_CURRENT_THREAD_KIND(ossia::thread_type::Ui); + if(!m_ossia_node) + return; ossia::musical_sync quantRate = m_score_node->musicalSync(); if(quantRate < 0) { @@ -222,6 +230,8 @@ void TimeSyncComponent::updateTriggerTime() void TimeSyncComponent::on_GUITrigger() { OSSIA_ENSURE_CURRENT_THREAD_KIND(ossia::thread_type::Ui); + if(!m_ossia_node) + return; in_exec([e = m_ossia_node] { OSSIA_ENSURE_CURRENT_THREAD_KIND(ossia::thread_type::Audio); e->start_trigger_request();