Subject: Re: bug#78943: feature/igc [PATCH] Trace current minor maps exactly

From: Helmut Eller <[email-masked]>
Date: Tue, 15 Jul 2025 19:12:05 +0200

On Sat, Jul 12 2025, Pip Cet wrote:

> Thanks.  It'd be nice to have a more general approach to growable
> vectors, but in this case, I don't understand why we use array
> structures at all: two simple linked lists should work just as well.

It seems that arrays are used because in some places the active minor
maps need to be processed in the reversed order from the order they are
stored in minor-mode-map-alist.

[...]
> My idea still would be to grow a vector aggressively until it's large
> enough to fit all elements, then "truncate" it in place in such a way
> that the GC knows to shrink it when it gets copied to the next
> generation.

This could work with MPS but I'm not so sure about the classic GC.

Here is a bit a different approach that doesn't use the cmm_vector:

  1. adds some test cases for the following changes

  2. uses Fcurrent_active_maps in keyboard.c and replaces the duplicated
     code with a common function

  3. replaces the remaining uses of current_minor_maps with
     functions that create some temporary lists

  4. This is an optional improvement for 2, if we are hellbent on
     avoiding consing & reversing the list returned from
     Fcurrent_active_maps.  This patch avoids the list at the cost of
     some complexity and alloca.

>From a277abba2f10c94af3d57ffae62570ec7f3fce0b Mon Sep 17 00:00:00 2001
From: Helmut Eller <[email-masked]>
Date: Mon, 14 Jul 2025 20:41:33 +0200
Subject: [PATCH 1/4] Add some minor-mode keymap related tests

* test/src/keymap-tests.el (keymap-tests--current-active-maps/global-map)
(keymap-tests--current-active-maps/local-map)
(keymap-tests--current-active-maps/minor-maps)
(keymap-tests--current-active-maps/olm)
(keymap-tests--current-minor-mode-maps)
(keymap-tests--minor-mode-key-binding): New tests.
---
 test/src/keymap-tests.el | 122 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 122 insertions(+)

diff --git a/test/src/keymap-tests.el b/test/src/keymap-tests.el
index c605c3eb09d..2acf57f9b82 100644
--- a/test/src/keymap-tests.el
+++ b/test/src/keymap-tests.el
@@ -509,6 +509,128 @@ keymap-unset-test-remove-and-inheritance
     ;; From the parent this time/
     (should (equal (keymap-lookup map "u") #'undo))))
 
+
+;;;; current-active-maps
+
+(ert-deftest keymap-tests--current-active-maps/global-map ()
+  (with-temp-buffer
+    (should (equal (current-active-maps) (list global-map)))))
+
+(ert-deftest keymap-tests--current-active-maps/local-map ()
+  (with-temp-buffer
+    (let ((local (make-sparse-keymap "local")))
+      (use-local-map local)
+      (should (equal (current-active-maps) (list local global-map)))
+      (let ((m1 (make-sparse-keymap "m1"))
+            (m2 (make-sparse-keymap "m2")))
+        (insert "abcd")
+        (add-text-properties 2 3 (list 'keymap m1 'local-map m2))
+        (should (equal (current-active-maps nil 1) (list local global-map)))
+        (should (equal (current-active-maps nil 2) (list m1 m2 global-map)))
+        ;; 'keymap and 'local-map are "rear-sticky"
+        (should (equal (current-active-maps nil 3) (list m1 m2 global-map)))
+        (should (equal (current-active-maps nil 4)
+                       (list local global-map)))))))
+
+(defvar keymap-tests-mm-2-x-map (make-sparse-keymap "mm-2 x"))
+(defvar keymap-tests-mm-4-x-map (make-sparse-keymap "mm-4 x"))
+
+(defvar-keymap keymap-tests-minor-mode-2-map "x" keymap-tests-mm-2-x-map)
+(defvar-keymap keymap-tests-minor-mode-3-map "x" 'keymap-tests--command-3)
+(defvar-keymap keymap-tests-minor-mode-4-map "x" keymap-tests-mm-4-x-map)
+
+(define-minor-mode keymap-tests-minor-mode-2 "mm 2.")
+(define-minor-mode keymap-tests-minor-mode-3 "mm 3.")
+(define-minor-mode keymap-tests-minor-mode-4 "mm 4.")
+
+(ert-deftest keymap-tests--current-active-maps/minor-maps ()
+  (with-temp-buffer
+    (keymap-tests-major-mode)
+    (keymap-tests-minor-mode)
+    (keymap-tests-minor-mode-3)
+    (should (equal (current-active-maps)
+                   (list keymap-tests-minor-mode-3-map
+                         keymap-tests-minor-mode-map
+                         keymap-tests-major-mode-map
+                         global-map))))
+  ;; the order of the minor maps is determined by minor-mode-map-alist
+  (with-temp-buffer
+    (keymap-tests-major-mode)
+    (keymap-tests-minor-mode-3)
+    (keymap-tests-minor-mode)
+    (should (equal (current-active-maps)
+                   (list keymap-tests-minor-mode-3-map
+                         keymap-tests-minor-mode-map
+                         keymap-tests-major-mode-map
+                         global-map)))))
+
+(ert-deftest keymap-tests--current-active-maps/olm ()
+  (with-temp-buffer
+    (keymap-tests-major-mode)
+    (keymap-tests-minor-mode)
+    (keymap-tests-minor-mode-3)
+    (let ((overriding-local-map (make-sparse-keymap "olp")))
+      (should (equal (current-active-maps t)
+                     (list overriding-local-map
+                           global-map)))
+      (should (equal (current-active-maps nil)
+                     (list keymap-tests-minor-mode-3-map
+                           keymap-tests-minor-mode-map
+                           keymap-tests-major-mode-map
+                           global-map))))
+    (let ((overriding-terminal-local-map (make-sparse-keymap "otlm")))
+      (should (equal (current-active-maps t)
+                     (list overriding-terminal-local-map
+                           keymap-tests-minor-mode-3-map
+                           keymap-tests-minor-mode-map
+                           keymap-tests-major-mode-map
+                           global-map))))
+    (let ((overriding-terminal-local-map (make-sparse-keymap "otlm"))
+          (overriding-local-map (make-sparse-keymap "olm"))
+          (m1 (make-sparse-keymap "m1"))
+          (m2 (make-sparse-keymap "m2")))
+      (insert "abcd")
+      (add-text-properties 2 3 (list 'keymap m1 'local-map m2))
+      (should (equal (current-active-maps t 2)
+                     (list overriding-terminal-local-map
+                           m1
+                           keymap-tests-minor-mode-3-map
+                           keymap-tests-minor-mode-map
+                           m2
+                           global-map))))))
+
+
+;;;; current-minor-mode-maps
+
+(ert-deftest keymap-tests--current-minor-mode-maps ()
+  (with-temp-buffer
+    (keymap-tests-minor-mode)
+    (keymap-tests-minor-mode-3)
+    (keymap-tests-minor-mode-2)
+    (keymap-tests-minor-mode-4)
+    (should (equal (current-minor-mode-maps)
+                   (list keymap-tests-minor-mode-4-map
+                         keymap-tests-minor-mode-3-map
+                         keymap-tests-minor-mode-2-map
+                         keymap-tests-minor-mode-map)))))
+
+(ert-deftest keymap-tests--minor-mode-key-binding ()
+  (with-temp-buffer
+    (keymap-tests-minor-mode)
+    (keymap-tests-minor-mode-2)
+    (keymap-tests-minor-mode-3)
+    (should (equal (minor-mode-key-binding "x")
+                   '((keymap-tests-minor-mode-3 . keymap-tests--command-3)))))
+  (with-temp-buffer
+    (keymap-tests-minor-mode)
+    (keymap-tests-minor-mode-2)
+    (keymap-tests-minor-mode-3)
+    (keymap-tests-minor-mode-4)
+    (should
+     (equal (minor-mode-key-binding "x")
+            `((keymap-tests-minor-mode-4 . ,keymap-tests-mm-4-x-map)
+              (keymap-tests-minor-mode-2 . ,keymap-tests-mm-2-x-map))))))
+
 (provide 'keymap-tests)
 
 ;;; keymap-tests.el ends here
-- 
2.39.5

>From 5bb20d21b29f56c56416ae7d748a497eb19dfc8c Mon Sep 17 00:00:00 2001
From: Helmut Eller <[email-masked]>
Date: Mon, 14 Jul 2025 21:44:37 +0200
Subject: [PATCH 2/4] Refactor menu_bar_items, tab_bar_items, and
 tool_bar_items

Move duplicated code to the new function process_prefix_maps that the
others can call.

Also use Fcurrent_active_maps instead of manually building an array
of active keymaps.

* src/keyboard.c (process_prefix_maps): New function.
(menu_bar_items, tab_bar_items, tool_bar_items): Use it.
(menu_bar_item): Reset menu_bar_one_keymap_changed_items here.
---
 src/keyboard.c | 233 +++++++++----------------------------------------
 1 file changed, 41 insertions(+), 192 deletions(-)

diff --git a/src/keyboard.c b/src/keyboard.c
index b389d98feaa..4dd38f13bb7 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -8568,32 +8568,41 @@ menu_separator_name_p (const char *label)
   return 0;
 }
 
+/* Look up in each active map the event PREFIX (e.g. Qmenu_bar) and if
+   the result is a keymap, then call F for each binding in that keymap.
+
+   The active maps are processed in reverse order, i.e. global-map
+   first. */
+static void
+process_prefix_maps (Lisp_Object prefix, map_keymap_function_t f)
+{
+  for (Lisp_Object maps = Fnreverse (Fcurrent_active_maps (Qnil, Qnil));
+       !NILP (maps); maps = XCDR (maps))
+    {
+      Lisp_Object map = XCAR (maps);
+      Lisp_Object def = get_keymap (access_keymap (map, prefix, 1, 0, 1),
+				    0, 1);
+      if (CONSP (def))
+	map_keymap_canonical (def, f, Qnil, NULL);
+    }
+}
 
 /* Return a vector of menu items for a menu bar, appropriate
    to the current buffer.  Each item has three elements in the vector:
    KEY STRING MAPLIST.
 
-   OLD is an old vector we can optionally reuse, or nil.  */
+   OLD is an old vector we can optionally reuse, or nil.
+
+   Note that menu-bar bindings in the local-map and keymap properties
+   may not work reliable, as they are only recognized when the menu-bar
+   (or mode-line) is updated, which does not normally happen after every
+   command.  */
 
 Lisp_Object
 menu_bar_items (Lisp_Object old)
 {
-  /* The number of keymaps we're scanning right now, and the number of
-     keymaps we have allocated space for.  */
-  ptrdiff_t nmaps;
-
-  /* maps[0..nmaps-1] are the prefix definitions of KEYBUF[0..t-1]
-     in the current keymaps, or nil where it is not a prefix.  */
-  Lisp_Object *maps;
-
-  Lisp_Object mapsbuf[3];
-  Lisp_Object def;
-
-  ptrdiff_t mapno;
   Lisp_Object oquit;
 
-  USE_SAFE_ALLOCA;
-
   /* In order to build the menus, we need to call the keymap
      accessors.  They all call maybe_quit.  But this function is called
      during redisplay, during which a quit is fatal.  So inhibit
@@ -8609,64 +8618,7 @@ menu_bar_items (Lisp_Object old)
     menu_bar_items_vector = make_nil_vector (24);
   menu_bar_items_index = 0;
 
-  /* Build our list of keymaps.
-     If we recognize a function key and replace its escape sequence in
-     keybuf with its symbol, or if the sequence starts with a mouse
-     click and we need to switch buffers, we jump back here to rebuild
-     the initial keymaps from the current buffer.  */
-  {
-    Lisp_Object *tmaps;
-
-    /* Should overriding-terminal-local-map and overriding-local-map apply?  */
-    if (!NILP (Voverriding_local_map_menu_flag)
-	&& !NILP (Voverriding_local_map))
-      {
-	/* Yes, use them (if non-nil) as well as the global map.  */
-	maps = mapsbuf;
-	nmaps = 0;
-	if (!NILP (KVAR (current_kboard, Voverriding_terminal_local_map)))
-	  maps[nmaps++] = KVAR (current_kboard, Voverriding_terminal_local_map);
-	if (!NILP (Voverriding_local_map))
-	  maps[nmaps++] = Voverriding_local_map;
-      }
-    else
-      {
-	/* No, so use major and minor mode keymaps and keymap property.
-	   Note that menu-bar bindings in the local-map and keymap
-	   properties may not work reliable, as they are only
-	   recognized when the menu-bar (or mode-line) is updated,
-	   which does not normally happen after every command.  */
-	ptrdiff_t nminor = current_minor_maps (NULL, &tmaps);
-	SAFE_NALLOCA (maps, 1, nminor + 4);
-	nmaps = 0;
-	Lisp_Object tem = KVAR (current_kboard, Voverriding_terminal_local_map);
-	if (!NILP (tem) && !NILP (Voverriding_local_map_menu_flag))
-	  maps[nmaps++] = tem;
-	if (tem = get_local_map (PT, current_buffer, Qkeymap), !NILP (tem))
-	  maps[nmaps++] = tem;
-	if (nminor != 0)
-	  {
-	    memcpy (maps + nmaps, tmaps, nminor * sizeof (maps[0]));
-	    nmaps += nminor;
-	  }
-	maps[nmaps++] = get_local_map (PT, current_buffer, Qlocal_map);
-      }
-    maps[nmaps++] = current_global_map;
-  }
-
-  /* Look up in each map the dummy prefix key `menu-bar'.  */
-
-  for (mapno = nmaps - 1; mapno >= 0; mapno--)
-    if (!NILP (maps[mapno]))
-      {
-	def = get_keymap (access_keymap (maps[mapno], Qmenu_bar, 1, 0, 1),
-			  0, 1);
-	if (CONSP (def))
-	  {
-	    menu_bar_one_keymap_changed_items = Qnil;
-	    map_keymap_canonical (def, menu_bar_item, Qnil, NULL);
-	  }
-      }
+  process_prefix_maps (Qmenu_bar, menu_bar_item);
 
   /* Move to the end those items that should be at the end.  */
 
@@ -8712,7 +8664,6 @@ menu_bar_items (Lisp_Object old)
   }
 
   Vinhibit_quit = oquit;
-  SAFE_FREE ();
   return menu_bar_items_vector;
 }
 
@@ -8728,6 +8679,8 @@ menu_bar_item (Lisp_Object key, Lisp_Object item, Lisp_Object dummy1, void *dumm
   bool parsed;
   Lisp_Object tem;
 
+  menu_bar_one_keymap_changed_items = Qnil;
+
   if (EQ (item, Qundefined))
     {
       /* If a map has an explicit `undefined' as definition,
@@ -9143,17 +9096,17 @@ parse_menu_item (Lisp_Object item, int inmenubar)
 
 /* Return a vector of tab bar items for keymaps currently in effect.
    Reuse vector REUSE if non-nil.  Return in *NITEMS the number of
-   tab bar items found.  */
+   tab bar items found.
+
+   Note that tab-bar bindings in the local-map and keymap properties may
+   not work reliably, as they are only recognized when the tab-bar (or
+   mode-line) is updated, which does not normally happen after every
+   command.  */
 
 Lisp_Object
 tab_bar_items (Lisp_Object reuse, int *nitems)
 {
-  Lisp_Object *maps;
-  Lisp_Object mapsbuf[3];
-  ptrdiff_t nmaps, i;
   Lisp_Object oquit;
-  Lisp_Object *tmaps;
-  USE_SAFE_ALLOCA;
 
   *nitems = 0;
 
@@ -9169,62 +9122,10 @@ tab_bar_items (Lisp_Object reuse, int *nitems)
   /* Initialize tab_bar_items_vector and protect it from GC.  */
   init_tab_bar_items (reuse);
 
-  /* Build list of keymaps in maps.  Set nmaps to the number of maps
-     to process.  */
-
-  /* Should overriding-terminal-local-map and overriding-local-map apply?  */
-  if (!NILP (Voverriding_local_map_menu_flag)
-      && !NILP (Voverriding_local_map))
-    {
-      /* Yes, use them (if non-nil) as well as the global map.  */
-      maps = mapsbuf;
-      nmaps = 0;
-      if (!NILP (KVAR (current_kboard, Voverriding_terminal_local_map)))
-	maps[nmaps++] = KVAR (current_kboard, Voverriding_terminal_local_map);
-      if (!NILP (Voverriding_local_map))
-	maps[nmaps++] = Voverriding_local_map;
-    }
-  else
-    {
-      /* No, so use major and minor mode keymaps and keymap property.
-	 Note that tab-bar bindings in the local-map and keymap
-	 properties may not work reliably, as they are only
-	 recognized when the tab-bar (or mode-line) is updated,
-	 which does not normally happen after every command.  */
-      ptrdiff_t nminor = current_minor_maps (NULL, &tmaps);
-      SAFE_NALLOCA (maps, 1, nminor + 4);
-      nmaps = 0;
-      Lisp_Object tem = KVAR (current_kboard, Voverriding_terminal_local_map);
-      if (!NILP (tem) && !NILP (Voverriding_local_map_menu_flag))
-	maps[nmaps++] = tem;
-      if (tem = get_local_map (PT, current_buffer, Qkeymap), !NILP (tem))
-	maps[nmaps++] = tem;
-      if (nminor != 0)
-	{
-	  memcpy (maps + nmaps, tmaps, nminor * sizeof (maps[0]));
-	  nmaps += nminor;
-	}
-      maps[nmaps++] = get_local_map (PT, current_buffer, Qlocal_map);
-    }
-
-  /* Add global keymap at the end.  */
-  maps[nmaps++] = current_global_map;
-
-  /* Process maps in reverse order and look up in each map the prefix
-     key `tab-bar'.  */
-  for (i = nmaps - 1; i >= 0; --i)
-    if (!NILP (maps[i]))
-      {
-	Lisp_Object keymap;
-
-	keymap = get_keymap (access_keymap (maps[i], Qtab_bar, 1, 0, 1), 0, 1);
-	if (CONSP (keymap))
-	  map_keymap (keymap, process_tab_bar_item, Qnil, NULL, 1);
-      }
+  process_prefix_maps (Qtab_bar, process_tab_bar_item);
 
   Vinhibit_quit = oquit;
   *nitems = ntab_bar_items / TAB_BAR_ITEM_NSLOTS;
-  SAFE_FREE ();
   return tab_bar_items_vector;
 }
 
@@ -9527,17 +9428,17 @@ append_tab_bar_item (void)
 
 /* Return a vector of tool bar items for keymaps currently in effect.
    Reuse vector REUSE if non-nil.  Return in *NITEMS the number of
-   tool bar items found.  */
+   tool bar items found.
+
+   Note that tool-bar bindings in the local-map and keymap properties
+   may not work reliably, as they are only recognized when the tool-bar
+   (or mode-line) is updated, which does not normally happen after every
+   command.  */
 
 Lisp_Object
 tool_bar_items (Lisp_Object reuse, int *nitems)
 {
-  Lisp_Object *maps;
-  Lisp_Object mapsbuf[3];
-  ptrdiff_t nmaps, i;
   Lisp_Object oquit;
-  Lisp_Object *tmaps;
-  USE_SAFE_ALLOCA;
 
   *nitems = 0;
 
@@ -9553,62 +9454,10 @@ tool_bar_items (Lisp_Object reuse, int *nitems)
   /* Initialize tool_bar_items_vector and protect it from GC.  */
   init_tool_bar_items (reuse);
 
-  /* Build list of keymaps in maps.  Set nmaps to the number of maps
-     to process.  */
-
-  /* Should overriding-terminal-local-map and overriding-local-map apply?  */
-  if (!NILP (Voverriding_local_map_menu_flag)
-      && !NILP (Voverriding_local_map))
-    {
-      /* Yes, use them (if non-nil) as well as the global map.  */
-      maps = mapsbuf;
-      nmaps = 0;
-      if (!NILP (KVAR (current_kboard, Voverriding_terminal_local_map)))
-	maps[nmaps++] = KVAR (current_kboard, Voverriding_terminal_local_map);
-      if (!NILP (Voverriding_local_map))
-	maps[nmaps++] = Voverriding_local_map;
-    }
-  else
-    {
-      /* No, so use major and minor mode keymaps and keymap property.
-	 Note that tool-bar bindings in the local-map and keymap
-	 properties may not work reliably, as they are only
-	 recognized when the tool-bar (or mode-line) is updated,
-	 which does not normally happen after every command.  */
-      ptrdiff_t nminor = current_minor_maps (NULL, &tmaps);
-      SAFE_NALLOCA (maps, 1, nminor + 4);
-      nmaps = 0;
-      Lisp_Object tem = KVAR (current_kboard, Voverriding_terminal_local_map);
-      if (!NILP (tem) && !NILP (Voverriding_local_map_menu_flag))
-	maps[nmaps++] = tem;
-      if (tem = get_local_map (PT, current_buffer, Qkeymap), !NILP (tem))
-	maps[nmaps++] = tem;
-      if (nminor != 0)
-	{
-	  memcpy (maps + nmaps, tmaps, nminor * sizeof (maps[0]));
-	  nmaps += nminor;
-	}
-      maps[nmaps++] = get_local_map (PT, current_buffer, Qlocal_map);
-    }
-
-  /* Add global keymap at the end.  */
-  maps[nmaps++] = current_global_map;
-
-  /* Process maps in reverse order and look up in each map the prefix
-     key `tool-bar'.  */
-  for (i = nmaps - 1; i >= 0; --i)
-    if (!NILP (maps[i]))
-      {
-	Lisp_Object keymap;
-
-	keymap = get_keymap (access_keymap (maps[i], Qtool_bar, 1, 0, 1), 0, 1);
-	if (CONSP (keymap))
-	  map_keymap (keymap, process_tool_bar_item, Qnil, NULL, 1);
-      }
+  process_prefix_maps (Qtool_bar, process_tool_bar_item);
 
   Vinhibit_quit = oquit;
   *nitems = ntool_bar_items / TOOL_BAR_ITEM_NSLOTS;
-  SAFE_FREE ();
   return tool_bar_items_vector;
 }
 
-- 
2.39.5

>From dcc83202622c50fca47aebdf90980acae3e27939 Mon Sep 17 00:00:00 2001
From: Helmut Eller <[email-masked]>
Date: Tue, 15 Jul 2025 13:23:17 +0200
Subject: [PATCH 3/4] Eliminate the global arrays cmm_modes and cmm_maps

Replace the function current_minor_maps with traverse_minor_maps which
doesn't use these arrays.

* src/keymap.h (current_minor_maps): Deleted.
* src/keymap.c (current_minor_maps): Deleted.
(traverse_minor_maps): Traversal code factored out from the old
current_minor_maps.
(current_minor_mode_map_alist): New function. For cases where consing up
a temporary list is acceptable.
(prepend_minor_mode_keymaps): New function. For use in
Fcurrent_active_maps.
(struct tconc, tconc_push, tconc_append, prepend_minor_mode_keymaps_2)
(current_minor_mode_map_alist_2): New helpers.
(Fcurrent_active_maps, Fcurrent_minor_mode_maps): Use
prepend_minor_mode_keymaps instead of current_minor_modes.
(Fminor_mode_key_binding, Fdescribe_buffer_bindings): Use
current_minor_mode_map_alist instead current_minor_modes.
---
 src/keymap.c | 251 ++++++++++++++++++++++++---------------------------
 src/keymap.h |   1 -
 2 files changed, 119 insertions(+), 133 deletions(-)

diff --git a/src/keymap.c b/src/keymap.c
index 2c250578b00..e6ae0b5abf2 100644
--- a/src/keymap.c
+++ b/src/keymap.c
@@ -1504,34 +1504,17 @@ silly_event_symbol_error (Lisp_Object c)
 
 /* Global, local, and minor mode keymap stuff.				*/
 
-/* We can't put these variables inside current_minor_maps, since under
-   some systems, static gets macro-defined to be the empty string.
-   Ickypoo.  */
-static Lisp_Object *cmm_modes = NULL, *cmm_maps = NULL;
-static ptrdiff_t cmm_size = 0;
-
-/* Store a pointer to an array of the currently active minor modes in
-   *modeptr, a pointer to an array of the keymaps of the currently
-   active minor modes in *mapptr, and return the number of maps
-   *mapptr contains.
-
-   This function always returns a pointer to the same buffer, and may
-   free or reallocate it, so if you want to keep it for a long time or
-   hand it out to lisp code, copy it.  This procedure will be called
-   for every key sequence read, so the nice lispy approach (return a
-   new assoclist, list, what have you) for each invocation would
-   result in a lot of consing over time.
-
-   If we used xrealloc/xmalloc and ran out of memory, they would throw
-   back to the command loop, which would try to read a key sequence,
-   which would call this function again, resulting in an infinite
-   loop.  Instead, we'll use realloc/malloc and silently truncate the
-   list, let the key sequence be read, and hope some other piece of
-   code signals the error.  */
-ptrdiff_t
-current_minor_maps (Lisp_Object **modeptr, Lisp_Object **mapptr)
-{
-  ptrdiff_t i = 0;
+/* Traverse the currently active minor modes, calling F for each
+   (minor-mode, keymap) pair.  Abort the traversal early, iff F returns
+   false.
+
+   Minor modes are visited in the same order as they occur in
+   Vminor_mode_map_alist and Vminor_mode_overriding_map_alist. */
+static void
+traverse_minor_maps (bool (*f) (Lisp_Object mode, Lisp_Object map,
+				void *closure),
+		     void *closure)
+{
   Lisp_Object alist, assoc, var, val;
   Lisp_Object emulation_alists = Vemulation_mode_map_alists;
   Lisp_Object lists[2];
@@ -1552,17 +1535,16 @@ current_minor_maps (Lisp_Object **modeptr, Lisp_Object **mapptr)
       else
 	alist = lists[list_number];
 
-      for ( ; CONSP (alist); alist = XCDR (alist))
+      for (; CONSP (alist); alist = XCDR (alist))
 	if ((assoc = XCAR (alist), CONSP (assoc))
 	    && (var = XCAR (assoc), SYMBOLP (var))
-	    && (val = find_symbol_value (var), !BASE_EQ (val, Qunbound))
+	    && (val = find_symbol_value (var),
+		!BASE_EQ (val, Qunbound))
 	    && !NILP (val))
 	  {
-	    Lisp_Object temp;
-
-	    /* If a variable has an entry in Vminor_mode_overriding_map_alist,
-	       and also an entry in Vminor_mode_map_alist,
-	       ignore the latter.  */
+	    /* If a variable has an entry in
+	       Vminor_mode_overriding_map_alist, and also an entry in
+	       Vminor_mode_map_alist, ignore the latter.  */
 	    if (list_number == 1)
 	      {
 		val = assq_no_quit (var, lists[0]);
@@ -1570,68 +1552,83 @@ current_minor_maps (Lisp_Object **modeptr, Lisp_Object **mapptr)
 		  continue;
 	      }
 
-	    if (i >= cmm_size)
-	      {
-		ptrdiff_t newsize, allocsize;
-		Lisp_Object *newmodes, *newmaps;
-
-		/* Check for size calculation overflow.  Other code
-		   (e.g., read_key_sequence) adds 3 to the count
-		   later, so subtract 3 from the limit here.  */
-		if (min (PTRDIFF_MAX, SIZE_MAX) / (2 * sizeof *newmodes) - 3
-		    < cmm_size)
-		  break;
-
-		newsize = cmm_size == 0 ? 30 : cmm_size * 2;
-		allocsize = newsize * sizeof *newmodes;
-
-		/* Use malloc here.  See the comment above this function.
-		   Avoid realloc here; it causes spurious traps on GNU/Linux [KFS] */
-		block_input ();
-		newmodes = malloc (allocsize);
-		if (newmodes)
-		  {
-		    if (cmm_modes)
-		      {
-			memcpy (newmodes, cmm_modes,
-				cmm_size * sizeof cmm_modes[0]);
-			free (cmm_modes);
-		      }
-		    cmm_modes = newmodes;
-		  }
+	    /* Get the keymap definition--or nil if it is not
+	       defined. */
+	    Lisp_Object map = Findirect_function (XCDR (assoc), Qt);
+	    if (NILP (map))
+	      continue;
+	    if (!f (var, map, closure))
+	      return;
+	  }
+    }
+}
 
-		newmaps = malloc (allocsize);
-		if (newmaps)
-		  {
-		    if (cmm_maps)
-		      {
-			memcpy (newmaps, cmm_maps,
-				cmm_size * sizeof cmm_maps[0]);
-			free (cmm_maps);
-		      }
-		    cmm_maps = newmaps;
-		  }
-		unblock_input ();
+/* Used for "tail concatenation". */
+struct tconc
+{
+  Lisp_Object head, tail; /* head is nil, if the list is empty */
+};
 
-		if (newmodes == NULL || newmaps == NULL)
-		  break;
-		cmm_size = newsize;
-	      }
+/* Append an element at the end of T. */
+static void
+tconc_push (struct tconc *t, Lisp_Object item)
+{
+  Lisp_Object tail = list1 (item);
+  if (NILP (t->head))
+    t->head = tail;
+  else
+    XSETCDR (t->tail, tail);
+  t->tail = tail;
+}
 
-	    /* Get the keymap definition--or nil if it is not defined.  */
-	    temp = Findirect_function (XCDR (assoc), Qt);
-	    if (!NILP (temp))
-	      {
-		cmm_modes[i] = var;
-		cmm_maps [i] = temp;
-		i++;
-	      }
-	  }
-    }
+/* Concatenate T and LIST and return the result as a list. */
+static Lisp_Object
+tconc_append (struct tconc t, Lisp_Object list)
+{
+  if (NILP (t.head))
+    return list;
+  XSETCDR (t.tail, list);
+  return t.head;
+}
+
+static bool
+prepend_minor_mode_keymaps_2 (Lisp_Object mode, Lisp_Object map,
+			      void *closure)
+{
+  struct tconc *l = closure;
+  eassert (!NILP (map));
+  tconc_push (l, map);
+  return true;
+}
 
-  if (modeptr) *modeptr = cmm_modes;
-  if (mapptr)  *mapptr  = cmm_maps;
-  return i;
+/* Append the list of currently active minor mode keymaps and the list
+   KEYMAPS. */
+static Lisp_Object
+prepend_minor_mode_keymaps (Lisp_Object keymaps)
+{
+  struct tconc tconc = { Qnil, Qnil };
+  traverse_minor_maps (prepend_minor_mode_keymaps_2, &tconc);
+  return tconc_append (tconc, keymaps);
+}
+
+static bool
+current_minor_mode_map_alist_2 (Lisp_Object mode, Lisp_Object map,
+				void *closure)
+{
+  struct tconc *t = closure;
+  eassert (!NILP (map));
+  tconc_push (t, Fcons (mode, map));
+  return true;
+}
+
+/* Return an alist ((MODE . KEYMAP) ...) of the currently active minor
+   modes. */
+static Lisp_Object
+current_minor_mode_map_alist (void)
+{
+  struct tconc t = { Qnil, Qnil };
+  traverse_minor_maps (current_minor_mode_map_alist_2, &t);
+  return t.head;
 }
 
 /* Return the offset of POSITION, a click position, in the style of
@@ -1704,8 +1701,6 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
 
   if (NILP (XCDR (keymaps)))
     {
-      Lisp_Object *maps;
-      int nmaps;
       ptrdiff_t pt = click_position (position);
       /* This usually returns the buffer's local map,
 	 but that can be overridden by a `local-map' property.  */
@@ -1775,11 +1770,7 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
 	keymaps = Fcons (local_map, keymaps);
 
       /* Now put all the minor mode keymaps on the list.  */
-      nmaps = current_minor_maps (0, &maps);
-
-      for (int i = --nmaps; i >= 0; i--)
-	if (!NILP (maps[i]))
-	  keymaps = Fcons (maps[i], keymaps);
+      keymaps = prepend_minor_mode_keymaps (keymaps);
 
       if (!NILP (keymap))
 	keymaps = Fcons (keymap, keymaps);
@@ -1876,23 +1867,21 @@ DEFUN ("minor-mode-key-binding", Fminor_mode_key_binding, Sminor_mode_key_bindin
 bindings; see the description of `lookup-key' for more details about this.  */)
   (Lisp_Object key, Lisp_Object accept_default)
 {
-  Lisp_Object *modes, *maps;
-  int nmaps = current_minor_maps (&modes, &maps);
-  Lisp_Object binding = Qnil;
-
-  int j;
-  for (int i = j = 0; i < nmaps; i++)
-    if (!NILP (maps[i])
-	&& !NILP (binding = Flookup_key (maps[i], key, accept_default))
-	&& !FIXNUMP (binding))
-      {
-	if (KEYMAPP (binding))
-	  maps[j++] = Fcons (modes[i], binding);
-	else if (j == 0)
-	  return list1 (Fcons (modes[i], binding));
-      }
-
-  return Flist (j, maps);
+  Lisp_Object prefix_bindings = Qnil;
+  for (Lisp_Object alist = current_minor_mode_map_alist ();
+       !NILP (alist); alist = XCDR (alist))
+    {
+      Lisp_Object pair = XCAR (alist);
+      Lisp_Object mode = XCAR (pair);
+      Lisp_Object map = XCDR (pair);
+      eassert (!NILP (map));
+      Lisp_Object binding = Flookup_key (map, key, accept_default);
+      if (KEYMAPP (binding))
+	prefix_bindings = Fcons (Fcons (mode, binding), prefix_bindings);
+      else if (NILP (prefix_bindings))
+	return list1 (Fcons (mode, binding));
+    }
+  return Fnreverse (prefix_bindings);
 }
 
 DEFUN ("use-global-map", Fuse_global_map, Suse_global_map, 1, 1, 0,
@@ -1937,10 +1926,7 @@ DEFUN ("current-minor-mode-maps", Fcurrent_minor_mode_maps, Scurrent_minor_mode_
        doc: /* Return a list of keymaps for the minor modes of the current buffer.  */)
   (void)
 {
-  Lisp_Object *maps;
-  int nmaps = current_minor_maps (0, &maps);
-
-  return Flist (nmaps, maps);
+  return prepend_minor_mode_keymaps (Qnil);
 }
 
 /* Help functions for describing and documenting keymaps.		*/
@@ -2929,13 +2915,11 @@ DEFUN ("describe-buffer-bindings", Fdescribe_buffer_bindings, Sdescribe_buffer_b
   else
     {
       /* Print the minor mode and major mode keymaps.  */
-      Lisp_Object *modes, *maps;
 
       /* Temporarily switch to `buffer', so that we can get that buffer's
 	 minor modes correctly.  */
       Fset_buffer (buffer);
-
-      int nmaps = current_minor_maps (&modes, &maps);
+      Lisp_Object cmms = current_minor_mode_map_alist ();
       Fset_buffer (outbuf);
 
       start1 = get_local_map (BUF_PT (XBUFFER (buffer)),
@@ -2950,25 +2934,28 @@ DEFUN ("describe-buffer-bindings", Fdescribe_buffer_bindings, Sdescribe_buffer_b
 	}
 
       /* Print the minor mode maps.  */
-      for (int i = 0; i < nmaps; i++)
+      for (; !NILP (cmms); cmms = XCDR (cmms))
 	{
+	  Lisp_Object pair = XCAR (cmms);
+	  Lisp_Object mode = XCAR (pair);
+	  Lisp_Object map = XCDR (pair);
 	  /* The title for a minor mode keymap
 	     is constructed at run time.
 	     We let `help--describe-map-tree' do the actual insertion
 	     because it takes care of other features when doing so.  */
 	  char *title, *p;
 
-	  if (!SYMBOLP (modes[i]))
+	  if (!SYMBOLP (mode))
 	    emacs_abort ();
 
 	  USE_SAFE_ALLOCA;
-	  p = title = SAFE_ALLOCA (42 + SBYTES (SYMBOL_NAME (modes[i])));
+	  p = title = SAFE_ALLOCA (42 + SBYTES (SYMBOL_NAME (mode)));
 	  *p++ = '\f';
 	  *p++ = '\n';
 	  *p++ = '`';
-	  memcpy (p, SDATA (SYMBOL_NAME (modes[i])),
-		  SBYTES (SYMBOL_NAME (modes[i])));
-	  p += SBYTES (SYMBOL_NAME (modes[i]));
+	  memcpy (p, SDATA (SYMBOL_NAME (mode)),
+		  SBYTES (SYMBOL_NAME (mode)));
+	  p += SBYTES (SYMBOL_NAME (mode));
 	  *p++ = '\'';
 	  memcpy (p, " Minor Mode Bindings", strlen (" Minor Mode Bindings"));
 	  p += strlen (" Minor Mode Bindings");
@@ -2976,9 +2963,9 @@ DEFUN ("describe-buffer-bindings", Fdescribe_buffer_bindings, Sdescribe_buffer_b
 
 	  Lisp_Object msg = build_unibyte_string (title);
 	  calln (Qhelp__describe_map_tree,
-		 maps[i], Qt, shadow, prefix,
+		 map, Qt, shadow, prefix,
 		 msg, nomenu, Qnil, Qnil, Qnil, buffer);
-	  shadow = Fcons (maps[i], shadow);
+	  shadow = Fcons (map, shadow);
 	  SAFE_FREE ();
 	}
 
diff --git a/src/keymap.h b/src/keymap.h
index c9b47ffccd6..b93707b6368 100644
--- a/src/keymap.h
+++ b/src/keymap.h
@@ -36,7 +36,6 @@ #define KEYMAPP(m) (!NILP (get_keymap (m, false, false)))
 extern char *push_key_description (EMACS_INT, char *);
 extern Lisp_Object access_keymap (Lisp_Object, Lisp_Object, bool, bool, bool);
 extern Lisp_Object get_keymap (Lisp_Object, bool, bool);
-extern ptrdiff_t current_minor_maps (Lisp_Object **, Lisp_Object **);
 extern void initial_define_lispy_key (Lisp_Object, const char *, const char *);
 extern void syms_of_keymap (void);
 
-- 
2.39.5

>From 769275ebd14d0db0ecd5674e8f46ecac603c9286 Mon Sep 17 00:00:00 2001
From: Helmut Eller <[email-masked]>
Date: Tue, 15 Jul 2025 17:33:06 +0200
Subject: [PATCH 4/4] In menu_bar_items, avoid consing & reversing the list of
 active maps

* src/keymap.h (traverse_active_maps): New.
* src/keymap.c (traverse_active_maps): Factored out from
Fcurrent_active_maps.
(Fcurrent_active_maps): Use it.
(traverse_active_minor_maps, traverse_active_minor_maps_2)
(struct traverse_active_minor_maps_env, traverse_active_maps_2)
(struct traverse_active_maps_env, Fcurrent_active_maps_2): New helpers.
* src/keyboard.c (process_prefix_maps): Use traverse_active_maps instead
of Fcurrent_active_maps.
(process_prefix_maps_2, struct process_prefix_maps_env): New helpers.
---
 src/keyboard.c |  27 ++++---
 src/keymap.c   | 194 ++++++++++++++++++++++++++++++++++++++-----------
 src/keymap.h   |   4 +
 3 files changed, 173 insertions(+), 52 deletions(-)

diff --git a/src/keyboard.c b/src/keyboard.c
index 4dd38f13bb7..968720215ee 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -8568,6 +8568,22 @@ menu_separator_name_p (const char *label)
   return 0;
 }
 
+struct process_prefix_maps_env {
+  map_keymap_function_t f;
+  Lisp_Object prefix;
+};
+
+static bool
+process_prefix_maps_2 (Lisp_Object map, void *closure)
+{
+  struct process_prefix_maps_env *env = closure;
+  Lisp_Object def
+    = get_keymap (access_keymap (map, env->prefix, 1, 0, 1), 0, 1);
+  if (CONSP (def))
+    map_keymap_canonical (def, env->f, Qnil, NULL);
+  return true;
+}
+
 /* Look up in each active map the event PREFIX (e.g. Qmenu_bar) and if
    the result is a keymap, then call F for each binding in that keymap.
 
@@ -8576,15 +8592,8 @@ menu_separator_name_p (const char *label)
 static void
 process_prefix_maps (Lisp_Object prefix, map_keymap_function_t f)
 {
-  for (Lisp_Object maps = Fnreverse (Fcurrent_active_maps (Qnil, Qnil));
-       !NILP (maps); maps = XCDR (maps))
-    {
-      Lisp_Object map = XCAR (maps);
-      Lisp_Object def = get_keymap (access_keymap (map, prefix, 1, 0, 1),
-				    0, 1);
-      if (CONSP (def))
-	map_keymap_canonical (def, f, Qnil, NULL);
-    }
+  struct process_prefix_maps_env env = { .f = f, .prefix = prefix };
+  traverse_active_maps (false, Qnil, process_prefix_maps_2, &env);
 }
 
 /* Return a vector of menu items for a menu bar, appropriate
diff --git a/src/keymap.c b/src/keymap.c
index e6ae0b5abf2..e1ba1d309ef 100644
--- a/src/keymap.c
+++ b/src/keymap.c
@@ -1644,18 +1644,90 @@ click_position (Lisp_Object position)
   return pos;
 }
 
-DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
-       0, 2, 0,
-       doc: /* Return a list of the currently active keymaps.
-OLP if non-nil indicates that we should obey `overriding-local-map' and
-`overriding-terminal-local-map'.  POSITION can specify a click position
-like in the respective argument of `key-binding' or a live window which
-means to return the active maps for that window's buffer.  */)
-  (Lisp_Object olp, Lisp_Object position)
+struct traverse_active_minor_maps_env
+{
+  size_t capacity, len;
+  Lisp_Object *maps;
+};
+
+static bool
+traverse_active_minor_maps_2 (Lisp_Object mode, Lisp_Object map,
+			      void *closure)
+{
+  struct traverse_active_minor_maps_env *env = closure;
+  if (env->len < env->capacity)
+    env->maps[env->len] = map;
+  env->len++;
+  return true;
+}
+
+/* This is similar to traverse_minor_maps but here the keymaps are
+   visited in the reversed order. */
+static void
+traverse_active_minor_maps (bool (*f) (Lisp_Object map,
+				       void *closure),
+			    void *closure)
+{
+  struct traverse_active_minor_maps_env env;
+  USE_SAFE_ALLOCA;
+  do
+    {
+      /* We expect that the number of minor modes changes rarely and
+	 hence last_len will be a good guess for the needed size.  If
+	 the guessed capacity is too small, then we need a second
+	 round through the loop. */
+      static size_t last_len = 0;
+      env.len = 0;
+      env.capacity = max (last_len, 8);
+      SAFE_ALLOCA_LISP (env.maps, env.capacity);
+      traverse_minor_maps (traverse_active_minor_maps_2, &env);
+      last_len = env.len;
+    }
+  while (env.capacity < env.len);
+
+  for (size_t i = env.len; i != 0;)
+    {
+      i--;
+      Lisp_Object map = env.maps[i];
+      if (!f (map, closure))
+	break;
+    }
+  SAFE_FREE ();
+}
+
+struct traverse_active_maps_env
+{
+  bool (*f) (Lisp_Object map, void *closure);
+  void *closure;
+  bool abort;
+};
+
+static bool
+traverse_active_maps_2 (Lisp_Object map, void *closure)
+{
+  eassert (!NILP (map));
+  struct traverse_active_maps_env *env = closure;
+  if (!env->f (map, env->closure))
+    {
+      env->abort = true;
+      return false;
+    }
+  return true;
+}
+
+/* Call F for each of the currently active keymaps.
+
+   The keymaps are visited in reverse order of the list returned by
+   Fcurrent_active_maps, i.e. global-map comes first. */
+void
+traverse_active_maps (bool olp, Lisp_Object position,
+		      bool (*f) (Lisp_Object map, void *closure),
+		      void *closure)
 {
   specpdl_ref count = SPECPDL_INDEX ();
 
-  Lisp_Object keymaps = list1 (current_global_map);
+  if (!f (current_global_map, closure))
+    goto unwind;
 
   /* If a mouse click position is given, our variables are based on
      the buffer clicked on, not the current buffer.  So we may have to
@@ -1665,8 +1737,7 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
     {
       Lisp_Object window = POSN_WINDOW (position);
 
-      if (WINDOWP (window)
-	  && BUFFERP (XWINDOW (window)->contents)
+      if (WINDOWP (window) && BUFFERP (XWINDOW (window)->contents)
 	  && XBUFFER (XWINDOW (window)->contents) != current_buffer)
 	{
 	  /* Arrange to go back to the original buffer once we're done
@@ -1687,27 +1758,33 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
 	{
 	  /* See comment above.  */
 	  record_unwind_current_buffer ();
-	  set_buffer_internal (XBUFFER (XWINDOW (position)->contents));
+	  set_buffer_internal (
+	    XBUFFER (XWINDOW (position)->contents));
 	}
     }
 
-  if (!NILP (olp)
+  if (olp
       /* The doc said that overriding-terminal-local-map should
 	 override overriding-local-map.  The code used them both,
 	 but it seems clearer to use just one.  rms, jan 2005.  */
       && NILP (KVAR (current_kboard, Voverriding_terminal_local_map))
       && !NILP (Voverriding_local_map))
-    keymaps = Fcons (Voverriding_local_map, keymaps);
-
-  if (NILP (XCDR (keymaps)))
+    {
+      if (!f (Voverriding_local_map, closure))
+	goto unwind;
+    }
+  else
     {
       ptrdiff_t pt = click_position (position);
       /* This usually returns the buffer's local map,
 	 but that can be overridden by a `local-map' property.  */
-      Lisp_Object local_map = get_local_map (pt, current_buffer, Qlocal_map);
+      Lisp_Object local_map
+	= get_local_map (pt, current_buffer, Qlocal_map);
       /* This returns nil unless there is a `keymap' property.  */
-      Lisp_Object keymap = get_local_map (pt, current_buffer, Qkeymap);
-      Lisp_Object otlp = KVAR (current_kboard, Voverriding_terminal_local_map);
+      Lisp_Object keymap
+	= get_local_map (pt, current_buffer, Qkeymap);
+      Lisp_Object otlp
+	= KVAR (current_kboard, Voverriding_terminal_local_map);
 
       if (CONSP (position))
 	{
@@ -1719,11 +1796,12 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
 	  if (POSN_INBUFFER_P (position))
 	    {
 	      Lisp_Object pos = POSN_BUFFER_POSN (position);
-	      if (FIXNUMP (pos)
-		  && XFIXNUM (pos) >= BEG && XFIXNUM (pos) <= Z)
+	      if (FIXNUMP (pos) && XFIXNUM (pos) >= BEG
+		  && XFIXNUM (pos) <= Z)
 		{
-		  local_map = get_local_map (XFIXNUM (pos),
-					     current_buffer, Qlocal_map);
+		  local_map
+		    = get_local_map (XFIXNUM (pos), current_buffer,
+				     Qlocal_map);
 
 		  keymap = get_local_map (XFIXNUM (pos),
 					  current_buffer, Qkeymap);
@@ -1740,46 +1818,76 @@ DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
 	    {
 	      Lisp_Object pos = XCDR (string);
 	      string = XCAR (string);
-	      if (FIXNUMP (pos)
-		  && XFIXNUM (pos) >= 0
+	      if (FIXNUMP (pos) && XFIXNUM (pos) >= 0
 		  && XFIXNUM (pos) < SCHARS (string))
 		{
-		  Lisp_Object map = Fget_text_property (pos, Qlocal_map,
-							string);
+		  Lisp_Object map
+		    = Fget_text_property (pos, Qlocal_map, string);
 		  Lisp_Object pos_area = POSN_POSN (position);
 		  /* For clicks on mode line or header line, override
-		     the maps we found at POSITION unconditionally, even
-		     if the corresponding properties of the mode- or
-		     header-line string are nil, because propertries at
-		     point are not relevant in that case.  */
-		  if (!NILP (map)
-		      || EQ (pos_area, Qmode_line)
+		     the maps we found at POSITION unconditionally,
+		     even if the corresponding properties of the mode-
+		     or header-line string are nil, because
+		     propertries at point are not relevant in that
+		     case.  */
+		  if (!NILP (map) || EQ (pos_area, Qmode_line)
 		      || EQ (pos_area, Qheader_line))
 		    local_map = map;
 		  map = Fget_text_property (pos, Qkeymap, string);
-		  if (!NILP (map)
-		      || EQ (pos_area, Qmode_line)
+		  if (!NILP (map) || EQ (pos_area, Qmode_line)
 		      || EQ (pos_area, Qheader_line))
 		    keymap = map;
 		}
 	    }
-
 	}
 
       if (!NILP (local_map))
-	keymaps = Fcons (local_map, keymaps);
+	if (!f (local_map, closure))
+	  goto unwind;
 
-      /* Now put all the minor mode keymaps on the list.  */
-      keymaps = prepend_minor_mode_keymaps (keymaps);
+      /* Now visit all the minor mode keymaps.  */
+      {
+	struct traverse_active_maps_env env
+	  = { .f = f, .closure = closure, .abort = false };
+	traverse_active_minor_maps (traverse_active_maps_2, &env);
+	if (env.abort)
+	  goto unwind;
+      }
 
       if (!NILP (keymap))
-	keymaps = Fcons (keymap, keymaps);
+	if (!f (keymap, closure))
+	  goto unwind;
 
-      if (!NILP (olp) && !NILP (otlp))
-	keymaps = Fcons (otlp, keymaps);
+      if (olp && !NILP (otlp))
+	if (!f (otlp, closure))
+	  goto unwind;
     }
 
-  return unbind_to (count, keymaps);
+ unwind:
+  unbind_to (count, Qnil);
+}
+
+static bool
+Fcurrent_active_maps_2 (Lisp_Object map, void *closure)
+{
+  Lisp_Object *keymaps = closure;
+  *keymaps = Fcons (map, *keymaps);
+  return true;
+}
+
+DEFUN ("current-active-maps", Fcurrent_active_maps, Scurrent_active_maps,
+       0, 2, 0,
+       doc: /* Return a list of the currently active keymaps.
+OLP if non-nil indicates that we should obey `overriding-local-map' and
+`overriding-terminal-local-map'.  POSITION can specify a click position
+like in the respective argument of `key-binding' or a live window which
+means to return the active maps for that window's buffer.  */)
+  (Lisp_Object olp, Lisp_Object position)
+{
+  Lisp_Object keymaps = Qnil;
+  traverse_active_maps (!NILP (olp), position,
+			Fcurrent_active_maps_2, &keymaps);
+  return keymaps;
 }
 
 /* GC is possible in this function if it autoloads a keymap.  */
diff --git a/src/keymap.h b/src/keymap.h
index b93707b6368..56494343559 100644
--- a/src/keymap.h
+++ b/src/keymap.h
@@ -46,5 +46,9 @@ #define KEYMAPP(m) (!NILP (get_keymap (m, false, false)))
 extern void map_keymap_canonical (Lisp_Object map,
 				  map_keymap_function_t fun,
 				  Lisp_Object args, void *data);
+extern void traverse_active_maps (bool olp, Lisp_Object position,
+				  bool (*f) (Lisp_Object map,
+					     void *closure),
+				  void *closure);
 
 #endif
-- 
2.39.5