gitnexus 1.6.8-rc.9 → 1.6.8
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.
- package/README.md +3 -3
- package/dist/_shared/graph/types.d.ts +31 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +11 -1
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +6 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +31 -1
- package/dist/cli/ai-context.js +18 -16
- package/dist/cli/analyze-config.js +11 -0
- package/dist/cli/analyze.d.ts +14 -0
- package/dist/cli/analyze.js +98 -0
- package/dist/cli/embedding-dims.d.ts +33 -0
- package/dist/cli/embedding-dims.js +39 -0
- package/dist/cli/eval-server.d.ts +8 -0
- package/dist/cli/eval-server.js +242 -2
- package/dist/cli/help-i18n.js +19 -0
- package/dist/cli/i18n/en.d.ts +15 -1
- package/dist/cli/i18n/en.js +15 -1
- package/dist/cli/i18n/resources.d.ts +29 -1
- package/dist/cli/i18n/zh-CN.d.ts +14 -0
- package/dist/cli/i18n/zh-CN.js +15 -1
- package/dist/cli/index.js +89 -1
- package/dist/cli/mcp.d.ts +6 -1
- package/dist/cli/mcp.js +26 -1
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/setup.d.ts +3 -1
- package/dist/cli/setup.js +67 -17
- package/dist/cli/skill-gen.js +2 -1
- package/dist/cli/tool.d.ts +19 -1
- package/dist/cli/tool.js +96 -3
- package/dist/core/embeddings/hf-env.d.ts +0 -66
- package/dist/core/embeddings/http-client.d.ts +7 -0
- package/dist/core/embeddings/http-client.js +5 -3
- package/dist/core/embeddings/server-mapping.d.ts +1 -1
- package/dist/core/embeddings/server-mapping.js +7 -3
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +5 -1
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +22 -4
- package/dist/core/group/extractors/include-extractor.js +22 -13
- package/dist/core/group/sync.d.ts +27 -1
- package/dist/core/group/sync.js +0 -0
- package/dist/core/incremental/subgraph-extract.js +18 -1
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
- package/dist/core/ingestion/cfg/collect.d.ts +51 -0
- package/dist/core/ingestion/cfg/collect.js +77 -0
- package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
- package/dist/core/ingestion/cfg/control-dependence.js +182 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
- package/dist/core/ingestion/cfg/emit.d.ts +293 -0
- package/dist/core/ingestion/cfg/emit.js +646 -0
- package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
- package/dist/core/ingestion/cfg/post-dominators.js +176 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
- package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
- package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +274 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
- package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
- package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
- package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
- package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/go.js +638 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
- package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
- package/dist/core/ingestion/cfg/visitors/java.js +812 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
- package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
- package/dist/core/ingestion/cfg/visitors/php.js +721 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
- package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
- package/dist/core/ingestion/cfg/visitors/python.js +558 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
- package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
- package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
- package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
- package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
- package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
- package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
- package/dist/core/ingestion/import-resolvers/utils.js +2 -0
- package/dist/core/ingestion/language-provider.d.ts +11 -0
- package/dist/core/ingestion/languages/c-cpp.js +6 -1
- package/dist/core/ingestion/languages/cobol.js +4 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
- package/dist/core/ingestion/languages/cpp/captures.js +50 -1
- package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
- package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
- package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
- package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
- package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
- package/dist/core/ingestion/languages/csharp.js +2 -0
- package/dist/core/ingestion/languages/dart.js +2 -0
- package/dist/core/ingestion/languages/go.js +2 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +3 -0
- package/dist/core/ingestion/languages/php.js +2 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/ruby.js +2 -0
- package/dist/core/ingestion/languages/rust.js +2 -0
- package/dist/core/ingestion/languages/swift.js +2 -0
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/languages/vue.js +6 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/mro-processor.js +109 -0
- package/dist/core/ingestion/parsing-processor.js +23 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/index.js +2 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
- package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
- package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
- package/dist/core/ingestion/pipeline.d.ts +92 -10
- package/dist/core/ingestion/pipeline.js +11 -2
- package/dist/core/ingestion/scope-extractor.js +12 -2
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
- package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
- package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
- package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
- package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
- package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
- package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
- package/dist/core/ingestion/taint/call-summary-model.js +25 -0
- package/dist/core/ingestion/taint/emit.d.ts +125 -0
- package/dist/core/ingestion/taint/emit.js +208 -0
- package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
- package/dist/core/ingestion/taint/interproc-emit.js +88 -0
- package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
- package/dist/core/ingestion/taint/interproc-solver.js +312 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +659 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
- package/dist/core/ingestion/taint/source-sink-config.js +35 -12
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
- package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
- package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
- package/dist/core/ingestion/taint/summary-harvest.js +537 -0
- package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
- package/dist/core/ingestion/taint/summary-model.js +86 -0
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
- package/dist/core/ingestion/utils/env.d.ts +7 -0
- package/dist/core/ingestion/utils/env.js +12 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
- package/dist/core/ingestion/workers/parse-worker.js +76 -17
- package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
- package/dist/core/ingestion/workers/worker-pool.js +15 -2
- package/dist/core/lbug/csv-generator.d.ts +51 -3
- package/dist/core/lbug/csv-generator.js +369 -255
- package/dist/core/lbug/lbug-adapter.d.ts +58 -18
- package/dist/core/lbug/lbug-adapter.js +346 -84
- package/dist/core/lbug/lbug-config.d.ts +19 -0
- package/dist/core/lbug/lbug-config.js +29 -0
- package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
- package/dist/core/lbug/pdg-emit-sink.js +358 -0
- package/dist/core/lbug/pool-adapter.d.ts +35 -0
- package/dist/core/lbug/pool-adapter.js +90 -1
- package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
- package/dist/core/lbug/rel-pair-routing.js +140 -0
- package/dist/core/lbug/schema.d.ts +2 -2
- package/dist/core/lbug/schema.js +3 -0
- package/dist/core/logger.d.ts +0 -5
- package/dist/core/run-analyze.d.ts +96 -0
- package/dist/core/run-analyze.js +207 -3
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/core/wiki/generator.js +3 -1
- package/dist/core/wiki/graph-queries.d.ts +6 -0
- package/dist/core/wiki/graph-queries.js +9 -1
- package/dist/mcp/http-transport.d.ts +134 -0
- package/dist/mcp/http-transport.js +503 -0
- package/dist/mcp/local/local-backend.d.ts +110 -24
- package/dist/mcp/local/local-backend.js +1225 -19
- package/dist/mcp/local/pdg-impact.d.ts +384 -0
- package/dist/mcp/local/pdg-impact.js +1972 -0
- package/dist/mcp/resources.js +13 -1
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.js +214 -7
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/api.d.ts +17 -0
- package/dist/server/api.js +132 -168
- package/dist/server/git-clone.d.ts +43 -2
- package/dist/server/git-clone.js +211 -16
- package/dist/server/mcp-http.d.ts +5 -6
- package/dist/server/mcp-http.js +9 -82
- package/dist/server/middleware.d.ts +40 -0
- package/dist/server/middleware.js +92 -0
- package/dist/server/private-ip.d.ts +1 -0
- package/dist/server/private-ip.js +15 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +37 -0
- package/dist/server/upload-paths.js +57 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/git.js +5 -8
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +41 -10
- package/dist/storage/repo-manager.d.ts +121 -10
- package/dist/storage/repo-manager.js +43 -5
- package/dist/types/pipeline.d.ts +9 -0
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
- package/hooks/claude/gitnexus-hook.cjs +110 -13
- package/hooks/claude/hook-db-lock-probe.cjs +513 -26
- package/package.json +8 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/scripts/cross-platform-tests.ts +1 -0
- package/skills/gitnexus-debugging.md +16 -4
- package/skills/gitnexus-exploring.md +3 -3
- package/skills/gitnexus-guide.md +33 -0
- package/skills/gitnexus-pdg-query.md +89 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/skills/gitnexus-taint-analysis.md +178 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
- package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
- package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
- package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
- package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
- package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
- package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
- package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
- package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
- package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
- package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
- package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
- package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
- package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
- package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
- package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
- package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
- package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
- package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
- package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
- package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
- package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
- package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
- package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
- package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
- package/web/assets/index-BvYev1UJ.js +635 -0
- package/web/assets/index-_lgn7hs5.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
- package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
- package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
- package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
- package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
- package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
- package/web/index.html +13 -15
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
- package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
- package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
- package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
- package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
- package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
- package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
- package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
- package/web/assets/chunk-CilyBKbf.js +0 -1
- package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
- package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
- package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
- package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
- package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
- package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
- package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
- package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
- package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
- package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
- package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
- package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
- package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
- package/web/assets/index-BKWA-m7o.css +0 -2
- package/web/assets/index-CG6q8eTs.js +0 -626
- package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
- /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python CfgVisitor — the most STRUCTURALLY DIVERGENT CFG target (PDG layer
|
|
3
|
+
* beyond the C-family). Python has no braces (suites are indented `block` nodes),
|
|
4
|
+
* `elif` clauses, `for`/`while ... else` (the else runs on NORMAL completion, not
|
|
5
|
+
* on `break`), `with` (deterministic `__exit__` on both normal AND exception
|
|
6
|
+
* exit — a try/finally analogue), `try` / `except` / `except-group` (except-star)
|
|
7
|
+
* / `else` / `finally`, and `match`/`case` (no fallthrough). A good stress test
|
|
8
|
+
* that the shared CFG core
|
|
9
|
+
* carries no hidden brace-family assumptions.
|
|
10
|
+
*
|
|
11
|
+
* Walks a Python `function_definition` / `lambda`'s tree-sitter AST and drives
|
|
12
|
+
* the language-agnostic {@link CfgBuilder} to produce a serializable
|
|
13
|
+
* {@link FunctionCfg}, plus a def/use harvest ({@link PythonHarvester}) for the
|
|
14
|
+
* reaching-defs / CDG solvers. Structured like the C-family visitors — a
|
|
15
|
+
* `visit_<node_type>` dispatch over the statement taxonomy, driving a
|
|
16
|
+
* per-function {@link ControlFlowContext} for break/continue and the `with` /
|
|
17
|
+
* `finally` completion chain (finalizer route-through). NO call-site `sites[]`
|
|
18
|
+
* are harvested (taint substrate is a later step — see python-harvest.ts).
|
|
19
|
+
*
|
|
20
|
+
* Every node type and field literal below was grammar-validated against
|
|
21
|
+
* tree-sitter-python (0.23.x) via the introspection probe before use (mandatory
|
|
22
|
+
* pre-step). Python shapes pre-empted (verified by a real parse):
|
|
23
|
+
* - functions: `function_definition` (fields `name`/`parameters`/`body`; async
|
|
24
|
+
* is the SAME node with an `async` token child), `lambda` (fields
|
|
25
|
+
* `parameters`/`body`; the body is an EXPRESSION, not a `block`).
|
|
26
|
+
* - `if_statement` fields `condition`/`consequence` plus ZERO-OR-MORE
|
|
27
|
+
* `alternative` fields, each an `elif_clause` (fields `condition`/`consequence`)
|
|
28
|
+
* or an `else_clause` (field `body`) — Python has no nested-`if` else chain.
|
|
29
|
+
* - `for_statement` fields `left`/`right`/`body` + optional `alternative`
|
|
30
|
+
* (`else_clause`); `while_statement` fields `condition`/`body` + optional
|
|
31
|
+
* `alternative` (`else_clause`). The loop `else` runs on the cond-false /
|
|
32
|
+
* normal-completion path, NOT on `break`.
|
|
33
|
+
* - `with_statement` field `body`; a `with_clause` of `with_item`s (field
|
|
34
|
+
* `value` = `as_pattern` or a bare expression). `__exit__` runs on normal AND
|
|
35
|
+
* exception exit — modeled as a finalizer (try/finally analogue).
|
|
36
|
+
* - `try_statement` field `body`; children `except_clause` /
|
|
37
|
+
* `except_group_clause` (each holds the exception expr/`as_pattern` + a
|
|
38
|
+
* `block`), `else_clause` (field `body`, runs if NO exception), and
|
|
39
|
+
* `finally_clause` (holds a `block`).
|
|
40
|
+
* - `match_statement` fields `subject`/`body`; the `body` `block` holds
|
|
41
|
+
* `case_clause`s (field `alternative`), each with `case_pattern` child(ren),
|
|
42
|
+
* an optional `guard` (`if_clause`), and a `consequence` `block`. No
|
|
43
|
+
* fallthrough between cases.
|
|
44
|
+
* - `return_statement` / `raise_statement` / `break_statement` /
|
|
45
|
+
* `continue_statement` / `pass_statement`.
|
|
46
|
+
*
|
|
47
|
+
* Edge-kind contract (matches the existing visitors — RD/CDG consume these):
|
|
48
|
+
* - if/elif/else → `cond-true` / `cond-false`
|
|
49
|
+
* - for/while → `cond-true` / `loop-back` / `cond-false`; the loop `else` runs
|
|
50
|
+
* on the `cond-false` / normal-completion path (NOT on `break`)
|
|
51
|
+
* - match dispatch → `switch-case` (no fallthrough — like Go's switch)
|
|
52
|
+
* - try/except → `throw` (every protected block → each except handler)
|
|
53
|
+
* - a `break`/`continue`/`return` crossing a `with` `__exit__` or a `finally`
|
|
54
|
+
* threads through as `break`/`continue`/`return` (first leg) +
|
|
55
|
+
* `finally-break`/`finally-continue`/`finally-return` (each completion leg)
|
|
56
|
+
* - return/raise/break/continue → the matching terminator kind
|
|
57
|
+
* - straight-line → `seq`
|
|
58
|
+
*
|
|
59
|
+
* Python-specific modeling decisions (documented approximations):
|
|
60
|
+
* - `with EXPR as t:` runs the body then `__exit__` deterministically on BOTH
|
|
61
|
+
* the normal exit and an exception (it can suppress the exception, but the
|
|
62
|
+
* common case re-raises). Modeled exactly like `try/finally`: a finalizer
|
|
63
|
+
* frame for the body's exit-dispose, with the protected body edging the
|
|
64
|
+
* dispose block on `throw`. APPROXIMATION: exception SUPPRESSION by a context
|
|
65
|
+
* manager is not modeled (the dispose re-propagates), the sound direction.
|
|
66
|
+
* - the loop `else` clause runs once on normal completion (the loop ran to
|
|
67
|
+
* exhaustion without `break`). It sits on the `cond-false` edge BEFORE the
|
|
68
|
+
* join; a `break` targets the loop exit AFTER the else, so `break` skips it.
|
|
69
|
+
* - `match`/`case` cases do NOT fall through (like Go). The dispatch fans a
|
|
70
|
+
* `switch-case` edge to each case body; a guarded / non-wildcard tail with no
|
|
71
|
+
* `case _` also reaches the join directly (no-match path), keeping EXIT
|
|
72
|
+
* reverse-reachable.
|
|
73
|
+
* - `while True:` (and any loop with no statically-false exit) STILL emits the
|
|
74
|
+
* structural `header → loopExit` `cond-false` edge — exactly like the
|
|
75
|
+
* C-family visitors — so EXIT stays reverse-reachable and the post-dominator /
|
|
76
|
+
* CDG pass is not silently skipped for the function. Highest-risk property.
|
|
77
|
+
* - `lambda` has an EXPRESSION body (no `block`): one block whose value is the
|
|
78
|
+
* returned expression.
|
|
79
|
+
* - comprehensions are harvested for their target bindings but kept INLINE (no
|
|
80
|
+
* separate CFG blocks) — the plan's explicit choice.
|
|
81
|
+
*
|
|
82
|
+
* Known limitations:
|
|
83
|
+
* - async (`async def`, `await`, `async for`, `async with`) is the same node
|
|
84
|
+
* shape as the sync form plus an `async` token; the suspension points are
|
|
85
|
+
* modeled as normal straight-line control flow (no scheduler edges).
|
|
86
|
+
* - generators (`yield` / `yield from`): a `yield` is a normal expression here
|
|
87
|
+
* (no suspend/resume edge); the generator's resumption flow is not modeled.
|
|
88
|
+
* - comprehension target scoping: comprehension targets are declared in the
|
|
89
|
+
* single function table (Python 3 gives them their own scope; the leaked-name
|
|
90
|
+
* distinction is not modeled) — see python-harvest.ts.
|
|
91
|
+
* - context-manager exception SUPPRESSION and `recover`-style flow are not
|
|
92
|
+
* modeled (the `with` dispose always re-propagates).
|
|
93
|
+
*
|
|
94
|
+
* Returns `undefined` (never throws) for an AST shape it cannot model, so a
|
|
95
|
+
* malformed function never drops the whole file's CFG group (R4).
|
|
96
|
+
*/
|
|
97
|
+
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
|
98
|
+
import type { CfgVisitor } from '../types.js';
|
|
99
|
+
/** Python node types that own a CFG-bearing function body. */
|
|
100
|
+
declare const PY_FUNCTION_TYPES: Set<string>;
|
|
101
|
+
/** The Python CFG visitor. */
|
|
102
|
+
export declare function createPythonCfgVisitor(): CfgVisitor<SyntaxNode>;
|
|
103
|
+
export { PY_FUNCTION_TYPES };
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
|
|
3
|
+
import { PythonHarvester } from './python-harvest.js';
|
|
4
|
+
/** Python node types that own a CFG-bearing function body. */
|
|
5
|
+
const PY_FUNCTION_TYPES = new Set(['function_definition', 'lambda']);
|
|
6
|
+
/** Statement node types that break a basic block (everything else coalesces). */
|
|
7
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
8
|
+
'if_statement',
|
|
9
|
+
'for_statement',
|
|
10
|
+
'while_statement',
|
|
11
|
+
'with_statement',
|
|
12
|
+
'try_statement',
|
|
13
|
+
'match_statement',
|
|
14
|
+
'return_statement',
|
|
15
|
+
'raise_statement',
|
|
16
|
+
'break_statement',
|
|
17
|
+
'continue_statement',
|
|
18
|
+
'block',
|
|
19
|
+
]);
|
|
20
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
21
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
22
|
+
/**
|
|
23
|
+
* Per-function Python walk state. One instance per function so the
|
|
24
|
+
* {@link ControlFlowContext}, the exception-handler stack, and the `with` /
|
|
25
|
+
* `finally` finalizer chain are scoped to that function and never leak across
|
|
26
|
+
* functions.
|
|
27
|
+
*/
|
|
28
|
+
class PythonCfgWalk {
|
|
29
|
+
builder;
|
|
30
|
+
harvest;
|
|
31
|
+
cfc = new ControlFlowContext();
|
|
32
|
+
/** Stack of exception-handler entry blocks (except/finally/with-dispose) a `raise` jumps to. */
|
|
33
|
+
handlers = [];
|
|
34
|
+
constructor(builder, harvest) {
|
|
35
|
+
this.builder = builder;
|
|
36
|
+
this.harvest = harvest;
|
|
37
|
+
}
|
|
38
|
+
/** Statements of a block node, ignoring comments. */
|
|
39
|
+
statementsOf(block) {
|
|
40
|
+
return block.namedChildren.filter((c) => c.type !== 'comment');
|
|
41
|
+
}
|
|
42
|
+
/** The `body` block of a node (field, or the first `block` child). */
|
|
43
|
+
bodyBlockOf(node) {
|
|
44
|
+
return node.childForFieldName('body') ?? node.namedChildren.find((c) => c.type === 'block');
|
|
45
|
+
}
|
|
46
|
+
/** Visit a body that may be a `block` or a single statement. */
|
|
47
|
+
visitBody(node) {
|
|
48
|
+
return this.builder.withNesting(() => {
|
|
49
|
+
if (!node)
|
|
50
|
+
return null;
|
|
51
|
+
if (node.type === 'block')
|
|
52
|
+
return this.visitSeq(this.statementsOf(node));
|
|
53
|
+
return this.visitStmt(node);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
57
|
+
visitSeq(stmts) {
|
|
58
|
+
return this.builder.withNesting(() => {
|
|
59
|
+
let entry;
|
|
60
|
+
let dangling = [];
|
|
61
|
+
let openSimple;
|
|
62
|
+
for (const stmt of stmts) {
|
|
63
|
+
if (CONTROL_FLOW_TYPES.has(stmt.type)) {
|
|
64
|
+
openSimple = undefined; // close any open straight-line block
|
|
65
|
+
const res = this.visitStmt(stmt);
|
|
66
|
+
if (res === null)
|
|
67
|
+
continue; // transparent (empty nested block)
|
|
68
|
+
if (entry === undefined)
|
|
69
|
+
entry = res.entry;
|
|
70
|
+
else
|
|
71
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
72
|
+
dangling = [...res.exits];
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
if (openSimple === undefined) {
|
|
76
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
77
|
+
if (entry === undefined)
|
|
78
|
+
entry = idx;
|
|
79
|
+
else
|
|
80
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
81
|
+
openSimple = idx;
|
|
82
|
+
dangling = [idx];
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (entry === undefined)
|
|
90
|
+
return null;
|
|
91
|
+
return { entry, exits: dangling };
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Dispatch one statement to its handler. Non-null except for empty blocks. */
|
|
95
|
+
visitStmt(stmt) {
|
|
96
|
+
switch (stmt.type) {
|
|
97
|
+
case 'if_statement':
|
|
98
|
+
return this.visitIf(stmt);
|
|
99
|
+
case 'for_statement':
|
|
100
|
+
return this.visitFor(stmt);
|
|
101
|
+
case 'while_statement':
|
|
102
|
+
return this.visitWhile(stmt);
|
|
103
|
+
case 'with_statement':
|
|
104
|
+
return this.visitWith(stmt);
|
|
105
|
+
case 'try_statement':
|
|
106
|
+
return this.visitTry(stmt);
|
|
107
|
+
case 'match_statement':
|
|
108
|
+
return this.visitMatch(stmt);
|
|
109
|
+
case 'return_statement':
|
|
110
|
+
return this.visitReturn(stmt);
|
|
111
|
+
case 'raise_statement':
|
|
112
|
+
return this.visitRaise(stmt);
|
|
113
|
+
case 'break_statement':
|
|
114
|
+
return this.visitBreak(stmt);
|
|
115
|
+
case 'continue_statement':
|
|
116
|
+
return this.visitContinue(stmt);
|
|
117
|
+
case 'block':
|
|
118
|
+
return this.visitSeq(this.statementsOf(stmt));
|
|
119
|
+
default:
|
|
120
|
+
return this.visitSimple(stmt);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
visitSimple(stmt) {
|
|
124
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
125
|
+
return { entry: idx, exits: [idx] };
|
|
126
|
+
}
|
|
127
|
+
/** `return [expr]` — threads through EVERY active `with`/`finally` before EXIT. */
|
|
128
|
+
visitReturn(stmt) {
|
|
129
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
130
|
+
wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
|
|
131
|
+
return { entry: idx, exits: [] };
|
|
132
|
+
}
|
|
133
|
+
/** `raise [expr]` — jumps to the nearest handler (except / with-dispose / EXIT). */
|
|
134
|
+
visitRaise(stmt) {
|
|
135
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
136
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
137
|
+
return { entry: idx, exits: [] };
|
|
138
|
+
}
|
|
139
|
+
visitBreak(stmt) {
|
|
140
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
141
|
+
const res = this.cfc.resolveBreak();
|
|
142
|
+
const { target, finalizers } = res ?? {
|
|
143
|
+
target: this.builder.exitIndex,
|
|
144
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
145
|
+
};
|
|
146
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
147
|
+
return { entry: idx, exits: [] };
|
|
148
|
+
}
|
|
149
|
+
visitContinue(stmt) {
|
|
150
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
151
|
+
const res = this.cfc.resolveContinue();
|
|
152
|
+
const { target, finalizers } = res ?? {
|
|
153
|
+
target: this.builder.exitIndex,
|
|
154
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
155
|
+
};
|
|
156
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
157
|
+
return { entry: idx, exits: [] };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* `if cond: … elif cond: … else: …`. Python has NO nested-if else chain: an
|
|
161
|
+
* `if_statement` carries the condition + consequence plus zero-or-more
|
|
162
|
+
* `alternative` fields, each an `elif_clause` (its own condition + consequence)
|
|
163
|
+
* or a single trailing `else_clause`. The elif chain is threaded on the
|
|
164
|
+
* `cond-false` edge.
|
|
165
|
+
*/
|
|
166
|
+
visitIf(stmt) {
|
|
167
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
168
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
169
|
+
const exits = [];
|
|
170
|
+
const thenRes = this.visitBody(stmt.childForFieldName('consequence'));
|
|
171
|
+
if (thenRes) {
|
|
172
|
+
this.builder.edge(header, thenRes.entry, 'cond-true');
|
|
173
|
+
exits.push(...thenRes.exits);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
exits.push(header); // empty then — true path falls through
|
|
177
|
+
}
|
|
178
|
+
// The alternatives, in source order: elif_clause* then optional else_clause.
|
|
179
|
+
const alternatives = this.alternativesOf(stmt);
|
|
180
|
+
let falseFrom = header; // block whose cond-false edge feeds the next alternative
|
|
181
|
+
for (const alt of alternatives) {
|
|
182
|
+
if (alt.type === 'elif_clause') {
|
|
183
|
+
const elifCond = alt.childForFieldName('condition') ?? alt;
|
|
184
|
+
const elifHeader = this.builder.newBlock(startLineOf(alt), endLineOf(elifCond), elifCond.text, 'normal', this.harvest.facts(elifCond));
|
|
185
|
+
this.builder.edge(falseFrom, elifHeader, 'cond-false');
|
|
186
|
+
const elifRes = this.visitBody(alt.childForFieldName('consequence'));
|
|
187
|
+
if (elifRes) {
|
|
188
|
+
this.builder.edge(elifHeader, elifRes.entry, 'cond-true');
|
|
189
|
+
exits.push(...elifRes.exits);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
exits.push(elifHeader);
|
|
193
|
+
}
|
|
194
|
+
falseFrom = elifHeader;
|
|
195
|
+
}
|
|
196
|
+
else if (alt.type === 'else_clause') {
|
|
197
|
+
const elseRes = this.visitBody(alt.childForFieldName('body'));
|
|
198
|
+
if (elseRes) {
|
|
199
|
+
this.builder.edge(falseFrom, elseRes.entry, 'cond-false');
|
|
200
|
+
exits.push(...elseRes.exits);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
exits.push(falseFrom);
|
|
204
|
+
}
|
|
205
|
+
falseFrom = -1; // an else consumes the false path entirely
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// No trailing else: the last header's cond-false falls through to the join.
|
|
209
|
+
if (falseFrom >= 0)
|
|
210
|
+
exits.push(falseFrom);
|
|
211
|
+
return { entry: header, exits: [...new Set(exits)] };
|
|
212
|
+
}
|
|
213
|
+
/** The `alternative`-field children of an `if_statement`, in source order. */
|
|
214
|
+
alternativesOf(stmt) {
|
|
215
|
+
const out = [];
|
|
216
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
217
|
+
if (stmt.fieldNameForChild(i) === 'alternative') {
|
|
218
|
+
const c = stmt.child(i);
|
|
219
|
+
if (c)
|
|
220
|
+
out.push(c);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* `for TARGET in ITER: … [else: …]`. Header = the iteration test (a use of the
|
|
227
|
+
* iterable + a def of the target). The loop `else` runs on NORMAL completion
|
|
228
|
+
* (the cond-false path) — a `break` targets the loop exit AFTER the else, so it
|
|
229
|
+
* skips the else.
|
|
230
|
+
*/
|
|
231
|
+
visitFor(stmt) {
|
|
232
|
+
const left = stmt.childForFieldName('left');
|
|
233
|
+
const right = stmt.childForFieldName('right');
|
|
234
|
+
const headEnd = right ? endLineOf(right) : startLineOf(stmt);
|
|
235
|
+
const header = this.builder.newBlock(startLineOf(stmt), headEnd, this.loopHeaderText(stmt, left, right), 'normal', this.harvest.loopHeadFacts(stmt));
|
|
236
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
237
|
+
this.cfc.pushLoop(header, loopExit, []);
|
|
238
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
239
|
+
this.cfc.pop();
|
|
240
|
+
if (body) {
|
|
241
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
242
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
246
|
+
}
|
|
247
|
+
this.wireLoopElse(stmt, header, loopExit);
|
|
248
|
+
return { entry: header, exits: [loopExit] };
|
|
249
|
+
}
|
|
250
|
+
/** `while cond: … [else: …]`. Same `else`-on-normal-completion semantics as `for`. */
|
|
251
|
+
visitWhile(stmt) {
|
|
252
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
253
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
254
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
255
|
+
this.cfc.pushLoop(header, loopExit, []);
|
|
256
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
257
|
+
this.cfc.pop();
|
|
258
|
+
if (body) {
|
|
259
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
260
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
this.builder.edge(header, header, 'loop-back'); // empty `while c: pass` re-tests
|
|
264
|
+
}
|
|
265
|
+
this.wireLoopElse(stmt, header, loopExit);
|
|
266
|
+
return { entry: header, exits: [loopExit] };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Wire the optional loop `else` clause. The else runs once on normal completion
|
|
270
|
+
* (the header's `cond-false` edge). With an else, the cond-false edge goes
|
|
271
|
+
* `header → elseEntry` and the else's exits reach `loopExit`; without one, the
|
|
272
|
+
* structural `header → loopExit` `cond-false` edge keeps EXIT reverse-reachable
|
|
273
|
+
* (critical for `while True:` — and matches the C-family visitors). A `break`
|
|
274
|
+
* always targets `loopExit` directly, so it never runs the else.
|
|
275
|
+
*/
|
|
276
|
+
wireLoopElse(stmt, header, loopExit) {
|
|
277
|
+
const elseClause = this.loopElseOf(stmt);
|
|
278
|
+
if (elseClause) {
|
|
279
|
+
const elseRes = this.visitBody(elseClause.childForFieldName('body'));
|
|
280
|
+
if (elseRes) {
|
|
281
|
+
this.builder.edge(header, elseRes.entry, 'cond-false');
|
|
282
|
+
this.builder.connect(elseRes.exits, loopExit, 'seq');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// No (or empty) else — normal completion falls straight to the loop exit.
|
|
287
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
288
|
+
}
|
|
289
|
+
/** The `else_clause` of a `for`/`while` (its `alternative` field), if any. */
|
|
290
|
+
loopElseOf(stmt) {
|
|
291
|
+
const alt = stmt.childForFieldName('alternative');
|
|
292
|
+
return alt?.type === 'else_clause' ? alt : undefined;
|
|
293
|
+
}
|
|
294
|
+
loopHeaderText(stmt, left, right) {
|
|
295
|
+
const l = left?.text ?? '';
|
|
296
|
+
const r = right?.text ?? '';
|
|
297
|
+
return l || r ? `for ${l} in ${r}` : stmt.text.split('\n')[0];
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* `with EXPR [as t], …: BODY`. The context managers' `__exit__` runs
|
|
301
|
+
* deterministically on BOTH the normal exit and an exception — modeled exactly
|
|
302
|
+
* like `try/finally`: a finalizer frame holding the dispose block, plus a
|
|
303
|
+
* `throw` edge from every protected-body block to the dispose. A
|
|
304
|
+
* `return`/`break`/`continue` inside the body threads through the dispose. The
|
|
305
|
+
* dispose re-propagates on the exception path (suppression is not modeled).
|
|
306
|
+
*/
|
|
307
|
+
visitWith(stmt) {
|
|
308
|
+
// The dispose block carries the `with`-header facts (the `as` aliases are
|
|
309
|
+
// defs, the manager expressions uses) — it runs on every exit, so attaching
|
|
310
|
+
// the binding facts here is the single execution point of the bindings.
|
|
311
|
+
const items = this.withItems(stmt);
|
|
312
|
+
const dispose = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), this.withHeaderText(stmt));
|
|
313
|
+
for (const item of items)
|
|
314
|
+
this.builder.attachFacts(dispose, this.harvest.withItemFacts(item));
|
|
315
|
+
const finFrame = this.cfc.pushFinalizer(dispose);
|
|
316
|
+
// The body raises into the dispose (which re-propagates to the outer handler).
|
|
317
|
+
this.handlers.push(dispose);
|
|
318
|
+
const protectedStart = this.builder.blockCount;
|
|
319
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
320
|
+
this.handlers.pop();
|
|
321
|
+
// Conservative exceptional edges: ANY block in the with-body may raise to the
|
|
322
|
+
// dispose (an exception fires mid-block) — sound over-approximation.
|
|
323
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
324
|
+
this.builder.edge(b, dispose, 'throw');
|
|
325
|
+
}
|
|
326
|
+
this.cfc.pop();
|
|
327
|
+
drainFinalizerPending(this.builder, finFrame, [dispose]);
|
|
328
|
+
// Normal completion of the body flows into the dispose; the dispose's normal
|
|
329
|
+
// exit is the with-statement's exit. The dispose re-propagates the exception
|
|
330
|
+
// path to the OUTER handler (a CM normally re-raises).
|
|
331
|
+
if (body)
|
|
332
|
+
this.builder.connect(body.exits, dispose, 'seq');
|
|
333
|
+
this.builder.edge(dispose, this.currentHandler(), 'throw');
|
|
334
|
+
const entry = body?.entry ?? dispose;
|
|
335
|
+
return { entry, exits: [dispose] };
|
|
336
|
+
}
|
|
337
|
+
/** The `with_item`s of a `with_statement` (under its `with_clause`). */
|
|
338
|
+
withItems(stmt) {
|
|
339
|
+
const clause = stmt.namedChildren.find((c) => c.type === 'with_clause');
|
|
340
|
+
if (!clause)
|
|
341
|
+
return [];
|
|
342
|
+
return clause.namedChildren.filter((c) => c.type === 'with_item');
|
|
343
|
+
}
|
|
344
|
+
withHeaderText(stmt) {
|
|
345
|
+
const clause = stmt.namedChildren.find((c) => c.type === 'with_clause');
|
|
346
|
+
return clause ? `with ${clause.text}` : 'with';
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* `try: BODY [except …: H]* [else: E] [finally: F]`. Mirrors the TS visitor's
|
|
350
|
+
* try-route-through:
|
|
351
|
+
* - `finally` runs on every exit (normal, exception, and early jumps) — a
|
|
352
|
+
* finalizer frame for early-exit threading + a normal/exceptional join.
|
|
353
|
+
* - each `except` / except-group handler catches from the protected body.
|
|
354
|
+
* - `else` runs only if the body completed with no exception.
|
|
355
|
+
*/
|
|
356
|
+
visitTry(stmt) {
|
|
357
|
+
const bodyNode = stmt.childForFieldName('body');
|
|
358
|
+
const exceptClauses = [];
|
|
359
|
+
let elseClause;
|
|
360
|
+
let finallyClause;
|
|
361
|
+
for (let i = 0; i < stmt.namedChildCount; i++) {
|
|
362
|
+
const c = stmt.namedChild(i);
|
|
363
|
+
if (!c)
|
|
364
|
+
continue;
|
|
365
|
+
if (c.type === 'except_clause' || c.type === 'except_group_clause')
|
|
366
|
+
exceptClauses.push(c);
|
|
367
|
+
else if (c.type === 'else_clause')
|
|
368
|
+
elseClause = c;
|
|
369
|
+
else if (c.type === 'finally_clause')
|
|
370
|
+
finallyClause = c;
|
|
371
|
+
}
|
|
372
|
+
// Build finally first — known as both a normal join and a handler target. It
|
|
373
|
+
// runs OUTSIDE this try's finalizer frame (a return inside finally threads
|
|
374
|
+
// only OUTER finallys).
|
|
375
|
+
const finallyBlock = finallyClause
|
|
376
|
+
? (this.bodyBlockOf(finallyClause) ??
|
|
377
|
+
finallyClause.namedChildren.find((c) => c.type === 'block'))
|
|
378
|
+
: undefined;
|
|
379
|
+
const finallyRes = finallyBlock ? this.visitSeq(this.statementsOf(finallyBlock)) : null;
|
|
380
|
+
const finFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
|
|
381
|
+
// Each except handler. A `raise` inside a handler propagates to finally (if
|
|
382
|
+
// any), else the outer handler.
|
|
383
|
+
const handlerEntries = [];
|
|
384
|
+
const handlerExits = [];
|
|
385
|
+
for (const clause of exceptClauses) {
|
|
386
|
+
if (finallyRes)
|
|
387
|
+
this.handlers.push(finallyRes.entry);
|
|
388
|
+
const handlerBlock = clause.namedChildren.find((c) => c.type === 'block');
|
|
389
|
+
// The `except E as e:` header binds `e` — its own facts-only block in front
|
|
390
|
+
// of the handler body (the binding happens once, on handler entry).
|
|
391
|
+
const headFacts = this.harvest.exceptHeadFacts(clause);
|
|
392
|
+
const headBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', headFacts);
|
|
393
|
+
const bodyRes = handlerBlock ? this.visitSeq(this.statementsOf(handlerBlock)) : null;
|
|
394
|
+
if (bodyRes) {
|
|
395
|
+
this.builder.edge(headBlock, bodyRes.entry, 'seq');
|
|
396
|
+
handlerExits.push(...bodyRes.exits);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
handlerExits.push(headBlock); // empty handler body — header is the exit
|
|
400
|
+
}
|
|
401
|
+
handlerEntries.push(headBlock);
|
|
402
|
+
if (finallyRes)
|
|
403
|
+
this.handlers.pop();
|
|
404
|
+
}
|
|
405
|
+
// Handler for the try body: the first except if present, else finally, else
|
|
406
|
+
// the outer handler.
|
|
407
|
+
const tryHandler = handlerEntries[0] ?? finallyRes?.entry ?? this.currentHandler();
|
|
408
|
+
const protectedStart = this.builder.blockCount;
|
|
409
|
+
this.handlers.push(tryHandler);
|
|
410
|
+
const bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
|
|
411
|
+
this.handlers.pop();
|
|
412
|
+
// Conservative exceptional edges: ANY protected-region block may raise to
|
|
413
|
+
// EACH handler (an unmatched exception type tries the next handler).
|
|
414
|
+
if (exceptClauses.length > 0 || finallyClause) {
|
|
415
|
+
const targets = handlerEntries.length > 0 ? handlerEntries : finallyRes ? [finallyRes.entry] : [];
|
|
416
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
417
|
+
for (const h of targets)
|
|
418
|
+
this.builder.edge(b, h, 'throw');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// The `else` runs only on no-exception normal completion of the body.
|
|
422
|
+
let normalAfterBody = bodyRes ? [...bodyRes.exits] : [];
|
|
423
|
+
if (elseClause) {
|
|
424
|
+
const elseRes = this.visitBody(elseClause.childForFieldName('body'));
|
|
425
|
+
if (elseRes && bodyRes) {
|
|
426
|
+
this.builder.connect(bodyRes.exits, elseRes.entry, 'seq');
|
|
427
|
+
normalAfterBody = [...elseRes.exits];
|
|
428
|
+
}
|
|
429
|
+
else if (elseRes) {
|
|
430
|
+
normalAfterBody = [...elseRes.exits];
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Close the finalizer frame; wire crossing-jump completion legs.
|
|
434
|
+
if (finFrame && finallyRes) {
|
|
435
|
+
this.cfc.pop();
|
|
436
|
+
drainFinalizerPending(this.builder, finFrame, finallyRes.exits);
|
|
437
|
+
}
|
|
438
|
+
const exits = [];
|
|
439
|
+
if (finallyRes) {
|
|
440
|
+
// Normal completion of (body→else) AND each handler flows through finally.
|
|
441
|
+
this.builder.connect(normalAfterBody, finallyRes.entry, 'seq');
|
|
442
|
+
this.builder.connect(handlerExits, finallyRes.entry, 'seq');
|
|
443
|
+
exits.push(...finallyRes.exits);
|
|
444
|
+
// A try with no except → an uncaught exception re-propagates after finally.
|
|
445
|
+
if (handlerEntries.length === 0) {
|
|
446
|
+
this.builder.connect(finallyRes.exits, this.currentHandler(), 'throw');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
exits.push(...normalAfterBody);
|
|
451
|
+
exits.push(...handlerExits);
|
|
452
|
+
}
|
|
453
|
+
const entry = bodyRes?.entry ?? finallyRes?.entry ?? handlerEntries[0];
|
|
454
|
+
if (entry === undefined)
|
|
455
|
+
return null;
|
|
456
|
+
return { entry, exits: [...new Set(exits)] };
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* `match SUBJECT: case P [if guard]: BODY …`. Cases do NOT fall through (like
|
|
460
|
+
* Go's switch). Each case body is dispatched from the subject block with a
|
|
461
|
+
* `switch-case` edge; a `match` with no `case _` wildcard also reaches the join
|
|
462
|
+
* directly (no-match path), keeping EXIT reverse-reachable.
|
|
463
|
+
*/
|
|
464
|
+
visitMatch(stmt) {
|
|
465
|
+
const subject = stmt.childForFieldName('subject');
|
|
466
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), subject ? endLineOf(subject) : startLineOf(stmt), subject ? `match ${subject.text}` : 'match', 'normal', subject ? this.harvest.facts(subject) : undefined);
|
|
467
|
+
const matchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
468
|
+
const body = stmt.childForFieldName('body') ?? stmt.namedChildren.find((c) => c.type === 'block');
|
|
469
|
+
const cases = body ? body.namedChildren.filter((c) => c.type === 'case_clause') : [];
|
|
470
|
+
// A case guard (`case P if g:`) evaluates conditionally — harvest its uses
|
|
471
|
+
// onto the dispatch block (a later case only tests when earlier patterns
|
|
472
|
+
// didn't match; defs there are may-defs).
|
|
473
|
+
for (const c of cases) {
|
|
474
|
+
const guard = c.childForFieldName('guard');
|
|
475
|
+
if (guard)
|
|
476
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(guard));
|
|
477
|
+
}
|
|
478
|
+
this.cfc.pushSwitch(matchExit, []);
|
|
479
|
+
let hasWildcard = false;
|
|
480
|
+
for (const c of cases) {
|
|
481
|
+
const caseBody = this.visitBody(c.childForFieldName('consequence'));
|
|
482
|
+
const entry = caseBody?.entry ?? matchExit;
|
|
483
|
+
this.builder.edge(dispatch, entry, 'switch-case');
|
|
484
|
+
if (caseBody)
|
|
485
|
+
this.builder.connect(caseBody.exits, matchExit, 'seq');
|
|
486
|
+
if (this.isWildcardCase(c))
|
|
487
|
+
hasWildcard = true;
|
|
488
|
+
}
|
|
489
|
+
this.cfc.pop();
|
|
490
|
+
// No catch-all `case _` (or no cases) → a no-match path reaches the exit
|
|
491
|
+
// directly. Keeps EXIT reverse-reachable even when every case body jumps.
|
|
492
|
+
if (!hasWildcard)
|
|
493
|
+
this.builder.edge(dispatch, matchExit, 'switch-case');
|
|
494
|
+
return { entry: dispatch, exits: [matchExit] };
|
|
495
|
+
}
|
|
496
|
+
/** A `case _:` (bare wildcard with no guard) is the unconditional catch-all. */
|
|
497
|
+
isWildcardCase(caseClause) {
|
|
498
|
+
if (caseClause.childForFieldName('guard'))
|
|
499
|
+
return false;
|
|
500
|
+
const pattern = caseClause.namedChildren.find((c) => c.type === 'case_pattern');
|
|
501
|
+
return pattern?.text.trim() === '_';
|
|
502
|
+
}
|
|
503
|
+
/** Nearest enclosing exception handler, or the function EXIT. */
|
|
504
|
+
currentHandler() {
|
|
505
|
+
return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/** Build the CFG for one Python function/lambda node, or `undefined` if not modelable. */
|
|
509
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
510
|
+
try {
|
|
511
|
+
if (!PY_FUNCTION_TYPES.has(fnNode.type))
|
|
512
|
+
return undefined;
|
|
513
|
+
const startLine = startLineOf(fnNode);
|
|
514
|
+
const endLine = endLineOf(fnNode);
|
|
515
|
+
const startColumn = fnNode.startPosition.column;
|
|
516
|
+
const body = fnNode.childForFieldName('body');
|
|
517
|
+
if (!body)
|
|
518
|
+
return undefined; // no body — nothing to model
|
|
519
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
520
|
+
const harvest = new PythonHarvester(fnNode);
|
|
521
|
+
const paramFacts = harvest.paramFacts();
|
|
522
|
+
if (paramFacts)
|
|
523
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
524
|
+
if (fnNode.type === 'lambda' || body.type !== 'block') {
|
|
525
|
+
// `lambda x: expr` — the body is an EXPRESSION (no `block`): one block whose
|
|
526
|
+
// value is returned. Threads through no finally (a lambda has none).
|
|
527
|
+
const blk = builder.newBlock(startLineOf(body), endLineOf(body), body.text, 'normal', harvest.facts(body));
|
|
528
|
+
builder.edge(builder.entryIndex, blk, 'seq');
|
|
529
|
+
builder.edge(blk, builder.exitIndex, 'return');
|
|
530
|
+
return builder.finish(harvest.bindingTable());
|
|
531
|
+
}
|
|
532
|
+
const walk = new PythonCfgWalk(builder, harvest);
|
|
533
|
+
const res = walk.visitSeq(body.namedChildren.filter((c) => c.type !== 'comment'));
|
|
534
|
+
if (!res) {
|
|
535
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
|
536
|
+
return builder.finish(harvest.bindingTable());
|
|
537
|
+
}
|
|
538
|
+
builder.edge(builder.entryIndex, res.entry, 'seq');
|
|
539
|
+
builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
|
|
540
|
+
return builder.finish(harvest.bindingTable());
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
// Never throw out of buildFunctionCfg — a malformed AST shape must skip only
|
|
544
|
+
// this one function's CFG, never drop the whole file's language group (R4).
|
|
545
|
+
// eslint-disable-next-line no-console
|
|
546
|
+
console.warn(`[cfg] Python buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/** Whether a node is a Python function this visitor builds a CFG for. */
|
|
551
|
+
function isFunction(node) {
|
|
552
|
+
return PY_FUNCTION_TYPES.has(node.type);
|
|
553
|
+
}
|
|
554
|
+
/** The Python CFG visitor. */
|
|
555
|
+
export function createPythonCfgVisitor() {
|
|
556
|
+
return { buildFunctionCfg, isFunction };
|
|
557
|
+
}
|
|
558
|
+
export { PY_FUNCTION_TYPES };
|