recursion.py 4.8 KB

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