From aa98330bec71d84a3788879e80262d16426b2da6 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 27 Jun 2023 16:42:45 -0400 Subject: [PATCH 1/5] Avoid expensive list/tuple multiplication operations --- ChangeLog | 4 ++++ astroid/protocols.py | 3 +++ tests/test_protocols.py | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/ChangeLog b/ChangeLog index d05a19c90..4179d0e62 100644 --- a/ChangeLog +++ b/ChangeLog @@ -189,6 +189,10 @@ Release date: TBA 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 e3b89b7ef..f512015b7 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -167,6 +167,9 @@ def _multiply_seq_by_int( context: InferenceContext, ) -> _TupleListNodeT: node = self.__class__(parent=opnode) + if other.value > 1e8: + node.elts = [nodes.Const(NotImplemented)] + return node filtered_elts = ( helpers.safe_infer(elt, context) or util.Uninferable for elt in self.elts diff --git a/tests/test_protocols.py b/tests/test_protocols.py index d24659ba4..f85c22314 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 NotImplemented + def test_named_expr_inference() -> None: code = """ From 61837184873d09bbd3f58a919bda16c77134e708 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 27 Jun 2023 19:10:35 -0400 Subject: [PATCH 2/5] Don't compare str with int --- astroid/protocols.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index f512015b7..46cb5d9ac 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -167,7 +167,7 @@ def _multiply_seq_by_int( context: InferenceContext, ) -> _TupleListNodeT: node = self.__class__(parent=opnode) - if other.value > 1e8: + if isinstance(other.value, int) and other.value > 1e8: node.elts = [nodes.Const(NotImplemented)] return node filtered_elts = ( From 3a1b0ddd6290f2e69e2518ecfa591763fcbbe7fd Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 28 Jun 2023 08:52:05 -0400 Subject: [PATCH 3/5] Use Uninferable --- astroid/protocols.py | 2 +- tests/test_protocols.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index 46cb5d9ac..d15f19ecf 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -168,7 +168,7 @@ def _multiply_seq_by_int( ) -> _TupleListNodeT: node = self.__class__(parent=opnode) if isinstance(other.value, int) and other.value > 1e8: - node.elts = [nodes.Const(NotImplemented)] + node.elts = [util.Uninferable] return node filtered_elts = ( helpers.safe_infer(elt, context) or util.Uninferable diff --git a/tests/test_protocols.py b/tests/test_protocols.py index f85c22314..8c318252b 100644 --- a/tests/test_protocols.py +++ b/tests/test_protocols.py @@ -284,7 +284,7 @@ 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 NotImplemented + assert element.value is Uninferable def test_named_expr_inference() -> None: From 4fb738d028f5dbd345f973be1bb08bec35f09d6b Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 28 Jun 2023 08:59:35 -0400 Subject: [PATCH 4/5] Review suggestion --- astroid/protocols.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index d15f19ecf..3f78b5543 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -163,11 +163,11 @@ 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 isinstance(other.value, int) and other.value > 1e8: + if value > 1e8: node.elts = [util.Uninferable] return node filtered_elts = ( @@ -175,7 +175,7 @@ def _multiply_seq_by_int( 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 @@ -224,14 +224,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): + # 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 From 3a873c6fec7dc6b9ff9031bcd4889e087038f917 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 28 Jun 2023 09:56:29 -0400 Subject: [PATCH 5/5] Add pragma --- astroid/protocols.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/protocols.py b/astroid/protocols.py index 3f78b5543..6f201c997 100644 --- a/astroid/protocols.py +++ b/astroid/protocols.py @@ -230,7 +230,7 @@ def tl_infer_binary_op( as_index = helpers.class_instance_as_index(other) if not as_index: yield util.Uninferable - elif not isinstance(as_index.value, int): + 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: