You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.2 KiB
139 lines
4.2 KiB
6 years ago
|
"""
|
||
|
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 <settings-recursion>`.
|
||
|
|
||
|
Next to :mod:`jedi.evaluate.cache` this module also makes |jedi| not
|
||
|
thread-safe. Why? ``execution_recursion_decorator`` uses class variables to
|
||
|
count the function calls.
|
||
|
|
||
|
.. _settings-recursion:
|
||
|
|
||
|
Settings
|
||
|
~~~~~~~~~~
|
||
|
|
||
|
Recursion settings are important if you don't want extremly
|
||
|
recursive python code to go absolutely crazy.
|
||
|
|
||
|
The default values are based on experiments while completing the |jedi| library
|
||
|
itself (inception!). But I don't think there's any other Python library that
|
||
|
uses recursion in a similarly extreme way. Completion should also be fast and
|
||
|
therefore the quality might not always be maximal.
|
||
|
|
||
|
.. autodata:: recursion_limit
|
||
|
.. autodata:: total_function_execution_limit
|
||
|
.. autodata:: per_function_execution_limit
|
||
|
.. autodata:: per_function_recursion_limit
|
||
|
"""
|
||
|
|
||
|
from contextlib import contextmanager
|
||
|
|
||
|
from jedi import debug
|
||
|
from jedi.evaluate.base_context import NO_CONTEXTS
|
||
|
|
||
|
|
||
|
recursion_limit = 15
|
||
|
"""
|
||
|
Like ``sys.getrecursionlimit()``, just for |jedi|.
|
||
|
"""
|
||
|
total_function_execution_limit = 200
|
||
|
"""
|
||
|
This is a hard limit of how many non-builtin functions can be executed.
|
||
|
"""
|
||
|
per_function_execution_limit = 6
|
||
|
"""
|
||
|
The maximal amount of times a specific function may be executed.
|
||
|
"""
|
||
|
per_function_recursion_limit = 2
|
||
|
"""
|
||
|
A function may not be executed more than this number of times recursively.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class RecursionDetector(object):
|
||
|
def __init__(self):
|
||
|
self.pushed_nodes = []
|
||
|
|
||
|
|
||
|
@contextmanager
|
||
|
def execution_allowed(evaluator, node):
|
||
|
"""
|
||
|
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.
|
||
|
"""
|
||
|
pushed_nodes = evaluator.recursion_detector.pushed_nodes
|
||
|
|
||
|
if node in pushed_nodes:
|
||
|
debug.warning('catched stmt recursion: %s @%s', node,
|
||
|
getattr(node, 'start_pos', None))
|
||
|
yield False
|
||
|
else:
|
||
|
try:
|
||
|
pushed_nodes.append(node)
|
||
|
yield True
|
||
|
finally:
|
||
|
pushed_nodes.pop()
|
||
|
|
||
|
|
||
|
def execution_recursion_decorator(default=NO_CONTEXTS):
|
||
|
def decorator(func):
|
||
|
def wrapper(self, **kwargs):
|
||
|
detector = self.evaluator.execution_recursion_detector
|
||
|
allowed = detector.push_execution(self)
|
||
|
try:
|
||
|
if allowed:
|
||
|
result = default
|
||
|
else:
|
||
|
result = func(self, **kwargs)
|
||
|
finally:
|
||
|
detector.pop_execution()
|
||
|
return result
|
||
|
return wrapper
|
||
|
return decorator
|
||
|
|
||
|
|
||
|
class ExecutionRecursionDetector(object):
|
||
|
"""
|
||
|
Catches recursions of executions.
|
||
|
"""
|
||
|
def __init__(self, evaluator):
|
||
|
self._evaluator = evaluator
|
||
|
|
||
|
self._recursion_level = 0
|
||
|
self._parent_execution_funcs = []
|
||
|
self._funcdef_execution_counts = {}
|
||
|
self._execution_count = 0
|
||
|
|
||
|
def pop_execution(self):
|
||
|
self._parent_execution_funcs.pop()
|
||
|
self._recursion_level -= 1
|
||
|
|
||
|
def push_execution(self, execution):
|
||
|
funcdef = execution.tree_node
|
||
|
|
||
|
# These two will be undone in pop_execution.
|
||
|
self._recursion_level += 1
|
||
|
self._parent_execution_funcs.append(funcdef)
|
||
|
|
||
|
module = execution.get_root_context()
|
||
|
if module == self._evaluator.builtins_module:
|
||
|
# We have control over builtins so we know they are not recursing
|
||
|
# like crazy. Therefore we just let them execute always, because
|
||
|
# they usually just help a lot with getting good results.
|
||
|
return False
|
||
|
|
||
|
if self._recursion_level > recursion_limit:
|
||
|
return True
|
||
|
|
||
|
if self._execution_count >= total_function_execution_limit:
|
||
|
return True
|
||
|
self._execution_count += 1
|
||
|
|
||
|
if self._funcdef_execution_counts.setdefault(funcdef, 0) >= per_function_execution_limit:
|
||
|
return True
|
||
|
self._funcdef_execution_counts[funcdef] += 1
|
||
|
|
||
|
if self._parent_execution_funcs.count(funcdef) > per_function_recursion_limit:
|
||
|
return True
|
||
|
return False
|