Skip to content

Commit b954645

Browse files
serhiy-storchakaterryjreedyclaude
authored
gh-55646: Do not crash IDLE on an invalid key binding (#152747)
A typo in a key binding, such as <Alt-Key-up> for <Alt-Key-Up>, crashed IDLE at startup. It is now ignored with a warning. On macOS an invalid binding such as <Alt-Key-up> is not parsed into a MultiCall triplet, so it falls back to Tk's event_add(), which was left unguarded and still crashed. Guard the fallback too, and test both the parsed (bind()) and unparsed (event_add()) paths on every platform. --------- Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 80f9aa0 commit b954645

3 files changed

Lines changed: 66 additions & 4 deletions

File tree

‎Lib/idlelib/idle_test/test_multicall.py‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from idlelib import multicall
44
import unittest
5-
from test.support import requires
5+
from test.support import requires, captured_stderr
66
from tkinter import Tk, Text
77

88

@@ -43,6 +43,36 @@ def test_yview(self):
4343
mctext = self.mc(self.root)
4444
self.assertIs(mctext.yview.__func__, Text.yview)
4545

46+
def test_valid_binding(self):
47+
# A valid key binding must bind without a warning (cf. gh-55646).
48+
mctext = self.mc(self.root)
49+
with captured_stderr() as stderr:
50+
mctext.event_add('<<test-good>>', '<Control-Key-Up>')
51+
mctext.bind('<<test-good>>', lambda e: None)
52+
self.assertEqual(stderr.getvalue(), '')
53+
54+
def test_invalid_triplet_binding(self):
55+
# gh-55646: '<Control-Key-up>' parses into a triplet on every platform,
56+
# so Tk rejects it in bind().
57+
mctext = self.mc(self.root)
58+
with captured_stderr() as stderr:
59+
mctext.event_add('<<test-bad>>', '<Control-Key-up>') # Must not raise.
60+
mctext.bind('<<test-bad>>', lambda e: None) # Must not raise.
61+
warning = stderr.getvalue()
62+
self.assertIn('invalid key binding', warning)
63+
self.assertIn('test-bad', warning) # The offending action is named.
64+
65+
def test_invalid_nontriplet_binding(self):
66+
# gh-55646: '<Foo-Key-Up>' has no valid modifier, so MultiCall does not
67+
# parse it and falls back to Tk's event_add, which rejects it.
68+
mctext = self.mc(self.root)
69+
with captured_stderr() as stderr:
70+
mctext.event_add('<<test-bad>>', '<Foo-Key-Up>') # Must not raise.
71+
mctext.bind('<<test-bad>>', lambda e: None) # Must not raise.
72+
warning = stderr.getvalue()
73+
self.assertIn('invalid key binding', warning)
74+
self.assertIn('test-bad', warning) # The offending action is named.
75+
4676
def test_event_delete_unbound_sequence(self):
4777
# gh-89360: deleting a sequence that was not added to a virtual
4878
# event is ignored instead of raising ValueError.

‎Lib/idlelib/multicall.py‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,17 @@ def _triplet_to_sequence(triplet):
310310
else:
311311
return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
312312

313+
314+
def warn_bad_binding(virtual, sequence, err):
315+
# gh-55646: warn instead of crashing on an invalid key binding.
316+
action = virtual[2:-2] if virtual[:2] == '<<' and virtual[-2:] == '>>' \
317+
else virtual
318+
print(f'Warning: ignoring invalid key binding {sequence!r} '
319+
f'for {action!r}: {err}. '
320+
f'Please reconfigure it in the IDLE Settings dialog.',
321+
file=sys.stderr)
322+
323+
313324
_multicall_dict = {}
314325
def MultiCallCreator(widget):
315326
"""Return a MultiCall class which inherits its methods from the
@@ -343,8 +354,17 @@ def bind(self, sequence=None, func=None, add=None):
343354
self.__binders[triplet[1]].unbind(triplet, ei[0])
344355
ei[0] = func
345356
if ei[0] is not None:
357+
bad = []
346358
for triplet in ei[1]:
347-
self.__binders[triplet[1]].bind(triplet, func)
359+
try:
360+
self.__binders[triplet[1]].bind(triplet, func)
361+
except tkinter.TclError as err:
362+
warn_bad_binding(sequence,
363+
_triplet_to_sequence(triplet),
364+
err)
365+
bad.append(triplet)
366+
for triplet in bad: # Drop the invalid sequences.
367+
ei[1].remove(triplet)
348368
else:
349369
self.__eventinfo[sequence] = [func, []]
350370
return widget.bind(self, sequence, func, add)
@@ -371,10 +391,19 @@ def event_add(self, virtual, *sequences):
371391
triplet = _parse_sequence(seq)
372392
if triplet is None:
373393
#print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
374-
widget.event_add(self, virtual, seq)
394+
try:
395+
widget.event_add(self, virtual, seq)
396+
except tkinter.TclError as err:
397+
warn_bad_binding(virtual, seq, err)
398+
continue # Drop the invalid sequence.
375399
else:
376400
if func is not None:
377-
self.__binders[triplet[1]].bind(triplet, func)
401+
try:
402+
self.__binders[triplet[1]].bind(triplet, func)
403+
except tkinter.TclError as err:
404+
warn_bad_binding(virtual,
405+
_triplet_to_sequence(triplet), err)
406+
continue # Drop the invalid sequence.
378407
triplets.append(triplet)
379408

380409
def event_delete(self, virtual, *sequences):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix IDLE crash at startup when the user configuration contains an invalid key
2+
binding, such as ``<Alt-Key-up>`` instead of ``<Alt-Key-Up>``. The invalid
3+
binding is now ignored with a warning.

0 commit comments

Comments
 (0)