Drain deferred FX parks before the view model is serialized; re-validate track handles per intent; pin the restore-plan round trip
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#define REAPERAPI_WANT_TrackFX_GetFXGUID
|
||||
#define REAPERAPI_WANT_TrackFX_GetOffline
|
||||
#define REAPERAPI_WANT_TrackFX_SetOffline
|
||||
#define REAPERAPI_WANT_ValidatePtr2
|
||||
#define REAPERAPI_WANT_guidToString
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
@@ -29,6 +30,17 @@ namespace {
|
||||
|
||||
FxParkQueue g_queue;
|
||||
ReaProject* g_owner = nullptr; // the project the pending intents were enqueued against
|
||||
bool g_draining = false;
|
||||
|
||||
// Makes "one drain at a time" explicit rather than implied by the call sites.
|
||||
// RAII because an apply can throw and a stuck flag would silence the queue for
|
||||
// the rest of the session.
|
||||
struct DrainScope {
|
||||
DrainScope() { g_draining = true; }
|
||||
~DrainScope() { g_draining = false; }
|
||||
DrainScope(const DrainScope&) = delete;
|
||||
DrainScope& operator=(const DrainScope&) = delete;
|
||||
};
|
||||
|
||||
ReaProject* currentProject() { return EnumProjects(-1, nullptr, 0); }
|
||||
|
||||
@@ -127,6 +139,7 @@ void discardDeferredFxParks() {
|
||||
|
||||
void drainDeferredFxParks() {
|
||||
if (g_queue.empty()) return;
|
||||
if (g_draining) return; // re-entered mid-apply: those intents are the outer drain's next pass
|
||||
|
||||
if (g_owner != currentProject()) {
|
||||
// The project the intents were planned against was closed or switched
|
||||
@@ -139,9 +152,12 @@ void drainDeferredFxParks() {
|
||||
}
|
||||
|
||||
ReaProject* const proj = g_owner;
|
||||
const DrainScope scope;
|
||||
|
||||
// Detached before the first write: applying pumps the message loop (plugins
|
||||
// load and unload), so a re-entrant switch can enqueue while this runs.
|
||||
// Detached before the first write: [verify — DAW] applying is ASSUMED to pump
|
||||
// the message loop (plugins load and unload), so a re-entrant switch can
|
||||
// enqueue while this runs. Everything defensive below rests on that one
|
||||
// assumption; each piece is correct regardless of whether it holds.
|
||||
const std::vector<FxParkIntent> draining = g_queue.take();
|
||||
|
||||
std::unordered_map<std::string, MediaTrack*> byGuid;
|
||||
@@ -159,6 +175,16 @@ void drainDeferredFxParks() {
|
||||
for (const FxParkIntent& intent : draining) {
|
||||
auto it = byGuid.find(intent.guid);
|
||||
if (it == byGuid.end()) continue; // track deleted since the switch — prune
|
||||
|
||||
// Re-validated PER INTENT, not once above: under the same pumping
|
||||
// assumption, a project closed or a track deleted between two applies
|
||||
// leaves the handle resolved above dangling — a use-after-free, not a
|
||||
// pruned intent. Same gate capture_realtime_shell's teardown uses; a null
|
||||
// first argument validates the ReaProject* itself (SDK: proj is ignored
|
||||
// when the pointer is a project).
|
||||
if (!ValidatePtr2(nullptr, proj, "ReaProject*")) return;
|
||||
if (!ValidatePtr2(proj, it->second, "MediaTrack*")) continue;
|
||||
|
||||
if (intent.park) {
|
||||
applyPark(it->second);
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user