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).
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user