fix(pS-usage): close delete-ward residuals — remint on corrupt, named abort keys, truncation protect-all, abort->protect-all set

This commit is contained in:
2026-07-28 13:54:10 -04:00
parent a4aeb9dcc8
commit ec83f14738
8 changed files with 197 additions and 36 deletions
+27 -8
View File
@@ -69,8 +69,9 @@ using FxParmGetter =
// True if any FX in the (possibly container-nested) sub-chain rooted at `fxId` is a
// ReaSampler 9000. BOTH fx_ident and original_name are checked on BOTH chain kinds (a
// renamed instance keeps its original_name; fx_ident carries the module path — the
// review's take-path gap is closed by sharing this one walk). Containers are walked via
// renamed instance may keep its original_name; fx_ident carries the module path — the
// primary identification net is the module filename base via fx_ident, which holds even
// after a user renames the FX instance). Containers are walked via
// the documented container_count / container_item.X addressing (v7.06+); on a chain
// kind or REAPER version without containers the parm read returns empty and recursion
// is a no-op. `depth` bounds pathological nesting. fx_ident is queried per FX — chain
@@ -83,9 +84,17 @@ bool fxSubtreeHasInstance(const FxParmGetter& parm, int fxId,
identityMatches(parm(fxId, "original_name"), id.uidHexUpper, id.nameUpper,
id.outputNameUpper))
return true;
if (depth <= 0) return false;
const std::string countStr = parm(fxId, "container_count");
if (countStr.empty()) return false; // not a container
if (countStr.empty()) return false; // not a container; no children to miss
if (depth <= 0) {
// This node IS a container but we have exhausted our descent budget. We cannot
// prove that none of its children is a ReaSampler 9000 instance — treat the
// incomplete walk as a positive identification (the protect direction). This is
// defense-in-depth: kMaxContainerDepth = 32 should prevent reaching this branch
// in any real project, but if it IS reached the fail-safe fires rather than
// silently missing a live nested instance.
return true;
}
const int n = std::atoi(countStr.c_str());
for (int k = 0; k < n; ++k) {
const std::string item =
@@ -98,7 +107,10 @@ bool fxSubtreeHasInstance(const FxParmGetter& parm, int fxId,
return false;
}
constexpr int kMaxContainerDepth = 8;
// Raised from 8 to 32 (defense in depth against truncation). Real-world FX containers
// are typically 24 levels deep; 32 is unreachable in practice while remaining finite.
// Even at 32, the truncation→protect-all guard below is the primary protection.
constexpr int kMaxContainerDepth = 32;
std::string trackFxParm(MediaTrack* tr, int fxId, const char* parm) {
char buf[2048] = {0};
@@ -183,6 +195,7 @@ UsageScanResult liveInstanceHeldPaths(void* projOpaque) {
// undecodable -> the pure fold ABORTS the prune.
std::vector<std::string> usageKeys;
{
const std::string prefix = kProjExtUsageKeyPrefix; // hoisted: one alloc, not N
char keyBuf[256];
for (int idx = 0;; ++idx) {
keyBuf[0] = '\0';
@@ -190,7 +203,6 @@ UsageScanResult liveInstanceHeldPaths(void* projOpaque) {
static_cast<int>(sizeof(keyBuf)), nullptr, 0))
break;
const std::string key(keyBuf);
const std::string prefix = kProjExtUsageKeyPrefix;
if (key.compare(0, prefix.size(), prefix) == 0) usageKeys.push_back(key);
}
}
@@ -198,13 +210,17 @@ UsageScanResult liveInstanceHeldPaths(void* projOpaque) {
std::vector<std::optional<UsageRecord>> decoded;
decoded.reserve(usageKeys.size());
for (const std::string& key : usageKeys) {
for (std::size_t ki = 0; ki < usageKeys.size(); ++ki) {
const std::string& key = usageKeys[ki];
const std::optional<std::string> value = readExtStateValue(proj, key.c_str());
if (!value) {
decoded.push_back(std::nullopt); // unreadable -> abort (pure fold)
result.offendingKeys.push_back(key);
continue;
}
decoded.push_back(decodeUsageRecord(*value)); // undecodable -> nullopt -> abort
const std::optional<UsageRecord> rec = decodeUsageRecord(*value);
if (!rec) result.offendingKeys.push_back(key);
decoded.push_back(rec); // undecodable nullopt -> abort
}
// 2. Enumerate live ReaSampler 9000 hosts. One channel-frozen needle set drives
@@ -250,6 +266,9 @@ UsageScanResult liveInstanceHeldPaths(void* projOpaque) {
const UsageFoldResult fold = foldUsageRecords(decoded, liveTrackGuids, anyLive);
result.abortPrune = fold.abortPrune;
result.heldPaths = fold.heldPaths;
// offendingKeys already populated above (unreadable + undecodable entries);
// clear it on success so callers see it only when abortPrune is set.
if (!result.abortPrune) result.offendingKeys.clear();
return result;
}