1 """ 2 Recursions are the recipe of |jedi| to conquer Python code. However, someone 3 must stop recursions going mad. Some settings are here to make |jedi| stop at 4 the right time. You can read more about them :ref:`here <settings-recursion>`. 5 6 Next to the internal ``jedi.inference.cache`` this module also makes |jedi| not 7 thread-safe, because ``execution_recursion_decorator`` uses class variables to 8 count the function calls. 9 10 .. _settings-recursion: 11 12 Settings 13 ~~~~~~~~~~ 14 15 Recursion settings are important if you don't want extremely 16 recursive python code to go absolutely crazy. 17 18 The default values are based on experiments while completing the |jedi| library 19 itself (inception!). But I don't think there's any other Python library that 20 uses recursion in a similarly extreme way. Completion should also be fast and 21 therefore the quality might not always be maximal. 22 23 .. autodata:: recursion_limit 24 .. autodata:: total_function_execution_limit 25 .. autodata:: per_function_execution_limit 26 .. autodata:: per_function_recursion_limit 27 """ 28 29 from contextlib import contextmanager 30 31 from jedi import debug 32 from jedi.inference.base_value import NO_VALUES 33 34 35 recursion_limit = 15 36 """ 37 Like :func:`sys.getrecursionlimit()`, just for |jedi|. 38 """ 39 total_function_execution_limit = 200 40 """ 41 This is a hard limit of how many non-builtin functions can be executed. 42 """ 43 per_function_execution_limit = 6 44 """ 45 The maximal amount of times a specific function may be executed. 46 """ 47 per_function_recursion_limit = 2 48 """ 49 A function may not be executed more than this number of times recursively. 50 """ 51 52 53 class RecursionDetector: 54 def __init__(self): 55 self.pushed_nodes = [] 56 57 58 @contextmanager 59 def execution_allowed(inference_state, node): 60 """ 61 A decorator to detect recursions in statements. In a recursion a statement 62 at the same place, in the same module may not be executed two times. 63 """ 64 pushed_nodes = inference_state.recursion_detector.pushed_nodes 65 66 if node in pushed_nodes: 67 debug.warning('catched stmt recursion: %s @%s', node, 68 getattr(node, 'start_pos', None)) 69 yield False 70 else: 71 try: 72 pushed_nodes.append(node) 73 yield True 74 finally: 75 pushed_nodes.pop() 76 77 78 def execution_recursion_decorator(default=NO_VALUES): 79 def decorator(func): 80 def wrapper(self, **kwargs): 81 detector = self.inference_state.execution_recursion_detector 82 limit_reached = detector.push_execution(self) 83 try: 84 if limit_reached: 85 result = default 86 else: 87 result = func(self, **kwargs) 88 finally: 89 detector.pop_execution() 90 return result 91 return wrapper 92 return decorator 93 94 95 class ExecutionRecursionDetector: 96 """ 97 Catches recursions of executions. 98 """ 99 def __init__(self, inference_state): 100 self._inference_state = inference_state 101 102 self._recursion_level = 0 103 self._parent_execution_funcs = [] 104 self._funcdef_execution_counts = {} 105 self._execution_count = 0 106 107 def pop_execution(self): 108 self._parent_execution_funcs.pop() 109 self._recursion_level -= 1 110 111 def push_execution(self, execution): 112 funcdef = execution.tree_node 113 114 # These two will be undone in pop_execution. 115 self._recursion_level += 1 116 self._parent_execution_funcs.append(funcdef) 117 118 module_context = execution.get_root_context() 119 120 if module_context.is_builtins_module(): 121 # We have control over builtins so we know they are not recursing 122 # like crazy. Therefore we just let them execute always, because 123 # they usually just help a lot with getting good results. 124 return False 125 126 if self._recursion_level > recursion_limit: 127 debug.warning('Recursion limit (%s) reached', recursion_limit) 128 return True 129 130 if self._execution_count >= total_function_execution_limit: 131 debug.warning('Function execution limit (%s) reached', total_function_execution_limit) 132 return True 133 self._execution_count += 1 134 135 if self._funcdef_execution_counts.setdefault(funcdef, 0) >= per_function_execution_limit: 136 if module_context.py__name__() == 'typing': 137 return False 138 debug.warning( 139 'Per function execution limit (%s) reached: %s', 140 per_function_execution_limit, 141 funcdef 142 ) 143 return True 144 self._funcdef_execution_counts[funcdef] += 1 145 146 if self._parent_execution_funcs.count(funcdef) > per_function_recursion_limit: 147 debug.warning( 148 'Per function recursion limit (%s) reached: %s', 149 per_function_recursion_limit, 150 funcdef 151 ) 152 return True 153 return False 154