""" Recursions are the recipe of |jedi| to conquer Python code. However, someone must stop recursions going mad. Some settings are here to make |jedi| stop at the right time. You can read more about them :ref:`here `. Next to :mod:`cache` this module also makes |jedi| not thread-safe. Why? ``ExecutionRecursionDecorator`` uses class variables to count the function calls. """ from jedi.parser import representation as pr from jedi import debug from jedi import settings from jedi.evaluate import builtin from jedi.evaluate import interfaces def recursion_decorator(func): def run(evaluator, stmt, *args, **kwargs): rec_detect = evaluator.recursion_detector # print stmt, len(self.node_statements()) if rec_detect.push_stmt(stmt): return [] else: result = func(evaluator, stmt, *args, **kwargs) rec_detect.pop_stmt() return result return run class RecursionDetector(object): """ A decorator to detect recursions in statements. In a recursion a statement at the same place, in the same module may not be executed two times. """ def __init__(self): self.top = None self.current = None def push_stmt(self, stmt): self.current = _RecursionNode(stmt, self.current) check = self._check_recursion() if check: # TODO remove False!!!! debug.warning('catched stmt recursion: %s against %s @%s' % (stmt, check.stmt, stmt.start_pos)) self.pop_stmt() return True return False def pop_stmt(self): if self.current is not None: # I don't know how current can be None, but sometimes it happens # with Python3. self.current = self.current.parent def _check_recursion(self): test = self.current while True: test = test.parent if self.current == test: return test if not test: return False def node_statements(self): result = [] n = self.current while n: result.insert(0, n.stmt) n = n.parent return result class _RecursionNode(object): """ A node of the RecursionDecorator. """ def __init__(self, stmt, parent): self.script = stmt.get_parent_until() self.position = stmt.start_pos self.parent = parent self.stmt = stmt # Don't check param instances, they are not causing recursions # The same's true for the builtins, because the builtins are really # simple. self.is_ignored = isinstance(stmt, pr.Param) \ or (self.script == builtin.Builtin.scope) def __eq__(self, other): if not other: return None is_list_comp = lambda x: isinstance(x, pr.ForFlow) and x.is_list_comp return self.script == other.script \ and self.position == other.position \ and not is_list_comp(self.stmt.parent) \ and not is_list_comp(other.parent) \ and not self.is_ignored and not other.is_ignored def execution_recursion_decorator(func): def run(execution, evaluate_generator=False): detector = execution._evaluator.execution_recursion_detector if detector.push_execution(execution, evaluate_generator): result = [] else: result = func(execution, evaluate_generator) detector.pop_execution() return result return run class ExecutionRecursionDetector(object): """ Catches recursions of executions. It is designed like a Singelton. Only one instance should exist. """ def __init__(self): self.recursion_level = 0 self.parent_execution_funcs = [] self.execution_funcs = set() self.execution_count = 0 def __call__(self, execution, evaluate_generator=False): debug.dbg('Execution recursions: %s' % execution, self.recursion_level, self.execution_count, len(self.execution_funcs)) if self.check_recursion(execution, evaluate_generator): result = [] else: result = self.func(execution, evaluate_generator) self.pop_execution() return result def pop_execution(cls): cls.parent_execution_funcs.pop() cls.recursion_level -= 1 def push_execution(cls, execution, evaluate_generator): in_par_execution_funcs = execution.base in cls.parent_execution_funcs in_execution_funcs = execution.base in cls.execution_funcs cls.recursion_level += 1 cls.execution_count += 1 cls.execution_funcs.add(execution.base) cls.parent_execution_funcs.append(execution.base) if cls.execution_count > settings.max_executions: return True if isinstance(execution.base, interfaces.Iterable): return False module = execution.get_parent_until() if evaluate_generator or module == builtin.Builtin.scope: return False if in_par_execution_funcs: if cls.recursion_level > settings.max_function_recursion_level: return True if in_execution_funcs and \ len(cls.execution_funcs) > settings.max_until_execution_unique: return True if cls.execution_count > settings.max_executions_without_builtins: return True return False