Skip to content

Commit 2aeb09e

Browse files
ProfKenUstondaschuer
authored andcommitted
EffectsBackend: fix crash when AU manifest loading times out
AudioUnitBackend loads manifests asynchronously via dispatch_group_async with a 6-second timeout. If the timeout fires, m_manifestsById is only partially populated, but getEffectIds() was iterating m_componentsById (populated synchronously for every discovered AU). Callers would then receive null EffectManifestPointers from getManifest(), and the next std::sort on the combined manifest list would dereference null in EffectManifest::sortLexigraphically and segfault. Fix: - AudioUnitBackend::getEffectIds() now returns keys from m_manifestsById so only fully loaded manifests are reported. The three map accessors also take m_mutex (now mutable) to stop racing the loader threads. - EffectManifest::sortLexigraphically null-checks both arguments as defensive insurance for any other backend that might expose a null.
1 parent 30a3fb9 commit 2aeb09e

2 files changed

Lines changed: 20 additions & 9 deletions

File tree

src/effects/backends/audiounit/audiounitbackend.mm

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,22 @@ EffectBackendType getType() const override {
3333
};
3434

3535
const QList<QString> getEffectIds() const override {
36-
QList<QString> effectIds;
37-
38-
for (NSString* effectId in m_componentsById) {
39-
effectIds.append(QString::fromNSString(effectId));
40-
}
41-
42-
return effectIds;
36+
// Only report effects whose manifest actually finished loading.
37+
// If loadAudioUnitsOfType() timed out, some components in
38+
// m_componentsById may not have a corresponding manifest yet, and
39+
// callers would get a null EffectManifestPointer from getManifest(),
40+
// which crashes downstream (e.g. EffectManifest::sortLexigraphically).
41+
auto locker = lockMutex(&m_mutex);
42+
return m_manifestsById.keys();
4343
}
4444

4545
EffectManifestPointer getManifest(const QString& effectId) const override {
46-
return m_manifestsById[effectId];
46+
auto locker = lockMutex(&m_mutex);
47+
return m_manifestsById.value(effectId);
4748
}
4849

4950
const QList<EffectManifestPointer> getManifests() const override {
51+
auto locker = lockMutex(&m_mutex);
5052
return m_manifestsById.values();
5153
}
5254

@@ -64,7 +66,7 @@ bool canInstantiateEffect(const QString& effectId) const override {
6466
private:
6567
NSMutableDictionary<NSString*, AVAudioUnitComponent*>* m_componentsById;
6668
QHash<QString, EffectManifestPointer> m_manifestsById;
67-
QMutex m_mutex;
69+
mutable QMutex m_mutex;
6870

6971
void loadAudioUnits() {
7072
qDebug() << "Loading audio units...";

src/effects/backends/effectmanifest.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ bool EffectManifest::hasMetaKnobLinking() const {
1616

1717
bool EffectManifest::sortLexigraphically(
1818
EffectManifestPointer pManifest1, EffectManifestPointer pManifest2) {
19+
// Defensive: null manifests sort to the end. A backend whose async
20+
// manifest loading times out may briefly expose null entries; crashing
21+
// std::sort on them is worse than a transiently misordered list.
22+
if (!pManifest1) {
23+
return false;
24+
}
25+
if (!pManifest2) {
26+
return true;
27+
}
1928
// Sort built-in effects first before external plugins
2029
int backendNameComparision = static_cast<int>(pManifest1->backendType()) -
2130
static_cast<int>(pManifest2->backendType());

0 commit comments

Comments
 (0)