From 51aa7abe548ccaec66f64b08c4bdc9a3fcdba67f Mon Sep 17 00:00:00 2001 From: Philipp Albrecht Date: Fri, 18 Aug 2023 14:19:40 +0200 Subject: 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 --- qutebrowser/keyinput/basekeyparser.py | 17 +++++++++++++---- tests/unit/keyinput/test_basekeyparser.py | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 8c8ca4613..df6b66f7f 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -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: diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 68239d4b4..ec7c225bf 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -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 -- cgit v1.2.3-54-g00ecf