Skip to content

RecursionError with hundreds of consecutive unary operators #2520

Closed

Description

@correctmost

Steps to reproduce

The following code triggers a RecursionError in astroid:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~0

This bug was discovered by OSS-Fuzz:
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=57943 (report not public yet)

Current behavior

Traceback (most recent call last):
  File "pylint/pylint/lint/pylinter.py", line 786, in _lint_file
    check_astroid_module(module)
  File "pylint/pylint/lint/pylinter.py", line 1015, in check_astroid_module
    retval = self._check_astroid_module(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "pylint/pylint/lint/pylinter.py", line 1063, in _check_astroid_module
    raw_checker.process_module(node)
  File "pylint/pylint/checkers/symilar.py", line 841, in process_module
    self.append_stream(self.linter.current_name, stream, node.file_encoding)
  File "pylint/pylint/checkers/symilar.py", line 377, in append_stream
    LineSet(
  File "pylint/pylint/checkers/symilar.py", line 682, in __init__
    self._stripped_lines = stripped_lines(
                           ^^^^^^^^^^^^^^^
  File "pylint/pylint/checkers/symilar.py", line 590, in stripped_lines
    tree = astroid.parse("".join(lines))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/builder.py", line 300, in parse
    return builder.string_build(code, modname=module_name, path=path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/builder.py", line 151, in string_build
    module, builder = self._data_build(data, modname, path)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/builder.py", line 206, in _data_build
    module = builder.visit_module(node, modname, node_file, package)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 171, in visit_module
    [self.visit(child, newnode) for child in node.body],
     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 448, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 972, in visit_expr
    newnode.postinit(self.visit(node.value, newnode))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 448, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 1637, in visit_unaryop
    newnode.postinit(self.visit(node.operand, newnode))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 448, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 1637, in visit_unaryop
    newnode.postinit(self.visit(node.operand, newnode))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 448, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 1637, in visit_unaryop
    newnode.postinit(self.visit(node.operand, newnode))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[...snip...]

  File "astroid/astroid/rebuilder.py", line 448, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 1637, in visit_unaryop
    newnode.postinit(self.visit(node.operand, newnode))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 448, in visit
    return visit_method(node, parent)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/rebuilder.py", line 1395, in visit_constant
    return nodes.Const(
           ^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "pylint/pylint/lint/pylinter.py", line 750, in _lint_files
    self._lint_file(fileitem, module, check_astroid_module)
  File "pylint/pylint/lint/pylinter.py", line 788, in _lint_file
    raise astroid.AstroidError from e
astroid.exceptions.AstroidError

Expected behavior

No crash

Version

d8dbc46

Activity

  1. jacobtylerwalls commented on Jan 25, 2026

    @jacobtylerwalls
    Member

    I actually don't think we view these as problems. We trap these errors in pylint and advise to raise the system recursion limit. If using astroid directly, I don't think we want to prevent users from analyzing deeply nested ASTs.

    I think we should just close the similar issues from the fuzzer. (Is there a way to ignore them on the fuzzer dashboard?)

  2. correctmost commented on Jan 25, 2026

    @correctmost
    Author

    Would it make sense to ignore RecursionErrors in the fuzz target?

    https://github.com/google/oss-fuzz/blob/be8fded65f99abd577dbe46ac4f0b8f8266a1a9b/projects/astroid/fuzz_parse.py#L29-L36

    That would cause the existing test cases on the dashboard to be marked as resolved.

  3. jacobtylerwalls commented on Jan 26, 2026

    @jacobtylerwalls
    Member

    Great idea!

  4. correctmost commented on Jan 27, 2026

    @correctmost
    Author

    Here's the OSS-Fuzz PR: google/oss-fuzz#14847

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions