Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/recursion.py: 30%

67 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1""" 

2Recursions are the recipe of |jedi| to conquer Python code. However, someone 

3must stop recursions going mad. Some settings are here to make |jedi| stop at 

4the right time. You can read more about them :ref:`here <settings-recursion>`. 

5 

6Next to the internal ``jedi.inference.cache`` this module also makes |jedi| not 

7thread-safe, because ``execution_recursion_decorator`` uses class variables to 

8count the function calls. 

9 

10.. _settings-recursion: 

11 

12Settings 

13~~~~~~~~~~ 

14 

15Recursion settings are important if you don't want extremely 

16recursive python code to go absolutely crazy. 

17 

18The default values are based on experiments while completing the |jedi| library 

19itself (inception!). But I don't think there's any other Python library that 

20uses recursion in a similarly extreme way. Completion should also be fast and 

21therefore 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 

29from contextlib import contextmanager 

30 

31from jedi import debug 

32from jedi.inference.base_value import NO_VALUES 

33 

34 

35recursion_limit = 15 

36""" 

37Like :func:`sys.getrecursionlimit()`, just for |jedi|. 

38""" 

39total_function_execution_limit = 200 

40""" 

41This is a hard limit of how many non-builtin functions can be executed. 

42""" 

43per_function_execution_limit = 6 

44""" 

45The maximal amount of times a specific function may be executed. 

46""" 

47per_function_recursion_limit = 2 

48""" 

49A function may not be executed more than this number of times recursively. 

50""" 

51 

52 

53class RecursionDetector: 

54 def __init__(self): 

55 self.pushed_nodes = [] 

56 

57 

58@contextmanager 

59def 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 

78def 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 

95class 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