pyflow-analysis 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (757) hide show
  1. pyflow/__init__.py +22 -0
  2. pyflow/_pyflow.py +50 -0
  3. pyflow/analysis/__init__.py +14 -0
  4. pyflow/analysis/alias/__init__.py +3 -0
  5. pyflow/analysis/alias/flow_sensitive/__init__.py +148 -0
  6. pyflow/analysis/alias/flow_sensitive/_transfer_calls.py +3493 -0
  7. pyflow/analysis/alias/flow_sensitive/_transfer_ops.py +1969 -0
  8. pyflow/analysis/alias/flow_sensitive/abstraction.py +1632 -0
  9. pyflow/analysis/alias/flow_sensitive/heap_analysis.py +280 -0
  10. pyflow/analysis/alias/flow_sensitive/heap_effects.py +1209 -0
  11. pyflow/analysis/alias/flow_sensitive/heap_state.py +381 -0
  12. pyflow/analysis/alias/flow_sensitive/heap_summary.py +299 -0
  13. pyflow/analysis/alias/flow_sensitive/intrinsics.py +948 -0
  14. pyflow/analysis/alias/flow_sensitive/model.py +449 -0
  15. pyflow/analysis/alias/flow_sensitive/points_to_graph.py +722 -0
  16. pyflow/analysis/alias/flow_sensitive/transfer.py +1483 -0
  17. pyflow/analysis/alias/kcfa/__init__.py +33 -0
  18. pyflow/analysis/alias/kcfa/_pythonstan/__init__.py +2 -0
  19. pyflow/analysis/alias/kcfa/_pythonstan/analysis/__init__.py +1 -0
  20. pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/__init__.py +53 -0
  21. pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/logging.py +490 -0
  22. pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/operation.py +1601 -0
  23. pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/pointer_adapter.py +353 -0
  24. pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/solver.py +559 -0
  25. pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/state.py +701 -0
  26. pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/value.py +804 -0
  27. pyflow/analysis/alias/kcfa/_pythonstan/analysis/analysis.py +86 -0
  28. pyflow/analysis/alias/kcfa/_pythonstan/analysis/class_hierarchy.py +83 -0
  29. pyflow/analysis/alias/kcfa/_pythonstan/analysis/closure.py +69 -0
  30. pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/__init__.py +4 -0
  31. pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/analysis.py +58 -0
  32. pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/driver.py +38 -0
  33. pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/liveness.py +33 -0
  34. pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/reaching_definition.py +52 -0
  35. pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/solver.py +178 -0
  36. pyflow/analysis/alias/kcfa/_pythonstan/analysis/defuse.py +46 -0
  37. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/__init__.py +18 -0
  38. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/__init__.py +110 -0
  39. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/analysis.py +382 -0
  40. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/builtin_api_handler.py +1872 -0
  41. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/call_graph_analyzer.py +289 -0
  42. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/class_hierarchy.py +304 -0
  43. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/config.py +139 -0
  44. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/constraints.py +576 -0
  45. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/context.py +496 -0
  46. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/context_selector.py +297 -0
  47. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/debug_monitor.py +34 -0
  48. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/heap_model.py +221 -0
  49. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/ir_translator.py +1181 -0
  50. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/libs/__init__.py +2 -0
  51. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/libs/math.py +244 -0
  52. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/module_analysis.py +269 -0
  53. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/module_summary.py +191 -0
  54. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/object.py +685 -0
  55. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/pointer_flow_graph.py +282 -0
  56. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/points_to_set.py +301 -0
  57. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/__init__.py +21 -0
  58. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/attribute_semantics.py +512 -0
  59. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/compose_processor.py +46 -0
  60. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/container.py +128 -0
  61. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/generator.py +353 -0
  62. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/normal_call.py +664 -0
  63. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/processor.py +36 -0
  64. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/super_resolve.py +131 -0
  65. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/pta_query_adapter.py +329 -0
  66. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/solver.py +917 -0
  67. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/solver_interface.py +76 -0
  68. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/state.py +735 -0
  69. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/unknown_tracker.py +126 -0
  70. pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/variable.py +144 -0
  71. pyflow/analysis/alias/kcfa/_pythonstan/analysis/scope/__init__.py +2 -0
  72. pyflow/analysis/alias/kcfa/_pythonstan/analysis/scope/analysis.py +37 -0
  73. pyflow/analysis/alias/kcfa/_pythonstan/analysis/scope/closure.py +81 -0
  74. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/__init__.py +8 -0
  75. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/block_cfg.py +133 -0
  76. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/cfg.py +95 -0
  77. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/driver.py +26 -0
  78. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/__init__.py +0 -0
  79. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/base.py +112 -0
  80. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/baseprocess.py +524 -0
  81. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/icfg.py +0 -0
  82. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/icfg_pre_transform.py +69 -0
  83. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/icfg_transform.py +0 -0
  84. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/postprocessor.py +308 -0
  85. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/preprocessor.py +375 -0
  86. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/ir.py +361 -0
  87. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/three_address.py +1161 -0
  88. pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/transform.py +27 -0
  89. pyflow/analysis/alias/kcfa/_pythonstan/graph/__init__.py +0 -0
  90. pyflow/analysis/alias/kcfa/_pythonstan/graph/call_graph/__init__.py +2 -0
  91. pyflow/analysis/alias/kcfa/_pythonstan/graph/call_graph/call_edge.py +45 -0
  92. pyflow/analysis/alias/kcfa/_pythonstan/graph/call_graph/call_graph.py +77 -0
  93. pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/__init__.py +3 -0
  94. pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/base_block.py +83 -0
  95. pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/cfg.py +200 -0
  96. pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/dump.py +20 -0
  97. pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/edges.py +162 -0
  98. pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/visualize.py +112 -0
  99. pyflow/analysis/alias/kcfa/_pythonstan/graph/dominator_tree.py +126 -0
  100. pyflow/analysis/alias/kcfa/_pythonstan/graph/graph.py +81 -0
  101. pyflow/analysis/alias/kcfa/_pythonstan/graph/icfg/__init__.py +0 -0
  102. pyflow/analysis/alias/kcfa/_pythonstan/graph/icfg/icfg.py +98 -0
  103. pyflow/analysis/alias/kcfa/_pythonstan/ir/__init__.py +2 -0
  104. pyflow/analysis/alias/kcfa/_pythonstan/ir/ir_statements.py +1579 -0
  105. pyflow/analysis/alias/kcfa/_pythonstan/ir/ir_visitor.py +21 -0
  106. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/LICENSE.pythonstan +28 -0
  107. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/README.md +99 -0
  108. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/abc.cpython-313.pyc +0 -0
  109. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/base64.cpython-313.pyc +0 -0
  110. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/bisect.cpython-313.pyc +0 -0
  111. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/collections.cpython-313.pyc +0 -0
  112. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/contextlib.cpython-313.pyc +0 -0
  113. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/copy.cpython-313.pyc +0 -0
  114. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/csv.cpython-313.pyc +0 -0
  115. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/dataclasses.cpython-313.pyc +0 -0
  116. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/datetime.cpython-313.pyc +0 -0
  117. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/email.cpython-313.pyc +0 -0
  118. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/enum.cpython-313.pyc +0 -0
  119. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/fnmatch.cpython-313.pyc +0 -0
  120. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/functools.cpython-313.pyc +0 -0
  121. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/glob.cpython-313.pyc +0 -0
  122. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/hashlib.cpython-313.pyc +0 -0
  123. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/heapq.cpython-313.pyc +0 -0
  124. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/hmac.cpython-313.pyc +0 -0
  125. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/http.cpython-313.pyc +0 -0
  126. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/importlib.cpython-313.pyc +0 -0
  127. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/inspect.cpython-313.pyc +0 -0
  128. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/io.cpython-313.pyc +0 -0
  129. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/itertools.cpython-313.pyc +0 -0
  130. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/json.cpython-313.pyc +0 -0
  131. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/logging.cpython-313.pyc +0 -0
  132. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/math.cpython-313.pyc +0 -0
  133. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/mimetypes.cpython-313.pyc +0 -0
  134. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/operator.cpython-313.pyc +0 -0
  135. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/os.cpython-313.pyc +0 -0
  136. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/pathlib.cpython-313.pyc +0 -0
  137. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/pickle.cpython-313.pyc +0 -0
  138. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/random.cpython-313.pyc +0 -0
  139. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/re.cpython-313.pyc +0 -0
  140. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/secrets.cpython-313.pyc +0 -0
  141. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/shutil.cpython-313.pyc +0 -0
  142. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/somelib.cpython-313.pyc +0 -0
  143. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/tempfile.cpython-313.pyc +0 -0
  144. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/time.cpython-313.pyc +0 -0
  145. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/traceback.cpython-313.pyc +0 -0
  146. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/types.cpython-313.pyc +0 -0
  147. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/uuid.cpython-313.pyc +0 -0
  148. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/warnings.cpython-313.pyc +0 -0
  149. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/weakref.cpython-313.pyc +0 -0
  150. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/abc.py +80 -0
  151. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/base64.py +62 -0
  152. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/bisect.py +52 -0
  153. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/collections.py +187 -0
  154. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/contextlib.py +113 -0
  155. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/copy.py +15 -0
  156. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/csv.py +163 -0
  157. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/dataclasses.py +150 -0
  158. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/datetime.py +171 -0
  159. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/email.py +270 -0
  160. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/enum.py +144 -0
  161. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/fnmatch.py +14 -0
  162. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/functools.py +84 -0
  163. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/glob.py +10 -0
  164. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/hashlib.py +74 -0
  165. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/heapq.py +91 -0
  166. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/hmac.py +31 -0
  167. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/http.py +317 -0
  168. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/importlib.py +126 -0
  169. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/inspect.py +246 -0
  170. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/io.py +471 -0
  171. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/itertools.py +164 -0
  172. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/json.py +80 -0
  173. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/logging.py +193 -0
  174. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/math.py +244 -0
  175. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/mimetypes.py +119 -0
  176. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/operator.py +310 -0
  177. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/os.py +286 -0
  178. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/pathlib.py +196 -0
  179. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/pickle.py +153 -0
  180. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/random.py +125 -0
  181. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/re.py +125 -0
  182. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/secrets.py +29 -0
  183. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/shutil.py +99 -0
  184. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/somelib.py +2 -0
  185. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/tempfile.py +76 -0
  186. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/time.py +65 -0
  187. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/traceback.py +151 -0
  188. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/types.py +247 -0
  189. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/__init__.py +1 -0
  190. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/__pycache__/__init__.cpython-313.pyc +0 -0
  191. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/__pycache__/parse.cpython-313.pyc +0 -0
  192. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/parse.py +221 -0
  193. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/uuid.py +34 -0
  194. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/warnings.py +82 -0
  195. pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/weakref.py +272 -0
  196. pyflow/analysis/alias/kcfa/_pythonstan/utils/__init__.py +1 -0
  197. pyflow/analysis/alias/kcfa/_pythonstan/utils/ast_rename.py +35 -0
  198. pyflow/analysis/alias/kcfa/_pythonstan/utils/common.py +70 -0
  199. pyflow/analysis/alias/kcfa/_pythonstan/utils/persistent_rb_tree.py +364 -0
  200. pyflow/analysis/alias/kcfa/_pythonstan/utils/temp_var_generator.py +68 -0
  201. pyflow/analysis/alias/kcfa/_pythonstan/utils/toposort.py +63 -0
  202. pyflow/analysis/alias/kcfa/_pythonstan/utils/var_collector.py +72 -0
  203. pyflow/analysis/alias/kcfa/_pythonstan/world/__init__.py +1 -0
  204. pyflow/analysis/alias/kcfa/_pythonstan/world/analysis_manager.py +141 -0
  205. pyflow/analysis/alias/kcfa/_pythonstan/world/class_hierarchy.py +56 -0
  206. pyflow/analysis/alias/kcfa/_pythonstan/world/config.py +93 -0
  207. pyflow/analysis/alias/kcfa/_pythonstan/world/import_manager.py +16 -0
  208. pyflow/analysis/alias/kcfa/_pythonstan/world/namespace.py +327 -0
  209. pyflow/analysis/alias/kcfa/_pythonstan/world/pipeline.py +153 -0
  210. pyflow/analysis/alias/kcfa/_pythonstan/world/scope_manager.py +133 -0
  211. pyflow/analysis/alias/kcfa/_pythonstan/world/world.py +49 -0
  212. pyflow/analysis/alias/kcfa/bridge.py +136 -0
  213. pyflow/analysis/astcollector.py +171 -0
  214. pyflow/analysis/cache/incremental.py +226 -0
  215. pyflow/analysis/callgraph/__init__.py +58 -0
  216. pyflow/analysis/callgraph/ast_based.py +184 -0
  217. pyflow/analysis/callgraph/callgraph.py +86 -0
  218. pyflow/analysis/callgraph/constraint_based/__init__.py +26 -0
  219. pyflow/analysis/callgraph/constraint_based/_analyzer.py +1976 -0
  220. pyflow/analysis/callgraph/constraint_based/_collector.py +704 -0
  221. pyflow/analysis/callgraph/constraint_based/_evaluator.py +984 -0
  222. pyflow/analysis/callgraph/constraint_based/_loader.py +115 -0
  223. pyflow/analysis/callgraph/constraint_based/_resolver.py +2852 -0
  224. pyflow/analysis/callgraph/constraint_based/api.py +233 -0
  225. pyflow/analysis/callgraph/constraint_based/engine.py +717 -0
  226. pyflow/analysis/callgraph/constraint_based/model.py +303 -0
  227. pyflow/analysis/callgraph/formats.py +99 -0
  228. pyflow/analysis/callgraph/pycg_based.py +273 -0
  229. pyflow/analysis/cdg/__init__.py +52 -0
  230. pyflow/analysis/cdg/construction.py +256 -0
  231. pyflow/analysis/cdg/dump.py +415 -0
  232. pyflow/analysis/cdg/graph.py +456 -0
  233. pyflow/analysis/cfg/__init__.py +38 -0
  234. pyflow/analysis/cfg/dfs.py +55 -0
  235. pyflow/analysis/cfg/dom.py +243 -0
  236. pyflow/analysis/cfg/dump.py +322 -0
  237. pyflow/analysis/cfg/expandphi.py +113 -0
  238. pyflow/analysis/cfg/gc.py +93 -0
  239. pyflow/analysis/cfg/graph.py +878 -0
  240. pyflow/analysis/cfg/inline.py +337 -0
  241. pyflow/analysis/cfg/killflow.py +360 -0
  242. pyflow/analysis/cfg/optimize.py +164 -0
  243. pyflow/analysis/cfg/simplify.py +19 -0
  244. pyflow/analysis/cfg/ssa.py +693 -0
  245. pyflow/analysis/cfg/ssatransform.py +13 -0
  246. pyflow/analysis/cfg/structuralanalysis.py +422 -0
  247. pyflow/analysis/cfg/transform.py +527 -0
  248. pyflow/analysis/cpa/__init__.py +657 -0
  249. pyflow/analysis/cpa/base.py +430 -0
  250. pyflow/analysis/cpa/codecloner.py +262 -0
  251. pyflow/analysis/cpa/constraintextractor.py +797 -0
  252. pyflow/analysis/cpa/constraints.py +1071 -0
  253. pyflow/analysis/cpa/simpleimagebuilder.py +299 -0
  254. pyflow/analysis/cpasignature.py +132 -0
  255. pyflow/analysis/cpg/__init__.py +93 -0
  256. pyflow/analysis/cpg/build.py +221 -0
  257. pyflow/analysis/cpg/dump.py +102 -0
  258. pyflow/analysis/cpg/graph.py +1877 -0
  259. pyflow/analysis/cpg/persist.py +346 -0
  260. pyflow/analysis/cpg/profiles.py +286 -0
  261. pyflow/analysis/cpg/rules.py +365 -0
  262. pyflow/analysis/cpg/taint.py +1934 -0
  263. pyflow/analysis/dataflowIR/__init__.py +6 -0
  264. pyflow/analysis/dataflowIR/annotations.py +174 -0
  265. pyflow/analysis/dataflowIR/convert.py +795 -0
  266. pyflow/analysis/dataflowIR/dump.py +443 -0
  267. pyflow/analysis/dataflowIR/graph.py +1648 -0
  268. pyflow/analysis/dataflowIR/ordering.py +134 -0
  269. pyflow/analysis/dataflowIR/predicate.py +233 -0
  270. pyflow/analysis/dataflowIR/transform/__init__.py +15 -0
  271. pyflow/analysis/dataflowIR/transform/dce.py +215 -0
  272. pyflow/analysis/dataflowIR/transform/loadelimination.py +185 -0
  273. pyflow/analysis/dataflowIR/traverse.py +91 -0
  274. pyflow/analysis/ddg/__init__.py +53 -0
  275. pyflow/analysis/ddg/construction.py +310 -0
  276. pyflow/analysis/ddg/dump.py +218 -0
  277. pyflow/analysis/ddg/graph.py +295 -0
  278. pyflow/analysis/dump/__init__.py +15 -0
  279. pyflow/analysis/dump/dumpgraphs.py +93 -0
  280. pyflow/analysis/dump/dumpreport.py +841 -0
  281. pyflow/analysis/dump/dumputil.py +98 -0
  282. pyflow/analysis/fsdf/__init__.py +1107 -0
  283. pyflow/analysis/fsdf/canonicalset.py +176 -0
  284. pyflow/analysis/fsdf/canonicaltree.py +1071 -0
  285. pyflow/analysis/ifds/__init__.py +284 -0
  286. pyflow/analysis/ifds/annotation_fallback.py +21 -0
  287. pyflow/analysis/ifds/annotation_synthesis.py +803 -0
  288. pyflow/analysis/ifds/api.py +567 -0
  289. pyflow/analysis/ifds/backward_solver.py +328 -0
  290. pyflow/analysis/ifds/cfg_adapter.py +1575 -0
  291. pyflow/analysis/ifds/clients/_call_model.py +166 -0
  292. pyflow/analysis/ifds/clients/_client_common.py +1617 -0
  293. pyflow/analysis/ifds/clients/flow_path_collector.py +159 -0
  294. pyflow/analysis/ifds/clients/library_presets.py +1134 -0
  295. pyflow/analysis/ifds/clients/nullness.py +754 -0
  296. pyflow/analysis/ifds/clients/registry/__init__.py +26 -0
  297. pyflow/analysis/ifds/clients/registry/loader.py +688 -0
  298. pyflow/analysis/ifds/clients/registry/validate.py +37 -0
  299. pyflow/analysis/ifds/clients/taint.py +782 -0
  300. pyflow/analysis/ifds/clients/typestate.py +1550 -0
  301. pyflow/analysis/ifds/clients/typestate_engine.py +534 -0
  302. pyflow/analysis/ifds/diagnostics.py +29 -0
  303. pyflow/analysis/ifds/preparation.py +97 -0
  304. pyflow/analysis/ifds/problem.py +363 -0
  305. pyflow/analysis/ifds/queries.py +142 -0
  306. pyflow/analysis/ifds/reporting.py +289 -0
  307. pyflow/analysis/ifds/shadow_scan.py +349 -0
  308. pyflow/analysis/ifds/solver.py +1495 -0
  309. pyflow/analysis/ifds/supergraph.py +186 -0
  310. pyflow/analysis/ifds/transfers.py +187 -0
  311. pyflow/analysis/ipa/__init__.py +77 -0
  312. pyflow/analysis/ipa/calling/__init__.py +17 -0
  313. pyflow/analysis/ipa/calling/callbinder.py +93 -0
  314. pyflow/analysis/ipa/calling/cpa.py +197 -0
  315. pyflow/analysis/ipa/calling/transfer.py +208 -0
  316. pyflow/analysis/ipa/constraintextractor.py +804 -0
  317. pyflow/analysis/ipa/constraints/__init__.py +18 -0
  318. pyflow/analysis/ipa/constraints/base.py +66 -0
  319. pyflow/analysis/ipa/constraints/calls.py +424 -0
  320. pyflow/analysis/ipa/constraints/flow.py +445 -0
  321. pyflow/analysis/ipa/constraints/node.py +299 -0
  322. pyflow/analysis/ipa/constraints/qualifiers.py +17 -0
  323. pyflow/analysis/ipa/constraints/split.py +178 -0
  324. pyflow/analysis/ipa/dump.py +246 -0
  325. pyflow/analysis/ipa/entrypointbuilder.py +52 -0
  326. pyflow/analysis/ipa/escape/__init__.py +19 -0
  327. pyflow/analysis/ipa/escape/objectescape.py +77 -0
  328. pyflow/analysis/ipa/ipanalysis.py +319 -0
  329. pyflow/analysis/ipa/memory/__init__.py +16 -0
  330. pyflow/analysis/ipa/memory/extractorpolicy.py +38 -0
  331. pyflow/analysis/ipa/memory/storegraphpolicy.py +17 -0
  332. pyflow/analysis/ipa/model/__init__.py +18 -0
  333. pyflow/analysis/ipa/model/context.py +277 -0
  334. pyflow/analysis/ipa/model/invocation.py +159 -0
  335. pyflow/analysis/ipa/model/object.py +102 -0
  336. pyflow/analysis/ipa/model/objectname.py +77 -0
  337. pyflow/analysis/ipa/model/region.py +51 -0
  338. pyflow/analysis/ipa/summary/__init__.py +120 -0
  339. pyflow/analysis/ir_utils.py +215 -0
  340. pyflow/analysis/lifetimeanalysis/__init__.py +935 -0
  341. pyflow/analysis/lifetimeanalysis/database/__init__.py +17 -0
  342. pyflow/analysis/lifetimeanalysis/database/base.py +72 -0
  343. pyflow/analysis/lifetimeanalysis/database/lattice.py +189 -0
  344. pyflow/analysis/lifetimeanalysis/database/mapping.py +174 -0
  345. pyflow/analysis/lifetimeanalysis/database/structure.py +224 -0
  346. pyflow/analysis/lifetimeanalysis/database/tupleset.py +141 -0
  347. pyflow/analysis/numbering/__init__.py +17 -0
  348. pyflow/analysis/numbering/dataflow.py +214 -0
  349. pyflow/analysis/numbering/dominance.py +120 -0
  350. pyflow/analysis/numbering/readmodify.py +251 -0
  351. pyflow/analysis/numbering/ssa.py +604 -0
  352. pyflow/analysis/pdg/__init__.py +55 -0
  353. pyflow/analysis/pdg/construction.py +447 -0
  354. pyflow/analysis/pdg/cypher.py +1150 -0
  355. pyflow/analysis/pdg/dump.py +129 -0
  356. pyflow/analysis/pdg/graph.py +308 -0
  357. pyflow/analysis/programculler.py +224 -0
  358. pyflow/analysis/shape/__init__.py +466 -0
  359. pyflow/analysis/shape/constraintbuilder.py +933 -0
  360. pyflow/analysis/shape/constraints.py +510 -0
  361. pyflow/analysis/shape/dataflow.py +202 -0
  362. pyflow/analysis/shape/model/__init__.py +27 -0
  363. pyflow/analysis/shape/model/canonical.py +137 -0
  364. pyflow/analysis/shape/model/configuration.py +78 -0
  365. pyflow/analysis/shape/model/expressions.py +268 -0
  366. pyflow/analysis/shape/model/pathinformation.py +645 -0
  367. pyflow/analysis/shape/model/referencecount.py +212 -0
  368. pyflow/analysis/shape/model/secondary.py +31 -0
  369. pyflow/analysis/shape/model/slots.py +69 -0
  370. pyflow/analysis/shape/regionanalysis.py +152 -0
  371. pyflow/analysis/shape/transferfunctions.py +353 -0
  372. pyflow/analysis/storegraph/__init__.py +19 -0
  373. pyflow/analysis/storegraph/annotations.py +70 -0
  374. pyflow/analysis/storegraph/canonicalobjects.py +329 -0
  375. pyflow/analysis/storegraph/extendedtypes.py +311 -0
  376. pyflow/analysis/storegraph/setmanager.py +123 -0
  377. pyflow/analysis/storegraph/storegraph.py +577 -0
  378. pyflow/analysis/tools.py +112 -0
  379. pyflow/analysis/typeinfo/__init__.py +93 -0
  380. pyflow/analysis/typeinfo/_config.py +84 -0
  381. pyflow/analysis/typeinfo/annotation_resolver.py +483 -0
  382. pyflow/analysis/typeinfo/collector.py +361 -0
  383. pyflow/analysis/typeinfo/constants.py +378 -0
  384. pyflow/analysis/typeinfo/docstring_parser.py +289 -0
  385. pyflow/analysis/typeinfo/generic_binder.py +145 -0
  386. pyflow/analysis/typeinfo/gradual_typing.py +255 -0
  387. pyflow/analysis/typeinfo/models.py +43 -0
  388. pyflow/analysis/typeinfo/service.py +774 -0
  389. pyflow/analysis/typeinfo/string_subtype_inference.py +113 -0
  390. pyflow/analysis/typeinfo/string_subtypes.py +150 -0
  391. pyflow/analysis/typeinfo/stub_loader.py +667 -0
  392. pyflow/analysis/typeinfo/type_inference.py +747 -0
  393. pyflow/analysis/typeinfo/type_utils.py +361 -0
  394. pyflow/analysis/typeinfo/typesystem.py +2346 -0
  395. pyflow/analysis/typeinfo/typetracing.py +672 -0
  396. pyflow/api/__init__.py +94 -0
  397. pyflow/api/entrypoints/__init__.py +36 -0
  398. pyflow/api/entrypoints/declaration.py +345 -0
  399. pyflow/api/entrypoints/entry_point.py +59 -0
  400. pyflow/api/entrypoints/wrappers.py +83 -0
  401. pyflow/api/queries/__init__.py +65 -0
  402. pyflow/api/queries/_cfg_utils.py +53 -0
  403. pyflow/api/queries/_dataflow_ipa.py +29 -0
  404. pyflow/api/queries/_dataflow_reaching_defs.py +105 -0
  405. pyflow/api/queries/_dataflow_store_graph.py +152 -0
  406. pyflow/api/queries/_dataflow_taint.py +76 -0
  407. pyflow/api/queries/_models.py +177 -0
  408. pyflow/api/queries/call_graph.py +168 -0
  409. pyflow/api/queries/capabilities.py +106 -0
  410. pyflow/api/queries/context.py +152 -0
  411. pyflow/api/queries/control_flow.py +35 -0
  412. pyflow/api/queries/data_flow.py +157 -0
  413. pyflow/api/queries/engine.py +201 -0
  414. pyflow/api/queries/localization.py +8 -0
  415. pyflow/api/queries/service.py +339 -0
  416. pyflow/api/queries/tasks/__init__.py +8 -0
  417. pyflow/api/queries/tasks/code_localization.py +425 -0
  418. pyflow/api/queries/tasks/test_generation.py +338 -0
  419. pyflow/api/queries/test_generation.py +8 -0
  420. pyflow/application/__init__.py +109 -0
  421. pyflow/application/context.py +116 -0
  422. pyflow/application/errors.py +55 -0
  423. pyflow/application/makefile.py +213 -0
  424. pyflow/application/passes.py +515 -0
  425. pyflow/application/passmanager.py +755 -0
  426. pyflow/application/pipeline.py +459 -0
  427. pyflow/application/program.py +147 -0
  428. pyflow/checker/__init__.py +51 -0
  429. pyflow/checker/formatters/__init__.py +14 -0
  430. pyflow/checker/formatters/json.py +153 -0
  431. pyflow/checker/formatters/sarif.py +263 -0
  432. pyflow/checker/formatters/text.py +168 -0
  433. pyflow/checker/formatters/utils.py +17 -0
  434. pyflow/checker/llm/__init__.py +19 -0
  435. pyflow/checker/llm/exploit.py +129 -0
  436. pyflow/checker/llm/judge.py +105 -0
  437. pyflow/checker/llm/llm_utils.py +131 -0
  438. pyflow/checker/microbench.py +338 -0
  439. pyflow/checker/pattern/__init__.py +14 -0
  440. pyflow/checker/pattern/checkers/__init__.py +34 -0
  441. pyflow/checker/pattern/checkers/aws_security.py +234 -0
  442. pyflow/checker/pattern/checkers/blacklist_calls.py +54 -0
  443. pyflow/checker/pattern/checkers/blacklist_imports.py +73 -0
  444. pyflow/checker/pattern/checkers/class_pollution.py +459 -0
  445. pyflow/checker/pattern/checkers/command_injection.py +340 -0
  446. pyflow/checker/pattern/checkers/deserialization.py +373 -0
  447. pyflow/checker/pattern/checkers/django_security.py +285 -0
  448. pyflow/checker/pattern/checkers/exception_handling.py +317 -0
  449. pyflow/checker/pattern/checkers/exec_use.py +16 -0
  450. pyflow/checker/pattern/checkers/fastapi_security.py +291 -0
  451. pyflow/checker/pattern/checkers/flask_security.py +264 -0
  452. pyflow/checker/pattern/checkers/hardcoded_password.py +239 -0
  453. pyflow/checker/pattern/checkers/hardcoded_secrets.py +279 -0
  454. pyflow/checker/pattern/checkers/ldap_injection.py +281 -0
  455. pyflow/checker/pattern/checkers/path_traversal.py +124 -0
  456. pyflow/checker/pattern/checkers/shell_injection.py +46 -0
  457. pyflow/checker/pattern/checkers/sql_injection.py +170 -0
  458. pyflow/checker/pattern/checkers/sql_injection_enhanced.py +574 -0
  459. pyflow/checker/pattern/checkers/ssrf.py +419 -0
  460. pyflow/checker/pattern/checkers/template_security.py +310 -0
  461. pyflow/checker/pattern/checkers/weak_crypto.py +89 -0
  462. pyflow/checker/pattern/checkers/xxe_vulnerabilities.py +155 -0
  463. pyflow/checker/pattern/core/__init__.py +17 -0
  464. pyflow/checker/pattern/core/blacklist.py +569 -0
  465. pyflow/checker/pattern/core/config.py +15 -0
  466. pyflow/checker/pattern/core/constants.py +52 -0
  467. pyflow/checker/pattern/core/context.py +276 -0
  468. pyflow/checker/pattern/core/issue.py +325 -0
  469. pyflow/checker/pattern/core/manager.py +253 -0
  470. pyflow/checker/pattern/core/metrics.py +78 -0
  471. pyflow/checker/pattern/core/node_visitor.py +430 -0
  472. pyflow/checker/pattern/core/test_loader.py +41 -0
  473. pyflow/checker/pattern/core/test_properties.py +54 -0
  474. pyflow/checker/pattern/core/test_set.py +33 -0
  475. pyflow/checker/pattern/core/tester.py +244 -0
  476. pyflow/checker/pattern/core/utils.py +167 -0
  477. pyflow/checker/quality.py +374 -0
  478. pyflow/checker/semantic/__init__.py +16 -0
  479. pyflow/checker/semantic/core/__init__.py +23 -0
  480. pyflow/checker/semantic/core/base.py +26 -0
  481. pyflow/checker/semantic/core/context.py +270 -0
  482. pyflow/checker/semantic/core/issue.py +9 -0
  483. pyflow/checker/semantic/core/manager.py +150 -0
  484. pyflow/checker/semantic/core/runner.py +58 -0
  485. pyflow/checker/semantic/detectors/__init__.py +17 -0
  486. pyflow/checker/semantic/detectors/ast_helpers.py +114 -0
  487. pyflow/checker/semantic/detectors/leak.py +500 -0
  488. pyflow/checker/semantic/detectors/null_dereference.py +132 -0
  489. pyflow/checker/semantic/detectors/semantic_taint.py +2258 -0
  490. pyflow/checker/supply_chain.py +568 -0
  491. pyflow/cli/__init__.py +12 -0
  492. pyflow/cli/alias.py +397 -0
  493. pyflow/cli/callgraph.py +185 -0
  494. pyflow/cli/ir.py +461 -0
  495. pyflow/cli/main.py +125 -0
  496. pyflow/cli/optimize.py +603 -0
  497. pyflow/cli/security.py +1262 -0
  498. pyflow/cli/supply_chain.py +114 -0
  499. pyflow/config/nullness/django.json +21 -0
  500. pyflow/config/nullness/fastapi.json +17 -0
  501. pyflow/config/nullness/flask.json +37 -0
  502. pyflow/config/nullness/sql.json +21 -0
  503. pyflow/config/nullness/sqlalchemy.json +33 -0
  504. pyflow/config/nullness/stdlib.json +161 -0
  505. pyflow/config/rule-pack.schema.json +54 -0
  506. pyflow/config/taint/aiohttp.json +640 -0
  507. pyflow/config/taint/cloud.json +485 -0
  508. pyflow/config/taint/concurrency.json +25 -0
  509. pyflow/config/taint/django.json +710 -0
  510. pyflow/config/taint/falcon.json +375 -0
  511. pyflow/config/taint/fastapi.json +192 -0
  512. pyflow/config/taint/flask.json +504 -0
  513. pyflow/config/taint/injection.json +2294 -0
  514. pyflow/config/taint/network.json +367 -0
  515. pyflow/config/taint/nosql.json +166 -0
  516. pyflow/config/taint/pandas.json +463 -0
  517. pyflow/config/taint/requests.json +952 -0
  518. pyflow/config/taint/serialization.json +109 -0
  519. pyflow/config/taint/sql.json +730 -0
  520. pyflow/config/taint/sqlalchemy.json +235 -0
  521. pyflow/config/taint/stdlib.json +4388 -0
  522. pyflow/config/taint/tornado.json +151 -0
  523. pyflow/config/taint/xml.json +89 -0
  524. pyflow/config/typestate/concurrency.json +145 -0
  525. pyflow/config/typestate/network.json +130 -0
  526. pyflow/config/typestate/requests.json +88 -0
  527. pyflow/config/typestate/sql.json +107 -0
  528. pyflow/config/typestate/sqlalchemy.json +45 -0
  529. pyflow/config/typestate/stdlib.json +91 -0
  530. pyflow/config.py +30 -0
  531. pyflow/frontend/__init__.py +28 -0
  532. pyflow/frontend/ast_converter.py +2593 -0
  533. pyflow/frontend/class_hierarchy.py +665 -0
  534. pyflow/frontend/dependency_resolver.py +1836 -0
  535. pyflow/frontend/function_extractor.py +602 -0
  536. pyflow/frontend/object_manager.py +224 -0
  537. pyflow/frontend/programextractor.py +1033 -0
  538. pyflow/frontend/project_resolution.py +511 -0
  539. pyflow/frontend/source_locator.py +163 -0
  540. pyflow/frontend/stub_manager.py +425 -0
  541. pyflow/language/__init__.py +5 -0
  542. pyflow/language/asttools/__init__.py +34 -0
  543. pyflow/language/asttools/annotation.py +242 -0
  544. pyflow/language/asttools/antlrtranslator.py +344 -0
  545. pyflow/language/asttools/astpprint.py +132 -0
  546. pyflow/language/asttools/complexity.py +224 -0
  547. pyflow/language/asttools/decorators.py +71 -0
  548. pyflow/language/asttools/metaast/__init__.py +370 -0
  549. pyflow/language/asttools/metaast/codegeneration.py +622 -0
  550. pyflow/language/asttools/origin.py +83 -0
  551. pyflow/language/asttools/symbols.py +204 -0
  552. pyflow/language/asttools/visitors.py +179 -0
  553. pyflow/language/modules/__init__.py +18 -0
  554. pyflow/language/modules/_scope_graph.py +484 -0
  555. pyflow/language/modules/ast_helper.py +211 -0
  556. pyflow/language/modules/module_definitions.py +240 -0
  557. pyflow/language/modules/project_handler.py +110 -0
  558. pyflow/language/modules/transform.py +290 -0
  559. pyflow/language/modules/util.py +413 -0
  560. pyflow/language/python/__init__.py +21 -0
  561. pyflow/language/python/annotations.py +378 -0
  562. pyflow/language/python/ast.py +749 -0
  563. pyflow/language/python/collapser.py +604 -0
  564. pyflow/language/python/default_markers.py +8 -0
  565. pyflow/language/python/defuse.py +469 -0
  566. pyflow/language/python/fold.py +220 -0
  567. pyflow/language/python/program.py +371 -0
  568. pyflow/language/python/pythonbase.py +353 -0
  569. pyflow/language/python/pythonoutput.py +82 -0
  570. pyflow/language/python/simplecodegen.py +975 -0
  571. pyflow/optimization/__init__.py +29 -0
  572. pyflow/optimization/argumentnormalization.py +429 -0
  573. pyflow/optimization/callconverter.py +295 -0
  574. pyflow/optimization/clone.py +743 -0
  575. pyflow/optimization/codeinlining.py +587 -0
  576. pyflow/optimization/convertboolelimination.py +111 -0
  577. pyflow/optimization/cullprogram.py +155 -0
  578. pyflow/optimization/dataflow/__init__.py +17 -0
  579. pyflow/optimization/dataflow/base.py +780 -0
  580. pyflow/optimization/dataflow/forward.py +681 -0
  581. pyflow/optimization/dataflow/reverse.py +454 -0
  582. pyflow/optimization/dce.py +354 -0
  583. pyflow/optimization/fold.py +780 -0
  584. pyflow/optimization/loadelimination.py +342 -0
  585. pyflow/optimization/methodcall.py +554 -0
  586. pyflow/optimization/rewrite.py +129 -0
  587. pyflow/optimization/simplify.py +143 -0
  588. pyflow/optimization/storeelimination.py +129 -0
  589. pyflow/optimization/termrewrite.py +287 -0
  590. pyflow/stats/__init__.py +436 -0
  591. pyflow/stubs/__init__.py +34 -0
  592. pyflow/stubs/__pycache__/__init__.cpython-313.pyc +0 -0
  593. pyflow/stubs/__pycache__/lltranslator.cpython-313.pyc +0 -0
  594. pyflow/stubs/__pycache__/stubcollector.cpython-313.pyc +0 -0
  595. pyflow/stubs/lltranslator.py +493 -0
  596. pyflow/stubs/std/__init__.py +83 -0
  597. pyflow/stubs/std/__pycache__/__init__.cpython-313.pyc +0 -0
  598. pyflow/stubs/std/__pycache__/argparse_stubs.cpython-313.pyc +0 -0
  599. pyflow/stubs/std/__pycache__/collections_stubs.cpython-313.pyc +0 -0
  600. pyflow/stubs/std/__pycache__/container.cpython-313.pyc +0 -0
  601. pyflow/stubs/std/__pycache__/copy_stubs.cpython-313.pyc +0 -0
  602. pyflow/stubs/std/__pycache__/csv_stubs.cpython-313.pyc +0 -0
  603. pyflow/stubs/std/__pycache__/datetime_stubs.cpython-313.pyc +0 -0
  604. pyflow/stubs/std/__pycache__/functools_stubs.cpython-313.pyc +0 -0
  605. pyflow/stubs/std/__pycache__/hashlib_stubs.cpython-313.pyc +0 -0
  606. pyflow/stubs/std/__pycache__/interpreter.cpython-313.pyc +0 -0
  607. pyflow/stubs/std/__pycache__/io_stubs.cpython-313.pyc +0 -0
  608. pyflow/stubs/std/__pycache__/itertools_stubs.cpython-313.pyc +0 -0
  609. pyflow/stubs/std/__pycache__/json_stubs.cpython-313.pyc +0 -0
  610. pyflow/stubs/std/__pycache__/llfunc.cpython-313.pyc +0 -0
  611. pyflow/stubs/std/__pycache__/logging_stubs.cpython-313.pyc +0 -0
  612. pyflow/stubs/std/__pycache__/mathstubs.cpython-313.pyc +0 -0
  613. pyflow/stubs/std/__pycache__/operator_stubs.cpython-313.pyc +0 -0
  614. pyflow/stubs/std/__pycache__/os_stubs.cpython-313.pyc +0 -0
  615. pyflow/stubs/std/__pycache__/pathlib_stubs.cpython-313.pyc +0 -0
  616. pyflow/stubs/std/__pycache__/pickle_stubs.cpython-313.pyc +0 -0
  617. pyflow/stubs/std/__pycache__/random.cpython-313.pyc +0 -0
  618. pyflow/stubs/std/__pycache__/re_stubs.cpython-313.pyc +0 -0
  619. pyflow/stubs/std/__pycache__/sampler.cpython-313.pyc +0 -0
  620. pyflow/stubs/std/__pycache__/shutil_stubs.cpython-313.pyc +0 -0
  621. pyflow/stubs/std/__pycache__/subprocess_stubs.cpython-313.pyc +0 -0
  622. pyflow/stubs/std/__pycache__/sys_stubs.cpython-313.pyc +0 -0
  623. pyflow/stubs/std/__pycache__/tempfile_stubs.cpython-313.pyc +0 -0
  624. pyflow/stubs/std/__pycache__/threading_stubs.cpython-313.pyc +0 -0
  625. pyflow/stubs/std/__pycache__/time_stubs.cpython-313.pyc +0 -0
  626. pyflow/stubs/std/argparse_stubs.py +200 -0
  627. pyflow/stubs/std/collections_stubs.py +77 -0
  628. pyflow/stubs/std/container.py +374 -0
  629. pyflow/stubs/std/copy_stubs.py +54 -0
  630. pyflow/stubs/std/csv_stubs.py +187 -0
  631. pyflow/stubs/std/datetime_stubs.py +70 -0
  632. pyflow/stubs/std/functools_stubs.py +61 -0
  633. pyflow/stubs/std/hashlib_stubs.py +198 -0
  634. pyflow/stubs/std/interpreter.py +360 -0
  635. pyflow/stubs/std/io_stubs.py +339 -0
  636. pyflow/stubs/std/itertools_stubs.py +89 -0
  637. pyflow/stubs/std/json_stubs.py +40 -0
  638. pyflow/stubs/std/llfunc.py +240 -0
  639. pyflow/stubs/std/logging_stubs.py +295 -0
  640. pyflow/stubs/std/mathstubs.py +393 -0
  641. pyflow/stubs/std/objects/__init__.py +9 -0
  642. pyflow/stubs/std/objects/__pycache__/__init__.cpython-313.pyc +0 -0
  643. pyflow/stubs/std/objects/__pycache__/float.cpython-313.pyc +0 -0
  644. pyflow/stubs/std/objects/__pycache__/int.cpython-313.pyc +0 -0
  645. pyflow/stubs/std/objects/__pycache__/str.cpython-313.pyc +0 -0
  646. pyflow/stubs/std/objects/float.py +248 -0
  647. pyflow/stubs/std/objects/int.py +221 -0
  648. pyflow/stubs/std/objects/str.py +175 -0
  649. pyflow/stubs/std/operator_stubs.py +148 -0
  650. pyflow/stubs/std/os_stubs.py +727 -0
  651. pyflow/stubs/std/pathlib_stubs.py +364 -0
  652. pyflow/stubs/std/pickle_stubs.py +97 -0
  653. pyflow/stubs/std/random.py +29 -0
  654. pyflow/stubs/std/re_stubs.py +178 -0
  655. pyflow/stubs/std/sampler.py +17 -0
  656. pyflow/stubs/std/shutil_stubs.py +212 -0
  657. pyflow/stubs/std/subprocess_stubs.py +149 -0
  658. pyflow/stubs/std/sys_stubs.py +226 -0
  659. pyflow/stubs/std/tempfile_stubs.py +129 -0
  660. pyflow/stubs/std/threading_stubs.py +328 -0
  661. pyflow/stubs/std/time_stubs.py +283 -0
  662. pyflow/stubs/stubcollector.py +463 -0
  663. pyflow/util/PADS/Automata.py +640 -0
  664. pyflow/util/PADS/BFS.py +28 -0
  665. pyflow/util/PADS/Biconnectivity.py +270 -0
  666. pyflow/util/PADS/Bipartite.py +89 -0
  667. pyflow/util/PADS/BipartiteMatching.py +129 -0
  668. pyflow/util/PADS/CardinalityMatching.py +316 -0
  669. pyflow/util/PADS/Chordal.py +33 -0
  670. pyflow/util/PADS/CubicHam.py +351 -0
  671. pyflow/util/PADS/DFS.py +100 -0
  672. pyflow/util/PADS/Eratosthenes.py +138 -0
  673. pyflow/util/PADS/GraphExamples.py +36 -0
  674. pyflow/util/PADS/Graphs.py +82 -0
  675. pyflow/util/PADS/GrayGraph.py +36 -0
  676. pyflow/util/PADS/IntegerPartitions.py +317 -0
  677. pyflow/util/PADS/LCA.py +349 -0
  678. pyflow/util/PADS/LCS.py +38 -0
  679. pyflow/util/PADS/LexBFS.py +32 -0
  680. pyflow/util/PADS/LongestIncreasingSubsequence.py +65 -0
  681. pyflow/util/PADS/Medium.py +414 -0
  682. pyflow/util/PADS/MinimumSpanningTree.py +26 -0
  683. pyflow/util/PADS/OrderedSequence.py +133 -0
  684. pyflow/util/PADS/PartialCube.py +227 -0
  685. pyflow/util/PADS/PartialOrder.py +129 -0
  686. pyflow/util/PADS/PartitionRefinement.py +85 -0
  687. pyflow/util/PADS/ReadUndirectedGraph.py +431 -0
  688. pyflow/util/PADS/Repetitivity.py +160 -0
  689. pyflow/util/PADS/SMAWK.py +190 -0
  690. pyflow/util/PADS/Sequence.py +118 -0
  691. pyflow/util/PADS/StrongConnectivity.py +106 -0
  692. pyflow/util/PADS/Sudoku.py +2028 -0
  693. pyflow/util/PADS/UnionFind.py +63 -0
  694. pyflow/util/PADS/Util.py +28 -0
  695. pyflow/util/PADS/Wrap.py +79 -0
  696. pyflow/util/PADS/__init__.py +13 -0
  697. pyflow/util/PADS/xyzGraph.py +127 -0
  698. pyflow/util/__init__.py +19 -0
  699. pyflow/util/antlr3/__init__.py +134 -0
  700. pyflow/util/antlr3/compat.py +12 -0
  701. pyflow/util/antlr3/constants.py +24 -0
  702. pyflow/util/antlr3/dfa.py +179 -0
  703. pyflow/util/antlr3/dottreegen.py +170 -0
  704. pyflow/util/antlr3/exceptions.py +346 -0
  705. pyflow/util/antlr3/extras.py +11 -0
  706. pyflow/util/antlr3/main.py +198 -0
  707. pyflow/util/antlr3/recognizers.py +1341 -0
  708. pyflow/util/antlr3/streams.py +1250 -0
  709. pyflow/util/antlr3/tokens.py +370 -0
  710. pyflow/util/antlr3/tree.py +2190 -0
  711. pyflow/util/antlr3/treewizard.py +566 -0
  712. pyflow/util/application/__init__.py +13 -0
  713. pyflow/util/application/async_utils.py +101 -0
  714. pyflow/util/application/compilerexceptions.py +21 -0
  715. pyflow/util/application/console.py +235 -0
  716. pyflow/util/application/errorhandler.py +241 -0
  717. pyflow/util/canonical.py +204 -0
  718. pyflow/util/debug/__init__.py +11 -0
  719. pyflow/util/debug/blame.py +94 -0
  720. pyflow/util/debug/debugger.py +105 -0
  721. pyflow/util/debug/trace.py +56 -0
  722. pyflow/util/graphalgorithim/__init__.py +17 -0
  723. pyflow/util/graphalgorithim/basic.py +77 -0
  724. pyflow/util/graphalgorithim/color.py +104 -0
  725. pyflow/util/graphalgorithim/djtree.py +299 -0
  726. pyflow/util/graphalgorithim/dominator.py +531 -0
  727. pyflow/util/graphalgorithim/exclusiongraph.py +624 -0
  728. pyflow/util/graphalgorithim/merge.py +221 -0
  729. pyflow/util/io/__init__.py +6 -0
  730. pyflow/util/io/dot.py +540 -0
  731. pyflow/util/io/filesystem.py +210 -0
  732. pyflow/util/io/formatting.py +71 -0
  733. pyflow/util/io/report.py +224 -0
  734. pyflow/util/io/xmloutput.py +270 -0
  735. pyflow/util/json_dict.py +92 -0
  736. pyflow/util/monkeypatch/__init__.py +21 -0
  737. pyflow/util/monkeypatch/xcollections.py +206 -0
  738. pyflow/util/monkeypatch/xmath.py +108 -0
  739. pyflow/util/monkeypatch/xnamedtuple.py +180 -0
  740. pyflow/util/monkeypatch/xtypes.py +56 -0
  741. pyflow/util/orderedset.py +420 -0
  742. pyflow/util/pydot.py +1998 -0
  743. pyflow/util/python/__init__.py +136 -0
  744. pyflow/util/python/apply.py +180 -0
  745. pyflow/util/python/calling.py +558 -0
  746. pyflow/util/python/opnames.py +153 -0
  747. pyflow/util/randomness.py +230 -0
  748. pyflow/util/singleton.py +112 -0
  749. pyflow/util/tvl.py +321 -0
  750. pyflow/util/typedispatch.py +342 -0
  751. pyflow/util/unionfind.py +169 -0
  752. pyflow_analysis-0.1.0.dist-info/METADATA +184 -0
  753. pyflow_analysis-0.1.0.dist-info/RECORD +757 -0
  754. pyflow_analysis-0.1.0.dist-info/WHEEL +5 -0
  755. pyflow_analysis-0.1.0.dist-info/entry_points.txt +2 -0
  756. pyflow_analysis-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
  757. pyflow_analysis-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3493 @@
1
+ """Inter-procedural call transfer methods (mixin for HeapTransferEngine).
2
+
3
+ This module contains the inter-procedural call methods extracted from
4
+ :class:`HeapTransferEngine`. It is used as a mixin and should not be
5
+ instantiated directly.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pyflow.analysis.ir_utils import (
11
+ actual_argument_expressions,
12
+ assigned_locals,
13
+ call_keyword_spreads,
14
+ call_positional_items,
15
+ call_positional_spreads,
16
+ class_cell,
17
+ code_closure_cells,
18
+ code_definition_annotations,
19
+ resolve_call_name,
20
+ )
21
+ from pyflow.language.python import ast as py_ast
22
+ from pyflow.language.python.default_markers import MISSING_DEFAULT
23
+
24
+ from .abstraction import HeapEnvironment
25
+ from .heap_effects import (
26
+ CALL_RETURN_COPY,
27
+ CALL_RETURN_FRESH,
28
+ CALL_RETURN_OPAQUE,
29
+ CALL_RETURN_SUMMARY,
30
+ DYNAMIC_SUBSCRIPT_WILDCARD,
31
+ )
32
+ from .heap_state import HeapState
33
+ from .heap_summary import HeapSummary, ProcedureHeapSummary
34
+ from .intrinsics import (
35
+ CALL_RETURN_ARG,
36
+ CALL_RETURN_NONE,
37
+ CALL_RETURN_SELF,
38
+ )
39
+ from .model import HeapLocation, UpdatePolicy
40
+
41
+ # Import dataclasses from transfer.py at runtime.
42
+ # These are defined BEFORE the class in transfer.py, so importing this module
43
+ # after those definitions works without circular import issues when
44
+ # transfer.py places the mixin import after the dataclass definitions.
45
+ from .transfer import (
46
+ _CallBindingResult,
47
+ _CallSummary,
48
+ _DeferredActivation,
49
+ _FlowState,
50
+ )
51
+
52
+
53
+ class _TransferCallsMixin:
54
+ """Mixin providing inter-procedural call transfer methods.
55
+
56
+ This class contains only methods; it does not define ``__init__``.
57
+ At runtime ``self`` is the :class:`HeapTransferEngine` instance.
58
+ """
59
+
60
+ def _apply_definition_transfer(
61
+ self,
62
+ procedure: object,
63
+ operation: object,
64
+ *,
65
+ related_locations: tuple[HeapLocation, ...] | None = None,
66
+ ) -> HeapLocation | None:
67
+ if isinstance(operation, py_ast.TypeAlias):
68
+ deferred_state = self._capture_flow_state()
69
+ value_locations = self.locations_for_expression(
70
+ procedure,
71
+ operation.value,
72
+ )
73
+ parameter_locations = self._merge_expression_locations(
74
+ procedure,
75
+ *getattr(operation, "params", ()),
76
+ )
77
+ alias = HeapLocation(
78
+ self.heap.allocation_object(
79
+ procedure,
80
+ operation,
81
+ label=f"type alias {operation.name}",
82
+ context=self._current_context,
83
+ )
84
+ )
85
+ if value_locations:
86
+ self.state.write(
87
+ self.heap.dynamic_attribute_location(alias, "__value__"),
88
+ value_locations,
89
+ UpdatePolicy.STRONG,
90
+ )
91
+ if parameter_locations:
92
+ self.state.write(
93
+ self.heap.dynamic_attribute_location(alias, "__type_params__"),
94
+ parameter_locations,
95
+ UpdatePolicy.STRONG,
96
+ )
97
+ # PEP 695 aliases evaluate their value lazily. Preserve both the
98
+ # deferred state and the state in which the value has been forced;
99
+ # clients can therefore soundly analyze programs regardless of
100
+ # whether a later operation materializes ``__value__``.
101
+ forced_state = self._capture_flow_state()
102
+ self._restore_flow_state(
103
+ self._join_flow_states((deferred_state, forced_state))
104
+ )
105
+ alias_values = (alias,)
106
+ if self._is_module_scope(procedure):
107
+ target = self.effect_builder.global_location(
108
+ procedure,
109
+ operation.name,
110
+ )
111
+ self.state.write(target, alias_values, UpdatePolicy.STRONG)
112
+ self.heap.mark_all_escaped(alias_values)
113
+ self.state.mark_escaped(alias_values)
114
+ self._bind_definition_local(
115
+ procedure,
116
+ operation.name,
117
+ alias_values,
118
+ )
119
+ return alias
120
+ if not isinstance(operation, (py_ast.FunctionDef, py_ast.ClassDef)):
121
+ return None
122
+
123
+ label = (
124
+ f"function {operation.name}"
125
+ if isinstance(operation, py_ast.FunctionDef)
126
+ else f"class {operation.name}"
127
+ )
128
+ definition = HeapLocation(
129
+ self.heap.allocation_object(
130
+ procedure,
131
+ operation,
132
+ label=label,
133
+ context=self._current_context,
134
+ )
135
+ )
136
+ default_locations: list[HeapLocation] = []
137
+ if isinstance(operation, py_ast.FunctionDef):
138
+ defaults = tuple(
139
+ getattr(operation.code.codeparameters, "defaults", ())
140
+ )
141
+ for index, default in enumerate(defaults):
142
+ locations = self.locations_for_expression(procedure, default)
143
+ self._definition_default_locations[
144
+ (id(operation.code), index)
145
+ ] = locations
146
+ default_locations.extend(locations)
147
+
148
+ related_expressions = self._definition_header_expressions(operation)
149
+ if related_locations is None:
150
+ related_locations = tuple(
151
+ dict.fromkeys(
152
+ location
153
+ for expression in related_expressions
154
+ for location in self.locations_for_expression(
155
+ procedure,
156
+ expression,
157
+ )
158
+ )
159
+ )
160
+ decorator_kind = self._definition_decorator_kind(operation)
161
+ decorated_or_dynamic = ()
162
+ if getattr(operation, "decorators", ()) and decorator_kind == "dynamic":
163
+ decorated_or_dynamic = (self._external_value_location(procedure),)
164
+ values = tuple(dict.fromkeys((definition, *decorated_or_dynamic)))
165
+ if isinstance(operation, py_ast.ClassDef):
166
+ self._class_definitions[
167
+ (self._module_owner(procedure), operation.name)
168
+ ] = definition
169
+ self._class_locations_by_root[definition.root] = definition
170
+ self._class_locations_by_definition[id(operation)] = definition
171
+ if self._is_module_scope(procedure):
172
+ target = self.effect_builder.global_location(
173
+ procedure,
174
+ operation.name,
175
+ )
176
+ self.state.write(target, values, UpdatePolicy.STRONG)
177
+ self.heap.mark_all_escaped(values)
178
+ self.state.mark_escaped(values)
179
+ self._bind_definition_local(procedure, operation.name, values)
180
+
181
+ if default_locations:
182
+ self.state.write(
183
+ self.heap.dynamic_attribute_location(
184
+ definition,
185
+ "__defaults__",
186
+ ),
187
+ tuple(dict.fromkeys(default_locations)),
188
+ UpdatePolicy.STRONG,
189
+ )
190
+ if isinstance(operation, py_ast.FunctionDef):
191
+ closure_locations = tuple(
192
+ dict.fromkeys(
193
+ self.effect_builder.cell_location(cell, procedure)
194
+ for cell in code_closure_cells(operation.code)
195
+ )
196
+ )
197
+ if closure_locations:
198
+ self.state.write(
199
+ self.heap.dynamic_attribute_location(
200
+ definition,
201
+ "__closure__",
202
+ ),
203
+ closure_locations,
204
+ UpdatePolicy.STRONG,
205
+ )
206
+ self._function_codes_by_root[definition.root] = operation.code
207
+ self._function_binding_kinds[definition.root] = decorator_kind
208
+ if getattr(operation, "decorators", ()) and decorator_kind == "dynamic":
209
+ decorated = self._apply_known_definition_decorators(
210
+ procedure,
211
+ operation,
212
+ (definition,),
213
+ )
214
+ self._rebind_definition_value(
215
+ procedure,
216
+ operation.name,
217
+ decorated,
218
+ )
219
+ if isinstance(operation, py_ast.ClassDef):
220
+ base_locations = self._merge_expression_locations(
221
+ procedure,
222
+ *getattr(operation, "bases", ()),
223
+ )
224
+ named_bases = tuple(
225
+ self._class_definitions[
226
+ (self._module_owner(procedure), base.name)
227
+ ]
228
+ for base in getattr(operation, "bases", ())
229
+ if isinstance(base, py_ast.Local)
230
+ and (self._module_owner(procedure), base.name)
231
+ in self._class_definitions
232
+ )
233
+ base_locations = tuple(
234
+ dict.fromkeys((*base_locations, *named_bases))
235
+ )
236
+ if base_locations:
237
+ self.state.write(
238
+ self.heap.dynamic_attribute_location(definition, "__bases__"),
239
+ base_locations,
240
+ UpdatePolicy.STRONG,
241
+ )
242
+ self._class_bases_by_root[definition.root] = base_locations
243
+ implicit_class_cell = class_cell(operation)
244
+ if implicit_class_cell is not None:
245
+ self.state.write(
246
+ self.effect_builder.cell_location(
247
+ implicit_class_cell,
248
+ procedure,
249
+ ),
250
+ (definition,),
251
+ UpdatePolicy.STRONG,
252
+ )
253
+ if related_locations:
254
+ self.state.write(
255
+ self.heap.dynamic_attribute_location(
256
+ definition,
257
+ "__definition_inputs__",
258
+ ),
259
+ related_locations,
260
+ UpdatePolicy.STRONG,
261
+ )
262
+ return definition
263
+
264
+ def _apply_known_definition_decorators(
265
+ self,
266
+ procedure: object,
267
+ operation: object,
268
+ initial: tuple[HeapLocation, ...],
269
+ ) -> tuple[HeapLocation, ...]:
270
+ current = initial
271
+ for decorator_expression in reversed(
272
+ tuple(getattr(operation, "decorators", ()))
273
+ ):
274
+ decorator_locations = self.locations_for_expression(
275
+ procedure,
276
+ decorator_expression,
277
+ )
278
+ results: list[HeapLocation] = []
279
+ complete = bool(decorator_locations)
280
+ for decorator in decorator_locations:
281
+ code = self._function_codes_by_root.get(decorator.root)
282
+ receivers: tuple[HeapLocation, ...] = ()
283
+ if code is None:
284
+ bound = self._bound_methods_by_root.get(decorator.root)
285
+ if bound is not None:
286
+ code, receivers = bound
287
+ if code is None:
288
+ complete = False
289
+ continue
290
+ actual_groups = (
291
+ (receivers, current)
292
+ if receivers
293
+ else (current,)
294
+ )
295
+ results.extend(
296
+ self._evaluate_known_code(
297
+ procedure,
298
+ code,
299
+ actual_groups,
300
+ )
301
+ )
302
+ current = (
303
+ tuple(dict.fromkeys(results))
304
+ if complete and results
305
+ else (self._external_value_location(procedure),)
306
+ )
307
+ return current
308
+
309
+ def _rebind_definition_value(
310
+ self,
311
+ procedure: object,
312
+ name: str,
313
+ values: tuple[HeapLocation, ...],
314
+ ) -> None:
315
+ if self._is_module_scope(procedure):
316
+ self.state.write(
317
+ self.effect_builder.global_location(procedure, name),
318
+ values,
319
+ UpdatePolicy.STRONG,
320
+ )
321
+ self._bind_definition_local(procedure, name, values)
322
+
323
+ def _definition_decorator_kind(self, operation: object) -> str:
324
+ decorators = tuple(getattr(operation, "decorators", ()))
325
+ if not decorators:
326
+ return "instance"
327
+ recognized = None
328
+ for decorator in decorators:
329
+ name = resolve_call_name(decorator)
330
+ if name is None:
331
+ name = self.effect_builder._constant_string(decorator)
332
+ short = name.rsplit(".", 1)[-1] if isinstance(name, str) else None
333
+ if short in {"staticmethod", "classmethod", "property"}:
334
+ recognized = short
335
+ else:
336
+ return "dynamic"
337
+ return recognized or "dynamic"
338
+
339
+ def _is_module_scope(self, procedure: object) -> bool:
340
+ return (
341
+ isinstance(procedure, py_ast.Code)
342
+ and id(procedure) not in self._lexical_parents
343
+ )
344
+
345
+ def _module_owner(self, procedure: object) -> object:
346
+ explicit = getattr(procedure, "module", None)
347
+ if explicit is not None:
348
+ return explicit
349
+ cached = self._module_owners.get(id(procedure))
350
+ if cached is not None:
351
+ return cached
352
+ parent = self._lexical_parents.get(id(procedure))
353
+ if parent is not None:
354
+ owner = self._module_owner(parent)
355
+ self._module_owners[id(procedure)] = owner
356
+ return owner
357
+ origin = getattr(getattr(procedure, "annotation", None), "origin", ()) or ()
358
+ for item in origin:
359
+ if isinstance(item, str) and item.startswith("source("):
360
+ payload = item[len("source(") :].rstrip(")")
361
+ filename = payload.rsplit(":", 1)[0]
362
+ owner = ("source-module", filename)
363
+ self._module_owners[id(procedure)] = owner
364
+ return owner
365
+ owner = ("code-module", id(procedure))
366
+ self._module_owners[id(procedure)] = owner
367
+ return owner
368
+
369
+ def _bind_definition_local(
370
+ self,
371
+ procedure: object,
372
+ name: str,
373
+ locations: tuple[HeapLocation, ...],
374
+ ) -> None:
375
+ key = (id(procedure), name)
376
+ local = self._definition_locals.get(key)
377
+ if local is None:
378
+ local = py_ast.Local(name)
379
+ self._definition_locals[key] = local
380
+ self._bind_runtime_local(procedure, local, locations)
381
+
382
+ def _scope_members(
383
+ self,
384
+ procedure: object,
385
+ environment: HeapEnvironment,
386
+ ) -> dict[str, tuple[HeapLocation, ...]]:
387
+ members: dict[str, list[HeapLocation]] = {}
388
+ procedure_id = id(procedure)
389
+ keys = set(environment.storage_overrides) | set(
390
+ environment.allocation_sites
391
+ )
392
+ for key in keys:
393
+ if key[0] != procedure_id:
394
+ continue
395
+ name = environment.local_names.get(key)
396
+ if not name:
397
+ continue
398
+ storage = self.heap._environment_storage(environment, key)
399
+ members.setdefault(name, []).extend(
400
+ self.heap.location_for_raw(raw) for raw in storage
401
+ )
402
+ return {
403
+ name: tuple(dict.fromkeys(locations))
404
+ for name, locations in members.items()
405
+ if locations
406
+ }
407
+
408
+ @staticmethod
409
+ def _definition_header_expressions(operation: object) -> tuple[object, ...]:
410
+ expressions: list[object] = []
411
+ if isinstance(operation, py_ast.FunctionDef):
412
+ expressions.extend(
413
+ code_definition_annotations(operation.code)
414
+ )
415
+ if isinstance(operation, py_ast.ClassDef):
416
+ expressions.extend(getattr(operation, "bases", ()))
417
+ expressions.extend(
418
+ keyword[1]
419
+ if isinstance(keyword, tuple) and len(keyword) == 2
420
+ else keyword
421
+ for keyword in getattr(operation, "keywords", ())
422
+ )
423
+ expressions.extend(getattr(operation, "decorators", ()))
424
+ type_params = getattr(operation, "type_params", None)
425
+ if type_params is not None:
426
+ expressions.append(type_params)
427
+ return tuple(expressions)
428
+
429
+ def _apply_external_boundary_transfer(
430
+ self,
431
+ procedure: object,
432
+ operation: object,
433
+ ) -> None:
434
+ expressions: list[object] = []
435
+ if isinstance(operation, py_ast.OutputBlock):
436
+ expressions.extend(
437
+ output.expr
438
+ for output in getattr(operation, "outputs", ())
439
+ if getattr(output, "expr", None) is not None
440
+ )
441
+ elif isinstance(operation, py_ast.Output):
442
+ expressions.append(operation.expr)
443
+ elif isinstance(operation, py_ast.Print):
444
+ expressions.extend(
445
+ expression
446
+ for expression in (operation.target, operation.expr)
447
+ if expression is not None
448
+ )
449
+ if not expressions:
450
+ return
451
+ escaped = tuple(
452
+ dict.fromkeys(
453
+ location
454
+ for expression in expressions
455
+ for location in self.locations_for_expression(procedure, expression)
456
+ )
457
+ )
458
+ self.heap.mark_all_escaped(escaped)
459
+ self.state.mark_escaped(escaped)
460
+
461
+ def _apply_call_transfer(self, procedure: object, operation: object) -> None:
462
+ call = self._call_expression(operation)
463
+ if call is None:
464
+ return
465
+ application_key = self._call_application_key(call)
466
+ if application_key in self._applied_calls:
467
+ return
468
+ self._applied_calls.add(application_key)
469
+ if isinstance(call, py_ast.DirectCall) and isinstance(call.code, py_ast.Code):
470
+ self._bind_direct_call(procedure, operation, call)
471
+ return
472
+ if self._apply_finite_known_call(procedure, operation, call):
473
+ return
474
+ if self._known_class_locations(procedure, call):
475
+ return
476
+ call_name = resolve_call_name(call)
477
+ callback_returns = self._apply_known_callback_calls(
478
+ procedure,
479
+ call,
480
+ call_name,
481
+ )
482
+ if callback_returns:
483
+ self._callback_call_results[application_key] = callback_returns
484
+ pending = self._pending_call_results.get(id(operation))
485
+ if pending is not None:
486
+ _targets, slots = pending
487
+ for roots in slots:
488
+ for root in roots:
489
+ self.state.write(
490
+ self.heap.dynamic_subscript_location(
491
+ root,
492
+ DYNAMIC_SUBSCRIPT_WILDCARD,
493
+ ),
494
+ callback_returns,
495
+ UpdatePolicy.WEAK,
496
+ )
497
+ protocol_returns = self._apply_named_protocol_call(
498
+ procedure,
499
+ operation,
500
+ call,
501
+ call_name,
502
+ )
503
+ if protocol_returns:
504
+ self._protocol_call_results[application_key] = protocol_returns
505
+ function_model = self.intrinsics.function_model(call_name)
506
+ collection_model = self.intrinsics.collection_mutator(call_name)
507
+ fully_modeled_heap_calls = {
508
+ "setattr",
509
+ "builtins.setattr",
510
+ "delattr",
511
+ "builtins.delattr",
512
+ "interpreter_getitem",
513
+ "interpreter_setitem",
514
+ "interpreter_delitem",
515
+ }
516
+ if (
517
+ function_model is not None
518
+ or collection_model is not None
519
+ or call_name in fully_modeled_heap_calls
520
+ ):
521
+ escaped: list[HeapLocation] = []
522
+ actuals = tuple(actual_argument_expressions(call))
523
+ if collection_model is not None and isinstance(call, py_ast.MethodCall):
524
+ # Name-only method recognition cannot prove a builtin receiver.
525
+ # Retain the collection effect for precision, but conservatively
526
+ # expose receiver and operands as an arbitrary user method may.
527
+ escaped.extend(self.locations_for_expression(procedure, call.expr))
528
+ for actual in actuals:
529
+ escaped.extend(
530
+ self.locations_for_expression(procedure, actual)
531
+ )
532
+ if function_model is not None:
533
+ if function_model.escapes_self and isinstance(call, py_ast.MethodCall):
534
+ escaped.extend(
535
+ self.locations_for_expression(procedure, call.expr)
536
+ )
537
+ for index in function_model.escape_arg_indices:
538
+ if index < len(actuals):
539
+ escaped.extend(
540
+ self.locations_for_expression(
541
+ procedure,
542
+ actuals[index],
543
+ )
544
+ )
545
+ escaped_locations = tuple(dict.fromkeys(escaped))
546
+ self.heap.mark_all_escaped(escaped_locations)
547
+ self.state.mark_escaped(escaped_locations)
548
+ if function_model is not None and collection_model is None:
549
+ mutated: list[HeapLocation] = []
550
+ if function_model.mutates_self and isinstance(call, py_ast.MethodCall):
551
+ mutated.extend(
552
+ self.locations_for_expression(procedure, call.expr)
553
+ )
554
+ for index in function_model.write_arg_indices:
555
+ if index < len(actuals):
556
+ mutated.extend(
557
+ self.locations_for_expression(
558
+ procedure,
559
+ actuals[index],
560
+ )
561
+ )
562
+ self._contaminate_modeled_mutations(
563
+ procedure,
564
+ tuple(dict.fromkeys(mutated)),
565
+ call_name,
566
+ )
567
+ return
568
+ effect = self.effect_builder.unresolved_call_effect(procedure, call)
569
+ self.heap.mark_all_escaped(effect.escapes)
570
+ self.state.mark_escaped(effect.escapes)
571
+
572
+ def _contaminate_modeled_mutations(
573
+ self,
574
+ procedure: object,
575
+ roots: tuple[HeapLocation, ...],
576
+ call_name: str | None,
577
+ ) -> None:
578
+ if not roots:
579
+ return
580
+ unknown = HeapLocation(
581
+ self.heap.unknown_object(
582
+ (
583
+ "modeled-mutation",
584
+ call_name,
585
+ self._evaluation_epoch,
586
+ self._current_context,
587
+ ),
588
+ label=f"unknown mutation by {call_name or 'known call'}",
589
+ )
590
+ )
591
+ for root in roots:
592
+ self.state.write(
593
+ self.heap.dynamic_attribute_location(root, "*"),
594
+ (unknown,),
595
+ UpdatePolicy.WEAK,
596
+ )
597
+ self.state.write(
598
+ self.heap.dynamic_subscript_location(
599
+ root,
600
+ DYNAMIC_SUBSCRIPT_WILDCARD,
601
+ ),
602
+ (unknown,),
603
+ UpdatePolicy.WEAK,
604
+ )
605
+
606
+ def _apply_known_callback_calls(
607
+ self,
608
+ procedure: object,
609
+ call: object,
610
+ call_name: str | None,
611
+ ) -> tuple[HeapLocation, ...]:
612
+ short = call_name.rsplit(".", 1)[-1] if call_name else None
613
+ actuals = tuple(actual_argument_expressions(call))
614
+ callback_expression = None
615
+ iterable_expressions: tuple[object, ...] = ()
616
+ if short in {"map", "filter", "reduce"} and actuals:
617
+ callback_expression = actuals[0]
618
+ iterable_expressions = actuals[1:]
619
+ elif short in {"sorted", "sort", "min", "max"}:
620
+ for keyword in getattr(call, "kwds", ()):
621
+ if (
622
+ isinstance(keyword, tuple)
623
+ and len(keyword) == 2
624
+ and keyword[0] == "key"
625
+ ):
626
+ callback_expression = keyword[1]
627
+ break
628
+ iterable_expressions = actuals[:1]
629
+ if callback_expression is None:
630
+ return ()
631
+ callbacks = self.locations_for_expression(
632
+ procedure,
633
+ callback_expression,
634
+ )
635
+ arguments: list[HeapLocation] = []
636
+ for iterable_expression in iterable_expressions:
637
+ roots = self.locations_for_expression(
638
+ procedure,
639
+ iterable_expression,
640
+ )
641
+ contained = self._contained_values(roots)
642
+ arguments.extend(contained or roots)
643
+ argument_group = tuple(dict.fromkeys(arguments)) or (
644
+ self._external_value_location(procedure),
645
+ )
646
+ results: list[HeapLocation] = []
647
+ for callback in callbacks:
648
+ code = self._function_codes_by_root.get(callback.root)
649
+ receivers: tuple[HeapLocation, ...] = ()
650
+ if code is None:
651
+ bound = self._bound_methods_by_root.get(callback.root)
652
+ if bound is not None:
653
+ code, receivers = bound
654
+ if code is None:
655
+ continue
656
+ callback_groups = (
657
+ (argument_group, argument_group)
658
+ if short == "reduce"
659
+ else (argument_group,)
660
+ )
661
+ groups = (
662
+ (receivers, *callback_groups)
663
+ if receivers
664
+ else callback_groups
665
+ )
666
+ results.extend(
667
+ self._evaluate_known_code(procedure, code, groups)
668
+ )
669
+ return tuple(dict.fromkeys(results))
670
+
671
+ def _apply_named_protocol_call(
672
+ self,
673
+ procedure: object,
674
+ operation: object,
675
+ call: object,
676
+ call_name: str | None,
677
+ ) -> tuple[HeapLocation, ...]:
678
+ """Model builtins whose semantics include a special-method call."""
679
+ short_name = call_name.rsplit(".", 1)[-1] if call_name else None
680
+ protocol = {
681
+ "len": "__len__",
682
+ "iter": "__iter__",
683
+ "next": "__next__",
684
+ "bool": "__bool__",
685
+ "hash": "__hash__",
686
+ "repr": "__repr__",
687
+ "str": "__str__",
688
+ "bytes": "__bytes__",
689
+ "reversed": "__reversed__",
690
+ "int": "__int__",
691
+ "float": "__float__",
692
+ "complex": "__complex__",
693
+ "round": "__round__",
694
+ "format": "__format__",
695
+ "index": "__index__",
696
+ "getattr": "__getattribute__",
697
+ "setattr": "__setattr__",
698
+ "delattr": "__delattr__",
699
+ }.get(short_name)
700
+ actuals = tuple(actual_argument_expressions(call))
701
+ if protocol is None or not actuals:
702
+ return ()
703
+ operands = self._last_call_operands.get(id(call))
704
+ if operands is None:
705
+ operands = self._evaluate_call_operands(procedure, call)
706
+ receivers = operands.get(id(actuals[0]), ())
707
+ groups = tuple(
708
+ operands.get(id(actual), ())
709
+ for actual in actuals[1:]
710
+ )
711
+ returned = self._evaluate_known_protocol(
712
+ procedure,
713
+ receivers,
714
+ protocol,
715
+ groups,
716
+ )
717
+ if not returned:
718
+ return ()
719
+ pending = self._pending_call_results.get(id(operation))
720
+ if pending is None:
721
+ return returned
722
+ targets, slots = pending
723
+ self._pending_call_results[id(operation)] = (
724
+ targets,
725
+ tuple(
726
+ tuple(dict.fromkeys((*slot, *returned)))
727
+ for slot in slots
728
+ ),
729
+ )
730
+ return returned
731
+
732
+ def _apply_finite_known_call(
733
+ self,
734
+ caller: object,
735
+ operation: object,
736
+ call: object,
737
+ ) -> bool:
738
+ """Apply a generic/virtual call when its target set is finite and known.
739
+
740
+ Resolution is intentionally all-or-nothing. If any possible function,
741
+ receiver class, or method implementation is unknown, the ordinary
742
+ unresolved-call transfer remains responsible for the whole call.
743
+ """
744
+ candidates = self._finite_known_call_targets(caller, call)
745
+ if not candidates:
746
+ return False
747
+ base = self._capture_flow_state()
748
+ normal_states: list[_FlowState] = []
749
+ possible_returns: list[HeapLocation] = []
750
+ return_slots: list[list[HeapLocation]] = []
751
+ for code, receiver_locations in candidates:
752
+ self._restore_flow_state(base)
753
+ method_call = isinstance(call, py_ast.MethodCall)
754
+ direct = py_ast.DirectCall(
755
+ code,
756
+ None,
757
+ (
758
+ [call.expr, *getattr(call, "args", ())]
759
+ if method_call and receiver_locations
760
+ else list(getattr(call, "args", ()))
761
+ ),
762
+ list(getattr(call, "kwds", ())),
763
+ getattr(call, "vargs", None),
764
+ getattr(call, "kargs", None),
765
+ )
766
+ binding_result = self._direct_call_actual_locations(
767
+ caller,
768
+ code,
769
+ direct,
770
+ receiver_supplied=bool(receiver_locations),
771
+ receiver_consumes_positional=bool(receiver_locations)
772
+ and not method_call,
773
+ )
774
+ bindings = dict(binding_result.bindings)
775
+ if receiver_locations:
776
+ formals = self._callee_formals(code)
777
+ if formals:
778
+ if not method_call:
779
+ original = dict(bindings)
780
+ bindings = {
781
+ index + 1: locations
782
+ for index, locations in original.items()
783
+ if index + 1 < len(formals)
784
+ }
785
+ bindings[0] = receiver_locations
786
+ returned = self._evaluate_direct_call_with_bindings(
787
+ caller,
788
+ direct,
789
+ _CallBindingResult(
790
+ bindings,
791
+ binding_result.definitely_invalid,
792
+ binding_result.maybe_invalid,
793
+ binding_result.reasons,
794
+ ),
795
+ )
796
+ if self._operation_normal_possible[-1]:
797
+ normal_states.append(self._capture_flow_state())
798
+ possible_returns.extend(returned)
799
+ summary = self._last_direct_call_summary.get(id(direct))
800
+ if summary is not None:
801
+ while len(return_slots) < len(summary.returns):
802
+ return_slots.append([])
803
+ for return_index, locations in enumerate(summary.returns):
804
+ return_slots[return_index].extend(locations)
805
+ for formal_index in summary.param_returns.get(
806
+ return_index,
807
+ frozenset(),
808
+ ):
809
+ return_slots[return_index].extend(
810
+ bindings.get(formal_index, ())
811
+ )
812
+ # A target that raises exclusively only contributes its abrupt
813
+ # outcome, already recorded by _evaluate_direct_call_with_bindings.
814
+ self._operation_normal_possible[-1] = True
815
+ if normal_states:
816
+ self._restore_flow_state(self._join_flow_states(tuple(normal_states)))
817
+ else:
818
+ self._restore_flow_state(base)
819
+ self._operation_normal_possible[-1] = False
820
+ targets = assigned_locals(operation)
821
+ if targets:
822
+ values = tuple(dict.fromkeys(possible_returns))
823
+ if len(targets) == 1:
824
+ slots = (values,)
825
+ else:
826
+ slots = tuple(
827
+ tuple(dict.fromkeys(return_slots[index]))
828
+ if index < len(return_slots)
829
+ else ()
830
+ for index, _target in enumerate(targets)
831
+ )
832
+ self._pending_call_results[id(operation)] = (
833
+ targets,
834
+ slots,
835
+ )
836
+ self._finite_call_results[self._call_application_key(call)] = tuple(
837
+ dict.fromkeys(possible_returns)
838
+ )
839
+ return True
840
+
841
+ def _call_application_key(self, call: object) -> tuple[object, ...]:
842
+ return (
843
+ id(call),
844
+ self._evaluation_epoch,
845
+ self._current_context,
846
+ )
847
+
848
+ def _finite_known_call_targets(
849
+ self,
850
+ procedure: object,
851
+ call: object,
852
+ ) -> tuple[tuple[py_ast.Code, tuple[HeapLocation, ...]], ...]:
853
+ operands = self._last_call_operands.get(id(call))
854
+ if operands is None:
855
+ operands = self._evaluate_call_operands(procedure, call)
856
+ if isinstance(call, py_ast.Call):
857
+ functions = operands.get(id(call.expr), ())
858
+ if not functions:
859
+ return ()
860
+ codes: list[tuple[py_ast.Code, tuple[HeapLocation, ...]]] = []
861
+ for function in functions:
862
+ code = self._function_codes_by_root.get(function.root)
863
+ if code is not None:
864
+ codes.append((code, ()))
865
+ continue
866
+ bound = self._bound_methods_by_root.get(function.root)
867
+ if bound is not None:
868
+ codes.append(bound)
869
+ continue
870
+ classes = self.state.read(
871
+ self.heap.dynamic_attribute_location(
872
+ function,
873
+ "__class__",
874
+ ),
875
+ fallback=(),
876
+ )
877
+ if not classes:
878
+ return ()
879
+ for class_location in classes:
880
+ callable_code = self._resolve_known_class_method(
881
+ class_location,
882
+ "__call__",
883
+ )
884
+ if callable_code is None:
885
+ return ()
886
+ codes.append((callable_code, (function,)))
887
+ return tuple(dict.fromkeys(codes))
888
+ if not isinstance(call, py_ast.MethodCall):
889
+ return ()
890
+ name = self.effect_builder._constant_string(call.name)
891
+ receivers = operands.get(id(call.expr), ())
892
+ if name is None or not receivers:
893
+ return ()
894
+ candidates: list[tuple[py_ast.Code, tuple[HeapLocation, ...]]] = []
895
+ for receiver in receivers:
896
+ super_classes = self.state.read(
897
+ self.heap.dynamic_attribute_location(
898
+ receiver,
899
+ "__super_class__",
900
+ ),
901
+ fallback=(),
902
+ )
903
+ if super_classes:
904
+ super_receivers = self.state.read(
905
+ self.heap.dynamic_attribute_location(
906
+ receiver,
907
+ "__super_self__",
908
+ ),
909
+ fallback=(),
910
+ )
911
+ registered = self._super_methods_by_root.get(
912
+ receiver.root,
913
+ {},
914
+ ).get(name)
915
+ if registered is not None:
916
+ candidates.append(registered)
917
+ continue
918
+ resolved = False
919
+ for current_class in super_classes:
920
+ for next_class in self._known_class_mro(current_class)[1:]:
921
+ method = self._class_methods_by_root.get(
922
+ next_class.root,
923
+ {},
924
+ ).get(name)
925
+ if method is not None:
926
+ candidates.append((method, super_receivers))
927
+ resolved = True
928
+ break
929
+ if resolved:
930
+ break
931
+ if not resolved:
932
+ return ()
933
+ continue
934
+ classes = self.state.read(
935
+ self.heap.dynamic_attribute_location(receiver, "__class__"),
936
+ fallback=(),
937
+ )
938
+ if not classes:
939
+ return ()
940
+ for class_location in classes:
941
+ method = self._resolve_known_class_method(class_location, name)
942
+ if method is None:
943
+ return ()
944
+ kind = self._resolve_known_class_method_kind(
945
+ class_location,
946
+ name,
947
+ )
948
+ if kind == "staticmethod":
949
+ bound_receiver = ()
950
+ elif kind == "classmethod":
951
+ bound_receiver = (class_location,)
952
+ else:
953
+ bound_receiver = (receiver,)
954
+ candidates.append((method, bound_receiver))
955
+ return tuple(dict.fromkeys(candidates))
956
+
957
+ def _bind_direct_call(
958
+ self,
959
+ caller: object,
960
+ operation: object,
961
+ call: py_ast.DirectCall,
962
+ ) -> None:
963
+ callee = call.code
964
+ if getattr(callee, "module", None) is None:
965
+ self._module_owners.setdefault(id(callee), self._module_owner(caller))
966
+ binding_result = self._direct_call_actual_locations(caller, callee, call)
967
+ actual_bindings = binding_result.bindings
968
+ possible_returns = self._evaluate_direct_call_with_bindings(
969
+ caller,
970
+ call,
971
+ binding_result,
972
+ )
973
+ targets = assigned_locals(operation)
974
+ if not targets:
975
+ return
976
+ if len(targets) == 1:
977
+ if possible_returns:
978
+ self._bind_runtime_local(
979
+ caller,
980
+ targets[0],
981
+ possible_returns,
982
+ )
983
+ return
984
+ if id(call) not in self._last_direct_call_summary:
985
+ for target in targets:
986
+ if possible_returns:
987
+ self._bind_runtime_local(caller, target, possible_returns)
988
+ return
989
+ summary = self._last_direct_call_summary[id(call)]
990
+ for index, target in enumerate(targets):
991
+ if index >= len(summary.returns):
992
+ continue
993
+ target_locations = list(summary.returns[index])
994
+ for param_idx in summary.param_returns.get(index, frozenset()):
995
+ target_locations.extend(actual_bindings.get(param_idx, ()))
996
+ self._bind_runtime_local(
997
+ caller,
998
+ target,
999
+ tuple(dict.fromkeys(target_locations)),
1000
+ )
1001
+
1002
+ def _evaluate_direct_call_expression(
1003
+ self,
1004
+ caller: object,
1005
+ call: py_ast.DirectCall,
1006
+ ) -> tuple[HeapLocation, ...]:
1007
+ binding_result = self._direct_call_actual_locations(
1008
+ caller,
1009
+ call.code,
1010
+ call,
1011
+ )
1012
+ return self._evaluate_direct_call_with_bindings(
1013
+ caller,
1014
+ call,
1015
+ binding_result,
1016
+ )
1017
+
1018
+ def _evaluate_direct_call_with_bindings(
1019
+ self,
1020
+ caller: object,
1021
+ call: py_ast.DirectCall,
1022
+ binding_result: _CallBindingResult,
1023
+ ) -> tuple[HeapLocation, ...]:
1024
+ callee = call.code
1025
+ actual_bindings = binding_result.bindings
1026
+ if binding_result.definitely_invalid:
1027
+ raised_state = self.state.copy()
1028
+ exception = self._external_value_location(caller)
1029
+ raised_state.set_raised(caller, (exception,))
1030
+ if self._operation_call_raises:
1031
+ self._operation_call_raises[-1].append(
1032
+ _FlowState(
1033
+ raised_state,
1034
+ self.heap.snapshot_environment(),
1035
+ dict(self._definition_default_locations),
1036
+ )
1037
+ )
1038
+ if self._operation_normal_possible:
1039
+ self._operation_normal_possible[-1] = False
1040
+ return ()
1041
+ if binding_result.maybe_invalid and self._operation_call_raises:
1042
+ raised_state = self.state.copy()
1043
+ exception = self._external_value_location(caller)
1044
+ raised_state.set_raised(caller, (exception,))
1045
+ self._operation_call_raises[-1].append(
1046
+ _FlowState(
1047
+ raised_state,
1048
+ self.heap.snapshot_environment(),
1049
+ dict(self._definition_default_locations),
1050
+ )
1051
+ )
1052
+ cache_key = self._direct_call_cache_key(call, actual_bindings)
1053
+ cached = self._direct_call_evaluation_cache.get(cache_key)
1054
+ if cached is not None:
1055
+ return cached
1056
+
1057
+ deferred_kind = self._deferred_code_kind(callee)
1058
+ if deferred_kind is not None:
1059
+ deferred = HeapLocation(
1060
+ self.heap.allocation_object(
1061
+ caller,
1062
+ (deferred_kind, id(call)),
1063
+ label=deferred_kind,
1064
+ context=self._current_context,
1065
+ )
1066
+ )
1067
+ self._deferred_activations[deferred.root] = _DeferredActivation(
1068
+ callee,
1069
+ actual_bindings,
1070
+ )
1071
+ captured = tuple(
1072
+ dict.fromkeys(
1073
+ location
1074
+ for locations in actual_bindings.values()
1075
+ for location in locations
1076
+ )
1077
+ )
1078
+ if captured:
1079
+ self.state.write(
1080
+ self.heap.dynamic_attribute_location(
1081
+ deferred,
1082
+ "__captured_arguments__",
1083
+ ),
1084
+ captured,
1085
+ UpdatePolicy.STRONG,
1086
+ )
1087
+ result: tuple[HeapLocation, ...] = (deferred,)
1088
+ self._direct_call_evaluation_cache[cache_key] = result
1089
+ return result
1090
+
1091
+ previous_context = self._current_context
1092
+ self._current_context = (
1093
+ *previous_context,
1094
+ self._context_token(call),
1095
+ self._evaluation_epoch,
1096
+ )
1097
+ try:
1098
+ summary = self._callee_summary(callee, actual_bindings)
1099
+ finally:
1100
+ self._current_context = previous_context
1101
+ self._last_direct_call_summary[id(call)] = summary
1102
+ if summary.raise_state is not None and self._operation_call_raises:
1103
+ raised_state = summary.raise_state.copy()
1104
+ if summary.raises:
1105
+ raised_state.set_raised(caller, summary.raises)
1106
+ self._operation_call_raises[-1].append(
1107
+ _FlowState(
1108
+ raised_state,
1109
+ summary.raise_environment
1110
+ or summary.environment
1111
+ or self.heap.snapshot_environment(),
1112
+ dict(self._definition_default_locations),
1113
+ )
1114
+ )
1115
+ if summary.normal_state is None and self._operation_normal_possible:
1116
+ self._operation_normal_possible[-1] = False
1117
+ self._apply_callee_summary(summary, caller)
1118
+ for param_idx in summary.param_escapes:
1119
+ actual_locations = actual_bindings.get(param_idx, ())
1120
+ if actual_locations:
1121
+ self.heap.mark_all_escaped(tuple(actual_locations))
1122
+
1123
+ possible_returns: list[HeapLocation] = []
1124
+ for return_index, return_locations in enumerate(summary.returns):
1125
+ possible_returns.extend(return_locations)
1126
+ for param_idx in summary.param_returns.get(
1127
+ return_index,
1128
+ frozenset(),
1129
+ ):
1130
+ possible_returns.extend(actual_bindings.get(param_idx, ()))
1131
+ result = tuple(dict.fromkeys(possible_returns))
1132
+ self._direct_call_evaluation_cache[cache_key] = result
1133
+ return result
1134
+
1135
+ def _call_binding_status(
1136
+ self,
1137
+ callee: py_ast.Code,
1138
+ call: py_ast.DirectCall,
1139
+ *,
1140
+ receiver_supplied: bool = False,
1141
+ receiver_consumes_positional: bool = False,
1142
+ ) -> tuple[bool, bool, frozenset[str]]:
1143
+ """Classify Python argument binding without losing uncertain success.
1144
+
1145
+ Explicit contradictions are definite ``TypeError`` paths. A dynamic
1146
+ ``*args``/``**kwargs`` expansion can instead make the call both
1147
+ successful and failing, so callers retain both outcomes.
1148
+ """
1149
+ params = callee.codeparameters
1150
+ reasons: set[str] = set()
1151
+ uncertain_reasons: set[str] = set()
1152
+ positional_spread = bool(
1153
+ call_positional_spreads(call) or getattr(call, "vargs", None) is not None
1154
+ )
1155
+ keyword_spread = bool(
1156
+ call_keyword_spreads(call) or getattr(call, "kargs", None) is not None
1157
+ )
1158
+
1159
+ selfparam = getattr(params, "selfparam", None)
1160
+ if isinstance(selfparam, py_ast.Local) and not (
1161
+ receiver_supplied or getattr(call, "selfarg", None) is not None
1162
+ ):
1163
+ reasons.add("missing-receiver")
1164
+ receiver_consumes_positional = (
1165
+ receiver_consumes_positional
1166
+ and not isinstance(selfparam, py_ast.Local)
1167
+ )
1168
+
1169
+ posonly_entries = [
1170
+ (name, formal)
1171
+ for name, formal in zip(
1172
+ getattr(params, "posonlynames", ()),
1173
+ getattr(params, "posonlyparams", ()),
1174
+ )
1175
+ if isinstance(formal, py_ast.Local)
1176
+ ]
1177
+ regular_entries: list[tuple[str, py_ast.Local]] = []
1178
+ keyword_only_entries: list[tuple[str, py_ast.Local]] = []
1179
+ for name, formal in zip(
1180
+ getattr(params, "paramnames", ()),
1181
+ getattr(params, "params", ()),
1182
+ ):
1183
+ if not isinstance(name, str) or not isinstance(formal, py_ast.Local):
1184
+ continue
1185
+ if name.startswith("kwonly:"):
1186
+ keyword_only_entries.append((name[len("kwonly:") :], formal))
1187
+ else:
1188
+ regular_entries.append((name, formal))
1189
+
1190
+ positional_entries = [*posonly_entries, *regular_entries]
1191
+ explicit_positional_count = len(getattr(call, "args", ()))
1192
+ positional_items = call_positional_items(call)
1193
+ if positional_items:
1194
+ explicit_positional_count = sum(
1195
+ 1 for is_spread, _actual in positional_items if not is_spread
1196
+ )
1197
+ if receiver_consumes_positional:
1198
+ explicit_positional_count += 1
1199
+ if (
1200
+ explicit_positional_count > len(positional_entries)
1201
+ and getattr(params, "vparam", None) is None
1202
+ ):
1203
+ reasons.add("too-many-positional")
1204
+ elif positional_spread and getattr(params, "vparam", None) is None:
1205
+ uncertain_reasons.add("positional-spread-overflow")
1206
+
1207
+ definitely_positional: set[py_ast.Local] = set()
1208
+ if positional_items:
1209
+ definite_prefix = 0
1210
+ for is_spread, _actual in positional_items:
1211
+ if is_spread:
1212
+ break
1213
+ definite_prefix += 1
1214
+ else:
1215
+ definite_prefix = len(getattr(call, "args", ()))
1216
+ if receiver_consumes_positional:
1217
+ definite_prefix += 1
1218
+ definitely_positional.update(
1219
+ formal for _name, formal in positional_entries[:definite_prefix]
1220
+ )
1221
+
1222
+ keyword_bindable = {
1223
+ name: formal for name, formal in (*regular_entries, *keyword_only_entries)
1224
+ }
1225
+ posonly_names = {name for name, _formal in posonly_entries if isinstance(name, str)}
1226
+ keyword_names: list[str] = []
1227
+ definitely_keyword: set[py_ast.Local] = set()
1228
+ for keyword in getattr(call, "kwds", ()):
1229
+ if not (
1230
+ isinstance(keyword, tuple)
1231
+ and len(keyword) == 2
1232
+ and isinstance(keyword[0], str)
1233
+ ):
1234
+ if getattr(params, "kparam", None) is None:
1235
+ reasons.add("unknown-keyword")
1236
+ continue
1237
+ name = keyword[0]
1238
+ keyword_names.append(name)
1239
+ formal = keyword_bindable.get(name)
1240
+ if formal is not None:
1241
+ if formal in definitely_positional:
1242
+ reasons.add("multiple-values")
1243
+ definitely_keyword.add(formal)
1244
+ elif name in posonly_names:
1245
+ if getattr(params, "kparam", None) is None:
1246
+ reasons.add("positional-only-keyword")
1247
+ elif getattr(params, "kparam", None) is None:
1248
+ reasons.add("unexpected-keyword")
1249
+ if len(keyword_names) != len(set(keyword_names)):
1250
+ reasons.add("duplicate-keyword")
1251
+ keyword_spread_count = len(call_keyword_spreads(call)) + (
1252
+ 1 if getattr(call, "kargs", None) is not None else 0
1253
+ )
1254
+ if keyword_spread and (
1255
+ getattr(params, "kparam", None) is None
1256
+ or bool(keyword_names)
1257
+ or bool(definitely_positional.intersection(keyword_bindable.values()))
1258
+ or keyword_spread_count > 1
1259
+ ):
1260
+ uncertain_reasons.add("keyword-spread-collision")
1261
+ if positional_spread and definitely_keyword:
1262
+ uncertain_reasons.add("positional-spread-keyword-collision")
1263
+
1264
+ defaultable = [formal for _name, formal in positional_entries]
1265
+ defaultable.extend(formal for _name, formal in keyword_only_entries)
1266
+ defaults = tuple(getattr(params, "defaults", ()))
1267
+ defaulted: set[py_ast.Local] = set()
1268
+ if defaults:
1269
+ for formal, default in zip(defaultable[-len(defaults) :], defaults):
1270
+ if not (
1271
+ isinstance(default, py_ast.Existing)
1272
+ and getattr(default.object, "pyobj", None) is MISSING_DEFAULT
1273
+ ):
1274
+ defaulted.add(formal)
1275
+
1276
+ possibly_positional = (
1277
+ {formal for _name, formal in positional_entries}
1278
+ if positional_spread
1279
+ else set()
1280
+ )
1281
+ possibly_keyword = set(keyword_bindable.values()) if keyword_spread else set()
1282
+ for _name, formal in (*positional_entries, *keyword_only_entries):
1283
+ if formal in defaulted:
1284
+ continue
1285
+ if formal in definitely_positional or formal in definitely_keyword:
1286
+ continue
1287
+ if formal in possibly_positional or formal in possibly_keyword:
1288
+ uncertain_reasons.add("missing-required")
1289
+ else:
1290
+ reasons.add("missing-required")
1291
+
1292
+ definitely_invalid = bool(reasons)
1293
+ maybe_invalid = not definitely_invalid and bool(uncertain_reasons)
1294
+ return (
1295
+ definitely_invalid,
1296
+ maybe_invalid,
1297
+ frozenset((*reasons, *uncertain_reasons)),
1298
+ )
1299
+
1300
+ @staticmethod
1301
+ def _deferred_code_kind(callee: py_ast.Code) -> str | None:
1302
+ origin = getattr(getattr(callee, "annotation", None), "origin", ()) or ()
1303
+ if "converted_generator" in origin or "converted_genexpr" in origin:
1304
+ return "generator"
1305
+ if "converted_async_function" in origin:
1306
+ return "coroutine"
1307
+ return None
1308
+
1309
+ def _direct_call_cache_key(
1310
+ self,
1311
+ call: py_ast.DirectCall,
1312
+ actual_bindings: dict[int, tuple[HeapLocation, ...]],
1313
+ ) -> tuple[object, ...]:
1314
+ return (
1315
+ "direct-call",
1316
+ self._evaluation_epoch,
1317
+ id(call),
1318
+ self._summary_key(call.code, actual_bindings),
1319
+ )
1320
+
1321
+ def _propagate_escapes_transitively(self) -> None:
1322
+ """Propagate escape through container/closure values.
1323
+
1324
+ If container location C is marked escaped, and C's heap state
1325
+ (values/contaminants) holds location V, then V's root should also
1326
+ be marked escaped — the value is reachable from outside the
1327
+ procedure via the escaped container.
1328
+
1329
+ This is a fixed-point iteration because nested containers may
1330
+ transitively hold further values (e.g. outer -> inner -> value).
1331
+ """
1332
+ if not self.heap.policy.track_escapes:
1333
+ return
1334
+ changed = True
1335
+ while changed:
1336
+ changed = False
1337
+ new_escaped: list[HeapLocation] = []
1338
+ # Check all known location -> values mappings
1339
+ for mapping in (self.state.values, self.state.contaminants):
1340
+ for container_loc, value_locs in mapping.items():
1341
+ if not self._location_is_escaped(container_loc):
1342
+ continue
1343
+ for value_loc in value_locs:
1344
+ if not self._location_is_escaped(value_loc):
1345
+ new_escaped.append(value_loc)
1346
+ # Also propagate through return values: if a return slot (ret0)
1347
+ # contains locations, those are also reachable from outside.
1348
+ for ret_locs in self.state.returns.values():
1349
+ for ret_loc in ret_locs:
1350
+ if not self._location_is_escaped(ret_loc):
1351
+ new_escaped.append(ret_loc)
1352
+ if new_escaped:
1353
+ self.heap.mark_all_escaped(tuple(new_escaped))
1354
+ self.state.mark_escaped(tuple(new_escaped))
1355
+ changed = True
1356
+
1357
+ def _location_is_escaped(self, location: HeapLocation) -> bool:
1358
+ from .model import HeapEscapeState
1359
+
1360
+ return (
1361
+ location in self.state.escaped
1362
+ or location.root in self.heap._escaped_objects
1363
+ or location.root.escape
1364
+ in {
1365
+ HeapEscapeState.ESCAPED,
1366
+ HeapEscapeState.EXTERNAL,
1367
+ HeapEscapeState.UNKNOWN,
1368
+ }
1369
+ )
1370
+
1371
+ def _callee_summary(
1372
+ self,
1373
+ callee: py_ast.Code,
1374
+ actual_bindings: dict[int, tuple[HeapLocation, ...]],
1375
+ ) -> _CallSummary:
1376
+ if callee in self._summary_in_progress:
1377
+ return self._conservative_return_summary(callee)
1378
+
1379
+ self._summary_in_progress.add(callee)
1380
+ caller_state = self.state
1381
+ caller_environment = self.heap.snapshot_environment()
1382
+ # Analyze a known callee against the heap that exists at this call
1383
+ # site. Starting from an empty state loses field/global/cell reads and
1384
+ # makes summaries stale after caller-side mutations.
1385
+ self.state = caller_state.copy()
1386
+ self.state.returns.pop(callee, None)
1387
+ self.state.return_slots.pop(callee, None)
1388
+ self.state.yields.pop(callee, None)
1389
+ self.state.raised.pop(callee, None)
1390
+ summary_deletes: list[HeapLocation] = []
1391
+ summary_effects: list[object] = []
1392
+ degradation_start = len(self.precision_degradations)
1393
+ yield_events: list[
1394
+ tuple[int, _FlowState, tuple[HeapLocation, ...]]
1395
+ ] = []
1396
+ self._summary_delete_stack.append(summary_deletes)
1397
+ self._summary_effect_stack.append(summary_effects)
1398
+ is_generator = self._deferred_code_kind(callee) == "generator"
1399
+ if is_generator:
1400
+ self._yield_state_stack.append(yield_events)
1401
+ try:
1402
+ self._bind_callee_formals(callee, actual_bindings)
1403
+ self.bind_parameters(callee)
1404
+ initial_formal_locations = self._callee_formal_locations(callee)
1405
+ outcome = self.analyze_node(callee, callee.ast)
1406
+ normal_candidates = tuple(
1407
+ state
1408
+ for state in (
1409
+ outcome.normal,
1410
+ outcome.abrupt.get("return"),
1411
+ )
1412
+ if state is not None
1413
+ )
1414
+ normal_flow = (
1415
+ self._join_flow_states(normal_candidates)
1416
+ if normal_candidates
1417
+ else None
1418
+ )
1419
+ raise_flow = outcome.abrupt.get("raise")
1420
+ all_exit_states = tuple(
1421
+ state
1422
+ for state in (
1423
+ normal_flow,
1424
+ raise_flow,
1425
+ *(state for _depth, state, _yielded in yield_events),
1426
+ *(
1427
+ state
1428
+ for kind, state in outcome.abrupt.items()
1429
+ if kind not in {"return", "raise"}
1430
+ ),
1431
+ )
1432
+ if state is not None
1433
+ )
1434
+ joined = self._join_flow_states(all_exit_states)
1435
+ self._restore_flow_state(joined)
1436
+ self._propagate_escapes_transitively()
1437
+ summary_state = self.state
1438
+ return_locations = (
1439
+ normal_flow.heap_state.return_slots.get(
1440
+ callee,
1441
+ self._return_locations(callee),
1442
+ )
1443
+ if normal_flow is not None
1444
+ else ()
1445
+ )
1446
+ param_returns = self._compute_param_returns(
1447
+ return_locations,
1448
+ initial_formal_locations,
1449
+ )
1450
+ param_escapes = self._compute_param_escapes(
1451
+ summary_state,
1452
+ initial_formal_locations,
1453
+ )
1454
+ callee_environment = self.heap.snapshot_environment()
1455
+ # Callee locals/formals cease to be live at return and must not
1456
+ # inflate caller reference counts. Preserve caller bindings plus
1457
+ # globally relevant object labels and escape facts only.
1458
+ caller_environment.object_labels.update(
1459
+ callee_environment.object_labels
1460
+ )
1461
+ caller_environment.escaped_objects.update(
1462
+ callee_environment.escaped_objects
1463
+ )
1464
+
1465
+ def cleaned(flow: _FlowState | None) -> HeapState | None:
1466
+ if flow is None:
1467
+ return None
1468
+ state = flow.heap_state.copy()
1469
+ state.raised.pop(callee, None)
1470
+ state.yields.pop(callee, None)
1471
+ state.yield_depths.pop(callee, None)
1472
+ state.returns.pop(callee, None)
1473
+ state.return_slots.pop(callee, None)
1474
+ state.active_exceptions.pop(callee, None)
1475
+ return state
1476
+
1477
+ normal_state = cleaned(normal_flow)
1478
+ raise_state = cleaned(raise_flow)
1479
+ raised_locations = (
1480
+ raise_flow.heap_state.raised.get(callee, ())
1481
+ if raise_flow is not None
1482
+ else ()
1483
+ )
1484
+ yielded_locations = summary_state.yields.get(callee, ())
1485
+ yield_steps_list = []
1486
+ for depth in sorted({depth for depth, _state, _value in yield_events}):
1487
+ events = tuple(
1488
+ (event_state, yielded)
1489
+ for event_depth, event_state, yielded in yield_events
1490
+ if event_depth == depth
1491
+ )
1492
+ joined_event = self._join_flow_states(
1493
+ tuple(event_state for event_state, _yielded in events)
1494
+ )
1495
+ yielded = tuple(
1496
+ dict.fromkeys(
1497
+ location
1498
+ for _event_state, event_values in events
1499
+ for location in event_values
1500
+ )
1501
+ )
1502
+ yield_steps_list.append(
1503
+ (
1504
+ cleaned(joined_event) or joined_event.heap_state.copy(),
1505
+ joined_event.environment,
1506
+ yielded,
1507
+ )
1508
+ )
1509
+ yield_steps = tuple(yield_steps_list)
1510
+ post_state = normal_state or raise_state or cleaned(joined)
1511
+ assert post_state is not None
1512
+ result = _CallSummary(
1513
+ state=post_state,
1514
+ returns=return_locations,
1515
+ environment=caller_environment,
1516
+ normal_state=normal_state,
1517
+ normal_environment=caller_environment,
1518
+ raise_state=raise_state,
1519
+ raise_environment=caller_environment,
1520
+ deletes=tuple(dict.fromkeys(summary_deletes)),
1521
+ raises=raised_locations,
1522
+ yields=yielded_locations,
1523
+ yield_steps=yield_steps,
1524
+ param_returns=param_returns,
1525
+ param_escapes=param_escapes,
1526
+ )
1527
+ public_summary = ProcedureHeapSummary(
1528
+ normal_state=normal_state.copy() if normal_state is not None else None,
1529
+ raise_state=raise_state.copy() if raise_state is not None else None,
1530
+ returns=return_locations,
1531
+ raises=raised_locations,
1532
+ yields=yielded_locations,
1533
+ deletes=result.deletes,
1534
+ param_returns=param_returns,
1535
+ param_escapes=param_escapes,
1536
+ effects=HeapSummary.from_effects(summary_effects),
1537
+ precision_degradations=frozenset(
1538
+ reason
1539
+ for _node, reason in self.precision_degradations[
1540
+ degradation_start:
1541
+ ]
1542
+ ),
1543
+ )
1544
+ previous_summary = self.procedure_summaries.get(callee)
1545
+ self.procedure_summaries[callee] = (
1546
+ public_summary
1547
+ if previous_summary is None
1548
+ else previous_summary.merge(public_summary)
1549
+ )
1550
+ return result
1551
+ finally:
1552
+ if is_generator:
1553
+ self._yield_state_stack.pop()
1554
+ self._summary_delete_stack.pop()
1555
+ completed_effects = self._summary_effect_stack.pop()
1556
+ if self._summary_effect_stack:
1557
+ self._summary_effect_stack[-1].extend(completed_effects)
1558
+ self.state = caller_state
1559
+ self.heap.restore_environment(caller_environment)
1560
+ self._summary_in_progress.discard(callee)
1561
+
1562
+ def _apply_callee_summary(
1563
+ self,
1564
+ summary: _CallSummary,
1565
+ caller: object,
1566
+ *,
1567
+ preserve_current: bool = False,
1568
+ ) -> None:
1569
+ self._record_summary_deletes(summary.deletes)
1570
+ # The summary starts from this exact call site's state, so it already
1571
+ # is the complete post-call state. Joining it with the pre-call state
1572
+ # would resurrect values removed by strong writes and must-deletes.
1573
+ selected_state = summary.normal_state or summary.state
1574
+ self.state = (
1575
+ self.state.join(selected_state)
1576
+ if preserve_current
1577
+ else selected_state.copy()
1578
+ )
1579
+ selected_environment = summary.normal_environment or summary.environment
1580
+ if selected_environment is not None and not preserve_current:
1581
+ self.heap.restore_environment(selected_environment)
1582
+ self.heap.mark_all_escaped(tuple(summary.state.escaped))
1583
+ # Propagate transitively after merging summary state: the caller
1584
+ # may now have new escaped containers holding values that should
1585
+ # also be considered escaped.
1586
+ self._propagate_escapes_transitively()
1587
+
1588
+ def _record_summary_deletes(self, deletes: tuple[HeapLocation, ...]) -> None:
1589
+ if self._summary_delete_stack and deletes:
1590
+ self._summary_delete_stack[-1].extend(deletes)
1591
+
1592
+ def _summary_key(
1593
+ self,
1594
+ callee: py_ast.Code,
1595
+ actual_bindings: dict[int, tuple[HeapLocation, ...]],
1596
+ ) -> tuple[object, ...]:
1597
+ actual_key = tuple(
1598
+ actual_bindings.get(index, ())
1599
+ for index, _formal in enumerate(self._callee_formals(callee))
1600
+ )
1601
+ return callee, actual_key
1602
+
1603
+ def _return_locations(
1604
+ self,
1605
+ callee: py_ast.Code,
1606
+ ) -> tuple[tuple[HeapLocation, ...], ...]:
1607
+ code_parameters = getattr(callee, "codeparameters", None)
1608
+ if code_parameters is None:
1609
+ return ()
1610
+ locations: list[tuple[HeapLocation, ...]] = []
1611
+ for index, target in enumerate(getattr(code_parameters, "returnparams", ())):
1612
+ if isinstance(target, py_ast.Local):
1613
+ target_locations = self.heap.locations_for_local(callee, target)
1614
+ if target_locations:
1615
+ locations.append(target_locations)
1616
+ continue
1617
+ locations.append((HeapLocation(self.heap.return_object(callee, index)),))
1618
+ return tuple(locations)
1619
+
1620
+ def _callee_formal_locations(
1621
+ self,
1622
+ callee: py_ast.Code,
1623
+ ) -> dict[int, tuple[HeapLocation, ...]]:
1624
+ """Return a dict mapping formal param index -> its heap locations."""
1625
+ formal_locations: dict[int, tuple[HeapLocation, ...]] = {}
1626
+ for idx, formal in enumerate(self._callee_formals(callee)):
1627
+ locs = self.heap.locations_for_local(callee, formal)
1628
+ if locs:
1629
+ formal_locations[idx] = locs
1630
+ return formal_locations
1631
+
1632
+ def _compute_param_returns(
1633
+ self,
1634
+ return_locations: tuple[tuple[HeapLocation, ...], ...],
1635
+ formal_locations: dict[int, tuple[HeapLocation, ...]],
1636
+ ) -> dict[int, frozenset[int]]:
1637
+ """Return a dict mapping return_index -> formal_param_index when a
1638
+ return directly carries a formal parameter's location."""
1639
+ if not return_locations:
1640
+ return {}
1641
+ if not formal_locations:
1642
+ return {}
1643
+ param_returns: dict[int, frozenset[int]] = {}
1644
+ for ret_idx, ret_locs in enumerate(return_locations):
1645
+ matches: set[int] = set()
1646
+ for formal_idx, formal_locs in formal_locations.items():
1647
+ if any(ret_loc in formal_locs for ret_loc in ret_locs):
1648
+ matches.add(formal_idx)
1649
+ if matches:
1650
+ param_returns[ret_idx] = frozenset(matches)
1651
+ return param_returns
1652
+
1653
+ def _compute_param_escapes(
1654
+ self,
1655
+ summary_state: HeapState,
1656
+ formal_locations: dict[int, tuple[HeapLocation, ...]],
1657
+ ) -> frozenset[int]:
1658
+ """Return the set of formal parameter indices whose locations escape."""
1659
+ if not summary_state.escaped:
1660
+ return frozenset()
1661
+ if not formal_locations:
1662
+ return frozenset()
1663
+ escaped: set[int] = set()
1664
+ for idx, locs in formal_locations.items():
1665
+ for loc in locs:
1666
+ if loc in summary_state.escaped:
1667
+ escaped.add(idx)
1668
+ break
1669
+ return frozenset(escaped)
1670
+
1671
+ def _conservative_return_summary(
1672
+ self,
1673
+ callee: py_ast.Code,
1674
+ ) -> _CallSummary:
1675
+ state = self.state.copy()
1676
+ returns = self._return_locations(callee)
1677
+ if not returns:
1678
+ code_parameters = getattr(callee, "codeparameters", None)
1679
+ returns = tuple(
1680
+ (HeapLocation(self.heap.return_object(callee, index)),)
1681
+ for index, _target in enumerate(
1682
+ getattr(code_parameters, "returnparams", ())
1683
+ if code_parameters is not None
1684
+ else ()
1685
+ )
1686
+ )
1687
+ return _CallSummary(
1688
+ state=state,
1689
+ returns=returns,
1690
+ environment=self.heap.snapshot_environment(),
1691
+ normal_state=state,
1692
+ normal_environment=self.heap.snapshot_environment(),
1693
+ )
1694
+
1695
+ def _bind_callee_formals(
1696
+ self,
1697
+ callee: py_ast.Code,
1698
+ actual_bindings: dict[int, tuple[HeapLocation, ...]],
1699
+ ) -> None:
1700
+ for index, formal in enumerate(self._callee_formals(callee)):
1701
+ actual_locations = actual_bindings.get(index, ())
1702
+ self.heap.bind_parameter(callee, formal, index, actual_locations)
1703
+
1704
+ @staticmethod
1705
+ def _callee_formals(callee: py_ast.Code) -> tuple[py_ast.Local, ...]:
1706
+ params = getattr(callee, "codeparameters", None)
1707
+ if params is None:
1708
+ return ()
1709
+ formals: list[py_ast.Local] = []
1710
+ for candidate in (
1711
+ getattr(params, "selfparam", None),
1712
+ *getattr(params, "posonlyparams", ()),
1713
+ *getattr(params, "params", ()),
1714
+ getattr(params, "vparam", None),
1715
+ getattr(params, "kparam", None),
1716
+ ):
1717
+ if isinstance(candidate, py_ast.Local) and candidate not in formals:
1718
+ formals.append(candidate)
1719
+ return tuple(formals)
1720
+
1721
+ def _direct_call_actual_locations(
1722
+ self,
1723
+ caller: object,
1724
+ callee: py_ast.Code,
1725
+ call: py_ast.DirectCall,
1726
+ *,
1727
+ receiver_supplied: bool = False,
1728
+ receiver_consumes_positional: bool = False,
1729
+ ) -> _CallBindingResult:
1730
+ """Bind a resolved call according to Python's formal parameter layout."""
1731
+ params = callee.codeparameters
1732
+ formals = self._callee_formals(callee)
1733
+ evaluated_operands = self._evaluate_call_operands(caller, call)
1734
+ formal_indices = {id(formal): index for index, formal in enumerate(formals)}
1735
+ bindings: dict[int, list[HeapLocation]] = {
1736
+ index: [] for index in range(len(formals))
1737
+ }
1738
+ uncertainly_bound: set[int] = set()
1739
+
1740
+ def evaluate(expression_procedure, expression):
1741
+ cached = evaluated_operands.get(id(expression))
1742
+ if cached is not None:
1743
+ return cached
1744
+ return self.locations_for_expression(
1745
+ expression_procedure,
1746
+ expression,
1747
+ )
1748
+
1749
+ def bind(formal, locations):
1750
+ bindings[formal_indices[id(formal)]].extend(locations)
1751
+
1752
+ selfparam = getattr(params, "selfparam", None)
1753
+ selfarg = getattr(call, "selfarg", None)
1754
+ if isinstance(selfparam, py_ast.Local) and selfarg is not None:
1755
+ bind(selfparam, evaluate(caller, selfarg))
1756
+
1757
+ encoded_params = list(getattr(params, "params", ()))
1758
+ encoded_names = list(getattr(params, "paramnames", ()))
1759
+ parameter_entries = list(zip(encoded_names, encoded_params))
1760
+ regular_params = [
1761
+ formal
1762
+ for name, formal in parameter_entries
1763
+ if not (isinstance(name, str) and name.startswith("kwonly:"))
1764
+ ]
1765
+ positional_slots = [
1766
+ *getattr(params, "posonlyparams", ()),
1767
+ *regular_params,
1768
+ ]
1769
+ positional_items = call_positional_items(call)
1770
+ positional_actuals = list(getattr(call, "args", ()))
1771
+ extra_positional: list[tuple[HeapLocation, ...]] = []
1772
+ positional_index = 0
1773
+ if positional_items:
1774
+ uncertain_spread = False
1775
+ for is_spread, actual in positional_items:
1776
+ locations = evaluate(caller, actual)
1777
+ expanded = (
1778
+ self._ordered_contained_values(locations)
1779
+ if is_spread
1780
+ else locations
1781
+ )
1782
+ if is_spread and not expanded:
1783
+ possible = tuple(
1784
+ dict.fromkeys(
1785
+ (*locations, self._external_value_location(caller))
1786
+ )
1787
+ )
1788
+ for formal in positional_slots[positional_index:]:
1789
+ if isinstance(formal, py_ast.Local):
1790
+ bind(formal, possible)
1791
+ uncertainly_bound.add(formal_indices[id(formal)])
1792
+ extra_positional.append(possible)
1793
+ uncertain_spread = True
1794
+ continue
1795
+ if uncertain_spread and not is_spread:
1796
+ for formal in positional_slots[positional_index:]:
1797
+ if isinstance(formal, py_ast.Local):
1798
+ bind(formal, expanded)
1799
+ uncertainly_bound.add(formal_indices[id(formal)])
1800
+ extra_positional.append(expanded)
1801
+ continue
1802
+ for item_locations in (
1803
+ ((location,) for location in expanded)
1804
+ if is_spread
1805
+ else (expanded,)
1806
+ ):
1807
+ if positional_index < len(positional_slots):
1808
+ formal = positional_slots[positional_index]
1809
+ if isinstance(formal, py_ast.Local):
1810
+ bind(formal, item_locations)
1811
+ else:
1812
+ extra_positional.append(item_locations)
1813
+ positional_index += 1
1814
+ else:
1815
+ for index, actual in enumerate(positional_actuals):
1816
+ locations = evaluate(caller, actual)
1817
+ if index < len(positional_slots):
1818
+ formal = positional_slots[index]
1819
+ if isinstance(formal, py_ast.Local):
1820
+ bind(formal, locations)
1821
+ else:
1822
+ extra_positional.append(locations)
1823
+
1824
+ named_formals = {
1825
+ (
1826
+ name[len("kwonly:") :]
1827
+ if name.startswith("kwonly:")
1828
+ else name
1829
+ ): formal
1830
+ for name, formal in parameter_entries
1831
+ if isinstance(name, str) and isinstance(formal, py_ast.Local)
1832
+ }
1833
+ extra_keywords: list[tuple[str | None, tuple[HeapLocation, ...]]] = []
1834
+ for keyword in getattr(call, "kwds", ()):
1835
+ if not (isinstance(keyword, tuple) and len(keyword) == 2):
1836
+ extra_keywords.append((None, evaluate(caller, keyword)))
1837
+ continue
1838
+ name, actual = keyword
1839
+ locations = evaluate(caller, actual)
1840
+ formal = named_formals.get(name)
1841
+ if formal is None:
1842
+ extra_keywords.append((name if isinstance(name, str) else None, locations))
1843
+ continue
1844
+ bind(formal, locations)
1845
+
1846
+ vargs_locations: tuple[HeapLocation, ...] = ()
1847
+ positional_spreads = call_positional_spreads(call)
1848
+ if positional_spreads:
1849
+ vargs_locations = tuple(
1850
+ dict.fromkeys(
1851
+ location
1852
+ for spread in positional_spreads
1853
+ for location in evaluate(caller, spread)
1854
+ )
1855
+ )
1856
+ elif getattr(call, "vargs", None) is not None:
1857
+ vargs_locations = evaluate(caller, call.vargs)
1858
+
1859
+ kargs_locations: tuple[HeapLocation, ...] = ()
1860
+ keyword_spreads = call_keyword_spreads(call)
1861
+ if keyword_spreads:
1862
+ kargs_locations = tuple(
1863
+ dict.fromkeys(
1864
+ location
1865
+ for spread in keyword_spreads
1866
+ for location in evaluate(caller, spread)
1867
+ )
1868
+ )
1869
+ elif getattr(call, "kargs", None) is not None:
1870
+ kargs_locations = evaluate(caller, call.kargs)
1871
+
1872
+ explicitly_bound = {
1873
+ index for index, locations in bindings.items() if locations
1874
+ }
1875
+
1876
+ if vargs_locations and not positional_items:
1877
+ expanded_vargs = self._contained_values(vargs_locations)
1878
+ if not expanded_vargs:
1879
+ expanded_vargs = (
1880
+ *vargs_locations,
1881
+ self._external_value_location(caller),
1882
+ )
1883
+ for formal in positional_slots:
1884
+ if not isinstance(formal, py_ast.Local):
1885
+ continue
1886
+ index = formal_indices[id(formal)]
1887
+ if not bindings[index]:
1888
+ bind(formal, expanded_vargs)
1889
+
1890
+ if kargs_locations:
1891
+ for name, formal in named_formals.items():
1892
+ index = formal_indices[id(formal)]
1893
+ if bindings[index]:
1894
+ continue
1895
+ possible: list[HeapLocation] = []
1896
+ for root in kargs_locations:
1897
+ exact = self.heap.dynamic_subscript_location(
1898
+ root,
1899
+ f"[{name!r}]",
1900
+ )
1901
+ wildcard = self.heap.dynamic_subscript_location(
1902
+ root,
1903
+ DYNAMIC_SUBSCRIPT_WILDCARD,
1904
+ )
1905
+ possible.extend(self.state.read(exact, fallback=()))
1906
+ possible.extend(self.state.read_contained(wildcard))
1907
+ if not possible:
1908
+ possible.extend(kargs_locations)
1909
+ possible.append(self._external_value_location(caller))
1910
+ uncertainly_bound.add(index)
1911
+ bind(formal, tuple(dict.fromkeys(possible)))
1912
+
1913
+ defaults = list(getattr(params, "defaults", ()))
1914
+ if defaults:
1915
+ defaultable_formals = [
1916
+ *getattr(params, "posonlyparams", ()),
1917
+ *encoded_params,
1918
+ ]
1919
+ default_formals = defaultable_formals[-len(defaults):]
1920
+ for default_index, (formal, default) in enumerate(
1921
+ zip(default_formals, defaults)
1922
+ ):
1923
+ if not isinstance(formal, py_ast.Local):
1924
+ continue
1925
+ if (
1926
+ isinstance(default, py_ast.Existing)
1927
+ and getattr(default.object, "pyobj", None) is MISSING_DEFAULT
1928
+ ):
1929
+ continue
1930
+ index = formal_indices[id(formal)]
1931
+ if (
1932
+ not bindings[index]
1933
+ or index in uncertainly_bound
1934
+ or (
1935
+ index not in explicitly_bound
1936
+ and (vargs_locations or kargs_locations)
1937
+ )
1938
+ ):
1939
+ locations = self._definition_default_locations.get(
1940
+ (id(callee), default_index)
1941
+ )
1942
+ if locations is None:
1943
+ locations = evaluate(callee, default)
1944
+ bind(formal, locations)
1945
+
1946
+ vparam = getattr(params, "vparam", None)
1947
+ if isinstance(vparam, py_ast.Local):
1948
+ packed = HeapLocation(
1949
+ self.heap.summary_object(
1950
+ ("varargs", id(callee), id(call)),
1951
+ label="*args",
1952
+ )
1953
+ )
1954
+ element_index = 0
1955
+ for locations in extra_positional:
1956
+ self.state.write(
1957
+ self.heap.dynamic_subscript_location(
1958
+ packed,
1959
+ f"[{element_index}]",
1960
+ ),
1961
+ locations,
1962
+ UpdatePolicy.STRONG,
1963
+ )
1964
+ element_index += 1
1965
+ expanded_vargs = self._expand_contained_locations(vargs_locations)
1966
+ if expanded_vargs:
1967
+ self.state.write(
1968
+ self.heap.dynamic_subscript_location(
1969
+ packed,
1970
+ DYNAMIC_SUBSCRIPT_WILDCARD,
1971
+ ),
1972
+ expanded_vargs,
1973
+ UpdatePolicy.WEAK,
1974
+ )
1975
+ bind(vparam, (packed,))
1976
+
1977
+ kparam = getattr(params, "kparam", None)
1978
+ if isinstance(kparam, py_ast.Local):
1979
+ packed = HeapLocation(
1980
+ self.heap.summary_object(
1981
+ ("kwargs", id(callee), id(call)),
1982
+ label="**kwargs",
1983
+ )
1984
+ )
1985
+ for name, locations in extra_keywords:
1986
+ self.state.write(
1987
+ self.heap.dynamic_subscript_location(
1988
+ packed,
1989
+ f"[{name!r}]" if name is not None else DYNAMIC_SUBSCRIPT_WILDCARD,
1990
+ ),
1991
+ locations,
1992
+ UpdatePolicy.STRONG if name is not None else UpdatePolicy.WEAK,
1993
+ )
1994
+ expanded_kargs = self._expand_contained_locations(kargs_locations)
1995
+ if expanded_kargs:
1996
+ self.state.write(
1997
+ self.heap.dynamic_subscript_location(
1998
+ packed,
1999
+ DYNAMIC_SUBSCRIPT_WILDCARD,
2000
+ ),
2001
+ expanded_kargs,
2002
+ UpdatePolicy.WEAK,
2003
+ )
2004
+ bind(kparam, (packed,))
2005
+
2006
+ resolved_bindings = {
2007
+ index: tuple(dict.fromkeys(locations))
2008
+ for index, locations in bindings.items()
2009
+ }
2010
+ definitely_invalid, maybe_invalid, reasons = self._call_binding_status(
2011
+ callee,
2012
+ call,
2013
+ receiver_supplied=receiver_supplied,
2014
+ receiver_consumes_positional=receiver_consumes_positional,
2015
+ )
2016
+ return _CallBindingResult(
2017
+ resolved_bindings,
2018
+ definitely_invalid,
2019
+ maybe_invalid,
2020
+ reasons,
2021
+ )
2022
+
2023
+ def _bind_call_result_targets(
2024
+ self,
2025
+ procedure: object,
2026
+ targets: tuple[py_ast.Local, ...],
2027
+ call_expression: object,
2028
+ *,
2029
+ bind: bool = True,
2030
+ ) -> tuple[tuple[HeapLocation, ...], ...]:
2031
+ if not self.heap.policy.bind_call_results:
2032
+ return ()
2033
+ operand_locations = self._evaluate_call_operands(
2034
+ procedure,
2035
+ call_expression,
2036
+ )
2037
+ kind = self.effect_builder.call_return_kind(call_expression)
2038
+ label = self.effect_builder._call_result_label(call_expression)
2039
+ modeled_locations = self._modeled_call_return_locations(
2040
+ procedure,
2041
+ call_expression,
2042
+ kind,
2043
+ operand_locations,
2044
+ )
2045
+ known_classes = self._known_class_locations(
2046
+ procedure,
2047
+ call_expression,
2048
+ operand_locations,
2049
+ )
2050
+ constructed = self._evaluate_known_class_targets(
2051
+ procedure,
2052
+ call_expression,
2053
+ known_classes,
2054
+ operand_locations,
2055
+ label,
2056
+ )
2057
+ slots: list[tuple[HeapLocation, ...]] = []
2058
+ for index, target in enumerate(targets):
2059
+ site = self.effect_builder.call_return_site(call_expression, index, kind)
2060
+ result_locations: tuple[HeapLocation, ...]
2061
+ if constructed:
2062
+ result_locations = constructed
2063
+ elif kind == CALL_RETURN_NONE:
2064
+ result_locations = ()
2065
+ elif kind in {CALL_RETURN_SELF, CALL_RETURN_ARG}:
2066
+ if modeled_locations:
2067
+ result_locations = modeled_locations
2068
+ else:
2069
+ result_locations = (
2070
+ HeapLocation(self.heap.summary_object(site, label=label)),
2071
+ )
2072
+ elif modeled_locations:
2073
+ result_locations = modeled_locations
2074
+ call_name = resolve_call_name(call_expression)
2075
+ if (
2076
+ kind == CALL_RETURN_OPAQUE
2077
+ and isinstance(call_expression, py_ast.MethodCall)
2078
+ and isinstance(call_name, str)
2079
+ and "." not in call_name
2080
+ ):
2081
+ # A method name alone does not establish a builtin
2082
+ # receiver. Preserve the useful container/property model
2083
+ # while retaining an arbitrary user-method return.
2084
+ result_locations = tuple(
2085
+ dict.fromkeys(
2086
+ (
2087
+ *result_locations,
2088
+ HeapLocation(
2089
+ self.heap.call_result_object(
2090
+ procedure,
2091
+ site,
2092
+ label=label,
2093
+ context=self._current_context,
2094
+ )
2095
+ ),
2096
+ )
2097
+ )
2098
+ )
2099
+ elif kind in {CALL_RETURN_FRESH, CALL_RETURN_COPY}:
2100
+ result_location = HeapLocation(
2101
+ self.heap.allocation_object(
2102
+ procedure,
2103
+ site,
2104
+ label=label,
2105
+ context=self._current_context,
2106
+ )
2107
+ )
2108
+ result_locations = (result_location,)
2109
+ if kind == CALL_RETURN_COPY:
2110
+ self._copy_call_result_contents(
2111
+ procedure,
2112
+ None,
2113
+ call_expression,
2114
+ (result_location,),
2115
+ )
2116
+ elif kind == CALL_RETURN_SUMMARY:
2117
+ result_locations = (
2118
+ HeapLocation(self.heap.summary_object(site, label=label)),
2119
+ )
2120
+ else:
2121
+ result_locations = (
2122
+ HeapLocation(
2123
+ self.heap.call_result_object(
2124
+ procedure,
2125
+ site,
2126
+ label=label,
2127
+ context=self._current_context,
2128
+ )
2129
+ ),
2130
+ )
2131
+ slots.append(tuple(dict.fromkeys(result_locations)))
2132
+ if not constructed:
2133
+ self._attach_known_class(
2134
+ procedure,
2135
+ call_expression,
2136
+ result_locations,
2137
+ )
2138
+ if bind:
2139
+ if result_locations:
2140
+ self._bind_runtime_local(
2141
+ procedure,
2142
+ target,
2143
+ result_locations,
2144
+ )
2145
+ else:
2146
+ self._clear_runtime_local(procedure, target)
2147
+ return tuple(slots)
2148
+
2149
+ def _known_class_locations(
2150
+ self,
2151
+ procedure: object,
2152
+ call: object,
2153
+ operand_locations: dict[int, tuple[HeapLocation, ...]] | None = None,
2154
+ ) -> tuple[HeapLocation, ...]:
2155
+ if not isinstance(call, py_ast.Call):
2156
+ return ()
2157
+ operands = operand_locations or self._last_call_operands.get(id(call))
2158
+ if operands is None:
2159
+ operands = self._evaluate_call_operands(procedure, call)
2160
+ return tuple(
2161
+ dict.fromkeys(
2162
+ self._class_locations_by_root[location.root]
2163
+ for location in operands.get(id(call.expr), ())
2164
+ if location.root in self._class_locations_by_root
2165
+ )
2166
+ )
2167
+
2168
+ def _evaluate_known_class_targets(
2169
+ self,
2170
+ procedure: object,
2171
+ call: object,
2172
+ classes: tuple[HeapLocation, ...],
2173
+ operand_locations: dict[int, tuple[HeapLocation, ...]],
2174
+ label: str,
2175
+ ) -> tuple[HeapLocation, ...]:
2176
+ if not classes:
2177
+ return ()
2178
+ base = self._capture_flow_state()
2179
+ states: list[_FlowState] = []
2180
+ results: list[HeapLocation] = []
2181
+ for class_location in classes:
2182
+ self._restore_flow_state(base)
2183
+ allocator = self._resolve_class_method(
2184
+ class_location,
2185
+ self._class_allocators_by_root,
2186
+ )
2187
+ allocated = self._evaluate_known_class_allocator(
2188
+ procedure,
2189
+ call,
2190
+ class_location,
2191
+ operand_locations,
2192
+ ) if allocator is not None else ()
2193
+ if allocator is None:
2194
+ allocated = (
2195
+ HeapLocation(
2196
+ self.heap.allocation_object(
2197
+ procedure,
2198
+ ("class-instance", id(call), class_location.root),
2199
+ label=label,
2200
+ context=self._current_context,
2201
+ )
2202
+ ),
2203
+ )
2204
+ self.state.complete_roots.update(
2205
+ location.root for location in allocated
2206
+ )
2207
+ elif not allocated:
2208
+ allocated = (self._external_value_location(procedure),)
2209
+ self._attach_known_class(
2210
+ procedure,
2211
+ call,
2212
+ allocated,
2213
+ allocator_known=allocator is not None,
2214
+ class_location=class_location,
2215
+ )
2216
+ results.extend(allocated)
2217
+ states.append(self._capture_flow_state())
2218
+ self._restore_flow_state(self._join_flow_states(tuple(states)))
2219
+ return tuple(dict.fromkeys(results))
2220
+
2221
+ def _evaluate_known_class_allocator(
2222
+ self,
2223
+ procedure: object,
2224
+ call: object,
2225
+ class_location: HeapLocation,
2226
+ operand_locations: dict[int, tuple[HeapLocation, ...]],
2227
+ ) -> tuple[HeapLocation, ...]:
2228
+ allocator = self._resolve_class_method(
2229
+ class_location,
2230
+ self._class_allocators_by_root,
2231
+ )
2232
+ if allocator is None:
2233
+ return ()
2234
+ formals = self._callee_formals(allocator)
2235
+ bindings: dict[int, tuple[HeapLocation, ...]] = {}
2236
+ if formals:
2237
+ bindings[0] = (class_location,)
2238
+ for index, actual in enumerate(getattr(call, "args", ()), start=1):
2239
+ if index >= len(formals):
2240
+ break
2241
+ locations = operand_locations.get(id(actual))
2242
+ if locations is None:
2243
+ locations = self.locations_for_expression(procedure, actual)
2244
+ bindings[index] = locations
2245
+ parameter_names = tuple(
2246
+ getattr(allocator.codeparameters, "paramnames", ())
2247
+ )
2248
+ named_indices = {
2249
+ name: index
2250
+ for index, name in enumerate(parameter_names, start=1)
2251
+ if isinstance(name, str) and index < len(formals)
2252
+ }
2253
+ for keyword in getattr(call, "kwds", ()):
2254
+ if not (isinstance(keyword, tuple) and len(keyword) == 2):
2255
+ continue
2256
+ name, actual = keyword
2257
+ index = named_indices.get(name)
2258
+ if index is None:
2259
+ continue
2260
+ locations = operand_locations.get(id(actual))
2261
+ if locations is None:
2262
+ locations = self.locations_for_expression(procedure, actual)
2263
+ bindings[index] = locations
2264
+ summary = self._callee_summary(allocator, bindings)
2265
+ if summary.raise_state is not None and self._operation_call_raises:
2266
+ self._operation_call_raises[-1].append(
2267
+ _FlowState(
2268
+ summary.raise_state.copy(),
2269
+ summary.raise_environment
2270
+ or summary.environment
2271
+ or self.heap.snapshot_environment(),
2272
+ dict(self._definition_default_locations),
2273
+ )
2274
+ )
2275
+ if summary.normal_state is None:
2276
+ if self._operation_normal_possible:
2277
+ self._operation_normal_possible[-1] = False
2278
+ return ()
2279
+ self._apply_callee_summary(summary, procedure)
2280
+ returns: list[HeapLocation] = []
2281
+ for index, locations in enumerate(summary.returns):
2282
+ returns.extend(locations)
2283
+ for formal_index in summary.param_returns.get(index, frozenset()):
2284
+ returns.extend(bindings.get(formal_index, ()))
2285
+ return tuple(dict.fromkeys(returns))
2286
+
2287
+ def _attach_known_class(
2288
+ self,
2289
+ procedure: object,
2290
+ call: object,
2291
+ instances: tuple[HeapLocation, ...],
2292
+ *,
2293
+ allocator_known: bool = False,
2294
+ class_location: HeapLocation | None = None,
2295
+ ) -> None:
2296
+ if class_location is None:
2297
+ classes = self._known_class_locations(procedure, call)
2298
+ class_location = classes[0] if len(classes) == 1 else None
2299
+ if class_location is None:
2300
+ return
2301
+ compatible: list[HeapLocation] = []
2302
+ for instance in instances:
2303
+ class_slot = self.heap.dynamic_attribute_location(
2304
+ instance,
2305
+ "__class__",
2306
+ )
2307
+ existing_classes = self.state.read(class_slot, fallback=())
2308
+ if not existing_classes:
2309
+ # A default allocation is definitely an instance. A value
2310
+ # returned by a known __new__ is only possibly compatible when
2311
+ # its concrete class is not otherwise known.
2312
+ self.state.write(
2313
+ class_slot,
2314
+ (class_location,),
2315
+ UpdatePolicy.WEAK if allocator_known else UpdatePolicy.STRONG,
2316
+ )
2317
+ compatible.append(instance)
2318
+ continue
2319
+ if any(
2320
+ class_location in self._known_class_mro(existing_class)
2321
+ for existing_class in existing_classes
2322
+ ):
2323
+ compatible.append(instance)
2324
+ initializer = self._resolve_class_initializer(class_location)
2325
+ if (
2326
+ initializer is None
2327
+ or not compatible
2328
+ or (id(call), class_location.root) in self._initialized_class_calls
2329
+ ):
2330
+ return
2331
+ self._initialized_class_calls.add((id(call), class_location.root))
2332
+ formals = self._callee_formals(initializer)
2333
+ bindings: dict[int, tuple[HeapLocation, ...]] = {}
2334
+ if formals:
2335
+ bindings[0] = tuple(compatible)
2336
+ actuals = tuple(getattr(call, "args", ()))
2337
+ evaluated = self._last_call_operands.get(id(call), {})
2338
+ for index, actual in enumerate(actuals, start=1):
2339
+ if index >= len(formals):
2340
+ break
2341
+ locations = evaluated.get(id(actual))
2342
+ if locations is None:
2343
+ locations = self.locations_for_expression(procedure, actual)
2344
+ bindings[index] = locations
2345
+ params = initializer.codeparameters
2346
+ encoded_names = list(getattr(params, "paramnames", ()))
2347
+ encoded_formals = list(getattr(params, "params", ()))
2348
+ formal_indices = {id(formal): index for index, formal in enumerate(formals)}
2349
+ named = {
2350
+ (
2351
+ name[len("kwonly:") :]
2352
+ if name.startswith("kwonly:")
2353
+ else name
2354
+ ): formal_indices[id(formal)]
2355
+ for name, formal in zip(encoded_names, encoded_formals)
2356
+ if isinstance(name, str)
2357
+ and isinstance(formal, py_ast.Local)
2358
+ and id(formal) in formal_indices
2359
+ }
2360
+ for keyword in getattr(call, "kwds", ()):
2361
+ if not (isinstance(keyword, tuple) and len(keyword) == 2):
2362
+ continue
2363
+ name, actual = keyword
2364
+ index = named.get(name)
2365
+ if index is None:
2366
+ continue
2367
+ locations = evaluated.get(id(actual))
2368
+ if locations is None:
2369
+ locations = self.locations_for_expression(procedure, actual)
2370
+ bindings[index] = tuple(
2371
+ dict.fromkeys((*bindings.get(index, ()), *locations))
2372
+ )
2373
+ if getattr(call, "vargs", None) is not None or getattr(call, "kargs", None) is not None:
2374
+ unknown = (self._external_value_location(procedure),)
2375
+ for index in range(len(formals)):
2376
+ bindings.setdefault(index, unknown)
2377
+ summary = self._callee_summary(initializer, bindings)
2378
+ if summary.raise_state is not None and self._operation_call_raises:
2379
+ raised_state = summary.raise_state.copy()
2380
+ if summary.raises:
2381
+ raised_state.set_raised(procedure, summary.raises)
2382
+ self._operation_call_raises[-1].append(
2383
+ _FlowState(
2384
+ raised_state,
2385
+ summary.raise_environment
2386
+ or summary.environment
2387
+ or self.heap.snapshot_environment(),
2388
+ dict(self._definition_default_locations),
2389
+ )
2390
+ )
2391
+ if summary.normal_state is None and self._operation_normal_possible:
2392
+ self._operation_normal_possible[-1] = False
2393
+ self._apply_callee_summary(summary, procedure)
2394
+
2395
+ def _resolve_class_initializer(
2396
+ self,
2397
+ class_location: HeapLocation,
2398
+ ) -> py_ast.Code | None:
2399
+ """Resolve ``__init__`` through the known class base graph."""
2400
+ return self._resolve_class_method(
2401
+ class_location,
2402
+ self._class_initializers_by_root,
2403
+ )
2404
+
2405
+ def _apply_known_class_creation_hooks(
2406
+ self,
2407
+ procedure: object,
2408
+ class_node: py_ast.ClassDef,
2409
+ class_location: HeapLocation,
2410
+ members: dict[str, tuple[HeapLocation, ...]],
2411
+ ) -> None:
2412
+ """Apply closed-world descriptor and subclass creation hooks."""
2413
+ inherited_dispatch: dict[str, py_ast.Code] = {}
2414
+ for base in self._known_class_mro(class_location)[1:]:
2415
+ for name, method in self._class_methods_by_root.get(
2416
+ base.root,
2417
+ {},
2418
+ ).items():
2419
+ inherited_dispatch.setdefault(name, method)
2420
+ self._super_dispatch_by_class_root[
2421
+ class_location.root
2422
+ ] = inherited_dispatch
2423
+ for name, descriptors in members.items():
2424
+ self._evaluate_known_protocol(
2425
+ procedure,
2426
+ descriptors,
2427
+ "__set_name__",
2428
+ (
2429
+ (class_location,),
2430
+ (self._external_value_location(procedure),),
2431
+ ),
2432
+ )
2433
+ bases = self.state.read(
2434
+ self.heap.dynamic_attribute_location(class_location, "__bases__"),
2435
+ fallback=(),
2436
+ )
2437
+ for base in bases:
2438
+ hook = self._resolve_known_class_method(base, "__init_subclass__")
2439
+ if hook is not None:
2440
+ self._evaluate_known_code(
2441
+ procedure,
2442
+ hook,
2443
+ ((class_location,),),
2444
+ )
2445
+
2446
+ metaclass_expressions = tuple(
2447
+ keyword[1]
2448
+ for keyword in getattr(class_node, "keywords", ())
2449
+ if isinstance(keyword, tuple)
2450
+ and len(keyword) == 2
2451
+ and keyword[0] == "metaclass"
2452
+ )
2453
+ metaclasses = self._merge_expression_locations(
2454
+ procedure,
2455
+ *metaclass_expressions,
2456
+ )
2457
+ if not metaclasses:
2458
+ return
2459
+ self.state.write(
2460
+ self.heap.dynamic_attribute_location(class_location, "__class__"),
2461
+ metaclasses,
2462
+ UpdatePolicy.STRONG,
2463
+ )
2464
+ for metaclass in metaclasses:
2465
+ for hook_name in ("__new__", "__init__"):
2466
+ hook = self._resolve_known_class_method(metaclass, hook_name)
2467
+ if hook is not None:
2468
+ self._evaluate_known_code(
2469
+ procedure,
2470
+ hook,
2471
+ (
2472
+ (metaclass,),
2473
+ (class_location,),
2474
+ bases,
2475
+ ),
2476
+ )
2477
+
2478
+ def _resolve_class_method(
2479
+ self,
2480
+ class_location: HeapLocation,
2481
+ methods: dict[object, py_ast.Code],
2482
+ ) -> py_ast.Code | None:
2483
+ for current in self._known_class_mro(class_location):
2484
+ method = methods.get(current.root)
2485
+ if method is not None:
2486
+ return method
2487
+ return None
2488
+
2489
+ def _known_class_mro(
2490
+ self,
2491
+ class_location: HeapLocation,
2492
+ ) -> tuple[HeapLocation, ...]:
2493
+ """Compute a C3 linearization for the statically known base graph."""
2494
+ memo: dict[object, tuple[HeapLocation, ...]] = {}
2495
+ active: set[object] = set()
2496
+
2497
+ def linearize(current: HeapLocation) -> tuple[HeapLocation, ...]:
2498
+ cached = memo.get(current.root)
2499
+ if cached is not None:
2500
+ return cached
2501
+ if current.root in active:
2502
+ return (current,)
2503
+ active.add(current.root)
2504
+ bases = tuple(
2505
+ dict.fromkeys(
2506
+ (
2507
+ *self._class_bases_by_root.get(current.root, ()),
2508
+ *self.state.read(
2509
+ self.heap.dynamic_attribute_location(
2510
+ current,
2511
+ "__bases__",
2512
+ ),
2513
+ fallback=(),
2514
+ ),
2515
+ )
2516
+ )
2517
+ )
2518
+ sequences = [list(linearize(base)) for base in bases]
2519
+ sequences.append(list(bases))
2520
+ merged: list[HeapLocation] = []
2521
+ while any(sequences):
2522
+ sequences = [sequence for sequence in sequences if sequence]
2523
+ candidate = next(
2524
+ (
2525
+ sequence[0]
2526
+ for sequence in sequences
2527
+ if all(
2528
+ sequence[0] not in other[1:]
2529
+ for other in sequences
2530
+ )
2531
+ ),
2532
+ None,
2533
+ )
2534
+ if candidate is None:
2535
+ # An invalid or partially unknown hierarchy would fail
2536
+ # class creation concretely. Retain every remaining base
2537
+ # as a conservative analysis fallback.
2538
+ merged.extend(
2539
+ item for sequence in sequences for item in sequence
2540
+ )
2541
+ break
2542
+ merged.append(candidate)
2543
+ for sequence in sequences:
2544
+ if sequence and sequence[0] == candidate:
2545
+ sequence.pop(0)
2546
+ active.remove(current.root)
2547
+ result = tuple(dict.fromkeys((current, *merged)))
2548
+ memo[current.root] = result
2549
+ return result
2550
+
2551
+ return linearize(class_location)
2552
+
2553
+ def _resolve_known_class_method(
2554
+ self,
2555
+ class_location: HeapLocation,
2556
+ name: str,
2557
+ ) -> py_ast.Code | None:
2558
+ for current in self._known_class_mro(class_location):
2559
+ method = self._class_methods_by_root.get(current.root, {}).get(name)
2560
+ if method is not None:
2561
+ return method
2562
+ return None
2563
+
2564
+ def _resolve_known_class_method_kind(
2565
+ self,
2566
+ class_location: HeapLocation,
2567
+ name: str,
2568
+ ) -> str:
2569
+ for current in self._known_class_mro(class_location):
2570
+ kinds = self._class_method_kinds_by_root.get(current.root, {})
2571
+ if name in kinds:
2572
+ return kinds[name]
2573
+ return "instance"
2574
+
2575
+ def _evaluate_known_protocol(
2576
+ self,
2577
+ procedure: object,
2578
+ receivers: tuple[HeapLocation, ...],
2579
+ name: str,
2580
+ actual_groups: tuple[tuple[HeapLocation, ...], ...] = (),
2581
+ ) -> tuple[HeapLocation, ...]:
2582
+ """Analyze every statically known implementation of a Python protocol."""
2583
+ base = self._capture_flow_state()
2584
+ normal_states: list[_FlowState] = [base]
2585
+ returns: list[HeapLocation] = []
2586
+ seen: set[tuple[object, object]] = set()
2587
+ for receiver in receivers:
2588
+ classes = self.state.read(
2589
+ self.heap.dynamic_attribute_location(receiver, "__class__"),
2590
+ fallback=(),
2591
+ )
2592
+ for class_location in classes:
2593
+ method = self._resolve_known_class_method(class_location, name)
2594
+ if method is None or (receiver.root, method) in seen:
2595
+ continue
2596
+ seen.add((receiver.root, method))
2597
+ self._restore_flow_state(base)
2598
+ formals = self._callee_formals(method)
2599
+ bindings: dict[int, tuple[HeapLocation, ...]] = {}
2600
+ if formals:
2601
+ bindings[0] = (receiver,)
2602
+ for index, locations in enumerate(actual_groups, start=1):
2603
+ if index >= len(formals):
2604
+ break
2605
+ bindings[index] = locations or (
2606
+ self._external_value_location(procedure),
2607
+ )
2608
+ summary = self._callee_summary(method, bindings)
2609
+ if summary.normal_state is not None:
2610
+ normal_states.append(
2611
+ _FlowState(
2612
+ summary.normal_state.copy(),
2613
+ summary.normal_environment
2614
+ or summary.environment
2615
+ or base.environment,
2616
+ dict(base.definition_defaults),
2617
+ )
2618
+ )
2619
+ if summary.raise_state is not None and self._operation_call_raises:
2620
+ self._operation_call_raises[-1].append(
2621
+ _FlowState(
2622
+ summary.raise_state.copy(),
2623
+ summary.raise_environment
2624
+ or summary.environment
2625
+ or base.environment,
2626
+ dict(base.definition_defaults),
2627
+ )
2628
+ )
2629
+ for return_index, locations in enumerate(summary.returns):
2630
+ returns.extend(locations)
2631
+ for formal_index in summary.param_returns.get(
2632
+ return_index,
2633
+ frozenset(),
2634
+ ):
2635
+ returns.extend(bindings.get(formal_index, ()))
2636
+ self._restore_flow_state(self._join_flow_states(tuple(normal_states)))
2637
+ return tuple(dict.fromkeys(returns))
2638
+
2639
+ def _evaluate_known_code(
2640
+ self,
2641
+ procedure: object,
2642
+ code: py_ast.Code,
2643
+ actual_groups: tuple[tuple[HeapLocation, ...], ...],
2644
+ ) -> tuple[HeapLocation, ...]:
2645
+ """Evaluate one already-resolved callable from explicit location groups."""
2646
+ formals = self._callee_formals(code)
2647
+ bindings = {
2648
+ index: locations
2649
+ for index, locations in enumerate(actual_groups)
2650
+ if index < len(formals)
2651
+ }
2652
+ summary = self._callee_summary(code, bindings)
2653
+ if summary.raise_state is not None and self._operation_call_raises:
2654
+ self._operation_call_raises[-1].append(
2655
+ _FlowState(
2656
+ summary.raise_state.copy(),
2657
+ summary.raise_environment
2658
+ or summary.environment
2659
+ or self.heap.snapshot_environment(),
2660
+ dict(self._definition_default_locations),
2661
+ )
2662
+ )
2663
+ if summary.normal_state is None:
2664
+ if self._operation_normal_possible:
2665
+ self._operation_normal_possible[-1] = False
2666
+ return ()
2667
+ self._apply_callee_summary(summary, procedure)
2668
+ returns: list[HeapLocation] = []
2669
+ for return_index, locations in enumerate(summary.returns):
2670
+ returns.extend(locations)
2671
+ for formal_index in summary.param_returns.get(
2672
+ return_index,
2673
+ frozenset(),
2674
+ ):
2675
+ returns.extend(bindings.get(formal_index, ()))
2676
+ return tuple(dict.fromkeys(returns))
2677
+
2678
+ def _apply_implicit_protocol_transfer(
2679
+ self,
2680
+ procedure: object,
2681
+ operation: object,
2682
+ ) -> None:
2683
+ protocol = None
2684
+ receiver_expression = None
2685
+ actual_expressions: tuple[object, ...] = ()
2686
+ if isinstance(operation, py_ast.SetAttr):
2687
+ protocol = "__setattr__"
2688
+ receiver_expression = operation.expr
2689
+ actual_expressions = (operation.name, operation.value)
2690
+ elif isinstance(operation, py_ast.SetSubscript):
2691
+ protocol = "__setitem__"
2692
+ receiver_expression = operation.expr
2693
+ actual_expressions = (operation.subscript, operation.value)
2694
+ elif isinstance(operation, py_ast.SetSlice):
2695
+ protocol = "__setitem__"
2696
+ receiver_expression = operation.expr
2697
+ actual_expressions = (
2698
+ operation.start,
2699
+ operation.stop,
2700
+ operation.step,
2701
+ operation.value,
2702
+ )
2703
+ elif isinstance(operation, py_ast.DeleteAttr):
2704
+ protocol = "__delattr__"
2705
+ receiver_expression = operation.expr
2706
+ actual_expressions = (operation.name,)
2707
+ elif isinstance(operation, py_ast.DeleteSubscript):
2708
+ protocol = "__delitem__"
2709
+ receiver_expression = operation.expr
2710
+ actual_expressions = (operation.subscript,)
2711
+ elif isinstance(operation, py_ast.DeleteSlice):
2712
+ protocol = "__delitem__"
2713
+ receiver_expression = operation.expr
2714
+ actual_expressions = (
2715
+ operation.start,
2716
+ operation.stop,
2717
+ operation.step,
2718
+ )
2719
+ if protocol is None or receiver_expression is None:
2720
+ return
2721
+ receivers = self.locations_for_expression(procedure, receiver_expression)
2722
+ actual_groups = tuple(
2723
+ self.locations_for_expression(procedure, expression)
2724
+ for expression in actual_expressions
2725
+ )
2726
+ self._evaluate_known_protocol(
2727
+ procedure,
2728
+ receivers,
2729
+ protocol,
2730
+ actual_groups,
2731
+ )
2732
+ if not isinstance(operation, (py_ast.SetAttr, py_ast.DeleteAttr)):
2733
+ return
2734
+ attribute = self.effect_builder._constant_string(operation.name)
2735
+ if attribute is None:
2736
+ return
2737
+ classes = tuple(
2738
+ dict.fromkeys(
2739
+ class_location
2740
+ for receiver in receivers
2741
+ for class_location in self.state.read(
2742
+ self.heap.dynamic_attribute_location(receiver, "__class__"),
2743
+ fallback=(),
2744
+ )
2745
+ )
2746
+ )
2747
+ descriptors = self._class_attribute_values(classes, attribute)
2748
+ descriptor_protocol = (
2749
+ "__set__" if isinstance(operation, py_ast.SetAttr) else "__delete__"
2750
+ )
2751
+ descriptor_actuals = (
2752
+ (receivers, actual_groups[-1])
2753
+ if isinstance(operation, py_ast.SetAttr)
2754
+ else (receivers,)
2755
+ )
2756
+ self._evaluate_known_protocol(
2757
+ procedure,
2758
+ descriptors,
2759
+ descriptor_protocol,
2760
+ descriptor_actuals,
2761
+ )
2762
+
2763
+ def _apply_pending_call_result(
2764
+ self,
2765
+ procedure: object,
2766
+ operation: object,
2767
+ ) -> None:
2768
+ pending = self._pending_call_results.pop(id(operation), None)
2769
+ if pending is None:
2770
+ return
2771
+ targets, slots = pending
2772
+ for index, target in enumerate(targets):
2773
+ locations = slots[index] if index < len(slots) else ()
2774
+ if locations:
2775
+ self._bind_runtime_local(procedure, target, locations)
2776
+ else:
2777
+ self._clear_runtime_local(procedure, target)
2778
+
2779
+ def _evaluate_call_operands(
2780
+ self,
2781
+ procedure: object,
2782
+ call: object,
2783
+ ) -> dict[int, tuple[HeapLocation, ...]]:
2784
+ """Evaluate a non-resolved call's operands once, in Python order."""
2785
+ evaluated: dict[int, tuple[HeapLocation, ...]] = {}
2786
+
2787
+ def evaluate(expression: object) -> None:
2788
+ if expression is None:
2789
+ return
2790
+ evaluated[id(expression)] = self.locations_for_expression(
2791
+ procedure,
2792
+ expression,
2793
+ )
2794
+
2795
+ if isinstance(call, py_ast.Call):
2796
+ evaluate(call.expr)
2797
+ elif isinstance(call, py_ast.MethodCall):
2798
+ evaluate(call.expr)
2799
+ evaluate(call.name)
2800
+ elif isinstance(call, py_ast.DirectCall):
2801
+ evaluate(call.selfarg)
2802
+ for actual in actual_argument_expressions(call):
2803
+ # DirectCall.selfarg is already the first element returned by the
2804
+ # shared helper; do not execute it twice.
2805
+ if isinstance(call, py_ast.DirectCall) and actual is call.selfarg:
2806
+ continue
2807
+ evaluate(actual)
2808
+ self._last_call_operands[id(call)] = evaluated
2809
+ return evaluated
2810
+
2811
+ def _modeled_call_return_locations(
2812
+ self,
2813
+ procedure: object,
2814
+ call: object,
2815
+ kind: str,
2816
+ operand_locations: dict[int, tuple[HeapLocation, ...]] | None = None,
2817
+ ) -> tuple[HeapLocation, ...]:
2818
+ call_name = resolve_call_name(call)
2819
+ actuals = tuple(actual_argument_expressions(call))
2820
+ receiver = getattr(call, "expr", None) if isinstance(
2821
+ call, py_ast.MethodCall
2822
+ ) else getattr(call, "selfarg", None)
2823
+
2824
+ def operand_locs(expression: object) -> tuple[HeapLocation, ...]:
2825
+ if operand_locations is not None:
2826
+ cached = operand_locations.get(id(expression))
2827
+ if cached is not None:
2828
+ return cached
2829
+ return self.locations_for_expression(procedure, expression)
2830
+
2831
+ if isinstance(call, py_ast.MethodCall):
2832
+ method_name = self.effect_builder._constant_string(call.name)
2833
+ if method_name is not None:
2834
+ for receiver_root in operand_locs(call.expr):
2835
+ registered = self._super_methods_by_root.get(
2836
+ receiver_root.root,
2837
+ {},
2838
+ ).get(method_name)
2839
+ if registered is None:
2840
+ continue
2841
+ code, bound_receivers = registered
2842
+ return self._evaluate_known_code(
2843
+ procedure,
2844
+ code,
2845
+ (
2846
+ bound_receivers,
2847
+ *tuple(
2848
+ operand_locs(actual)
2849
+ for actual in getattr(call, "args", ())
2850
+ ),
2851
+ ),
2852
+ )
2853
+
2854
+ if call_name in {"super", "builtins.super"}:
2855
+ enclosing = self._lexical_parents.get(id(procedure))
2856
+ current_class = self._class_locations_by_definition.get(id(enclosing))
2857
+ if actuals:
2858
+ explicit_class = operand_locs(actuals[0])
2859
+ current_class = explicit_class[0] if explicit_class else current_class
2860
+ receiver_locations: tuple[HeapLocation, ...] = ()
2861
+ if len(actuals) > 1:
2862
+ receiver_locations = operand_locs(actuals[1])
2863
+ elif current_class is not None:
2864
+ formals = self._callee_formals(procedure)
2865
+ if formals:
2866
+ receiver_locations = self.heap.locations_for_local(
2867
+ procedure,
2868
+ formals[0],
2869
+ )
2870
+ if current_class is not None:
2871
+ proxy = HeapLocation(
2872
+ self.heap.allocation_object(
2873
+ procedure,
2874
+ ("super", id(call), current_class.root),
2875
+ label="super proxy",
2876
+ context=self._current_context,
2877
+ )
2878
+ )
2879
+ self.state.write(
2880
+ self.heap.dynamic_attribute_location(proxy, "__super_class__"),
2881
+ (current_class,),
2882
+ UpdatePolicy.STRONG,
2883
+ )
2884
+ self.state.write(
2885
+ self.heap.dynamic_attribute_location(proxy, "__super_self__"),
2886
+ receiver_locations,
2887
+ UpdatePolicy.STRONG,
2888
+ )
2889
+ inherited: dict[
2890
+ str,
2891
+ tuple[py_ast.Code, tuple[HeapLocation, ...]],
2892
+ ] = {}
2893
+ for name, method in self._super_dispatch_by_class_root.get(
2894
+ current_class.root,
2895
+ {},
2896
+ ).items():
2897
+ inherited[name] = (method, receiver_locations)
2898
+ if not inherited and isinstance(enclosing, py_ast.ClassDef):
2899
+ for base_expression in getattr(enclosing, "bases", ()):
2900
+ if not isinstance(base_expression, py_ast.Local):
2901
+ continue
2902
+ base = self._class_definitions.get(
2903
+ (
2904
+ self._module_owner(procedure),
2905
+ base_expression.name,
2906
+ )
2907
+ )
2908
+ if base is None:
2909
+ continue
2910
+ for base_class in self._known_class_mro(base):
2911
+ for name, method in self._class_methods_by_root.get(
2912
+ base_class.root,
2913
+ {},
2914
+ ).items():
2915
+ inherited.setdefault(
2916
+ name,
2917
+ (method, receiver_locations),
2918
+ )
2919
+ self._super_methods_by_root[proxy.root] = inherited
2920
+ return (proxy,)
2921
+ return (self._external_value_location(procedure),)
2922
+ if call_name in {"type", "builtins.type"}:
2923
+ operand_types = tuple(
2924
+ dict.fromkeys(
2925
+ class_location
2926
+ for actual in actuals[:1]
2927
+ for location in operand_locs(actual)
2928
+ for class_location in self.state.read(
2929
+ self.heap.dynamic_attribute_location(
2930
+ location,
2931
+ "__class__",
2932
+ ),
2933
+ fallback=(),
2934
+ )
2935
+ )
2936
+ )
2937
+ if operand_types:
2938
+ return operand_types
2939
+ type_tokens = tuple(
2940
+ dict.fromkeys(
2941
+ token
2942
+ for actual in actuals[:1]
2943
+ for location in operand_locs(actual)
2944
+ for token in (self._known_builtin_type_token(location),)
2945
+ if token is not None
2946
+ )
2947
+ )
2948
+ if type_tokens:
2949
+ return tuple(
2950
+ HeapLocation(
2951
+ self.heap.external_object(
2952
+ ("builtin-type", token),
2953
+ label=f"type {token}",
2954
+ type_hint="type",
2955
+ stable_identity=True,
2956
+ )
2957
+ )
2958
+ for token in type_tokens
2959
+ )
2960
+ return (
2961
+ HeapLocation(
2962
+ self.heap.unknown_object(
2963
+ ("type-result", id(call), self._evaluation_epoch),
2964
+ label="unknown type result",
2965
+ type_hint="type",
2966
+ )
2967
+ ),
2968
+ )
2969
+ if call_name == "decimal.getcontext":
2970
+ return (
2971
+ HeapLocation(
2972
+ self.heap.external_object(
2973
+ ("decimal-context",),
2974
+ label="decimal context",
2975
+ stable_identity=True,
2976
+ )
2977
+ ),
2978
+ )
2979
+ if call_name == "logging.getLogger":
2980
+ logger_name = (
2981
+ self.effect_builder._constant_string(actuals[0])
2982
+ if actuals
2983
+ else "root"
2984
+ )
2985
+ return (
2986
+ HeapLocation(
2987
+ self.heap.external_object(
2988
+ ("logging.getLogger", logger_name),
2989
+ label=f"logger {logger_name or '<dynamic>'}",
2990
+ stable_identity=True,
2991
+ )
2992
+ ),
2993
+ )
2994
+ if call_name == "importlib.import_module" and actuals:
2995
+ module_name = self.effect_builder._constant_string(actuals[0])
2996
+ if module_name is not None:
2997
+ return (
2998
+ HeapLocation(
2999
+ self.heap.module_object(module_name, label=module_name)
3000
+ ),
3001
+ )
3002
+
3003
+ if call_name in {
3004
+ "next",
3005
+ "builtins.next",
3006
+ "__next__",
3007
+ "anext",
3008
+ "builtins.anext",
3009
+ "__anext__",
3010
+ "send",
3011
+ "throw",
3012
+ }:
3013
+ iterable = receiver if receiver is not None else (actuals[0] if actuals else None)
3014
+ if iterable is not None:
3015
+ roots = operand_locs(iterable)
3016
+ values: list[HeapLocation] = list(
3017
+ self._resume_deferred_activations(
3018
+ procedure,
3019
+ roots,
3020
+ use_yields=True,
3021
+ sent_values=(
3022
+ operand_locs(
3023
+ actuals[0]
3024
+ if receiver is not None
3025
+ else actuals[1]
3026
+ )
3027
+ if call_name == "send"
3028
+ and (
3029
+ (receiver is not None and actuals)
3030
+ or (receiver is None and len(actuals) > 1)
3031
+ )
3032
+ else ()
3033
+ ),
3034
+ )
3035
+ )
3036
+ for root in roots:
3037
+ wildcard = self.heap.dynamic_subscript_location(
3038
+ root,
3039
+ DYNAMIC_SUBSCRIPT_WILDCARD,
3040
+ )
3041
+ values.extend(self.state.read_contained(wildcard))
3042
+ if values:
3043
+ if len(actuals) >= 2:
3044
+ values.extend(operand_locs(actuals[1]))
3045
+ return tuple(dict.fromkeys(values))
3046
+ if len(actuals) >= 2:
3047
+ default = operand_locs(actuals[1])
3048
+ if default:
3049
+ return default
3050
+ return (self._external_value_location(procedure),)
3051
+ if call_name == "close" and receiver is not None:
3052
+ roots = operand_locs(receiver)
3053
+ self._resume_deferred_activations(
3054
+ procedure,
3055
+ roots,
3056
+ use_yields=False,
3057
+ )
3058
+ return ()
3059
+ if call_name in {"max", "builtins.max", "min", "builtins.min"}:
3060
+ positional = tuple(getattr(call, "args", ()))
3061
+ if len(positional) == 1:
3062
+ values = list(
3063
+ self._contained_values(operand_locs(positional[0]))
3064
+ )
3065
+ for keyword in getattr(call, "kwds", ()):
3066
+ if (
3067
+ isinstance(keyword, tuple)
3068
+ and len(keyword) == 2
3069
+ and keyword[0] == "default"
3070
+ ):
3071
+ values.extend(operand_locs(keyword[1]))
3072
+ return tuple(dict.fromkeys(values)) or (
3073
+ self._external_value_location(procedure),
3074
+ )
3075
+ if call_name in {"random.choice"} and actuals:
3076
+ roots = operand_locs(actuals[0])
3077
+ choice_values = self._contained_values(roots)
3078
+ return choice_values or (self._external_value_location(procedure),)
3079
+ if call_name in {"iter", "builtins.iter", "__iter__"}:
3080
+ iterable = receiver if receiver is not None else (actuals[0] if actuals else None)
3081
+ if iterable is not None:
3082
+ roots = operand_locs(iterable)
3083
+ iterator = HeapLocation(
3084
+ self.effect_builder.call_return_object(procedure, call)
3085
+ )
3086
+ self._copy_locations(roots, (iterator,))
3087
+ return tuple(
3088
+ dict.fromkeys(
3089
+ (
3090
+ *roots,
3091
+ iterator,
3092
+ self._external_value_location(procedure),
3093
+ )
3094
+ )
3095
+ )
3096
+ if kind == CALL_RETURN_SELF and receiver is not None:
3097
+ return operand_locs(receiver)
3098
+ if kind == CALL_RETURN_ARG:
3099
+ model = self.intrinsics.function_model(call_name)
3100
+ return_index = model.return_arg_index if model is not None else -1
3101
+ expressions = (
3102
+ actuals
3103
+ if return_index is None or return_index < 0
3104
+ else actuals[return_index:return_index + 1]
3105
+ )
3106
+ return tuple(
3107
+ dict.fromkeys(
3108
+ location
3109
+ for expression in expressions
3110
+ for location in operand_locs(expression)
3111
+ )
3112
+ )
3113
+
3114
+ if call_name in {"getattr", "builtins.getattr"} and len(actuals) >= 2:
3115
+ attribute = self.effect_builder._constant_string(actuals[1])
3116
+ attributes = (attribute,) if attribute is not None else ("*",)
3117
+ target_locations = self.heap.dynamic_attribute_locations(
3118
+ operand_locs(actuals[0]),
3119
+ attributes,
3120
+ )
3121
+ values = list(self._read_heap_locations(target_locations))
3122
+ if len(actuals) >= 3:
3123
+ values.extend(operand_locs(actuals[2]))
3124
+ return tuple(dict.fromkeys(values))
3125
+
3126
+ property_names = {
3127
+ "get",
3128
+ "setdefault",
3129
+ "pop",
3130
+ "dict.get",
3131
+ "dict.setdefault",
3132
+ "dict.pop",
3133
+ "list.pop",
3134
+ "set.pop",
3135
+ "popleft",
3136
+ "popitem",
3137
+ "popfirst",
3138
+ "get_and_del",
3139
+ "interpreter_getitem",
3140
+ }
3141
+ if call_name in property_names:
3142
+ container_expr = receiver
3143
+ args = actuals
3144
+ if container_expr is None and actuals:
3145
+ container_expr = actuals[0]
3146
+ args = actuals[1:]
3147
+ if container_expr is not None:
3148
+ roots = operand_locs(container_expr)
3149
+ if args:
3150
+ subscript = self.effect_builder._constant_subscript(args[0])
3151
+ target_locations = self.heap.dynamic_subscript_locations(
3152
+ roots,
3153
+ (
3154
+ (subscript, DYNAMIC_SUBSCRIPT_WILDCARD)
3155
+ if subscript is not None
3156
+ else (DYNAMIC_SUBSCRIPT_WILDCARD,)
3157
+ ),
3158
+ )
3159
+ else:
3160
+ target_locations = self.heap.dynamic_subscript_locations(
3161
+ roots,
3162
+ (DYNAMIC_SUBSCRIPT_WILDCARD,),
3163
+ )
3164
+ if args and subscript is None:
3165
+ values = [
3166
+ value
3167
+ for root in roots
3168
+ for value in self.state.read_contained(
3169
+ self.heap.dynamic_subscript_location(
3170
+ root,
3171
+ DYNAMIC_SUBSCRIPT_WILDCARD,
3172
+ )
3173
+ )
3174
+ ]
3175
+ elif not args:
3176
+ values = [
3177
+ value
3178
+ for root in roots
3179
+ for value in self.state.read_contained(
3180
+ self.heap.dynamic_subscript_location(
3181
+ root,
3182
+ DYNAMIC_SUBSCRIPT_WILDCARD,
3183
+ )
3184
+ )
3185
+ ]
3186
+ else:
3187
+ values = list(self._read_heap_locations(target_locations))
3188
+ if call_name in {"get", "dict.get", "pop", "dict.pop"} and len(args) >= 2:
3189
+ values.extend(operand_locs(args[1]))
3190
+ if call_name in {"setdefault", "dict.setdefault"} and len(args) >= 2:
3191
+ values.extend(operand_locs(args[1]))
3192
+ return tuple(dict.fromkeys(values))
3193
+
3194
+ return ()
3195
+
3196
+ @staticmethod
3197
+ def _known_builtin_type_token(location: HeapLocation) -> str | None:
3198
+ if location.root.type_hint:
3199
+ return location.root.type_hint
3200
+ label = location.root.label
3201
+ return {
3202
+ "list literal": "list",
3203
+ "tuple literal": "tuple",
3204
+ "set literal": "set",
3205
+ "map literal": "dict",
3206
+ "slice": "slice",
3207
+ }.get(label)
3208
+
3209
+ def _resume_deferred_activations(
3210
+ self,
3211
+ caller: object,
3212
+ roots: tuple[HeapLocation, ...],
3213
+ *,
3214
+ use_yields: bool,
3215
+ sent_values: tuple[HeapLocation, ...] = (),
3216
+ ) -> tuple[HeapLocation, ...]:
3217
+ values: list[HeapLocation] = []
3218
+ for root in roots:
3219
+ activation = self._deferred_activations.get(root.root)
3220
+ if activation is None:
3221
+ continue
3222
+ resume_base = self.state.copy()
3223
+ previous_context = self._current_context
3224
+ self._current_context = (
3225
+ *previous_context,
3226
+ "resume",
3227
+ root.root.key,
3228
+ )
3229
+ try:
3230
+ if activation.summary is None or sent_values or use_yields:
3231
+ external_environment = self.heap.snapshot_environment()
3232
+ if activation.frame_environment is not None:
3233
+ self.heap.restore_environment(
3234
+ self.heap.join_environments(
3235
+ (
3236
+ external_environment,
3237
+ activation.frame_environment,
3238
+ )
3239
+ )
3240
+ )
3241
+ if sent_values:
3242
+ self._resume_input_stack.append(
3243
+ (max(activation.resume_index - 1, 0), sent_values)
3244
+ )
3245
+ try:
3246
+ activation.summary = self._callee_summary(
3247
+ activation.callee,
3248
+ activation.actual_bindings,
3249
+ )
3250
+ finally:
3251
+ if sent_values:
3252
+ self._resume_input_stack.pop()
3253
+ self.heap.restore_environment(external_environment)
3254
+ summary = activation.summary
3255
+ finally:
3256
+ self._current_context = previous_context
3257
+ if summary.raise_state is not None and self._operation_call_raises:
3258
+ raised_state = summary.raise_state.copy()
3259
+ if summary.raises:
3260
+ raised_state.set_raised(caller, summary.raises)
3261
+ self._operation_call_raises[-1].append(
3262
+ _FlowState(
3263
+ raised_state,
3264
+ summary.raise_environment
3265
+ or summary.environment
3266
+ or self.heap.snapshot_environment(),
3267
+ dict(self._definition_default_locations),
3268
+ )
3269
+ )
3270
+ if use_yields:
3271
+ if activation.resume_index < len(summary.yield_steps):
3272
+ resume_index = activation.resume_index
3273
+ step_state, step_environment, yielded = summary.yield_steps[
3274
+ resume_index
3275
+ ]
3276
+ activation.resume_index += 1
3277
+ activation.frame_environment = step_environment
3278
+ caller_environment = self.heap.snapshot_environment()
3279
+ caller_environment.object_labels.update(
3280
+ step_environment.object_labels
3281
+ )
3282
+ caller_environment.escaped_objects.update(
3283
+ step_environment.escaped_objects
3284
+ )
3285
+ previous_frontier = (
3286
+ resume_base
3287
+ if resume_index == 0
3288
+ else summary.yield_steps[resume_index - 1][0]
3289
+ )
3290
+ self._apply_continuation_frontier(
3291
+ previous_frontier,
3292
+ step_state,
3293
+ )
3294
+ self.heap.restore_environment(caller_environment)
3295
+ values.extend(yielded)
3296
+ else:
3297
+ if summary.normal_state is None and self._operation_normal_possible:
3298
+ self._operation_normal_possible[-1] = False
3299
+ self._apply_callee_summary(
3300
+ summary,
3301
+ caller,
3302
+ preserve_current=activation.resume_index > 0,
3303
+ )
3304
+ else:
3305
+ if activation.resume_index == 0:
3306
+ activation.resume_index = 1
3307
+ if summary.normal_state is None and self._operation_normal_possible:
3308
+ self._operation_normal_possible[-1] = False
3309
+ self._apply_callee_summary(summary, caller)
3310
+ for slot in summary.returns:
3311
+ values.extend(slot)
3312
+ else:
3313
+ # Re-awaiting a consumed coroutine raises at runtime.
3314
+ values.append(self._external_value_location(caller))
3315
+ return tuple(dict.fromkeys(values))
3316
+
3317
+ def _apply_continuation_frontier(
3318
+ self,
3319
+ previous: HeapState,
3320
+ current: HeapState,
3321
+ ) -> None:
3322
+ """Rebase the delta between two suspension frontiers onto the caller.
3323
+
3324
+ Summaries are recomputed against the heap visible at each resume. By
3325
+ applying only facts changed after the preceding yield, effects in the
3326
+ already-consumed generator prefix are not replayed into the caller.
3327
+ """
3328
+ rebased = self.state.copy()
3329
+ for attribute in ("values", "contaminants", "versions"):
3330
+ before_map = getattr(previous, attribute)
3331
+ after_map = getattr(current, attribute)
3332
+ target_map = getattr(rebased, attribute)
3333
+ for location in set(before_map) | set(after_map):
3334
+ if before_map.get(location) == after_map.get(location):
3335
+ continue
3336
+ if location in after_map:
3337
+ target_map[location] = after_map[location]
3338
+ else:
3339
+ target_map.pop(location, None)
3340
+ changed_absence = previous.absent ^ current.absent
3341
+ for location in changed_absence:
3342
+ if location in current.absent:
3343
+ rebased.absent.add(location)
3344
+ else:
3345
+ rebased.absent.discard(location)
3346
+ changed_scalars = previous.scalar_present ^ current.scalar_present
3347
+ for location in changed_scalars:
3348
+ if location in current.scalar_present:
3349
+ rebased.scalar_present.add(location)
3350
+ rebased.absent.discard(location)
3351
+ else:
3352
+ rebased.scalar_present.discard(location)
3353
+ rebased.complete_roots.update(
3354
+ current.complete_roots - previous.complete_roots
3355
+ )
3356
+ rebased.escaped.update(current.escaped - previous.escaped)
3357
+ self.state = rebased
3358
+
3359
+ @staticmethod
3360
+ def _context_token(node: object) -> object:
3361
+ origin = getattr(getattr(node, "annotation", None), "origin", ()) or ()
3362
+ meaningful_origin = tuple(item for item in origin if item is not None)
3363
+ if meaningful_origin:
3364
+ return (
3365
+ type(node).__name__,
3366
+ tuple(repr(item) for item in meaningful_origin),
3367
+ getattr(node, "name", None),
3368
+ )
3369
+ line = getattr(node, "line", None)
3370
+ column = getattr(node, "column", None)
3371
+ if line is not None or column is not None:
3372
+ return type(node).__name__, line, column
3373
+ return type(node).__name__, id(node)
3374
+
3375
+ def _copy_call_result_contents(
3376
+ self,
3377
+ procedure: object,
3378
+ target: py_ast.Local | None,
3379
+ call: object,
3380
+ target_locations: tuple[HeapLocation, ...] | None = None,
3381
+ ) -> None:
3382
+ if target_locations is None:
3383
+ if target is None:
3384
+ return
3385
+ target_locations = self.heap.locations_for_local(procedure, target)
3386
+ actuals = tuple(actual_argument_expressions(call))
3387
+ call_name = resolve_call_name(call)
3388
+ source_exprs: tuple[object, ...]
3389
+ retain_all_arguments = {
3390
+ "functools.partial",
3391
+ "functools.lru_cache",
3392
+ "functools.cached_property",
3393
+ "functools.singledispatch",
3394
+ "functools.wraps",
3395
+ "collections.ChainMap",
3396
+ "collections.defaultdict",
3397
+ "collections.Counter",
3398
+ "collections.OrderedDict",
3399
+ "collections.deque",
3400
+ "collections.UserDict",
3401
+ "collections.UserList",
3402
+ "collections.UserString",
3403
+ "itertools.chain",
3404
+ "itertools.product",
3405
+ "itertools.compress",
3406
+ "itertools.starmap",
3407
+ "zip",
3408
+ "builtins.zip",
3409
+ "interpreter_build_map",
3410
+ "interpreter_merge_varargs",
3411
+ "interpreter_merge_kwargs",
3412
+ }
3413
+ if isinstance(call, py_ast.MethodCall):
3414
+ source_exprs = (call.expr,)
3415
+ elif call_name in retain_all_arguments:
3416
+ source_exprs = actuals
3417
+ elif call_name in {"map", "builtins.map", "filter", "builtins.filter"}:
3418
+ source_exprs = actuals[1:]
3419
+ else:
3420
+ source_exprs = actuals[:1]
3421
+ if not source_exprs:
3422
+ return
3423
+ evaluated = self._last_call_operands.get(id(call), {})
3424
+ source_locations = tuple(
3425
+ dict.fromkeys(
3426
+ location
3427
+ for source_expr in source_exprs
3428
+ for location in (
3429
+ evaluated.get(id(source_expr))
3430
+ if id(source_expr) in evaluated
3431
+ else self.locations_for_expression(procedure, source_expr)
3432
+ )
3433
+ )
3434
+ )
3435
+ self._copy_locations(source_locations, target_locations)
3436
+ if source_locations:
3437
+ for target_location in target_locations:
3438
+ self.state.write(
3439
+ self.heap.dynamic_attribute_location(
3440
+ target_location,
3441
+ "__source__",
3442
+ ),
3443
+ source_locations,
3444
+ UpdatePolicy.WEAK,
3445
+ )
3446
+ contained = list(self._contained_values(source_locations))
3447
+ if call_name in {"keys", "dict.keys"}:
3448
+ contained.extend(
3449
+ value
3450
+ for source in source_locations
3451
+ for value in self.state.read(
3452
+ self.heap.dynamic_attribute_location(source, "__keys__"),
3453
+ fallback=(),
3454
+ )
3455
+ )
3456
+ elif call_name in {"items", "dict.items"}:
3457
+ contained.extend(
3458
+ value
3459
+ for source in source_locations
3460
+ for value in self.state.read(
3461
+ self.heap.dynamic_attribute_location(source, "__keys__"),
3462
+ fallback=(),
3463
+ )
3464
+ )
3465
+ if call_name in {"map", "builtins.map"}:
3466
+ contained.append(self._external_value_location(procedure))
3467
+ if contained:
3468
+ for target_location in target_locations:
3469
+ self.state.write(
3470
+ self.heap.dynamic_subscript_location(
3471
+ target_location,
3472
+ DYNAMIC_SUBSCRIPT_WILDCARD,
3473
+ ),
3474
+ tuple(dict.fromkeys(contained)),
3475
+ UpdatePolicy.WEAK,
3476
+ )
3477
+
3478
+ def _copy_locations(
3479
+ self,
3480
+ source_locations: tuple[HeapLocation, ...],
3481
+ target_locations: tuple[HeapLocation, ...],
3482
+ ) -> None:
3483
+ stored_items = (
3484
+ *self.state.values.items(),
3485
+ *self.state.contaminants.items(),
3486
+ )
3487
+ for target_location in target_locations:
3488
+ for source_location in source_locations:
3489
+ for stored, values in stored_items:
3490
+ if stored.root != source_location.root or not stored.selectors:
3491
+ continue
3492
+ copied = HeapLocation(target_location.root, stored.selectors)
3493
+ self.state.write(copied, values, UpdatePolicy.WEAK)