From 2eb869e016dad4fd1bfd88d2ad39dc196f8d0583 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 1 Jul 2023 08:43:20 -0400 Subject: [PATCH] Avoid expensive list/tuple multiplication operations (#2228) (#2229) (cherry picked from commit 1a318a0c025c6474053bbac863648c715a054de6) Co-authored-by: Jacob Walls --- ChangeLog | 4 ++++ astroid/protocols.py | 14 ++++++++++---- tests/test_protocols.py | 7 +++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 92e0be95fa..e02189772a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -27,6 +27,10 @@ Release date: 2023-05-14 Closes pylint-dev/pylint#8749 +* Avoid expensive list/tuple multiplication operations that would result in ``MemoryError``. + + Closes pylint-dev/pylint#8748 + What's New in astroid 2.15.5? ============================= diff --git a/astroid/protocols.py b/astroid/protocols.py index dcc9e2b87a..d866f73326 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -167,16 +167,19 @@ def const_infer_binary_op( def _multiply_seq_by_int( self: _TupleListNodeT, opnode: nodes.AugAssign | nodes.BinOp, - other: nodes.Const, + value: int, context: InferenceContext, ) -> _TupleListNodeT: node = self.__class__(parent=opnode) + if value > 1e8: + node.elts = [util.Uninferable] + return node filtered_elts = ( helpers.safe_infer(elt, context) or util.Uninferable for elt in self.elts if not isinstance(elt, util.UninferableBase) ) - node.elts = list(filtered_elts) * other.value + node.elts = list(filtered_elts) * value return node @@ -225,14 +228,17 @@ def tl_infer_binary_op( if not isinstance(other.value, int): yield not_implemented return - yield _multiply_seq_by_int(self, opnode, other, context) + yield _multiply_seq_by_int(self, opnode, other.value, context) elif isinstance(other, bases.Instance) and operator == "*": # Verify if the instance supports __index__. as_index = helpers.class_instance_as_index(other) if not as_index: yield util.Uninferable + elif not isinstance(as_index.value, int): # pragma: no cover + # already checked by class_instance_as_index() but faster than casting + raise AssertionError("Please open a bug report.") else: - yield _multiply_seq_by_int(self, opnode, as_index, context) + yield _multiply_seq_by_int(self, opnode, as_index.value, context) else: yield not_implemented diff --git a/tests/test_protocols.py b/tests/test_protocols.py index 48351bcfb0..06f1f5f5c4 100644 --- a/tests/test_protocols.py +++ b/tests/test_protocols.py @@ -279,6 +279,13 @@ def test_uninferable_exponents() -> None: parsed = extract_node("None ** 2") assert parsed.inferred() == [Uninferable] + @staticmethod + def test_uninferable_list_multiplication() -> None: + """Attempting to calculate the result is prohibitively expensive.""" + parsed = extract_node("[0] * 123456789") + element = parsed.inferred()[0].elts[0] + assert element.value is Uninferable + @pytest.mark.skipif(not PY38_PLUS, reason="needs assignment expressions") def test_named_expr_inference() -> None: