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