Handle count exceeding string to int conversion

When handling counts during keyparsing we convert the count string to an integer. If the
count is too high (i.e. the count string has too many digits), we run into Python's
integer string conversion length limit[1]:
```
ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has
4301 digits; use sys.set_int_max_str_digits() to increase the limit
```

Instead of blowing up with an exception, we now handle this more gracefully by showing
an error message.

Reproducer:
```
$ qutebrowser --temp-basedir ":later 500 fake-key -g $(printf '1%.0s' {1..4301})j"
```

**NOTE:**
I had to rename `_debug_log()`'s `message` argument to `msg`, because pylint yelled at
me for redefined-outer-name[2].

[1] https://docs.python.org/3/library/stdtypes.html#integer-string-conversion-length-limitation
[2] https://pylint.readthedocs.io/en/stable/user_guide/messages/warning/redefined-outer-name.html
This commit is contained in:
Philipp Albrecht 2023-08-18 14:19:40 +02:00
parent 07e1376e64
commit 51aa7abe54
2 changed files with 33 additions and 4 deletions

View File

@ -7,13 +7,14 @@
import string
import types
import dataclasses
import traceback
from typing import Mapping, MutableMapping, Optional, Sequence
from qutebrowser.qt.core import QObject, pyqtSignal
from qutebrowser.qt.gui import QKeySequence, QKeyEvent
from qutebrowser.config import config
from qutebrowser.utils import log, usertypes, utils
from qutebrowser.utils import log, usertypes, utils, message
from qutebrowser.keyinput import keyutils
@ -189,7 +190,7 @@ class BaseKeyParser(QObject):
passthrough=self.passthrough,
supports_count=self._supports_count)
def _debug_log(self, message: str) -> None:
def _debug_log(self, msg: str) -> None:
"""Log a message to the debug log if logging is active.
Args:
@ -198,7 +199,7 @@ class BaseKeyParser(QObject):
if self._do_log:
prefix = '{} for mode {}: '.format(self.__class__.__name__,
self._mode.name)
log.keyboard.debug(prefix + message)
log.keyboard.debug(prefix + msg)
def _match_key(self, sequence: keyutils.KeySequence) -> MatchResult:
"""Try to match a given keystring with any bound keychain.
@ -315,7 +316,15 @@ class BaseKeyParser(QObject):
assert result.command is not None
self._debug_log("Definitive match for '{}'.".format(
result.sequence))
count = int(self._count) if self._count else None
try:
count = int(self._count) if self._count else None
except ValueError as err:
message.error(f"Failed to parse count: {err}",
stack=traceback.format_exc())
self.clear_keystring()
return
self.clear_keystring()
self.execute(result.command, count)
elif result.match_type == QKeySequence.SequenceMatch.PartialMatch:

View File

@ -4,6 +4,9 @@
"""Tests for BaseKeyParser."""
import logging
import re
import sys
from unittest import mock
from qutebrowser.qt.core import Qt
@ -342,3 +345,20 @@ def test_respect_config_when_matching_counts(keyparser, config_stub):
assert not keyparser._sequence
assert not keyparser._count
def test_count_limit_exceeded(handle_text, keyparser, caplog):
try:
max_digits = sys.get_int_max_str_digits()
except AttributeError:
pytest.skip('sys.get_int_max_str_digits() not available')
keys = (max_digits + 1) * [Qt.Key.Key_1]
with caplog.at_level(logging.ERROR):
handle_text(keyparser, *keys, Qt.Key.Key_B, Qt.Key.Key_A)
pattern = re.compile(r"^Failed to parse count: Exceeds the limit .* for integer string conversion: .*")
assert any(pattern.fullmatch(msg) for msg in caplog.messages)
assert not keyparser._sequence
assert not keyparser._count