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,1632 @@
1
+ """Heap abstraction engine — canonicalization, alias tracking, and update policies.
2
+
3
+ :class:`HeapAbstraction` is the main workhorse: it canonicalizes raw
4
+ annotation-provided storage identities into :class:`HeapLocation` roots,
5
+ tracks alias equivalence classes via union-find, gates strong/weak
6
+ updates via reference counting, and tracks escape status.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from .points_to_graph import PointsToGraph
16
+
17
+ from .model import (
18
+ AllocationSensitivity,
19
+ ContainerSensitivity,
20
+ FieldSensitivity,
21
+ HeapEscapeState,
22
+ HeapLocation,
23
+ HeapObject,
24
+ HeapObjectCardinality,
25
+ HeapObjectFreshness,
26
+ HeapObjectIdentity,
27
+ HeapObjectKind,
28
+ HeapPolicy,
29
+ HeapSelector,
30
+ HeapWrite,
31
+ RawStorageProvider,
32
+ UpdatePolicy,
33
+ )
34
+
35
+
36
+ @dataclass
37
+ class HeapEnvironment:
38
+ """Flow-sensitive snapshot of mutable heap binding metadata.
39
+
40
+ Canonical object caches are deliberately not included: they are stable
41
+ identities shared by every path. Bindings, escape state, alias metadata,
42
+ and reference counts do vary by path and therefore must be restored and
43
+ joined with the value-level :class:`HeapState`.
44
+ """
45
+
46
+ storage_overrides: dict[tuple[int, int], tuple[object, ...]]
47
+ allocation_sites: dict[tuple[int, int], int]
48
+ site_storage: dict[int, tuple[object, ...]]
49
+ object_labels: dict[HeapObject, str]
50
+ local_names: dict[tuple[int, int], str]
51
+ escaped_objects: set[HeapObject]
52
+ equiv_parent: dict[int, int]
53
+ equiv_members: dict[int, set[int]]
54
+ site_ref_counts: dict[int, int]
55
+
56
+
57
+ class HeapAbstraction:
58
+ """Location-based heap abstraction shared by concrete IFDS clients.
59
+
60
+ Locals initially map to annotation-provided raw storage identities, which
61
+ are canonicalized into ``HeapLocation`` roots before they enter IFDS facts.
62
+ Direct local aliases share the same raw-storage tuple and allocation-site
63
+ id; strong local updates break that sharing and allocate a fresh site.
64
+ Attribute and subscript accesses extend canonical roots with structural
65
+ selectors.
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ raw_storage_provider: RawStorageProvider,
71
+ *,
72
+ policy: HeapPolicy | None = None,
73
+ storage_overrides: dict[tuple[int, int], tuple[object, ...]] | None = None,
74
+ allocation_sites: dict[tuple[int, int], int] | None = None,
75
+ site_storage: dict[int, tuple[object, ...]] | None = None,
76
+ next_site: int = 0,
77
+ ) -> None:
78
+ self.policy = policy or HeapPolicy()
79
+ self._raw_storage_provider = raw_storage_provider
80
+ self.storage_overrides = (
81
+ storage_overrides if storage_overrides is not None else {}
82
+ )
83
+ self.allocation_sites = allocation_sites if allocation_sites is not None else {}
84
+ self.site_storage = site_storage if site_storage is not None else {}
85
+ self._raw_locations: dict[int, HeapLocation] = {}
86
+ self._objects: dict[tuple[object, ...], HeapObject] = {}
87
+ self._object_labels: dict[HeapObject, str] = {}
88
+ self._local_names: dict[tuple[int, int], str] = {}
89
+ self._escaped_objects: set[HeapObject] = set()
90
+ self._equiv_parent: dict[int, int] = {}
91
+ self._equiv_members: dict[int, set[int]] = {}
92
+ self._site_ref_counts: dict[int, int] = {}
93
+ self.next_site = next_site
94
+
95
+ def locations_for_local(
96
+ self,
97
+ procedure: object,
98
+ local: object,
99
+ ) -> tuple[HeapLocation, ...]:
100
+ """Return canonical locations currently bound to a local."""
101
+ return tuple(
102
+ self.location_for_raw(raw)
103
+ for raw in self._raw_storage_for_local(procedure, local)
104
+ )
105
+
106
+ def location_for_raw(self, raw: object) -> HeapLocation:
107
+ """Canonicalize raw annotation/storage identity into a heap location."""
108
+ if isinstance(raw, HeapLocation):
109
+ return raw
110
+ if isinstance(raw, HeapObject):
111
+ return HeapLocation(raw)
112
+
113
+ raw_identity = self._canonical_raw(raw)
114
+ key = id(raw_identity)
115
+ location = self._raw_locations.get(key)
116
+ if location is None:
117
+ location = HeapLocation(self._object_for_raw(raw_identity))
118
+ self._raw_locations[key] = location
119
+ return location
120
+
121
+ def write_for_location(
122
+ self,
123
+ location: HeapLocation,
124
+ *,
125
+ policy: UpdatePolicy | None = None,
126
+ ) -> HeapWrite:
127
+ """Return a semantic write descriptor for a canonical location."""
128
+ return HeapWrite(
129
+ location=location,
130
+ policy=policy or self.update_policy_for_location(location),
131
+ )
132
+
133
+ def update_policy_for_location(self, location: HeapLocation) -> UpdatePolicy:
134
+ """Default IFDS update policy gated by equivalence-class reference counts.
135
+
136
+ Root locations (locals, globals, cells) can be strongly updated when
137
+ the equivalence class they belong to has a single reference (ref count
138
+ ≤ 1). A precise nested field/element of a singleton abstract object
139
+ can be strongly updated regardless of how many access paths reach that
140
+ object: the concrete write overwrites the same field for all aliases.
141
+ Operation-level transfer separately downgrades writes through multiple
142
+ possible receiver roots.
143
+
144
+ Immutable types skip the reference-count check entirely for root
145
+ locations: reassignment always produces a fresh bind, so strong
146
+ updates are always safe.
147
+ """
148
+ if self._is_immutable_type(location.root):
149
+ return UpdatePolicy.STRONG if not location.is_nested() else UpdatePolicy.WEAK
150
+ if not location.root.is_singleton():
151
+ return UpdatePolicy.WEAK
152
+ if not location.is_precise():
153
+ return UpdatePolicy.WEAK
154
+ if location.is_nested() and self.policy.allow_strong_nested_fresh:
155
+ return UpdatePolicy.STRONG
156
+ if location.is_nested():
157
+ return UpdatePolicy.WEAK
158
+ ref_count = self.reference_count_for_root(location.root)
159
+ return UpdatePolicy.STRONG if ref_count <= 1 else UpdatePolicy.WEAK
160
+
161
+ def snapshot_environment(self) -> HeapEnvironment:
162
+ """Capture all path-dependent binding and escape metadata."""
163
+ return HeapEnvironment(
164
+ storage_overrides=dict(self.storage_overrides),
165
+ allocation_sites=dict(self.allocation_sites),
166
+ site_storage=dict(self.site_storage),
167
+ object_labels=dict(self._object_labels),
168
+ local_names=dict(self._local_names),
169
+ escaped_objects=set(self._escaped_objects),
170
+ equiv_parent=dict(self._equiv_parent),
171
+ equiv_members={
172
+ root: set(members) for root, members in self._equiv_members.items()
173
+ },
174
+ site_ref_counts=dict(self._site_ref_counts),
175
+ )
176
+
177
+ def restore_environment(self, environment: HeapEnvironment) -> None:
178
+ """Restore a previously captured flow-sensitive environment."""
179
+ self.storage_overrides = dict(environment.storage_overrides)
180
+ self.allocation_sites = dict(environment.allocation_sites)
181
+ self.site_storage = dict(environment.site_storage)
182
+ self._object_labels = dict(environment.object_labels)
183
+ self._local_names = dict(environment.local_names)
184
+ self._escaped_objects = set(environment.escaped_objects)
185
+ self._equiv_parent = dict(environment.equiv_parent)
186
+ self._equiv_members = {
187
+ root: set(members) for root, members in environment.equiv_members.items()
188
+ }
189
+ self._site_ref_counts = dict(environment.site_ref_counts)
190
+
191
+ def join_environments(
192
+ self,
193
+ environments: tuple[HeapEnvironment, ...],
194
+ ) -> HeapEnvironment:
195
+ """Join path environments by unioning each local's possible storage.
196
+
197
+ Allocation-site equivalence is a must-alias relation, so branch-local
198
+ unifications are not unioned. Joined bindings instead retain every
199
+ possible root and receive an independent site whenever the incoming
200
+ bindings differ. Reference counts are subsequently derived from live
201
+ bindings rather than historical union-find membership.
202
+ """
203
+ if not environments:
204
+ return self.snapshot_environment()
205
+
206
+ local_names: dict[tuple[int, int], str] = {}
207
+ object_labels: dict[HeapObject, str] = {}
208
+ escaped_objects: set[HeapObject] = set()
209
+ keys: set[tuple[int, int]] = set()
210
+ for environment in environments:
211
+ local_names.update(environment.local_names)
212
+ object_labels.update(environment.object_labels)
213
+ escaped_objects.update(environment.escaped_objects)
214
+ keys.update(environment.storage_overrides)
215
+ keys.update(environment.allocation_sites)
216
+
217
+ storage_overrides: dict[tuple[int, int], tuple[object, ...]] = {}
218
+ allocation_sites: dict[tuple[int, int], int] = {}
219
+ site_storage: dict[int, tuple[object, ...]] = {}
220
+ site_ref_counts: dict[int, int] = {}
221
+
222
+ for key in sorted(keys):
223
+ incoming = tuple(
224
+ self._environment_storage(environment, key)
225
+ for environment in environments
226
+ )
227
+ joined = self._join_storage(incoming)
228
+ if not joined:
229
+ continue
230
+
231
+ storage_overrides[key] = joined
232
+ sites = tuple(
233
+ environment.allocation_sites.get(key)
234
+ for environment in environments
235
+ )
236
+ concrete_sites = tuple(site for site in sites if site is not None)
237
+ same_storage = all(
238
+ self._canonicalize_storage(storage) == joined
239
+ for storage in incoming
240
+ if storage
241
+ )
242
+ if (
243
+ concrete_sites
244
+ and len(concrete_sites) == len(environments)
245
+ and same_storage
246
+ ):
247
+ site = concrete_sites[0]
248
+ else:
249
+ site = self.next_site
250
+ self.next_site += 1
251
+ allocation_sites[key] = site
252
+ site_storage[site] = joined
253
+ site_ref_counts[site] = site_ref_counts.get(site, 0) + 1
254
+
255
+ equiv_parent = {site: site for site in site_storage}
256
+ equiv_members = {site: {site} for site in site_storage}
257
+ return HeapEnvironment(
258
+ storage_overrides=storage_overrides,
259
+ allocation_sites=allocation_sites,
260
+ site_storage=site_storage,
261
+ object_labels=object_labels,
262
+ local_names=local_names,
263
+ escaped_objects=escaped_objects,
264
+ equiv_parent=equiv_parent,
265
+ equiv_members=equiv_members,
266
+ site_ref_counts=site_ref_counts,
267
+ )
268
+
269
+ @staticmethod
270
+ def _environment_storage(
271
+ environment: HeapEnvironment,
272
+ key: tuple[int, int],
273
+ ) -> tuple[object, ...]:
274
+ override = environment.storage_overrides.get(key)
275
+ if override is not None:
276
+ return override
277
+ site = environment.allocation_sites.get(key)
278
+ if site is None:
279
+ return ()
280
+ return environment.site_storage.get(site, ())
281
+
282
+ def _canonicalize_storage(
283
+ self,
284
+ storage: tuple[object, ...],
285
+ ) -> tuple[HeapLocation, ...]:
286
+ return tuple(
287
+ dict.fromkeys(self.location_for_raw(raw) for raw in storage)
288
+ )
289
+
290
+ def _join_storage(
291
+ self,
292
+ storages: tuple[tuple[object, ...], ...],
293
+ ) -> tuple[HeapLocation, ...]:
294
+ return tuple(
295
+ dict.fromkeys(
296
+ location
297
+ for storage in storages
298
+ for location in self._canonicalize_storage(storage)
299
+ )
300
+ )
301
+
302
+ def reference_count_for_root(self, root: HeapObject) -> int:
303
+ """Count live logical local bindings that may reference *root*."""
304
+ logical_bindings: set[tuple[object, ...]] = set()
305
+ keys = set(self.storage_overrides) | set(self.allocation_sites)
306
+ for key in keys:
307
+ storage = self.storage_overrides.get(key)
308
+ if storage is None:
309
+ site = self.allocation_sites.get(key)
310
+ storage = self.site_storage.get(site, ()) if site is not None else ()
311
+ if not any(self.location_for_raw(raw).root == root for raw in storage):
312
+ continue
313
+ name = self._local_names.get(key)
314
+ logical_bindings.add((key[0], name) if name is not None else key)
315
+ return len(logical_bindings)
316
+
317
+ def _is_immutable_type(self, root: HeapObject) -> bool:
318
+ if root.type_hint is not None and root.type_hint in self.policy.immutable_type_hints:
319
+ return True
320
+ return False
321
+
322
+ def _root_allocation_site(self, root: HeapObject) -> int | None:
323
+ """Get the allocation-site id for a HeapObject root, if one exists."""
324
+ for site, storage in self.site_storage.items():
325
+ for raw in storage:
326
+ if self.location_for_raw(raw).root == root:
327
+ return site
328
+ return None
329
+
330
+ # ── alias equivalence-class tracking ────────────────────────────────
331
+
332
+ def _find(self, site: int) -> int:
333
+ """Union-find: find canonical root of an allocation site."""
334
+ parent = self._equiv_parent.get(site)
335
+ if parent is None:
336
+ self._equiv_parent[site] = site
337
+ self._equiv_members.setdefault(site, set()).add(site)
338
+ return site
339
+ if parent != site:
340
+ root = self._find(parent)
341
+ if root != parent:
342
+ self._equiv_parent[site] = root
343
+ return self._equiv_parent[site]
344
+
345
+ def _unify(self, site_a: int, site_b: int) -> int:
346
+ """Union-find: merge two equivalence classes. Returns the new canonical root."""
347
+ if site_a == site_b:
348
+ return site_a
349
+ root_a = self._find(site_a)
350
+ root_b = self._find(site_b)
351
+ if root_a == root_b:
352
+ return root_a
353
+ members_a = self._equiv_members.get(root_a, {root_a})
354
+ members_b = self._equiv_members.get(root_b, {root_b})
355
+ refs_a = self._site_ref_counts.get(root_a, 0)
356
+ refs_b = self._site_ref_counts.get(root_b, 0)
357
+ if len(members_a) >= len(members_b):
358
+ self._equiv_parent[root_b] = root_a
359
+ merged = members_a | members_b
360
+ self._equiv_members[root_a] = merged
361
+ self._equiv_members.pop(root_b, None)
362
+ self._site_ref_counts[root_a] = refs_a + refs_b
363
+ self._site_ref_counts.pop(root_b, None)
364
+ return root_a
365
+ self._equiv_parent[root_a] = root_b
366
+ merged = members_b | members_a
367
+ self._equiv_members[root_b] = merged
368
+ self._equiv_members.pop(root_a, None)
369
+ self._site_ref_counts[root_b] = refs_b + refs_a
370
+ self._site_ref_counts.pop(root_a, None)
371
+ return root_b
372
+
373
+ def _is_site_in_equiv_class(self, site: int, canonical_root: int) -> bool:
374
+ return self._find(site) == self._find(canonical_root)
375
+
376
+ def _equiv_class_sites(self, root_site: int) -> frozenset[int]:
377
+ canonical = self._find(root_site)
378
+ return frozenset(self._equiv_members.get(canonical, {canonical}))
379
+
380
+ def _equiv_class_ref_count(self, root_site: int) -> int:
381
+ canonical = self._find(root_site)
382
+ return self._site_ref_counts.get(canonical, 0)
383
+
384
+ def _incr_site_ref(self, site: int) -> None:
385
+ canonical = self._find(site)
386
+ self._site_ref_counts[canonical] = self._site_ref_counts.get(canonical, 0) + 1
387
+
388
+ def _decr_site_ref(self, site: int) -> None:
389
+ canonical = self._find(site)
390
+ current = self._site_ref_counts.get(canonical, 0)
391
+ if current > 0:
392
+ self._site_ref_counts[canonical] = current - 1
393
+
394
+ def aliased_locations(self, location: HeapLocation) -> frozenset[HeapLocation]:
395
+ site = self._root_allocation_site(location.root)
396
+ if site is None or location.is_nested():
397
+ return frozenset({location})
398
+ equiv_sites = self._equiv_class_sites(site)
399
+ result: set[HeapLocation] = {location}
400
+ for member_site in equiv_sites:
401
+ if member_site == site:
402
+ continue
403
+ member_storage = self.site_storage.get(member_site)
404
+ if member_storage is None:
405
+ continue
406
+ for raw in member_storage:
407
+ member_loc = self.location_for_raw(raw)
408
+ result.add(member_loc)
409
+ return frozenset(result)
410
+
411
+ def dynamic_attribute_location(self, base: object, attribute: str) -> HeapLocation:
412
+ return self._append_selector(
413
+ self.location_for_raw(base),
414
+ self._field_selector(attribute),
415
+ )
416
+
417
+ def dynamic_subscript_location(self, base: object, subscript: str) -> HeapLocation:
418
+ return self._append_selector(
419
+ self.location_for_raw(base),
420
+ self._subscript_selector(subscript),
421
+ )
422
+
423
+ def extend_location(
424
+ self,
425
+ base: object,
426
+ selectors: tuple[HeapSelector, ...],
427
+ ) -> HeapLocation:
428
+ location = self.location_for_raw(base)
429
+ for selector in selectors:
430
+ location = self._append_selector(location, selector)
431
+ return location
432
+
433
+ def dynamic_attribute_locations(
434
+ self,
435
+ bases: tuple[object, ...],
436
+ attributes: tuple[str, ...],
437
+ ) -> tuple[HeapLocation, ...]:
438
+ locations: list[HeapLocation] = []
439
+ seen: set[HeapLocation] = set()
440
+ for base in bases:
441
+ for attribute in attributes:
442
+ location = self.dynamic_attribute_location(base, attribute)
443
+ if location in seen:
444
+ continue
445
+ seen.add(location)
446
+ locations.append(location)
447
+ return tuple(locations)
448
+
449
+ def dynamic_subscript_locations(
450
+ self,
451
+ bases: tuple[object, ...],
452
+ subscripts: tuple[str, ...],
453
+ ) -> tuple[HeapLocation, ...]:
454
+ locations: list[HeapLocation] = []
455
+ seen: set[HeapLocation] = set()
456
+ for base in bases:
457
+ for subscript in subscripts:
458
+ location = self.dynamic_subscript_location(base, subscript)
459
+ if location in seen:
460
+ continue
461
+ seen.add(location)
462
+ locations.append(location)
463
+ return tuple(locations)
464
+
465
+ def alias_locals(self, procedure: object, target: object, source: object) -> None:
466
+ """Make *target* share source storage identity when both are named locals."""
467
+ if not self._is_named_local(target) or not self._is_named_local(source):
468
+ return
469
+ source_storage = self._raw_storage_for_local(procedure, source)
470
+ if not source_storage:
471
+ return
472
+ target_key = self._local_key(procedure, target)
473
+ source_key = self._local_key(procedure, source)
474
+ target_name = getattr(target, "name", None)
475
+ source_name = getattr(source, "name", None)
476
+ if isinstance(target_name, str):
477
+ self._local_names[target_key] = target_name
478
+ if isinstance(source_name, str):
479
+ self._local_names[source_key] = source_name
480
+ self.storage_overrides[target_key] = source_storage
481
+ if isinstance(target_name, str):
482
+ for raw in source_storage:
483
+ location = self.location_for_raw(raw)
484
+ if location.root not in self._object_labels:
485
+ self._object_labels[location.root] = target_name
486
+ source_site = self.allocation_sites.get(source_key)
487
+ target_site = self.allocation_sites.get(target_key)
488
+ if source_site is not None:
489
+ self.allocation_sites[target_key] = source_site
490
+ if target_site is not None and target_site != source_site:
491
+ self._decr_site_ref(target_site)
492
+ self._unify(source_site, target_site)
493
+ else:
494
+ self._incr_site_ref(source_site)
495
+
496
+ def unalias_local(self, procedure: object, local: object) -> None:
497
+ """Break a local alias and allocate a fresh site for the local."""
498
+ if not self._is_named_local(local):
499
+ return
500
+ key = self._local_key(procedure, local)
501
+ name = getattr(local, "name", None)
502
+ if isinstance(name, str):
503
+ self._local_names[key] = name
504
+ old_site = self.allocation_sites.get(key)
505
+ if old_site is not None:
506
+ self._decr_site_ref(old_site)
507
+ self.storage_overrides.pop(key, None)
508
+ raw = self._raw_storage_provider(procedure, local)
509
+ self._assign_site(key, raw)
510
+
511
+ def clear_local_binding(self, procedure: object, local: object) -> None:
512
+ """Record that *local* currently contains no heap-relevant value."""
513
+ if not self._is_named_local(local):
514
+ return
515
+ key = self._local_key(procedure, local)
516
+ name = getattr(local, "name", None)
517
+ if isinstance(name, str):
518
+ self._local_names[key] = name
519
+ old_site = self.allocation_sites.pop(key, None)
520
+ if old_site is not None:
521
+ self._decr_site_ref(old_site)
522
+ self.storage_overrides[key] = ()
523
+ if isinstance(name, str):
524
+ proc_id = id(procedure)
525
+ for other_key in list(self.storage_overrides):
526
+ if (
527
+ other_key != key
528
+ and other_key[0] == proc_id
529
+ and self._local_names.get(other_key) == name
530
+ ):
531
+ self.storage_overrides[other_key] = ()
532
+ other_site = self.allocation_sites.pop(other_key, None)
533
+ if other_site is not None:
534
+ self._decr_site_ref(other_site)
535
+
536
+ def _sync_name_bindings(
537
+ self,
538
+ procedure: object,
539
+ name: str,
540
+ storage: tuple[object, ...],
541
+ except_key: tuple[int, int],
542
+ ) -> None:
543
+ """Update all storage_overrides entries for the same variable name to
544
+ point to the current *storage*, keeping the local alias class consistent
545
+ across distinct ``Local`` node identities that share a variable name."""
546
+ proc_id = id(procedure)
547
+ for (p_id, l_id), old_storage in list(self.storage_overrides.items()):
548
+ if p_id != proc_id or (p_id, l_id) == except_key:
549
+ continue
550
+ if self._local_names.get((p_id, l_id)) != name:
551
+ continue
552
+ if not old_storage:
553
+ continue
554
+ self.storage_overrides[(p_id, l_id)] = storage
555
+ old_site = self.allocation_sites.get((p_id, l_id))
556
+ if old_site is not None:
557
+ self._decr_site_ref(old_site)
558
+ self.allocation_sites.pop((p_id, l_id), None)
559
+
560
+ def bind_local_to_object(
561
+ self,
562
+ procedure: object,
563
+ local: object,
564
+ obj: HeapObject,
565
+ *,
566
+ include_raw_fallback: bool = False,
567
+ ) -> None:
568
+ """Bind a local directly to an abstract object root."""
569
+ if not self._is_named_local(local):
570
+ return
571
+ key = self._local_key(procedure, local)
572
+ name = getattr(local, "name", None)
573
+ if isinstance(name, str):
574
+ self._local_names[key] = name
575
+ old_site = self.allocation_sites.get(key)
576
+ if old_site is not None:
577
+ self._decr_site_ref(old_site)
578
+ storage = (obj,)
579
+ if include_raw_fallback:
580
+ storage = (*storage, *self._raw_storage_provider(procedure, local))
581
+ self.storage_overrides[key] = storage
582
+ if isinstance(name, str) and obj not in self._object_labels:
583
+ self._object_labels[obj] = name
584
+ self._assign_site(key, storage)
585
+ if isinstance(name, str):
586
+ self._sync_name_bindings(procedure, name, storage, except_key=key)
587
+
588
+ def bind_local_to_locations(
589
+ self,
590
+ procedure: object,
591
+ local: object,
592
+ locations: tuple[object, ...],
593
+ *,
594
+ include_raw_fallback: bool = False,
595
+ ) -> None:
596
+ """Bind a local to existing abstract locations."""
597
+ if not self._is_named_local(local):
598
+ return
599
+ storage = tuple(
600
+ dict.fromkeys(self.location_for_raw(location) for location in locations)
601
+ )
602
+ if include_raw_fallback:
603
+ storage = (*storage, *self._raw_storage_provider(procedure, local))
604
+ if not storage:
605
+ return
606
+ key = self._local_key(procedure, local)
607
+ name = getattr(local, "name", None)
608
+ if isinstance(name, str):
609
+ self._local_names[key] = name
610
+ self.storage_overrides[key] = storage
611
+ if isinstance(name, str):
612
+ for raw in storage:
613
+ location = self.location_for_raw(raw)
614
+ if location.root not in self._object_labels:
615
+ self._object_labels[location.root] = name
616
+ self._assign_site(key, storage)
617
+ if isinstance(name, str):
618
+ self._sync_name_bindings(procedure, name, storage, except_key=key)
619
+
620
+ def bind_parameter(
621
+ self,
622
+ procedure: object,
623
+ formal: object,
624
+ index: int,
625
+ actual_locations: tuple[object, ...],
626
+ *,
627
+ include_raw_fallback: bool = True,
628
+ ) -> None:
629
+ """Bind a callee formal to actual heap roots or to a parameter root."""
630
+ if actual_locations:
631
+ self.bind_local_to_locations(
632
+ procedure,
633
+ formal,
634
+ actual_locations,
635
+ include_raw_fallback=include_raw_fallback,
636
+ )
637
+ return
638
+ label = getattr(formal, "name", None)
639
+ self.bind_local_to_object(
640
+ procedure,
641
+ formal,
642
+ self.parameter_object(procedure, index, label=label),
643
+ include_raw_fallback=include_raw_fallback,
644
+ )
645
+
646
+ def bind_allocation_targets(
647
+ self,
648
+ procedure: object,
649
+ targets: tuple[object, ...],
650
+ site: object,
651
+ *,
652
+ label: str | None = None,
653
+ type_hint: str | None = None,
654
+ context: tuple[object, ...] = (),
655
+ include_raw_fallback: bool = False,
656
+ ) -> None:
657
+ """Bind assignment targets to a fixed allocation-site object."""
658
+ obj = self.allocation_object(
659
+ procedure,
660
+ site,
661
+ label=label,
662
+ type_hint=type_hint,
663
+ context=context,
664
+ )
665
+ for target in targets:
666
+ self.bind_local_to_object(
667
+ procedure,
668
+ target,
669
+ obj,
670
+ include_raw_fallback=include_raw_fallback,
671
+ )
672
+
673
+ def bind_fresh_return_targets(
674
+ self,
675
+ procedure: object,
676
+ targets: tuple[object, ...],
677
+ site: object,
678
+ *,
679
+ label: str | None = None,
680
+ type_hint: str | None = None,
681
+ context: tuple[object, ...] = (),
682
+ ) -> None:
683
+ """Bind call-result targets to a fresh allocation-like return object."""
684
+ obj = self.allocation_object(
685
+ procedure,
686
+ site,
687
+ label=label,
688
+ type_hint=type_hint,
689
+ context=context,
690
+ )
691
+ for target in targets:
692
+ self.bind_local_to_object(
693
+ procedure,
694
+ target,
695
+ obj,
696
+ include_raw_fallback=True,
697
+ )
698
+ name = getattr(target, "name", None)
699
+ if isinstance(name, str):
700
+ self._object_labels[obj] = name
701
+
702
+ def bind_call_result_targets(
703
+ self,
704
+ procedure: object,
705
+ targets: tuple[object, ...],
706
+ site: object,
707
+ *,
708
+ label: str | None = None,
709
+ type_hint: str | None = None,
710
+ context: tuple[object, ...] = (),
711
+ ) -> None:
712
+ """Bind assignment targets to a fixed call-result object."""
713
+ for index, target in enumerate(targets):
714
+ target_site = (site, index) if len(targets) > 1 else site
715
+ target_label = label if len(targets) == 1 else f"{label or 'call'}[{index}]"
716
+ obj = self.call_result_object(
717
+ procedure,
718
+ target_site,
719
+ label=target_label,
720
+ type_hint=type_hint,
721
+ context=context,
722
+ )
723
+ self.bind_local_to_object(
724
+ procedure,
725
+ target,
726
+ obj,
727
+ include_raw_fallback=True,
728
+ )
729
+
730
+ def bind_summary_targets(
731
+ self,
732
+ procedure: object,
733
+ targets: tuple[object, ...],
734
+ key: object,
735
+ *,
736
+ label: str | None = None,
737
+ type_hint: str | None = None,
738
+ ) -> None:
739
+ """Bind assignment targets to a summary object."""
740
+ obj = self.summary_object(key, label=label, type_hint=type_hint)
741
+ for target in targets:
742
+ self.bind_local_to_object(
743
+ procedure,
744
+ target,
745
+ obj,
746
+ include_raw_fallback=True,
747
+ )
748
+
749
+ def update_assignment_aliases(
750
+ self,
751
+ procedure: object,
752
+ targets: tuple[object, ...],
753
+ expr: object,
754
+ ) -> None:
755
+ """Apply direct local assignment alias policy for ``targets = expr``."""
756
+ for target in targets:
757
+ self.unalias_local(procedure, target)
758
+ if not self._is_named_local(expr):
759
+ return
760
+ for target in targets:
761
+ self.alias_locals(procedure, target, expr)
762
+
763
+ def local_object(
764
+ self,
765
+ procedure: object,
766
+ local: object,
767
+ *,
768
+ label: str | None = None,
769
+ ) -> HeapObject:
770
+ name = label or getattr(local, "name", None) or self._describe_raw_storage(local)
771
+ key = ("local", id(procedure), name)
772
+ return self._object(
773
+ HeapObjectKind.LOCAL,
774
+ key,
775
+ str(name),
776
+ freshness=HeapObjectFreshness.FRESH,
777
+ cardinality=HeapObjectCardinality.ONE,
778
+ identity=HeapObjectIdentity.SYMBOLIC,
779
+ escape=HeapEscapeState.LOCAL,
780
+ )
781
+
782
+ def parameter_object(
783
+ self,
784
+ procedure: object,
785
+ index: int,
786
+ *,
787
+ label: str | None = None,
788
+ type_hint: str | None = None,
789
+ ) -> HeapObject:
790
+ key = ("parameter", id(procedure), index)
791
+ return self._object(
792
+ HeapObjectKind.PARAMETER,
793
+ key,
794
+ label or f"param[{index}]",
795
+ type_hint=type_hint,
796
+ freshness=HeapObjectFreshness.UNKNOWN,
797
+ cardinality=HeapObjectCardinality.UNKNOWN,
798
+ identity=HeapObjectIdentity.SYMBOLIC,
799
+ escape=HeapEscapeState.UNKNOWN,
800
+ )
801
+
802
+ def return_object(
803
+ self,
804
+ procedure: object,
805
+ index: int,
806
+ *,
807
+ label: str | None = None,
808
+ type_hint: str | None = None,
809
+ ) -> HeapObject:
810
+ key = ("return", id(procedure), index)
811
+ return self._object(
812
+ HeapObjectKind.RETURN,
813
+ key,
814
+ label or f"return[{index}]",
815
+ type_hint=type_hint,
816
+ freshness=HeapObjectFreshness.UNKNOWN,
817
+ cardinality=HeapObjectCardinality.UNKNOWN,
818
+ identity=HeapObjectIdentity.SYMBOLIC,
819
+ escape=HeapEscapeState.UNKNOWN,
820
+ )
821
+
822
+ def allocation_object(
823
+ self,
824
+ procedure: object,
825
+ site: object,
826
+ *,
827
+ label: str | None = None,
828
+ type_hint: str | None = None,
829
+ context: tuple[object, ...] = (),
830
+ ) -> HeapObject:
831
+ key = self._site_key("allocation", procedure, site, context)
832
+ freshness = (
833
+ HeapObjectFreshness.FRESH
834
+ if self.policy.recency
835
+ else HeapObjectFreshness.SUMMARY
836
+ )
837
+ cardinality = (
838
+ HeapObjectCardinality.ONE
839
+ if self.policy.allocation_sensitivity is not AllocationSensitivity.NONE
840
+ and self.policy.recency
841
+ else HeapObjectCardinality.MANY
842
+ )
843
+ return self._object(
844
+ HeapObjectKind.ALLOCATION,
845
+ key,
846
+ label or self._site_label("alloc", site),
847
+ type_hint=type_hint,
848
+ allocation_site=key,
849
+ context=self._context_key(context),
850
+ freshness=freshness,
851
+ cardinality=cardinality,
852
+ identity=(
853
+ HeapObjectIdentity.SINGLETON
854
+ if cardinality is HeapObjectCardinality.ONE
855
+ else HeapObjectIdentity.SUMMARY
856
+ ),
857
+ escape=HeapEscapeState.LOCAL,
858
+ )
859
+
860
+ def call_result_object(
861
+ self,
862
+ procedure: object,
863
+ site: object,
864
+ *,
865
+ label: str | None = None,
866
+ type_hint: str | None = None,
867
+ context: tuple[object, ...] = (),
868
+ ) -> HeapObject:
869
+ key = self._site_key("call_result", procedure, site, context)
870
+ return self._object(
871
+ HeapObjectKind.CALL_RESULT,
872
+ key,
873
+ label or self._site_label("call", site),
874
+ type_hint=type_hint,
875
+ allocation_site=key,
876
+ context=self._context_key(context),
877
+ freshness=HeapObjectFreshness.UNKNOWN,
878
+ cardinality=HeapObjectCardinality.UNKNOWN,
879
+ identity=HeapObjectIdentity.SYMBOLIC,
880
+ escape=HeapEscapeState.UNKNOWN,
881
+ )
882
+
883
+ def external_object(
884
+ self,
885
+ key: object,
886
+ *,
887
+ label: str | None = None,
888
+ type_hint: str | None = None,
889
+ stable_identity: bool = False,
890
+ ) -> HeapObject:
891
+ return self._object(
892
+ HeapObjectKind.EXTERNAL,
893
+ ("external", key),
894
+ label or f"external:{key!r}",
895
+ type_hint=type_hint,
896
+ freshness=HeapObjectFreshness.UNKNOWN,
897
+ cardinality=HeapObjectCardinality.UNKNOWN,
898
+ identity=(
899
+ HeapObjectIdentity.SYMBOLIC
900
+ if stable_identity
901
+ else HeapObjectIdentity.SUMMARY
902
+ ),
903
+ escape=HeapEscapeState.EXTERNAL,
904
+ )
905
+
906
+ def unknown_object(
907
+ self,
908
+ key: object,
909
+ *,
910
+ label: str | None = None,
911
+ type_hint: str | None = None,
912
+ identity: HeapObjectIdentity = HeapObjectIdentity.SUMMARY,
913
+ ) -> HeapObject:
914
+ """Return an explicit top-like reference root with stable provenance."""
915
+ return self._object(
916
+ HeapObjectKind.UNKNOWN,
917
+ ("unknown", key),
918
+ label or f"unknown:{key!r}",
919
+ type_hint=type_hint,
920
+ freshness=HeapObjectFreshness.UNKNOWN,
921
+ cardinality=HeapObjectCardinality.UNKNOWN,
922
+ identity=identity,
923
+ escape=HeapEscapeState.UNKNOWN,
924
+ )
925
+
926
+ def global_object(
927
+ self,
928
+ name: object,
929
+ *,
930
+ module: object | None = None,
931
+ label: str | None = None,
932
+ type_hint: str | None = None,
933
+ ) -> HeapObject:
934
+ display = label or str(name)
935
+ key = ("global", module, name)
936
+ return self._object(
937
+ HeapObjectKind.GLOBAL,
938
+ key,
939
+ display,
940
+ type_hint=type_hint,
941
+ freshness=HeapObjectFreshness.FRESH,
942
+ cardinality=HeapObjectCardinality.ONE,
943
+ identity=HeapObjectIdentity.SYMBOLIC,
944
+ escape=HeapEscapeState.EXTERNAL,
945
+ )
946
+
947
+ def cell_object(
948
+ self,
949
+ name: object,
950
+ *,
951
+ label: str | None = None,
952
+ type_hint: str | None = None,
953
+ ) -> HeapObject:
954
+ cell_name = getattr(name, "name", name)
955
+ display = label or str(cell_name)
956
+ has_identity = hasattr(name, "name")
957
+ key = (
958
+ "cell-object",
959
+ id(name),
960
+ cell_name,
961
+ ) if has_identity else ("cell-name", cell_name)
962
+ return self._object(
963
+ HeapObjectKind.CELL,
964
+ key,
965
+ display,
966
+ type_hint=type_hint,
967
+ freshness=(
968
+ HeapObjectFreshness.FRESH
969
+ if has_identity
970
+ else HeapObjectFreshness.SUMMARY
971
+ ),
972
+ cardinality=(
973
+ HeapObjectCardinality.ONE
974
+ if has_identity
975
+ else HeapObjectCardinality.UNKNOWN
976
+ ),
977
+ identity=(
978
+ HeapObjectIdentity.SYMBOLIC
979
+ if has_identity
980
+ else HeapObjectIdentity.SUMMARY
981
+ ),
982
+ escape=(
983
+ HeapEscapeState.LOCAL
984
+ if has_identity
985
+ else HeapEscapeState.UNKNOWN
986
+ ),
987
+ )
988
+
989
+ def module_object(
990
+ self,
991
+ name: object,
992
+ *,
993
+ label: str | None = None,
994
+ type_hint: str | None = None,
995
+ ) -> HeapObject:
996
+ display = label or str(name)
997
+ return self._object(
998
+ HeapObjectKind.GLOBAL,
999
+ ("module", name),
1000
+ display,
1001
+ type_hint=type_hint or "module",
1002
+ freshness=HeapObjectFreshness.FRESH,
1003
+ cardinality=HeapObjectCardinality.ONE,
1004
+ identity=HeapObjectIdentity.SYMBOLIC,
1005
+ escape=HeapEscapeState.EXTERNAL,
1006
+ )
1007
+
1008
+ def summary_object(
1009
+ self,
1010
+ key: object,
1011
+ *,
1012
+ label: str | None = None,
1013
+ type_hint: str | None = None,
1014
+ ) -> HeapObject:
1015
+ return self._object(
1016
+ HeapObjectKind.SUMMARY,
1017
+ ("summary", key),
1018
+ label or f"summary:{key!r}",
1019
+ type_hint=type_hint,
1020
+ freshness=HeapObjectFreshness.SUMMARY,
1021
+ cardinality=HeapObjectCardinality.MANY,
1022
+ identity=HeapObjectIdentity.SUMMARY,
1023
+ escape=HeapEscapeState.ESCAPED,
1024
+ )
1025
+
1026
+ def display_label_for_location(self, location: HeapLocation) -> str:
1027
+ label = self._object_labels.get(location.root, location.root.label)
1028
+ for selector in location.selectors:
1029
+ if selector.kind == "field":
1030
+ label = f"{label}.{selector.value}"
1031
+ elif selector.kind == "element":
1032
+ label = f"{label}{selector.value}"
1033
+ elif selector.kind == "key":
1034
+ label = f"{label}[{selector.value!r}]"
1035
+ elif selector.kind == "index":
1036
+ label = f"{label}[{selector.value}]"
1037
+ elif selector.kind == "slice":
1038
+ label = f"{label}[slice]"
1039
+ elif selector.kind == "element_type":
1040
+ label = f"{label}[{selector.value}]"
1041
+ elif selector.kind == "summary":
1042
+ label = f"{label}.*"
1043
+ return label
1044
+
1045
+ def mark_escaped(self, location: object) -> None:
1046
+ """Mark a location's root as escaped under the fixed escape policy."""
1047
+ if not self.policy.track_escapes:
1048
+ return
1049
+ heap_location = self.location_for_raw(location)
1050
+ self._escaped_objects.add(heap_location.root)
1051
+
1052
+ def mark_all_escaped(self, locations: tuple[object, ...]) -> None:
1053
+ for location in locations:
1054
+ self.mark_escaped(location)
1055
+
1056
+ @staticmethod
1057
+ def access_path_prefix_matches(stored: object, query: object) -> bool:
1058
+ """Return whether *stored* implies *query* by location/access-path prefix."""
1059
+ if stored == query:
1060
+ return True
1061
+ stored_location = getattr(stored, "location", None)
1062
+ query_location = getattr(query, "location", None)
1063
+ if stored_location is None or query_location is None:
1064
+ return False
1065
+ if isinstance(stored_location, HeapLocation) and isinstance(
1066
+ query_location,
1067
+ HeapLocation,
1068
+ ):
1069
+ if not stored_location.is_prefix_of(query_location):
1070
+ return False
1071
+ elif stored_location != query_location:
1072
+ return False
1073
+ stored_path = getattr(stored, "access_path", ())
1074
+ query_path = getattr(query, "access_path", ())
1075
+ if stored_path == query_path:
1076
+ return True
1077
+ return (
1078
+ len(stored_path) <= len(query_path)
1079
+ and query_path[: len(stored_path)] == stored_path
1080
+ )
1081
+
1082
+ def to_points_to_graph(
1083
+ self,
1084
+ *,
1085
+ state: object | None = None,
1086
+ program_point_states: dict[int, tuple[object, object]] | None = None,
1087
+ program_point_outcomes: dict[int, dict[str, object]] | None = None,
1088
+ precision_degradations: dict[int, frozenset[str]] | None = None,
1089
+ ) -> "PointsToGraph":
1090
+ """Export heap state as a reusable :class:`PointsToGraph`.
1091
+
1092
+ Extracts a read-only snapshot mapping every canonical root
1093
+ :class:`HeapLocation` to a :class:`PointsToEntry` with alias,
1094
+ escape, reference-count, and update-policy metadata. The
1095
+ resulting graph is consumed by optimization passes and the
1096
+ semantic query API.
1097
+ """
1098
+ from .points_to_graph import HeapValueSnapshot, PointsToEntry, PointsToGraph
1099
+
1100
+ entries: dict[HeapLocation, PointsToEntry] = {}
1101
+ for obj_key, obj in self._objects.items():
1102
+ location = HeapLocation(obj)
1103
+ if location in entries:
1104
+ continue
1105
+ ref_count = self.reference_count_for_root(obj)
1106
+ escaped = (
1107
+ obj in self._escaped_objects
1108
+ or obj.escape
1109
+ in {
1110
+ HeapEscapeState.ESCAPED,
1111
+ HeapEscapeState.EXTERNAL,
1112
+ HeapEscapeState.UNKNOWN,
1113
+ }
1114
+ )
1115
+ entries[location] = PointsToEntry(
1116
+ location=location,
1117
+ label=self.display_label_for_location(location),
1118
+ aliases=self.aliased_locations(location),
1119
+ ref_count=ref_count,
1120
+ is_escaped=escaped,
1121
+ is_singleton=obj.is_singleton(),
1122
+ update_policy=self.update_policy_for_location(location),
1123
+ cardinality=obj.cardinality,
1124
+ identity=obj.identity,
1125
+ )
1126
+ values = getattr(state, "values", {}) if state is not None else {}
1127
+ contaminants = (
1128
+ getattr(state, "contaminants", {}) if state is not None else {}
1129
+ )
1130
+ point_values: dict[int, tuple[dict, dict]] = {}
1131
+ point_contaminants: dict[int, tuple[dict, dict]] = {}
1132
+ point_absent: dict[int, tuple[frozenset, frozenset]] = {}
1133
+ point_scalar_present: dict[int, tuple[frozenset, frozenset]] = {}
1134
+ point_complete_roots: dict[int, tuple[frozenset, frozenset]] = {}
1135
+ point_locals: dict[int, tuple[dict, dict]] = {}
1136
+ point_outcomes: dict[int, dict[str, HeapValueSnapshot]] = {}
1137
+
1138
+ def heap_state(flow):
1139
+ return getattr(flow, "heap_state", flow)
1140
+
1141
+ def local_values(flow) -> dict[tuple[int, str], frozenset[HeapLocation]]:
1142
+ environment = getattr(flow, "environment", None)
1143
+ if environment is None:
1144
+ return {}
1145
+ keys = set(environment.storage_overrides) | set(
1146
+ environment.allocation_sites
1147
+ )
1148
+ result: dict[tuple[int, str], set[HeapLocation]] = {}
1149
+ for key in keys:
1150
+ name = environment.local_names.get(key)
1151
+ if not name:
1152
+ continue
1153
+ storage = self._environment_storage(environment, key)
1154
+ result.setdefault((key[0], name), set()).update(
1155
+ self.location_for_raw(raw) for raw in storage
1156
+ )
1157
+ return {
1158
+ key: frozenset(locations)
1159
+ for key, locations in result.items()
1160
+ }
1161
+
1162
+ def payloads(mapping) -> dict[int, frozenset[HeapLocation]]:
1163
+ return {
1164
+ id(procedure): frozenset(locations)
1165
+ for procedure, locations in mapping.items()
1166
+ }
1167
+
1168
+ def return_payloads(mapping) -> dict[int, tuple[frozenset[HeapLocation], ...]]:
1169
+ return {
1170
+ id(procedure): tuple(frozenset(slot) for slot in slots)
1171
+ for procedure, slots in mapping.items()
1172
+ }
1173
+ for key, pair in (program_point_states or {}).items():
1174
+ before, after = pair
1175
+ before_heap = heap_state(before)
1176
+ after_heap = heap_state(after)
1177
+ point_values[key] = (
1178
+ {
1179
+ location: frozenset(stored)
1180
+ for location, stored in getattr(before_heap, "values", {}).items()
1181
+ },
1182
+ {
1183
+ location: frozenset(stored)
1184
+ for location, stored in getattr(after_heap, "values", {}).items()
1185
+ },
1186
+ )
1187
+ point_contaminants[key] = (
1188
+ {
1189
+ location: frozenset(stored)
1190
+ for location, stored in getattr(before_heap, "contaminants", {}).items()
1191
+ },
1192
+ {
1193
+ location: frozenset(stored)
1194
+ for location, stored in getattr(after_heap, "contaminants", {}).items()
1195
+ },
1196
+ )
1197
+ point_absent[key] = (
1198
+ frozenset(getattr(before_heap, "absent", ())),
1199
+ frozenset(getattr(after_heap, "absent", ())),
1200
+ )
1201
+ point_scalar_present[key] = (
1202
+ frozenset(getattr(before_heap, "scalar_present", ())),
1203
+ frozenset(getattr(after_heap, "scalar_present", ())),
1204
+ )
1205
+ point_complete_roots[key] = (
1206
+ frozenset(getattr(before_heap, "complete_roots", ())),
1207
+ frozenset(getattr(after_heap, "complete_roots", ())),
1208
+ )
1209
+ point_locals[key] = (local_values(before), local_values(after))
1210
+ for key, outcomes in (program_point_outcomes or {}).items():
1211
+ point_outcomes[key] = {
1212
+ label: HeapValueSnapshot(
1213
+ values={
1214
+ location: frozenset(stored)
1215
+ for location, stored in getattr(heap_state(outcome), "values", {}).items()
1216
+ },
1217
+ contaminants={
1218
+ location: frozenset(stored)
1219
+ for location, stored in getattr(
1220
+ heap_state(outcome),
1221
+ "contaminants",
1222
+ {},
1223
+ ).items()
1224
+ },
1225
+ absent=frozenset(getattr(heap_state(outcome), "absent", ())),
1226
+ complete_roots=frozenset(
1227
+ getattr(heap_state(outcome), "complete_roots", ())
1228
+ ),
1229
+ scalar_present=frozenset(
1230
+ getattr(heap_state(outcome), "scalar_present", ())
1231
+ ),
1232
+ locals=local_values(outcome),
1233
+ returns=return_payloads(
1234
+ getattr(heap_state(outcome), "return_slots", {})
1235
+ ),
1236
+ yields=payloads(getattr(heap_state(outcome), "yields", {})),
1237
+ raised=payloads(getattr(heap_state(outcome), "raised", {})),
1238
+ )
1239
+ for label, outcome in outcomes.items()
1240
+ }
1241
+ return PointsToGraph(
1242
+ entries=entries,
1243
+ allow_strong_nested_fresh=self.policy.allow_strong_nested_fresh,
1244
+ heap_values={
1245
+ location: frozenset(stored)
1246
+ for location, stored in values.items()
1247
+ },
1248
+ heap_contaminants={
1249
+ location: frozenset(stored)
1250
+ for location, stored in contaminants.items()
1251
+ },
1252
+ program_point_values=point_values,
1253
+ program_point_contaminants=point_contaminants,
1254
+ heap_absent=frozenset(getattr(state, "absent", ())),
1255
+ heap_scalar_present=frozenset(
1256
+ getattr(state, "scalar_present", ())
1257
+ ),
1258
+ complete_roots=frozenset(getattr(state, "complete_roots", ())),
1259
+ program_point_absent=point_absent,
1260
+ program_point_scalar_present=point_scalar_present,
1261
+ program_point_complete_roots=point_complete_roots,
1262
+ program_point_outcomes=point_outcomes,
1263
+ program_point_locals=point_locals,
1264
+ precision_degradations=dict(precision_degradations or {}),
1265
+ )
1266
+
1267
+ def to_dict(self) -> dict:
1268
+ """Serialize the full heap state for inspection and debugging.
1269
+
1270
+ Returns a dictionary with policy, allocation sites, equivalence
1271
+ classes, ref counts, and canonicalized object/location caches.
1272
+ The ``raw_storage_provider`` callable is not serializable and is
1273
+ omitted; reconstruction requires a new provider.
1274
+ """
1275
+ return {
1276
+ "policy": self.policy.to_dict(),
1277
+ "next_site": self.next_site,
1278
+ "storage_overrides": {
1279
+ str(k): [self._describe_raw_storage(r) for r in v]
1280
+ for k, v in self.storage_overrides.items()
1281
+ },
1282
+ "allocation_sites": {str(k): v for k, v in self.allocation_sites.items()},
1283
+ "site_count": len(self.site_storage),
1284
+ "escaped_objects": sorted(
1285
+ self.display_label_for_location(HeapLocation(obj))
1286
+ for obj in sorted(self._escaped_objects, key=lambda o: o.label)
1287
+ ),
1288
+ "equivalence_classes": len(self._equiv_parent),
1289
+ "total_ref_counts": sum(self._site_ref_counts.values()),
1290
+ "cached_objects": len(self._objects),
1291
+ "cached_locations": len(self._raw_locations),
1292
+ }
1293
+
1294
+ @staticmethod
1295
+ def _local_key(procedure: object, local: object) -> tuple[int, int]:
1296
+ return id(procedure), id(local)
1297
+
1298
+ @staticmethod
1299
+ def _is_named_local(value: object) -> bool:
1300
+ return isinstance(getattr(value, "name", None), str)
1301
+
1302
+ def _object(
1303
+ self,
1304
+ kind: HeapObjectKind,
1305
+ key: object,
1306
+ label: str,
1307
+ *,
1308
+ type_hint: str | None = None,
1309
+ allocation_site: object | None = None,
1310
+ context: tuple[object, ...] = (),
1311
+ freshness: HeapObjectFreshness = HeapObjectFreshness.FRESH,
1312
+ cardinality: HeapObjectCardinality = HeapObjectCardinality.UNKNOWN,
1313
+ identity: HeapObjectIdentity = HeapObjectIdentity.SUMMARY,
1314
+ escape: HeapEscapeState = HeapEscapeState.LOCAL,
1315
+ ) -> HeapObject:
1316
+ object_key = (
1317
+ kind.value,
1318
+ key,
1319
+ label,
1320
+ type_hint,
1321
+ allocation_site,
1322
+ context,
1323
+ freshness.value,
1324
+ cardinality.value,
1325
+ identity.value,
1326
+ escape.value,
1327
+ )
1328
+ obj = self._objects.get(object_key)
1329
+ if obj is None:
1330
+ obj = HeapObject(
1331
+ kind,
1332
+ key,
1333
+ label,
1334
+ type_hint=type_hint,
1335
+ allocation_site=allocation_site,
1336
+ context=context,
1337
+ freshness=freshness,
1338
+ cardinality=cardinality,
1339
+ identity=identity,
1340
+ escape=escape,
1341
+ )
1342
+ self._objects[object_key] = obj
1343
+ return obj
1344
+
1345
+ def _object_for_raw(self, raw: object) -> HeapObject:
1346
+ if isinstance(raw, HeapObject):
1347
+ return raw
1348
+ slot_name = getattr(raw, "slotName", None)
1349
+ if slot_name is not None:
1350
+ if hasattr(slot_name, "isLocal") and slot_name.isLocal():
1351
+ local = getattr(slot_name, "local", None)
1352
+ name = getattr(local, "name", None)
1353
+ label = str(name) if name is not None else str(slot_name)
1354
+ return self._object(
1355
+ HeapObjectKind.LOCAL,
1356
+ ("slot-local", id(slot_name), label),
1357
+ label,
1358
+ freshness=HeapObjectFreshness.FRESH,
1359
+ cardinality=HeapObjectCardinality.ONE,
1360
+ identity=HeapObjectIdentity.SYMBOLIC,
1361
+ escape=HeapEscapeState.LOCAL,
1362
+ )
1363
+ if hasattr(slot_name, "isExisting") and slot_name.isExisting():
1364
+ obj = getattr(slot_name, "object", None)
1365
+ label = self._object_label(obj) or str(slot_name)
1366
+ return self._object(
1367
+ HeapObjectKind.GLOBAL,
1368
+ ("slot-existing", id(obj), id(slot_name)),
1369
+ label,
1370
+ freshness=HeapObjectFreshness.FRESH,
1371
+ cardinality=HeapObjectCardinality.ONE,
1372
+ identity=HeapObjectIdentity.SYMBOLIC,
1373
+ escape=HeapEscapeState.EXTERNAL,
1374
+ )
1375
+ label = self._describe_raw_storage(raw)
1376
+ return self._object(
1377
+ HeapObjectKind.STORAGE,
1378
+ ("raw", id(raw)),
1379
+ label,
1380
+ freshness=HeapObjectFreshness.FRESH,
1381
+ cardinality=HeapObjectCardinality.ONE,
1382
+ identity=HeapObjectIdentity.SYMBOLIC,
1383
+ escape=HeapEscapeState.LOCAL,
1384
+ )
1385
+
1386
+ def _site_key(
1387
+ self,
1388
+ kind: str,
1389
+ procedure: object,
1390
+ site: object,
1391
+ context: tuple[object, ...],
1392
+ ) -> tuple[object, ...]:
1393
+ if self.policy.allocation_sensitivity is AllocationSensitivity.NONE:
1394
+ return (kind,)
1395
+ if isinstance(site, tuple) and site and site[0] == "call_return":
1396
+ return (kind, *site)
1397
+ if self.policy.allocation_sensitivity is AllocationSensitivity.SITE:
1398
+ # In bounded programs the same syntactic site may execute through
1399
+ # multiple distinct call activations. Retaining the activation
1400
+ # token prevents a multi-object site from being misclassified as
1401
+ # a singleton and used for unsound strong updates.
1402
+ return (
1403
+ (kind, self._site_identity(site), tuple(context))
1404
+ if context
1405
+ else (kind, self._site_identity(site))
1406
+ )
1407
+ if self.policy.allocation_sensitivity is AllocationSensitivity.PROCEDURE:
1408
+ return kind, self._site_identity(procedure), self._site_identity(site)
1409
+ return (
1410
+ kind,
1411
+ self._site_identity(procedure),
1412
+ self._site_identity(site),
1413
+ self._context_key(context),
1414
+ )
1415
+
1416
+ @staticmethod
1417
+ def _site_identity(node: object) -> object:
1418
+ origin = getattr(getattr(node, "annotation", None), "origin", ()) or ()
1419
+ meaningful_origin = tuple(item for item in origin if item is not None)
1420
+ if meaningful_origin:
1421
+ return (
1422
+ type(node).__name__,
1423
+ tuple(repr(item) for item in meaningful_origin),
1424
+ getattr(node, "name", None),
1425
+ )
1426
+ line = getattr(node, "line", None)
1427
+ column = getattr(node, "column", None)
1428
+ if line is not None or column is not None:
1429
+ return type(node).__name__, line, column
1430
+ return id(node)
1431
+
1432
+ def _context_key(self, context: tuple[object, ...]) -> tuple[object, ...]:
1433
+ depth = self.policy.context_sensitivity_depth
1434
+ if depth <= 0:
1435
+ return ()
1436
+ return tuple(context[-depth:])
1437
+
1438
+ @staticmethod
1439
+ def _site_label(prefix: str, site: object) -> str:
1440
+ line = getattr(site, "line", None)
1441
+ column = getattr(site, "column", None)
1442
+ if line is not None and column is not None:
1443
+ return f"{prefix}@{line}:{column}"
1444
+ if line is not None:
1445
+ return f"{prefix}@{line}"
1446
+ return f"{prefix}@{type(site).__name__}:{id(site):x}"
1447
+
1448
+ @staticmethod
1449
+ def _object_label(obj: object) -> str | None:
1450
+ pyobj = getattr(obj, "pyobj", None)
1451
+ if isinstance(pyobj, str):
1452
+ return pyobj
1453
+ if pyobj is not None:
1454
+ return repr(pyobj)
1455
+ name = getattr(obj, "name", None)
1456
+ if isinstance(name, str):
1457
+ return name
1458
+ return None
1459
+
1460
+ @staticmethod
1461
+ def _canonical_raw(raw: object) -> object:
1462
+ get_forward = getattr(raw, "getForward", None)
1463
+ if callable(get_forward):
1464
+ return get_forward()
1465
+ return raw
1466
+
1467
+ @staticmethod
1468
+ def _describe_raw_storage(raw: object) -> str:
1469
+ label = getattr(raw, "label", None)
1470
+ if isinstance(label, str):
1471
+ return label
1472
+ slot_name = getattr(raw, "slotName", None)
1473
+ if slot_name is not None:
1474
+ return str(slot_name)
1475
+ return repr(raw)
1476
+
1477
+ def _field_selector(self, attribute: str) -> HeapSelector:
1478
+ if self.policy.field_sensitivity is FieldSensitivity.NONE:
1479
+ return HeapSelector.unknown_field()
1480
+ if attribute == "*":
1481
+ return HeapSelector.unknown_field()
1482
+ return HeapSelector.field(attribute)
1483
+
1484
+ def _subscript_selector(self, subscript: str) -> HeapSelector:
1485
+ if subscript == "[slice]":
1486
+ return HeapSelector.slice()
1487
+ if subscript == "[*]":
1488
+ return HeapSelector.unknown_element()
1489
+ policy = self.policy.container_sensitivity
1490
+ if policy in {ContainerSensitivity.NONE, ContainerSensitivity.WILDCARD}:
1491
+ return HeapSelector.unknown_element()
1492
+ literal = self._subscript_literal(subscript)
1493
+ if policy is ContainerSensitivity.LITERAL_KEYS:
1494
+ if literal is not None:
1495
+ index = self._literal_index(literal)
1496
+ if index is not None:
1497
+ return HeapSelector.index(index)
1498
+ return HeapSelector.key(literal)
1499
+ return HeapSelector.unknown_element()
1500
+ if policy is ContainerSensitivity.BOUNDED_INDICES:
1501
+ if literal is not None:
1502
+ index = self._literal_index(literal)
1503
+ if index is not None and 0 <= index <= self.policy.max_index:
1504
+ return HeapSelector.index(index)
1505
+ return HeapSelector.key(literal)
1506
+ return HeapSelector.unknown_element()
1507
+ return HeapSelector.unknown_element()
1508
+
1509
+ @staticmethod
1510
+ def _subscript_literal(subscript: str) -> str | None:
1511
+ if len(subscript) < 3 or not subscript.startswith("[") or not subscript.endswith("]"):
1512
+ return None
1513
+ inner = subscript[1:-1]
1514
+ if not inner or inner == "*":
1515
+ return None
1516
+ if (
1517
+ len(inner) >= 2
1518
+ and inner[0] == inner[-1]
1519
+ and inner[0] in {"'", '"'}
1520
+ ):
1521
+ return inner[1:-1]
1522
+ return inner
1523
+
1524
+ @staticmethod
1525
+ def _literal_index(literal: str) -> int | None:
1526
+ try:
1527
+ return int(literal)
1528
+ except ValueError:
1529
+ return None
1530
+
1531
+ def _append_selector(
1532
+ self,
1533
+ location: HeapLocation,
1534
+ selector: HeapSelector,
1535
+ ) -> HeapLocation:
1536
+ if (
1537
+ self.policy.field_sensitivity is FieldSensitivity.BOUNDED_PATH
1538
+ and self.policy.max_selector_depth is not None
1539
+ and len(location.selectors) >= self.policy.max_selector_depth
1540
+ ):
1541
+ selector = HeapSelector.summary()
1542
+ return HeapLocation(location.root, (*location.selectors, selector))
1543
+
1544
+ def _raw_storage_for_local(
1545
+ self,
1546
+ procedure: object,
1547
+ local: object,
1548
+ ) -> tuple[object, ...]:
1549
+ key = self._local_key(procedure, local)
1550
+ name = getattr(local, "name", None)
1551
+ if isinstance(name, str):
1552
+ self._local_names[key] = name
1553
+ override = self.storage_overrides.get(key)
1554
+ if override is not None:
1555
+ return override
1556
+ site = self.allocation_sites.get(key)
1557
+ if site is not None:
1558
+ return self.site_storage[site]
1559
+ # Fallback: name-based lookup for the same variable. The IR may
1560
+ # produce distinct Local nodes (different id()) for the same name
1561
+ # (e.g. CodeParameters vs Assign.expr vs Call args). Try to find
1562
+ # canonical storage before creating a new empty site.
1563
+ canon = self._canonical_storage_for_name(procedure, local)
1564
+ if canon is not None:
1565
+ return canon
1566
+ raw = self._raw_storage_provider(procedure, local)
1567
+ self._assign_site(key, raw)
1568
+ return raw
1569
+
1570
+ def _name_matches_object(self, obj: object, name: str) -> bool:
1571
+ """Check if *obj* has the given *name*, checking both ``.label`` and
1572
+ the ``_object_labels`` override map (set by :meth:`alias_locals`)."""
1573
+ if getattr(obj, "label", None) == name:
1574
+ return True
1575
+ return self._object_labels.get(obj) == name
1576
+
1577
+ def _canonical_storage_for_name(
1578
+ self,
1579
+ procedure: object,
1580
+ local: object,
1581
+ ) -> tuple[object, ...] | None:
1582
+ """Look up storage by variable name across the same procedure.
1583
+
1584
+ Returns the canonical storage for the variable named by *local*
1585
+ if found in either ``storage_overrides`` or ``allocation_sites``
1586
+ under any ``Local`` id for this procedure.
1587
+
1588
+ Search in reverse insertion order so that the most recent
1589
+ assignment to the variable takes precedence over earlier ones.
1590
+ """
1591
+ name = getattr(local, "name", None)
1592
+ if not name:
1593
+ return None
1594
+ proc_id = id(procedure)
1595
+ # Prefer explicit key->name metadata. This distinguishes separate
1596
+ # locals that alias a value from distinct IR nodes for the same local.
1597
+ for key, storage in reversed(list(self.storage_overrides.items())):
1598
+ p_id, _l_id = key
1599
+ if p_id == proc_id and storage and self._local_names.get(key) == name:
1600
+ return storage
1601
+ for key, site in reversed(list(self.allocation_sites.items())):
1602
+ p_id, _l_id = key
1603
+ if p_id != proc_id or self._local_names.get(key) != name:
1604
+ continue
1605
+ stored = self.site_storage.get(site, ())
1606
+ if stored:
1607
+ return tuple(stored)
1608
+ # Search storage_overrides in reverse: most recent binding wins.
1609
+ for (p_id, _l_id), storage in reversed(list(self.storage_overrides.items())):
1610
+ if p_id != proc_id or not storage:
1611
+ continue
1612
+ root = storage[0]
1613
+ if self._name_matches_object(root, name):
1614
+ return storage
1615
+ # Search allocation_sites in reverse for the same reason.
1616
+ for (p_id, _l_id), site in reversed(list(self.allocation_sites.items())):
1617
+ if p_id != proc_id:
1618
+ continue
1619
+ stored = self.site_storage.get(site, ())
1620
+ if not stored:
1621
+ continue
1622
+ root = stored[0]
1623
+ if self._name_matches_object(root, name):
1624
+ return tuple(stored)
1625
+ return None
1626
+
1627
+ def _assign_site(self, key: tuple[int, int], storage: tuple[object, ...]) -> None:
1628
+ site = self.next_site
1629
+ self.next_site += 1
1630
+ self.allocation_sites[key] = site
1631
+ self.site_storage[site] = storage
1632
+ self._incr_site_ref(site)