Format: text/x-python
⬇ Download File (4.8 KB)
   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