Repository: python/mypy

Issue:
segmentation fault on indexing object with __getattr__
Please provide more information to help us understand the issue:

* Are you reporting a bug, or opening a feature request?
Bug.
* Please insert below the code you are checking with mypy,
  or a mock-up repro if the source is private. We would appreciate
  if you try to simplify your case to a minimal repro.
```python
class C:
    def __getattr__(self, name: str) -> 'C':
        ...


def f(v: C) -> None:
    v[0]
```
* What is the actual behavior/output?
segfault
* What is the behavior/output you expect?
not crashing
* What are the versions of mypy and Python you are using?
python 3.6.7 on Ubuntu 18.04.2 x86_64
mypy 0.670 installed march 20 2019 via pip

  Do you see the same issue after installing mypy from Git master?
yes
0.680+dev.4e0a1583aeb00b248e187054980771f1897a1d31
* What are the mypy flags you are using? (For example --strict-optional)
none



Discussion:
Confirmed, sort of. I get a traceback, not a segfault, but it's a `RecursionError`, and it's not impossible that could cause a segfault on some platforms. (I used a Mac.)
I can confirm that this does unfortunately lead to a segfault on Ubuntu 18.04 (and other Linux systems as well I presume). 
Also confirmed as a segfault on my 18.04 system. We call `sys.setrecursionlimit(2 ** 14)`, and if I change it to `2 ** 13` I get a RecursionError on my machine.

I am somewhat surprised by this, though. 2 ** 14 is 16384, which is just not a very big number.
> I am somewhat surprised by this, though. 2 ** 14 is 16384, which is just not a very big number.

Aha:
```
$ ulimit -s
8192
```
That is also not a very big number. So in addition to fixing the actual bug, we should reduce our recursion limit, probably. (Or call setrlimit, I guess)
Can python be compiled with split stacks? https://gcc.gnu.org/wiki/SplitStacks
> Can python be compiled with split stacks? https://gcc.gnu.org/wiki/SplitStacks

Out of scope for us, in any case.
(Also probably not?)
> > Can python be compiled with split stacks? https://gcc.gnu.org/wiki/SplitStacks
> 
> Out of scope for us, in any case.
> (Also probably not?)

I was mentioning split stacks since I think that would effectively remove the issue with running out of stack space causing a sigsegv since split stacks basically allow recursion until the heap is exhausted.
That doesn't seem particularly useful in this case since it would just delay the stack overflow, and it would probably make things worse for the system as mypy would eat up huge amounts of memory. As @msullivan said above, we should fix two things for this bug:

- Remove the infinite recursion in your test case above
- Avoid setting recursion limits higher than the rlimit for recursion, so that mypy doesn't segfault if it hits runaway recursion.
I think this is a duplicate of https://github.com/python/mypy/issues/5171, but I am going to close that instead since this one has more context and a simpler repro.

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -704,8 +704,8 @@ def check_call(self,
         elif isinstance(callee, UnionType):
             return self.check_union_call(callee, args, arg_kinds, arg_names, context, arg_messages)
         elif isinstance(callee, Instance):
-            call_function = analyze_member_access('__call__', callee, context,
-                                                  False, False, False, self.msg,
+            call_function = analyze_member_access('__call__', callee, context, is_lvalue=False,
+                                                  is_super=False, is_operator=True, msg=self.msg,
                                                   original_type=callee, chk=self.chk,
                                                   in_literal_context=self.is_literal_context())
             callable_name = callee.type.fullname() + ".__call__"
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -332,7 +332,8 @@ def analyze_member_var_access(name: str,
         return analyze_var(name, v, itype, info, mx, implicit=implicit)
     elif isinstance(v, FuncDef):
         assert False, "Did not expect a function"
-    elif not v and name not in ['__getattr__', '__setattr__', '__getattribute__']:
+    elif (not v and name not in ['__getattr__', '__setattr__', '__getattribute__'] and
+          not mx.is_operator):
         if not mx.is_lvalue:
             for method_name in ('__getattribute__', '__getattr__'):
                 method = info.get_method(method_name)


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -2494,6 +2494,22 @@ b = a.bar
 [out]
 main:9: error: Incompatible types in assignment (expression has type "A", variable has type "B")
 
+[case testGetattrWithGetitem]
+class A:
+    def __getattr__(self, x: str) -> 'A':
+        return A()
+
+a = A()
+a[0]  # E: Value of type "A" is not indexable
+
+[case testGetattrWithCall]
+class A:
+    def __getattr__(self, x: str) -> 'A':
+        return A()
+
+a = A()
+a.y()  # E: "A" not callable
+
 [case testNestedGetattr]
 def foo() -> object:
     def __getattr__() -> None:  # no error because not in a class