Files
emacs-doom/patches/0009-ns-harden-VoiceOver-accessibility-resource-safety.patch
Daneel acc2a2985e patches: add 0009 resource safety hardening + update 0007/0008
New patch 0009 fixes HIGH severity issues from Opus review:
- Announcement coalescing (50ms debounce)
- cachedText retain+autorelease in accessibilityValue
- EmacsView dealloc: nil out emacsView on all AX elements
- Nil guards on protocol methods + overlayZoomActive

0007 updated: revert accidental em-dash→triple-dash, add overlayZoomActive nil guards
0008 updated: specpdl exception safety for accessibilityUpdating, lastChildFrameBuffer staticpro

Series now 9 patches total (0001-0006 unchanged, 0007-0009 new/updated).
2026-02-28 18:45:30 +01:00

125 lines
4.2 KiB
Diff

From 0812e650c24f90bda79368078fa0ad45c18f39d2 Mon Sep 17 00:00:00 2001
From: T <t@t>
Date: Sat, 28 Feb 2026 18:45:14 +0100
Subject: [PATCH 3/3] ns: harden VoiceOver accessibility resource safety
Fix several resource safety issues found during maintainer review:
* Announcement coalescing: add 50ms minimum interval between
AnnouncementRequested notifications to prevent VoiceOver speech
synthesizer stalls from rapid-fire high-priority interruptions
(e.g. holding C-n in a completion list).
* cachedText thread safety: return [[cachedText retain] autorelease]
from accessibilityValue to prevent use-after-free when the main
thread replaces cachedText while the AX server thread is still
using the previous value.
* EmacsView dealloc safety: nil out emacsView back-references on
all accessibility elements before releasing them. Queued
dispatch_async blocks that hold a retained element reference would
otherwise access a dangling emacsView pointer.
* Nil guards: add emacsView nil checks in accessibilityParent,
accessibilityWindow, accessibilityTopLevelUIElement, and
overlayZoomActive access sites.
* src/nsterm.m (ns_ax_post_notification_with_info): Add timestamp
coalescing for AnnouncementRequested.
(accessibilityValue): Return retained+autoreleased cachedText.
(dealloc): Nil out emacsView on all accessibility elements.
(accessibilityParent, accessibilityWindow)
(accessibilityTopLevelUIElement): Add nil guards.
---
src/nsterm.m | 38 +++++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/src/nsterm.m b/src/nsterm.m
index abecb4c..3724b05 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -7521,11 +7521,32 @@ ns_ax_post_notification (id element,
});
}
+/* Minimum interval between AnnouncementRequested notifications
+ (in seconds). VoiceOver can stall if overwhelmed with rapid-fire
+ high-priority announcements that each interrupt the previous
+ utterance. 50ms lets the speech synthesizer start before the
+ next interruption. */
+#define NS_AX_ANNOUNCE_MIN_INTERVAL 0.05
+
static inline void
ns_ax_post_notification_with_info (id element,
NSAccessibilityNotificationName name,
NSDictionary *info)
{
+ /* Coalesce AnnouncementRequested: skip if the previous one was
+ less than NS_AX_ANNOUNCE_MIN_INTERVAL seconds ago. Prevents
+ speech synthesizer stalls from rapid-fire high-priority
+ interruptions (e.g. holding C-n in a completion list). */
+ if ([name isEqualToString:
+ NSAccessibilityAnnouncementRequestedNotification])
+ {
+ static CFAbsoluteTime lastAnnouncementTime;
+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent ();
+ if (now - lastAnnouncementTime < NS_AX_ANNOUNCE_MIN_INTERVAL)
+ return;
+ lastAnnouncementTime = now;
+ }
+
dispatch_async (dispatch_get_main_queue (), ^{
NSAccessibilityPostNotificationWithUserInfo (element, name, info);
});
@@ -7571,16 +7592,22 @@ ns_ax_post_notification_with_info (id element,
- (id)accessibilityParent
{
+ if (!self.emacsView)
+ return nil;
return NSAccessibilityUnignoredAncestor (self.emacsView);
}
- (id)accessibilityWindow
{
+ if (!self.emacsView)
+ return nil;
return [self.emacsView window];
}
- (id)accessibilityTopLevelUIElement
{
+ if (!self.emacsView)
+ return nil;
return [self.emacsView window];
}
@@ -8143,7 +8170,7 @@ ns_ax_completion_text_for_span (EmacsAccessibilityBuffer *elem,
return result;
}
[self ensureTextCache];
- return cachedText ? cachedText : @"";
+ return cachedText ? [[cachedText retain] autorelease] : @"";
}
- (NSInteger)accessibilityNumberOfCharacters
@@ -9659,6 +9686,15 @@ ns_ax_scan_interactive_spans (struct window *w,
[layer release];
#endif
+ /* Nil out back-references before releasing elements. Queued
+ dispatch_async blocks may still hold a retained reference to
+ an element; without this they would access a dangling
+ emacsView pointer after EmacsView is freed. */
+ for (id elem in accessibilityElements)
+ {
+ if ([elem respondsToSelector:@selector (setEmacsView:)])
+ [elem setEmacsView:nil];
+ }
[accessibilityElements release];
[[self menu] release];
[super dealloc];
--
2.43.0