1#! /usr/bin/env python
2"""Find the maximum recursion limit that prevents interpreter termination.
3
4This script finds the maximum safe recursion limit on a particular
5platform.  If you need to change the recursion limit on your system,
6this script will tell you a safe upper bound.  To use the new limit,
7call sys.setrecursionlimit().
8
9This module implements several ways to create infinite recursion in
10Python.  Different implementations end up pushing different numbers of
11C stack frames, depending on how many calls through Python's abstract
12C API occur.
13
14After each round of tests, it prints a message:
15"Limit of NNNN is fine".
16
17The highest printed value of "NNNN" is therefore the highest potentially
18safe limit for your system (which depends on the OS, architecture, but also
19the compilation flags). Please note that it is practically impossible to
20test all possible recursion paths in the interpreter, so the results of
21this test should not be trusted blindly -- although they give a good hint
22of which values are reasonable.
23
24NOTE: When the C stack space allocated by your system is exceeded due
25to excessive recursion, exact behaviour depends on the platform, although
26the interpreter will always fail in a likely brutal way: either a
27segmentation fault, a MemoryError, or just a silent abort.
28
29NB: A program that does not use __methods__ can set a higher limit.
30"""
31
32import sys
33import itertools
34
35class RecursiveBlowup1:
36    def __init__(self):
37        self.__init__()
38
39def test_init():
40    return RecursiveBlowup1()
41
42class RecursiveBlowup2:
43    def __repr__(self):
44        return repr(self)
45
46def test_repr():
47    return repr(RecursiveBlowup2())
48
49class RecursiveBlowup4:
50    def __add__(self, x):
51        return x + self
52
53def test_add():
54    return RecursiveBlowup4() + RecursiveBlowup4()
55
56class RecursiveBlowup5:
57    def __getattr__(self, attr):
58        return getattr(self, attr)
59
60def test_getattr():
61    return RecursiveBlowup5().attr
62
63class RecursiveBlowup6:
64    def __getitem__(self, item):
65        return self[item - 2] + self[item - 1]
66
67def test_getitem():
68    return RecursiveBlowup6()[5]
69
70def test_recurse():
71    return test_recurse()
72
73def test_cpickle(_cache={}):
74    try:
75        import cPickle
76    except ImportError:
77        print "cannot import cPickle, skipped!"
78        return
79    l = None
80    for n in itertools.count():
81        try:
82            l = _cache[n]
83            continue  # Already tried and it works, let's save some time
84        except KeyError:
85            for i in range(100):
86                l = [l]
87        cPickle.dumps(l, protocol=-1)
88        _cache[n] = l
89
90def check_limit(n, test_func_name):
91    sys.setrecursionlimit(n)
92    if test_func_name.startswith("test_"):
93        print test_func_name[5:]
94    else:
95        print test_func_name
96    test_func = globals()[test_func_name]
97    try:
98        test_func()
99    # AttributeError can be raised because of the way e.g. PyDict_GetItem()
100    # silences all exceptions and returns NULL, which is usually interpreted
101    # as "missing attribute".
102    except (RuntimeError, AttributeError):
103        pass
104    else:
105        print "Yikes!"
106
107limit = 1000
108while 1:
109    check_limit(limit, "test_recurse")
110    check_limit(limit, "test_add")
111    check_limit(limit, "test_repr")
112    check_limit(limit, "test_init")
113    check_limit(limit, "test_getattr")
114    check_limit(limit, "test_getitem")
115    check_limit(limit, "test_cpickle")
116    print "Limit of %d is fine" % limit
117    limit = limit + 100
118