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.
- pyflow/__init__.py +22 -0
- pyflow/_pyflow.py +50 -0
- pyflow/analysis/__init__.py +14 -0
- pyflow/analysis/alias/__init__.py +3 -0
- pyflow/analysis/alias/flow_sensitive/__init__.py +148 -0
- pyflow/analysis/alias/flow_sensitive/_transfer_calls.py +3493 -0
- pyflow/analysis/alias/flow_sensitive/_transfer_ops.py +1969 -0
- pyflow/analysis/alias/flow_sensitive/abstraction.py +1632 -0
- pyflow/analysis/alias/flow_sensitive/heap_analysis.py +280 -0
- pyflow/analysis/alias/flow_sensitive/heap_effects.py +1209 -0
- pyflow/analysis/alias/flow_sensitive/heap_state.py +381 -0
- pyflow/analysis/alias/flow_sensitive/heap_summary.py +299 -0
- pyflow/analysis/alias/flow_sensitive/intrinsics.py +948 -0
- pyflow/analysis/alias/flow_sensitive/model.py +449 -0
- pyflow/analysis/alias/flow_sensitive/points_to_graph.py +722 -0
- pyflow/analysis/alias/flow_sensitive/transfer.py +1483 -0
- pyflow/analysis/alias/kcfa/__init__.py +33 -0
- pyflow/analysis/alias/kcfa/_pythonstan/__init__.py +2 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/__init__.py +1 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/__init__.py +53 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/logging.py +490 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/operation.py +1601 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/pointer_adapter.py +353 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/solver.py +559 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/state.py +701 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/ai/value.py +804 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/analysis.py +86 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/class_hierarchy.py +83 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/closure.py +69 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/__init__.py +4 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/analysis.py +58 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/driver.py +38 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/liveness.py +33 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/reaching_definition.py +52 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/dataflow/solver.py +178 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/defuse.py +46 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/__init__.py +18 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/__init__.py +110 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/analysis.py +382 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/builtin_api_handler.py +1872 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/call_graph_analyzer.py +289 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/class_hierarchy.py +304 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/config.py +139 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/constraints.py +576 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/context.py +496 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/context_selector.py +297 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/debug_monitor.py +34 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/heap_model.py +221 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/ir_translator.py +1181 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/libs/__init__.py +2 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/libs/math.py +244 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/module_analysis.py +269 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/module_summary.py +191 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/object.py +685 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/pointer_flow_graph.py +282 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/points_to_set.py +301 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/__init__.py +21 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/attribute_semantics.py +512 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/compose_processor.py +46 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/container.py +128 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/generator.py +353 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/normal_call.py +664 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/processor.py +36 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/processor/super_resolve.py +131 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/pta_query_adapter.py +329 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/solver.py +917 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/solver_interface.py +76 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/state.py +735 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/unknown_tracker.py +126 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/pointer/kcfa/variable.py +144 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/scope/__init__.py +2 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/scope/analysis.py +37 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/scope/closure.py +81 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/__init__.py +8 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/block_cfg.py +133 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/cfg.py +95 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/driver.py +26 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/__init__.py +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/base.py +112 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/baseprocess.py +524 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/icfg.py +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/icfg_pre_transform.py +69 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/icfg_transform.py +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/postprocessor.py +308 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/icfg/preprocessor.py +375 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/ir.py +361 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/three_address.py +1161 -0
- pyflow/analysis/alias/kcfa/_pythonstan/analysis/transform/transform.py +27 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/__init__.py +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/call_graph/__init__.py +2 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/call_graph/call_edge.py +45 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/call_graph/call_graph.py +77 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/__init__.py +3 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/base_block.py +83 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/cfg.py +200 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/dump.py +20 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/edges.py +162 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/cfg/visualize.py +112 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/dominator_tree.py +126 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/graph.py +81 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/icfg/__init__.py +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/graph/icfg/icfg.py +98 -0
- pyflow/analysis/alias/kcfa/_pythonstan/ir/__init__.py +2 -0
- pyflow/analysis/alias/kcfa/_pythonstan/ir/ir_statements.py +1579 -0
- pyflow/analysis/alias/kcfa/_pythonstan/ir/ir_visitor.py +21 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/LICENSE.pythonstan +28 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/README.md +99 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/abc.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/base64.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/bisect.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/collections.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/contextlib.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/copy.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/csv.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/dataclasses.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/datetime.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/email.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/enum.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/fnmatch.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/functools.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/glob.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/hashlib.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/heapq.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/hmac.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/http.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/importlib.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/inspect.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/io.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/itertools.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/json.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/logging.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/math.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/mimetypes.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/operator.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/os.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/pathlib.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/pickle.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/random.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/re.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/secrets.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/shutil.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/somelib.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/tempfile.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/time.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/traceback.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/types.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/uuid.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/warnings.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/__pycache__/weakref.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/abc.py +80 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/base64.py +62 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/bisect.py +52 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/collections.py +187 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/contextlib.py +113 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/copy.py +15 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/csv.py +163 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/dataclasses.py +150 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/datetime.py +171 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/email.py +270 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/enum.py +144 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/fnmatch.py +14 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/functools.py +84 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/glob.py +10 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/hashlib.py +74 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/heapq.py +91 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/hmac.py +31 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/http.py +317 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/importlib.py +126 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/inspect.py +246 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/io.py +471 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/itertools.py +164 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/json.py +80 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/logging.py +193 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/math.py +244 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/mimetypes.py +119 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/operator.py +310 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/os.py +286 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/pathlib.py +196 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/pickle.py +153 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/random.py +125 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/re.py +125 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/secrets.py +29 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/shutil.py +99 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/somelib.py +2 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/tempfile.py +76 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/time.py +65 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/traceback.py +151 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/types.py +247 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/__init__.py +1 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/__pycache__/__init__.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/__pycache__/parse.cpython-313.pyc +0 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/urllib/parse.py +221 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/uuid.py +34 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/warnings.py +82 -0
- pyflow/analysis/alias/kcfa/_pythonstan/stubs/stdlib/weakref.py +272 -0
- pyflow/analysis/alias/kcfa/_pythonstan/utils/__init__.py +1 -0
- pyflow/analysis/alias/kcfa/_pythonstan/utils/ast_rename.py +35 -0
- pyflow/analysis/alias/kcfa/_pythonstan/utils/common.py +70 -0
- pyflow/analysis/alias/kcfa/_pythonstan/utils/persistent_rb_tree.py +364 -0
- pyflow/analysis/alias/kcfa/_pythonstan/utils/temp_var_generator.py +68 -0
- pyflow/analysis/alias/kcfa/_pythonstan/utils/toposort.py +63 -0
- pyflow/analysis/alias/kcfa/_pythonstan/utils/var_collector.py +72 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/__init__.py +1 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/analysis_manager.py +141 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/class_hierarchy.py +56 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/config.py +93 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/import_manager.py +16 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/namespace.py +327 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/pipeline.py +153 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/scope_manager.py +133 -0
- pyflow/analysis/alias/kcfa/_pythonstan/world/world.py +49 -0
- pyflow/analysis/alias/kcfa/bridge.py +136 -0
- pyflow/analysis/astcollector.py +171 -0
- pyflow/analysis/cache/incremental.py +226 -0
- pyflow/analysis/callgraph/__init__.py +58 -0
- pyflow/analysis/callgraph/ast_based.py +184 -0
- pyflow/analysis/callgraph/callgraph.py +86 -0
- pyflow/analysis/callgraph/constraint_based/__init__.py +26 -0
- pyflow/analysis/callgraph/constraint_based/_analyzer.py +1976 -0
- pyflow/analysis/callgraph/constraint_based/_collector.py +704 -0
- pyflow/analysis/callgraph/constraint_based/_evaluator.py +984 -0
- pyflow/analysis/callgraph/constraint_based/_loader.py +115 -0
- pyflow/analysis/callgraph/constraint_based/_resolver.py +2852 -0
- pyflow/analysis/callgraph/constraint_based/api.py +233 -0
- pyflow/analysis/callgraph/constraint_based/engine.py +717 -0
- pyflow/analysis/callgraph/constraint_based/model.py +303 -0
- pyflow/analysis/callgraph/formats.py +99 -0
- pyflow/analysis/callgraph/pycg_based.py +273 -0
- pyflow/analysis/cdg/__init__.py +52 -0
- pyflow/analysis/cdg/construction.py +256 -0
- pyflow/analysis/cdg/dump.py +415 -0
- pyflow/analysis/cdg/graph.py +456 -0
- pyflow/analysis/cfg/__init__.py +38 -0
- pyflow/analysis/cfg/dfs.py +55 -0
- pyflow/analysis/cfg/dom.py +243 -0
- pyflow/analysis/cfg/dump.py +322 -0
- pyflow/analysis/cfg/expandphi.py +113 -0
- pyflow/analysis/cfg/gc.py +93 -0
- pyflow/analysis/cfg/graph.py +878 -0
- pyflow/analysis/cfg/inline.py +337 -0
- pyflow/analysis/cfg/killflow.py +360 -0
- pyflow/analysis/cfg/optimize.py +164 -0
- pyflow/analysis/cfg/simplify.py +19 -0
- pyflow/analysis/cfg/ssa.py +693 -0
- pyflow/analysis/cfg/ssatransform.py +13 -0
- pyflow/analysis/cfg/structuralanalysis.py +422 -0
- pyflow/analysis/cfg/transform.py +527 -0
- pyflow/analysis/cpa/__init__.py +657 -0
- pyflow/analysis/cpa/base.py +430 -0
- pyflow/analysis/cpa/codecloner.py +262 -0
- pyflow/analysis/cpa/constraintextractor.py +797 -0
- pyflow/analysis/cpa/constraints.py +1071 -0
- pyflow/analysis/cpa/simpleimagebuilder.py +299 -0
- pyflow/analysis/cpasignature.py +132 -0
- pyflow/analysis/cpg/__init__.py +93 -0
- pyflow/analysis/cpg/build.py +221 -0
- pyflow/analysis/cpg/dump.py +102 -0
- pyflow/analysis/cpg/graph.py +1877 -0
- pyflow/analysis/cpg/persist.py +346 -0
- pyflow/analysis/cpg/profiles.py +286 -0
- pyflow/analysis/cpg/rules.py +365 -0
- pyflow/analysis/cpg/taint.py +1934 -0
- pyflow/analysis/dataflowIR/__init__.py +6 -0
- pyflow/analysis/dataflowIR/annotations.py +174 -0
- pyflow/analysis/dataflowIR/convert.py +795 -0
- pyflow/analysis/dataflowIR/dump.py +443 -0
- pyflow/analysis/dataflowIR/graph.py +1648 -0
- pyflow/analysis/dataflowIR/ordering.py +134 -0
- pyflow/analysis/dataflowIR/predicate.py +233 -0
- pyflow/analysis/dataflowIR/transform/__init__.py +15 -0
- pyflow/analysis/dataflowIR/transform/dce.py +215 -0
- pyflow/analysis/dataflowIR/transform/loadelimination.py +185 -0
- pyflow/analysis/dataflowIR/traverse.py +91 -0
- pyflow/analysis/ddg/__init__.py +53 -0
- pyflow/analysis/ddg/construction.py +310 -0
- pyflow/analysis/ddg/dump.py +218 -0
- pyflow/analysis/ddg/graph.py +295 -0
- pyflow/analysis/dump/__init__.py +15 -0
- pyflow/analysis/dump/dumpgraphs.py +93 -0
- pyflow/analysis/dump/dumpreport.py +841 -0
- pyflow/analysis/dump/dumputil.py +98 -0
- pyflow/analysis/fsdf/__init__.py +1107 -0
- pyflow/analysis/fsdf/canonicalset.py +176 -0
- pyflow/analysis/fsdf/canonicaltree.py +1071 -0
- pyflow/analysis/ifds/__init__.py +284 -0
- pyflow/analysis/ifds/annotation_fallback.py +21 -0
- pyflow/analysis/ifds/annotation_synthesis.py +803 -0
- pyflow/analysis/ifds/api.py +567 -0
- pyflow/analysis/ifds/backward_solver.py +328 -0
- pyflow/analysis/ifds/cfg_adapter.py +1575 -0
- pyflow/analysis/ifds/clients/_call_model.py +166 -0
- pyflow/analysis/ifds/clients/_client_common.py +1617 -0
- pyflow/analysis/ifds/clients/flow_path_collector.py +159 -0
- pyflow/analysis/ifds/clients/library_presets.py +1134 -0
- pyflow/analysis/ifds/clients/nullness.py +754 -0
- pyflow/analysis/ifds/clients/registry/__init__.py +26 -0
- pyflow/analysis/ifds/clients/registry/loader.py +688 -0
- pyflow/analysis/ifds/clients/registry/validate.py +37 -0
- pyflow/analysis/ifds/clients/taint.py +782 -0
- pyflow/analysis/ifds/clients/typestate.py +1550 -0
- pyflow/analysis/ifds/clients/typestate_engine.py +534 -0
- pyflow/analysis/ifds/diagnostics.py +29 -0
- pyflow/analysis/ifds/preparation.py +97 -0
- pyflow/analysis/ifds/problem.py +363 -0
- pyflow/analysis/ifds/queries.py +142 -0
- pyflow/analysis/ifds/reporting.py +289 -0
- pyflow/analysis/ifds/shadow_scan.py +349 -0
- pyflow/analysis/ifds/solver.py +1495 -0
- pyflow/analysis/ifds/supergraph.py +186 -0
- pyflow/analysis/ifds/transfers.py +187 -0
- pyflow/analysis/ipa/__init__.py +77 -0
- pyflow/analysis/ipa/calling/__init__.py +17 -0
- pyflow/analysis/ipa/calling/callbinder.py +93 -0
- pyflow/analysis/ipa/calling/cpa.py +197 -0
- pyflow/analysis/ipa/calling/transfer.py +208 -0
- pyflow/analysis/ipa/constraintextractor.py +804 -0
- pyflow/analysis/ipa/constraints/__init__.py +18 -0
- pyflow/analysis/ipa/constraints/base.py +66 -0
- pyflow/analysis/ipa/constraints/calls.py +424 -0
- pyflow/analysis/ipa/constraints/flow.py +445 -0
- pyflow/analysis/ipa/constraints/node.py +299 -0
- pyflow/analysis/ipa/constraints/qualifiers.py +17 -0
- pyflow/analysis/ipa/constraints/split.py +178 -0
- pyflow/analysis/ipa/dump.py +246 -0
- pyflow/analysis/ipa/entrypointbuilder.py +52 -0
- pyflow/analysis/ipa/escape/__init__.py +19 -0
- pyflow/analysis/ipa/escape/objectescape.py +77 -0
- pyflow/analysis/ipa/ipanalysis.py +319 -0
- pyflow/analysis/ipa/memory/__init__.py +16 -0
- pyflow/analysis/ipa/memory/extractorpolicy.py +38 -0
- pyflow/analysis/ipa/memory/storegraphpolicy.py +17 -0
- pyflow/analysis/ipa/model/__init__.py +18 -0
- pyflow/analysis/ipa/model/context.py +277 -0
- pyflow/analysis/ipa/model/invocation.py +159 -0
- pyflow/analysis/ipa/model/object.py +102 -0
- pyflow/analysis/ipa/model/objectname.py +77 -0
- pyflow/analysis/ipa/model/region.py +51 -0
- pyflow/analysis/ipa/summary/__init__.py +120 -0
- pyflow/analysis/ir_utils.py +215 -0
- pyflow/analysis/lifetimeanalysis/__init__.py +935 -0
- pyflow/analysis/lifetimeanalysis/database/__init__.py +17 -0
- pyflow/analysis/lifetimeanalysis/database/base.py +72 -0
- pyflow/analysis/lifetimeanalysis/database/lattice.py +189 -0
- pyflow/analysis/lifetimeanalysis/database/mapping.py +174 -0
- pyflow/analysis/lifetimeanalysis/database/structure.py +224 -0
- pyflow/analysis/lifetimeanalysis/database/tupleset.py +141 -0
- pyflow/analysis/numbering/__init__.py +17 -0
- pyflow/analysis/numbering/dataflow.py +214 -0
- pyflow/analysis/numbering/dominance.py +120 -0
- pyflow/analysis/numbering/readmodify.py +251 -0
- pyflow/analysis/numbering/ssa.py +604 -0
- pyflow/analysis/pdg/__init__.py +55 -0
- pyflow/analysis/pdg/construction.py +447 -0
- pyflow/analysis/pdg/cypher.py +1150 -0
- pyflow/analysis/pdg/dump.py +129 -0
- pyflow/analysis/pdg/graph.py +308 -0
- pyflow/analysis/programculler.py +224 -0
- pyflow/analysis/shape/__init__.py +466 -0
- pyflow/analysis/shape/constraintbuilder.py +933 -0
- pyflow/analysis/shape/constraints.py +510 -0
- pyflow/analysis/shape/dataflow.py +202 -0
- pyflow/analysis/shape/model/__init__.py +27 -0
- pyflow/analysis/shape/model/canonical.py +137 -0
- pyflow/analysis/shape/model/configuration.py +78 -0
- pyflow/analysis/shape/model/expressions.py +268 -0
- pyflow/analysis/shape/model/pathinformation.py +645 -0
- pyflow/analysis/shape/model/referencecount.py +212 -0
- pyflow/analysis/shape/model/secondary.py +31 -0
- pyflow/analysis/shape/model/slots.py +69 -0
- pyflow/analysis/shape/regionanalysis.py +152 -0
- pyflow/analysis/shape/transferfunctions.py +353 -0
- pyflow/analysis/storegraph/__init__.py +19 -0
- pyflow/analysis/storegraph/annotations.py +70 -0
- pyflow/analysis/storegraph/canonicalobjects.py +329 -0
- pyflow/analysis/storegraph/extendedtypes.py +311 -0
- pyflow/analysis/storegraph/setmanager.py +123 -0
- pyflow/analysis/storegraph/storegraph.py +577 -0
- pyflow/analysis/tools.py +112 -0
- pyflow/analysis/typeinfo/__init__.py +93 -0
- pyflow/analysis/typeinfo/_config.py +84 -0
- pyflow/analysis/typeinfo/annotation_resolver.py +483 -0
- pyflow/analysis/typeinfo/collector.py +361 -0
- pyflow/analysis/typeinfo/constants.py +378 -0
- pyflow/analysis/typeinfo/docstring_parser.py +289 -0
- pyflow/analysis/typeinfo/generic_binder.py +145 -0
- pyflow/analysis/typeinfo/gradual_typing.py +255 -0
- pyflow/analysis/typeinfo/models.py +43 -0
- pyflow/analysis/typeinfo/service.py +774 -0
- pyflow/analysis/typeinfo/string_subtype_inference.py +113 -0
- pyflow/analysis/typeinfo/string_subtypes.py +150 -0
- pyflow/analysis/typeinfo/stub_loader.py +667 -0
- pyflow/analysis/typeinfo/type_inference.py +747 -0
- pyflow/analysis/typeinfo/type_utils.py +361 -0
- pyflow/analysis/typeinfo/typesystem.py +2346 -0
- pyflow/analysis/typeinfo/typetracing.py +672 -0
- pyflow/api/__init__.py +94 -0
- pyflow/api/entrypoints/__init__.py +36 -0
- pyflow/api/entrypoints/declaration.py +345 -0
- pyflow/api/entrypoints/entry_point.py +59 -0
- pyflow/api/entrypoints/wrappers.py +83 -0
- pyflow/api/queries/__init__.py +65 -0
- pyflow/api/queries/_cfg_utils.py +53 -0
- pyflow/api/queries/_dataflow_ipa.py +29 -0
- pyflow/api/queries/_dataflow_reaching_defs.py +105 -0
- pyflow/api/queries/_dataflow_store_graph.py +152 -0
- pyflow/api/queries/_dataflow_taint.py +76 -0
- pyflow/api/queries/_models.py +177 -0
- pyflow/api/queries/call_graph.py +168 -0
- pyflow/api/queries/capabilities.py +106 -0
- pyflow/api/queries/context.py +152 -0
- pyflow/api/queries/control_flow.py +35 -0
- pyflow/api/queries/data_flow.py +157 -0
- pyflow/api/queries/engine.py +201 -0
- pyflow/api/queries/localization.py +8 -0
- pyflow/api/queries/service.py +339 -0
- pyflow/api/queries/tasks/__init__.py +8 -0
- pyflow/api/queries/tasks/code_localization.py +425 -0
- pyflow/api/queries/tasks/test_generation.py +338 -0
- pyflow/api/queries/test_generation.py +8 -0
- pyflow/application/__init__.py +109 -0
- pyflow/application/context.py +116 -0
- pyflow/application/errors.py +55 -0
- pyflow/application/makefile.py +213 -0
- pyflow/application/passes.py +515 -0
- pyflow/application/passmanager.py +755 -0
- pyflow/application/pipeline.py +459 -0
- pyflow/application/program.py +147 -0
- pyflow/checker/__init__.py +51 -0
- pyflow/checker/formatters/__init__.py +14 -0
- pyflow/checker/formatters/json.py +153 -0
- pyflow/checker/formatters/sarif.py +263 -0
- pyflow/checker/formatters/text.py +168 -0
- pyflow/checker/formatters/utils.py +17 -0
- pyflow/checker/llm/__init__.py +19 -0
- pyflow/checker/llm/exploit.py +129 -0
- pyflow/checker/llm/judge.py +105 -0
- pyflow/checker/llm/llm_utils.py +131 -0
- pyflow/checker/microbench.py +338 -0
- pyflow/checker/pattern/__init__.py +14 -0
- pyflow/checker/pattern/checkers/__init__.py +34 -0
- pyflow/checker/pattern/checkers/aws_security.py +234 -0
- pyflow/checker/pattern/checkers/blacklist_calls.py +54 -0
- pyflow/checker/pattern/checkers/blacklist_imports.py +73 -0
- pyflow/checker/pattern/checkers/class_pollution.py +459 -0
- pyflow/checker/pattern/checkers/command_injection.py +340 -0
- pyflow/checker/pattern/checkers/deserialization.py +373 -0
- pyflow/checker/pattern/checkers/django_security.py +285 -0
- pyflow/checker/pattern/checkers/exception_handling.py +317 -0
- pyflow/checker/pattern/checkers/exec_use.py +16 -0
- pyflow/checker/pattern/checkers/fastapi_security.py +291 -0
- pyflow/checker/pattern/checkers/flask_security.py +264 -0
- pyflow/checker/pattern/checkers/hardcoded_password.py +239 -0
- pyflow/checker/pattern/checkers/hardcoded_secrets.py +279 -0
- pyflow/checker/pattern/checkers/ldap_injection.py +281 -0
- pyflow/checker/pattern/checkers/path_traversal.py +124 -0
- pyflow/checker/pattern/checkers/shell_injection.py +46 -0
- pyflow/checker/pattern/checkers/sql_injection.py +170 -0
- pyflow/checker/pattern/checkers/sql_injection_enhanced.py +574 -0
- pyflow/checker/pattern/checkers/ssrf.py +419 -0
- pyflow/checker/pattern/checkers/template_security.py +310 -0
- pyflow/checker/pattern/checkers/weak_crypto.py +89 -0
- pyflow/checker/pattern/checkers/xxe_vulnerabilities.py +155 -0
- pyflow/checker/pattern/core/__init__.py +17 -0
- pyflow/checker/pattern/core/blacklist.py +569 -0
- pyflow/checker/pattern/core/config.py +15 -0
- pyflow/checker/pattern/core/constants.py +52 -0
- pyflow/checker/pattern/core/context.py +276 -0
- pyflow/checker/pattern/core/issue.py +325 -0
- pyflow/checker/pattern/core/manager.py +253 -0
- pyflow/checker/pattern/core/metrics.py +78 -0
- pyflow/checker/pattern/core/node_visitor.py +430 -0
- pyflow/checker/pattern/core/test_loader.py +41 -0
- pyflow/checker/pattern/core/test_properties.py +54 -0
- pyflow/checker/pattern/core/test_set.py +33 -0
- pyflow/checker/pattern/core/tester.py +244 -0
- pyflow/checker/pattern/core/utils.py +167 -0
- pyflow/checker/quality.py +374 -0
- pyflow/checker/semantic/__init__.py +16 -0
- pyflow/checker/semantic/core/__init__.py +23 -0
- pyflow/checker/semantic/core/base.py +26 -0
- pyflow/checker/semantic/core/context.py +270 -0
- pyflow/checker/semantic/core/issue.py +9 -0
- pyflow/checker/semantic/core/manager.py +150 -0
- pyflow/checker/semantic/core/runner.py +58 -0
- pyflow/checker/semantic/detectors/__init__.py +17 -0
- pyflow/checker/semantic/detectors/ast_helpers.py +114 -0
- pyflow/checker/semantic/detectors/leak.py +500 -0
- pyflow/checker/semantic/detectors/null_dereference.py +132 -0
- pyflow/checker/semantic/detectors/semantic_taint.py +2258 -0
- pyflow/checker/supply_chain.py +568 -0
- pyflow/cli/__init__.py +12 -0
- pyflow/cli/alias.py +397 -0
- pyflow/cli/callgraph.py +185 -0
- pyflow/cli/ir.py +461 -0
- pyflow/cli/main.py +125 -0
- pyflow/cli/optimize.py +603 -0
- pyflow/cli/security.py +1262 -0
- pyflow/cli/supply_chain.py +114 -0
- pyflow/config/nullness/django.json +21 -0
- pyflow/config/nullness/fastapi.json +17 -0
- pyflow/config/nullness/flask.json +37 -0
- pyflow/config/nullness/sql.json +21 -0
- pyflow/config/nullness/sqlalchemy.json +33 -0
- pyflow/config/nullness/stdlib.json +161 -0
- pyflow/config/rule-pack.schema.json +54 -0
- pyflow/config/taint/aiohttp.json +640 -0
- pyflow/config/taint/cloud.json +485 -0
- pyflow/config/taint/concurrency.json +25 -0
- pyflow/config/taint/django.json +710 -0
- pyflow/config/taint/falcon.json +375 -0
- pyflow/config/taint/fastapi.json +192 -0
- pyflow/config/taint/flask.json +504 -0
- pyflow/config/taint/injection.json +2294 -0
- pyflow/config/taint/network.json +367 -0
- pyflow/config/taint/nosql.json +166 -0
- pyflow/config/taint/pandas.json +463 -0
- pyflow/config/taint/requests.json +952 -0
- pyflow/config/taint/serialization.json +109 -0
- pyflow/config/taint/sql.json +730 -0
- pyflow/config/taint/sqlalchemy.json +235 -0
- pyflow/config/taint/stdlib.json +4388 -0
- pyflow/config/taint/tornado.json +151 -0
- pyflow/config/taint/xml.json +89 -0
- pyflow/config/typestate/concurrency.json +145 -0
- pyflow/config/typestate/network.json +130 -0
- pyflow/config/typestate/requests.json +88 -0
- pyflow/config/typestate/sql.json +107 -0
- pyflow/config/typestate/sqlalchemy.json +45 -0
- pyflow/config/typestate/stdlib.json +91 -0
- pyflow/config.py +30 -0
- pyflow/frontend/__init__.py +28 -0
- pyflow/frontend/ast_converter.py +2593 -0
- pyflow/frontend/class_hierarchy.py +665 -0
- pyflow/frontend/dependency_resolver.py +1836 -0
- pyflow/frontend/function_extractor.py +602 -0
- pyflow/frontend/object_manager.py +224 -0
- pyflow/frontend/programextractor.py +1033 -0
- pyflow/frontend/project_resolution.py +511 -0
- pyflow/frontend/source_locator.py +163 -0
- pyflow/frontend/stub_manager.py +425 -0
- pyflow/language/__init__.py +5 -0
- pyflow/language/asttools/__init__.py +34 -0
- pyflow/language/asttools/annotation.py +242 -0
- pyflow/language/asttools/antlrtranslator.py +344 -0
- pyflow/language/asttools/astpprint.py +132 -0
- pyflow/language/asttools/complexity.py +224 -0
- pyflow/language/asttools/decorators.py +71 -0
- pyflow/language/asttools/metaast/__init__.py +370 -0
- pyflow/language/asttools/metaast/codegeneration.py +622 -0
- pyflow/language/asttools/origin.py +83 -0
- pyflow/language/asttools/symbols.py +204 -0
- pyflow/language/asttools/visitors.py +179 -0
- pyflow/language/modules/__init__.py +18 -0
- pyflow/language/modules/_scope_graph.py +484 -0
- pyflow/language/modules/ast_helper.py +211 -0
- pyflow/language/modules/module_definitions.py +240 -0
- pyflow/language/modules/project_handler.py +110 -0
- pyflow/language/modules/transform.py +290 -0
- pyflow/language/modules/util.py +413 -0
- pyflow/language/python/__init__.py +21 -0
- pyflow/language/python/annotations.py +378 -0
- pyflow/language/python/ast.py +749 -0
- pyflow/language/python/collapser.py +604 -0
- pyflow/language/python/default_markers.py +8 -0
- pyflow/language/python/defuse.py +469 -0
- pyflow/language/python/fold.py +220 -0
- pyflow/language/python/program.py +371 -0
- pyflow/language/python/pythonbase.py +353 -0
- pyflow/language/python/pythonoutput.py +82 -0
- pyflow/language/python/simplecodegen.py +975 -0
- pyflow/optimization/__init__.py +29 -0
- pyflow/optimization/argumentnormalization.py +429 -0
- pyflow/optimization/callconverter.py +295 -0
- pyflow/optimization/clone.py +743 -0
- pyflow/optimization/codeinlining.py +587 -0
- pyflow/optimization/convertboolelimination.py +111 -0
- pyflow/optimization/cullprogram.py +155 -0
- pyflow/optimization/dataflow/__init__.py +17 -0
- pyflow/optimization/dataflow/base.py +780 -0
- pyflow/optimization/dataflow/forward.py +681 -0
- pyflow/optimization/dataflow/reverse.py +454 -0
- pyflow/optimization/dce.py +354 -0
- pyflow/optimization/fold.py +780 -0
- pyflow/optimization/loadelimination.py +342 -0
- pyflow/optimization/methodcall.py +554 -0
- pyflow/optimization/rewrite.py +129 -0
- pyflow/optimization/simplify.py +143 -0
- pyflow/optimization/storeelimination.py +129 -0
- pyflow/optimization/termrewrite.py +287 -0
- pyflow/stats/__init__.py +436 -0
- pyflow/stubs/__init__.py +34 -0
- pyflow/stubs/__pycache__/__init__.cpython-313.pyc +0 -0
- pyflow/stubs/__pycache__/lltranslator.cpython-313.pyc +0 -0
- pyflow/stubs/__pycache__/stubcollector.cpython-313.pyc +0 -0
- pyflow/stubs/lltranslator.py +493 -0
- pyflow/stubs/std/__init__.py +83 -0
- pyflow/stubs/std/__pycache__/__init__.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/argparse_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/collections_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/container.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/copy_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/csv_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/datetime_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/functools_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/hashlib_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/interpreter.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/io_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/itertools_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/json_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/llfunc.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/logging_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/mathstubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/operator_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/os_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/pathlib_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/pickle_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/random.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/re_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/sampler.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/shutil_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/subprocess_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/sys_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/tempfile_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/threading_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/__pycache__/time_stubs.cpython-313.pyc +0 -0
- pyflow/stubs/std/argparse_stubs.py +200 -0
- pyflow/stubs/std/collections_stubs.py +77 -0
- pyflow/stubs/std/container.py +374 -0
- pyflow/stubs/std/copy_stubs.py +54 -0
- pyflow/stubs/std/csv_stubs.py +187 -0
- pyflow/stubs/std/datetime_stubs.py +70 -0
- pyflow/stubs/std/functools_stubs.py +61 -0
- pyflow/stubs/std/hashlib_stubs.py +198 -0
- pyflow/stubs/std/interpreter.py +360 -0
- pyflow/stubs/std/io_stubs.py +339 -0
- pyflow/stubs/std/itertools_stubs.py +89 -0
- pyflow/stubs/std/json_stubs.py +40 -0
- pyflow/stubs/std/llfunc.py +240 -0
- pyflow/stubs/std/logging_stubs.py +295 -0
- pyflow/stubs/std/mathstubs.py +393 -0
- pyflow/stubs/std/objects/__init__.py +9 -0
- pyflow/stubs/std/objects/__pycache__/__init__.cpython-313.pyc +0 -0
- pyflow/stubs/std/objects/__pycache__/float.cpython-313.pyc +0 -0
- pyflow/stubs/std/objects/__pycache__/int.cpython-313.pyc +0 -0
- pyflow/stubs/std/objects/__pycache__/str.cpython-313.pyc +0 -0
- pyflow/stubs/std/objects/float.py +248 -0
- pyflow/stubs/std/objects/int.py +221 -0
- pyflow/stubs/std/objects/str.py +175 -0
- pyflow/stubs/std/operator_stubs.py +148 -0
- pyflow/stubs/std/os_stubs.py +727 -0
- pyflow/stubs/std/pathlib_stubs.py +364 -0
- pyflow/stubs/std/pickle_stubs.py +97 -0
- pyflow/stubs/std/random.py +29 -0
- pyflow/stubs/std/re_stubs.py +178 -0
- pyflow/stubs/std/sampler.py +17 -0
- pyflow/stubs/std/shutil_stubs.py +212 -0
- pyflow/stubs/std/subprocess_stubs.py +149 -0
- pyflow/stubs/std/sys_stubs.py +226 -0
- pyflow/stubs/std/tempfile_stubs.py +129 -0
- pyflow/stubs/std/threading_stubs.py +328 -0
- pyflow/stubs/std/time_stubs.py +283 -0
- pyflow/stubs/stubcollector.py +463 -0
- pyflow/util/PADS/Automata.py +640 -0
- pyflow/util/PADS/BFS.py +28 -0
- pyflow/util/PADS/Biconnectivity.py +270 -0
- pyflow/util/PADS/Bipartite.py +89 -0
- pyflow/util/PADS/BipartiteMatching.py +129 -0
- pyflow/util/PADS/CardinalityMatching.py +316 -0
- pyflow/util/PADS/Chordal.py +33 -0
- pyflow/util/PADS/CubicHam.py +351 -0
- pyflow/util/PADS/DFS.py +100 -0
- pyflow/util/PADS/Eratosthenes.py +138 -0
- pyflow/util/PADS/GraphExamples.py +36 -0
- pyflow/util/PADS/Graphs.py +82 -0
- pyflow/util/PADS/GrayGraph.py +36 -0
- pyflow/util/PADS/IntegerPartitions.py +317 -0
- pyflow/util/PADS/LCA.py +349 -0
- pyflow/util/PADS/LCS.py +38 -0
- pyflow/util/PADS/LexBFS.py +32 -0
- pyflow/util/PADS/LongestIncreasingSubsequence.py +65 -0
- pyflow/util/PADS/Medium.py +414 -0
- pyflow/util/PADS/MinimumSpanningTree.py +26 -0
- pyflow/util/PADS/OrderedSequence.py +133 -0
- pyflow/util/PADS/PartialCube.py +227 -0
- pyflow/util/PADS/PartialOrder.py +129 -0
- pyflow/util/PADS/PartitionRefinement.py +85 -0
- pyflow/util/PADS/ReadUndirectedGraph.py +431 -0
- pyflow/util/PADS/Repetitivity.py +160 -0
- pyflow/util/PADS/SMAWK.py +190 -0
- pyflow/util/PADS/Sequence.py +118 -0
- pyflow/util/PADS/StrongConnectivity.py +106 -0
- pyflow/util/PADS/Sudoku.py +2028 -0
- pyflow/util/PADS/UnionFind.py +63 -0
- pyflow/util/PADS/Util.py +28 -0
- pyflow/util/PADS/Wrap.py +79 -0
- pyflow/util/PADS/__init__.py +13 -0
- pyflow/util/PADS/xyzGraph.py +127 -0
- pyflow/util/__init__.py +19 -0
- pyflow/util/antlr3/__init__.py +134 -0
- pyflow/util/antlr3/compat.py +12 -0
- pyflow/util/antlr3/constants.py +24 -0
- pyflow/util/antlr3/dfa.py +179 -0
- pyflow/util/antlr3/dottreegen.py +170 -0
- pyflow/util/antlr3/exceptions.py +346 -0
- pyflow/util/antlr3/extras.py +11 -0
- pyflow/util/antlr3/main.py +198 -0
- pyflow/util/antlr3/recognizers.py +1341 -0
- pyflow/util/antlr3/streams.py +1250 -0
- pyflow/util/antlr3/tokens.py +370 -0
- pyflow/util/antlr3/tree.py +2190 -0
- pyflow/util/antlr3/treewizard.py +566 -0
- pyflow/util/application/__init__.py +13 -0
- pyflow/util/application/async_utils.py +101 -0
- pyflow/util/application/compilerexceptions.py +21 -0
- pyflow/util/application/console.py +235 -0
- pyflow/util/application/errorhandler.py +241 -0
- pyflow/util/canonical.py +204 -0
- pyflow/util/debug/__init__.py +11 -0
- pyflow/util/debug/blame.py +94 -0
- pyflow/util/debug/debugger.py +105 -0
- pyflow/util/debug/trace.py +56 -0
- pyflow/util/graphalgorithim/__init__.py +17 -0
- pyflow/util/graphalgorithim/basic.py +77 -0
- pyflow/util/graphalgorithim/color.py +104 -0
- pyflow/util/graphalgorithim/djtree.py +299 -0
- pyflow/util/graphalgorithim/dominator.py +531 -0
- pyflow/util/graphalgorithim/exclusiongraph.py +624 -0
- pyflow/util/graphalgorithim/merge.py +221 -0
- pyflow/util/io/__init__.py +6 -0
- pyflow/util/io/dot.py +540 -0
- pyflow/util/io/filesystem.py +210 -0
- pyflow/util/io/formatting.py +71 -0
- pyflow/util/io/report.py +224 -0
- pyflow/util/io/xmloutput.py +270 -0
- pyflow/util/json_dict.py +92 -0
- pyflow/util/monkeypatch/__init__.py +21 -0
- pyflow/util/monkeypatch/xcollections.py +206 -0
- pyflow/util/monkeypatch/xmath.py +108 -0
- pyflow/util/monkeypatch/xnamedtuple.py +180 -0
- pyflow/util/monkeypatch/xtypes.py +56 -0
- pyflow/util/orderedset.py +420 -0
- pyflow/util/pydot.py +1998 -0
- pyflow/util/python/__init__.py +136 -0
- pyflow/util/python/apply.py +180 -0
- pyflow/util/python/calling.py +558 -0
- pyflow/util/python/opnames.py +153 -0
- pyflow/util/randomness.py +230 -0
- pyflow/util/singleton.py +112 -0
- pyflow/util/tvl.py +321 -0
- pyflow/util/typedispatch.py +342 -0
- pyflow/util/unionfind.py +169 -0
- pyflow_analysis-0.1.0.dist-info/METADATA +184 -0
- pyflow_analysis-0.1.0.dist-info/RECORD +757 -0
- pyflow_analysis-0.1.0.dist-info/WHEEL +5 -0
- pyflow_analysis-0.1.0.dist-info/entry_points.txt +2 -0
- pyflow_analysis-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
- 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)
|