Repository: pylint-dev/astroid

Issue:
AssertionError when root is not a module
### Description

The following code triggers an `AssertionError` in astroid:

```python
a=eval.__get__(1).__gt__

@a
class c: ...
```

This bug was discovered by OSS-Fuzz:
https://issues.oss-fuzz.com/issues/389974978 (report not public yet)

### Backtrace
```
Traceback (most recent call last):
  File "pylint/pylint/lint/pylinter.py", line 979, in get_ast
    return MANAGER.ast_from_file(filepath, modname, source=True)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/manager.py", line 167, in ast_from_file
    return AstroidBuilder(self).file_build(filepath, modname)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/builder.py", line 145, in file_build
    return self._post_build(module, builder, encoding)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/builder.py", line 173, in _post_build
    module = self._manager.visit_transforms(module)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/manager.py", line 128, in visit_transforms
    return self._transform.visit(node)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/transforms.py", line 162, in visit
    return self._visit(node)
           ^^^^^^^^^^^^^^^^^
  File "astroid/astroid/transforms.py", line 84, in _visit
    visited = self._visit_generic(value)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/transforms.py", line 112, in _visit_generic
    return [self._visit_generic(child) for child in node]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/transforms.py", line 112, in <listcomp>
    return [self._visit_generic(child) for child in node]
            ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/transforms.py", line 119, in _visit_generic
    return self._visit(node)
           ^^^^^^^^^^^^^^^^^
  File "astroid/astroid/transforms.py", line 87, in _visit
    return self._transform(node)
           ^^^^^^^^^^^^^^^^^^^^^
  File "astroid/astroid/transforms.py", line 66, in _transform
    if predicate is None or predicate(node):
                            ^^^^^^^^^^^^^^^
  File "astroid/astroid/brain/brain_attrs.py", line 59, in is_decorated_with_attrs
    if inferred and inferred.root().name == "attr._next_gen":
                    ^^^^^^^^^^^^^^^
  File "astroid/astroid/nodes/node_ng.py", line 334, in root
    assert isinstance(self, nodes.Module)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
```

### Similar issues
- #2618

### Version
- 8527f5b6f7fb

Discussion:

Solution patch:
diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py
index c222a422..12180097 100644
--- a/astroid/brain/brain_six.py
+++ b/astroid/brain/brain_six.py
@@ -182,7 +182,11 @@ def transform_six_add_metaclass(node):  # pylint: disable=inconsistent-return-st
             func = next(decorator.func.infer())
         except (InferenceError, StopIteration):
             continue
-        if func.qname() == SIX_ADD_METACLASS and decorator.args:
+        if (
+            isinstance(func, (nodes.FunctionDef, nodes.ClassDef))
+            and func.qname() == SIX_ADD_METACLASS
+            and decorator.args
+        ):
             metaclass = decorator.args[0]
             node._metaclass = metaclass
             return node
diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py
index dc8942b7..d3be0472 100644
--- a/astroid/nodes/node_ng.py
+++ b/astroid/nodes/node_ng.py
@@ -309,13 +309,13 @@ class NodeNG:
             raise ParentMissingError(target=self)
         return self.parent.scope()
 
-    def root(self) -> nodes.Module:
+    def root(self) -> nodes.Module | nodes.Unknown:
         """Return the root node of the syntax tree.
 
         :returns: The root node.
         """
         if not (parent := self.parent):
-            assert isinstance(self, nodes.Module)
+            assert isinstance(self, (nodes.Module, nodes.Unknown))
             return self
 
         while parent.parent:


Test patch:
diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py
index 5321c7e0..9b347177 100644
--- a/tests/test_regrtest.py
+++ b/tests/test_regrtest.py
@@ -500,7 +500,38 @@ def test_regression_missing_callcontext() -> None:
     assert node.inferred()[0].value == "mystr"
 
 
+def test_regression_root_is_not_a_module() -> None:
+    """Regression test for #2672."""
+    node: nodes.Attribute = _extract_single_node(
+        textwrap.dedent(
+            """
+        a=eval.__get__(1).__gt__
+
+        @a
+        class c: ...
+        """
+        )
+    )
+    assert node.name == "c"
+
+
 def test_regression_no_crash_during_build() -> None:
     node: nodes.Attribute = extract_node("__()")
     assert node.args == []
     assert node.as_string() == "__()"
+
+
+def test_regression_no_crash_on_called_slice() -> None:
+    """Regression test for issue #2721."""
+    node: nodes.Attribute = extract_node(
+        textwrap.dedent(
+            """
+        s = slice(-2)
+        @s()
+        @six.add_metaclass()
+        class a: ...
+        """
+        )
+    )
+    assert isinstance(node, nodes.ClassDef)
+    assert node.name == "a"