gitnexus 1.6.8-rc.8 → 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 +23 -2
- package/dist/core/ingestion/languages/c-cpp.js +17 -5
- 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 +8 -1
- 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 +44 -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/clone-safety.d.ts +109 -0
- package/dist/core/ingestion/workers/clone-safety.js +465 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +23 -0
- package/dist/core/ingestion/workers/parse-worker.js +81 -53
- package/dist/core/ingestion/workers/post-result.d.ts +22 -0
- package/dist/core/ingestion/workers/post-result.js +87 -0
- package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
- package/dist/core/ingestion/workers/result-merge.js +43 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -40
- package/dist/core/ingestion/workers/worker-pool.js +43 -16
- 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-ipc.d.ts +58 -0
- package/dist/server/analyze-worker-ipc.js +16 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/analyze-worker.js +7 -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 +44 -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,787 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, wireJumpThroughFinalizers } from '../control-flow-context.js';
|
|
3
|
+
import { drainFinalizerPending } from '../control-flow-context.js';
|
|
4
|
+
import { SwiftHarvester } from './swift-harvest.js';
|
|
5
|
+
/** Swift node types that own a CFG-bearing function body. */
|
|
6
|
+
const SWIFT_FUNCTION_TYPES = new Set([
|
|
7
|
+
'function_declaration',
|
|
8
|
+
'init_declaration',
|
|
9
|
+
'deinit_declaration',
|
|
10
|
+
'lambda_literal',
|
|
11
|
+
]);
|
|
12
|
+
/** Statement node types that break a basic block (everything else coalesces). */
|
|
13
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
14
|
+
'if_statement',
|
|
15
|
+
'guard_statement',
|
|
16
|
+
'for_statement',
|
|
17
|
+
'while_statement',
|
|
18
|
+
'repeat_while_statement',
|
|
19
|
+
'switch_statement',
|
|
20
|
+
'do_statement',
|
|
21
|
+
'control_transfer_statement',
|
|
22
|
+
'statements',
|
|
23
|
+
]);
|
|
24
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
25
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
26
|
+
/**
|
|
27
|
+
* Per-function Swift walk state. One instance per function so the
|
|
28
|
+
* {@link ControlFlowContext}, the `defer` finalizer chain, and the label tables
|
|
29
|
+
* are scoped to that function and never leak across functions.
|
|
30
|
+
*/
|
|
31
|
+
class SwiftCfgWalk {
|
|
32
|
+
builder;
|
|
33
|
+
harvest;
|
|
34
|
+
cfc = new ControlFlowContext();
|
|
35
|
+
/** Stack of `do`/`catch` handler entry blocks a `throw`/`try` jumps to. */
|
|
36
|
+
handlers = [];
|
|
37
|
+
/** Label(s) pending attachment to the NEXT pushed loop/switch frame. */
|
|
38
|
+
pendingLabels = [];
|
|
39
|
+
/**
|
|
40
|
+
* Active `defer` finalizer frames in source (push) order. Innermost-LIFO is the
|
|
41
|
+
* REVERSE of this list. Frames stay active for the whole function tail and are
|
|
42
|
+
* drained once at the top-level walk's end (mirrors the Go visitor).
|
|
43
|
+
*/
|
|
44
|
+
deferFrames = [];
|
|
45
|
+
constructor(builder, harvest) {
|
|
46
|
+
this.builder = builder;
|
|
47
|
+
this.harvest = harvest;
|
|
48
|
+
}
|
|
49
|
+
/** Statements of a `statements` node, ignoring comments. */
|
|
50
|
+
statementsOf(block) {
|
|
51
|
+
return block.namedChildren.filter((c) => c.type !== 'comment' && c.type !== 'multiline_comment');
|
|
52
|
+
}
|
|
53
|
+
/** The body `statements` of a loop/branch node (the LAST `statements` child). */
|
|
54
|
+
bodyStatements(node) {
|
|
55
|
+
const all = node.namedChildren.filter((c) => c.type === 'statements');
|
|
56
|
+
return all.length ? all[all.length - 1] : undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Visit a body that is a `statements` node (or a single statement). */
|
|
59
|
+
visitBody(node) {
|
|
60
|
+
return this.builder.withNesting(() => {
|
|
61
|
+
if (!node)
|
|
62
|
+
return null;
|
|
63
|
+
if (node.type === 'statements')
|
|
64
|
+
return this.visitSeq(this.statementsOf(node));
|
|
65
|
+
return this.visitStmt(node);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
69
|
+
visitSeq(stmts) {
|
|
70
|
+
return this.builder.withNesting(() => {
|
|
71
|
+
let entry;
|
|
72
|
+
let dangling = [];
|
|
73
|
+
let openSimple;
|
|
74
|
+
for (const stmt of stmts) {
|
|
75
|
+
if (this.isControlFlow(stmt)) {
|
|
76
|
+
openSimple = undefined; // close any open straight-line block
|
|
77
|
+
const res = this.visitStmt(stmt);
|
|
78
|
+
if (res === null)
|
|
79
|
+
continue; // transparent (empty nested block / label)
|
|
80
|
+
if (entry === undefined)
|
|
81
|
+
entry = res.entry;
|
|
82
|
+
else
|
|
83
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
84
|
+
dangling = [...res.exits];
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (openSimple === undefined) {
|
|
88
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
89
|
+
if (entry === undefined)
|
|
90
|
+
entry = idx;
|
|
91
|
+
else
|
|
92
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
93
|
+
openSimple = idx;
|
|
94
|
+
dangling = [idx];
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
98
|
+
}
|
|
99
|
+
// A straight-line statement may contain a `try` — it can error-return.
|
|
100
|
+
this.wireTryExits(stmt, openSimple);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (entry === undefined)
|
|
104
|
+
return null;
|
|
105
|
+
return { entry, exits: dangling };
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Whether a statement node breaks the current straight-line block. */
|
|
109
|
+
isControlFlow(stmt) {
|
|
110
|
+
if (stmt.type === 'statement_label')
|
|
111
|
+
return true; // queue label, emit no block
|
|
112
|
+
if (this.isDeferCall(stmt))
|
|
113
|
+
return true;
|
|
114
|
+
// `let x = if … / switch …` (Swift 5.9, #2207): a value-position branch breaks
|
|
115
|
+
// so `visitStmt` models the arms as control flow instead of coalescing.
|
|
116
|
+
if (stmt.type === 'property_declaration') {
|
|
117
|
+
const v = this.directValue(stmt);
|
|
118
|
+
return v !== undefined && this.isModelableValueBranch(v);
|
|
119
|
+
}
|
|
120
|
+
return CONTROL_FLOW_TYPES.has(stmt.type);
|
|
121
|
+
}
|
|
122
|
+
/** Dispatch one statement to its handler. Non-null except for empty / label-only. */
|
|
123
|
+
visitStmt(stmt) {
|
|
124
|
+
if (stmt.type === 'statement_label') {
|
|
125
|
+
// A label preceding its loop/switch — queue it; the construct picks it up.
|
|
126
|
+
const name = this.labelName(stmt);
|
|
127
|
+
if (name !== undefined)
|
|
128
|
+
this.pendingLabels = [...this.pendingLabels, name];
|
|
129
|
+
return null; // emits no block of its own
|
|
130
|
+
}
|
|
131
|
+
if (this.isDeferCall(stmt))
|
|
132
|
+
return this.visitDefer(stmt);
|
|
133
|
+
switch (stmt.type) {
|
|
134
|
+
case 'property_declaration': {
|
|
135
|
+
// `let x = if … / switch …` (Swift 5.9, #2207): the value is a value-
|
|
136
|
+
// position branch — model it as control flow and bind on the rejoin.
|
|
137
|
+
const value = this.directValue(stmt);
|
|
138
|
+
if (value && this.isModelableValueBranch(value))
|
|
139
|
+
return this.visitBindBranch(stmt, value);
|
|
140
|
+
return this.visitSimple(stmt);
|
|
141
|
+
}
|
|
142
|
+
case 'if_statement':
|
|
143
|
+
return this.visitIf(stmt);
|
|
144
|
+
case 'guard_statement':
|
|
145
|
+
return this.visitGuard(stmt);
|
|
146
|
+
case 'for_statement':
|
|
147
|
+
return this.visitFor(stmt);
|
|
148
|
+
case 'while_statement':
|
|
149
|
+
return this.visitWhile(stmt);
|
|
150
|
+
case 'repeat_while_statement':
|
|
151
|
+
return this.visitRepeatWhile(stmt);
|
|
152
|
+
case 'switch_statement':
|
|
153
|
+
return this.visitSwitch(stmt);
|
|
154
|
+
case 'do_statement':
|
|
155
|
+
return this.visitDo(stmt);
|
|
156
|
+
case 'control_transfer_statement':
|
|
157
|
+
return this.visitControlTransfer(stmt);
|
|
158
|
+
case 'statements':
|
|
159
|
+
return this.visitSeq(this.statementsOf(stmt));
|
|
160
|
+
default:
|
|
161
|
+
return this.visitSimple(stmt);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
visitSimple(stmt) {
|
|
165
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
166
|
+
this.wireTryExits(stmt, idx);
|
|
167
|
+
return { entry: idx, exits: [idx] };
|
|
168
|
+
}
|
|
169
|
+
// ── control transfer (break / continue / return / throw) ─────────────────
|
|
170
|
+
/** The leading keyword of a `control_transfer_statement` decides its kind. */
|
|
171
|
+
transferKeyword(stmt) {
|
|
172
|
+
const first = stmt.child(0);
|
|
173
|
+
return first?.text ?? '';
|
|
174
|
+
}
|
|
175
|
+
visitControlTransfer(stmt) {
|
|
176
|
+
const kw = this.transferKeyword(stmt);
|
|
177
|
+
switch (kw) {
|
|
178
|
+
case 'return':
|
|
179
|
+
return this.visitReturn(stmt);
|
|
180
|
+
case 'throw':
|
|
181
|
+
return this.visitThrow(stmt);
|
|
182
|
+
case 'break':
|
|
183
|
+
return this.visitBreak(stmt);
|
|
184
|
+
case 'continue':
|
|
185
|
+
return this.visitContinue(stmt);
|
|
186
|
+
default:
|
|
187
|
+
// `fallthrough` standalone (rare outside a switch) / unknown — straight
|
|
188
|
+
// through (the switch handler treats the in-case `fallthrough` keyword).
|
|
189
|
+
return this.visitSimple(stmt);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** `return [expr]` — threads through every active `defer` (LIFO) before EXIT. */
|
|
193
|
+
visitReturn(stmt) {
|
|
194
|
+
// `return if … / switch …` (Swift 5.9, #2207): the returned value is a value-
|
|
195
|
+
// position branch — model it as control flow, with each arm returning (its
|
|
196
|
+
// value IS the function result), threading every active finalizer per arm.
|
|
197
|
+
const branch = stmt.namedChildren.find((c) => c.type === 'if_statement' || c.type === 'switch_statement');
|
|
198
|
+
if (branch && this.isModelableValueBranch(branch)) {
|
|
199
|
+
const res = this.visitBranchExpr(branch);
|
|
200
|
+
const finalizers = this.cfc.finalizersForReturn();
|
|
201
|
+
for (const ex of res.exits) {
|
|
202
|
+
wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
|
|
203
|
+
}
|
|
204
|
+
return { entry: res.entry, exits: [] };
|
|
205
|
+
}
|
|
206
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
207
|
+
this.wireTryExits(stmt, idx);
|
|
208
|
+
wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
|
|
209
|
+
return { entry: idx, exits: [] };
|
|
210
|
+
}
|
|
211
|
+
/** `throw e` — routes to the nearest enclosing `catch` handler, else EXIT. */
|
|
212
|
+
visitThrow(stmt) {
|
|
213
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
214
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
215
|
+
return { entry: idx, exits: [] };
|
|
216
|
+
}
|
|
217
|
+
visitBreak(stmt) {
|
|
218
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
219
|
+
const label = this.jumpLabel(stmt);
|
|
220
|
+
const res = this.cfc.resolveBreak(label);
|
|
221
|
+
const { target, finalizers } = res ?? {
|
|
222
|
+
target: this.builder.exitIndex,
|
|
223
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
224
|
+
};
|
|
225
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
226
|
+
return { entry: idx, exits: [] };
|
|
227
|
+
}
|
|
228
|
+
visitContinue(stmt) {
|
|
229
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
230
|
+
const label = this.jumpLabel(stmt);
|
|
231
|
+
const res = this.cfc.resolveContinue(label);
|
|
232
|
+
const { target, finalizers } = res ?? {
|
|
233
|
+
target: this.builder.exitIndex,
|
|
234
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
235
|
+
};
|
|
236
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
237
|
+
return { entry: idx, exits: [] };
|
|
238
|
+
}
|
|
239
|
+
/** The `result` label of a `break outer` / `continue outer`, if any. */
|
|
240
|
+
jumpLabel(stmt) {
|
|
241
|
+
const result = stmt.childForFieldName('result');
|
|
242
|
+
return result?.type === 'simple_identifier' ? result.text : undefined;
|
|
243
|
+
}
|
|
244
|
+
/** The bare name of a `statement_label` (`outer:` ⇒ `outer`). */
|
|
245
|
+
labelName(label) {
|
|
246
|
+
const id = label.namedChildren.find((c) => c.type === 'simple_identifier');
|
|
247
|
+
if (id?.text)
|
|
248
|
+
return id.text;
|
|
249
|
+
const stripped = label.text.replace(/:\s*$/, '').trim();
|
|
250
|
+
return stripped || undefined;
|
|
251
|
+
}
|
|
252
|
+
/** Take and clear the labels queued by a preceding `statement_label`. */
|
|
253
|
+
takeLabels() {
|
|
254
|
+
const labels = this.pendingLabels;
|
|
255
|
+
this.pendingLabels = [];
|
|
256
|
+
return labels;
|
|
257
|
+
}
|
|
258
|
+
// ── value-position branches (#2207) ─────────────────────────────────────────
|
|
259
|
+
/**
|
|
260
|
+
* The value-position branch of a `property_declaration` (`let x = if … / switch
|
|
261
|
+
* …`, Swift 5.9): the direct `if_statement` / `switch_statement` child (the value
|
|
262
|
+
* after `=`), or undefined. tree-sitter-swift reuses the statement nodes for the
|
|
263
|
+
* value form — there is no separate `if_expression` / `switch_expression`.
|
|
264
|
+
*/
|
|
265
|
+
directValue(stmt) {
|
|
266
|
+
return stmt.namedChildren.find((c) => c.type === 'if_statement' || c.type === 'switch_statement');
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Whether `node` is a value-position branch worth modeling as control flow
|
|
270
|
+
* (#2207): an `if` with an `else` (a value-position `if` always has one), or a
|
|
271
|
+
* `switch` with ≥2 entries — a real dispatch. The ternary `?:` and `??` are
|
|
272
|
+
* excluded by design (micro-branches, like the Kotlin elvis).
|
|
273
|
+
*/
|
|
274
|
+
isModelableValueBranch(node) {
|
|
275
|
+
if (node.type === 'if_statement')
|
|
276
|
+
return this.elseNodeOf(node) !== undefined;
|
|
277
|
+
if (node.type === 'switch_statement') {
|
|
278
|
+
return node.namedChildren.filter((c) => c.type === 'switch_entry').length >= 2;
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
/** Model a value-position `if`/`switch` as control flow, bypassing position. */
|
|
283
|
+
visitBranchExpr(node) {
|
|
284
|
+
return node.type === 'switch_statement' ? this.visitSwitch(node) : this.visitIf(node);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* `let x = if … / switch …` (#2207): visit the branch as control flow, then
|
|
288
|
+
* rejoin its arms at a facts-only continuation carrying ONLY the bound name's
|
|
289
|
+
* def (the condition + arm-value uses are already on the branch's blocks). The
|
|
290
|
+
* arms are now control-dependent on the branch — mirrors Kotlin / Rust.
|
|
291
|
+
*/
|
|
292
|
+
visitBindBranch(stmt, branch) {
|
|
293
|
+
const res = this.visitBranchExpr(branch);
|
|
294
|
+
const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.bindingDefFacts(stmt));
|
|
295
|
+
this.builder.connect(res.exits, cont, 'seq');
|
|
296
|
+
return { entry: res.entry, exits: [cont] };
|
|
297
|
+
}
|
|
298
|
+
// ── branches ──────────────────────────────────────────────────────────────
|
|
299
|
+
/**
|
|
300
|
+
* `if COND { … } [else { … } | else if …]`. COND may be a plain expression or
|
|
301
|
+
* an optional binding (`if let y = opt`). The else is the node AFTER the `else`
|
|
302
|
+
* keyword — a nested `if_statement` (`else if`) or the else-body `statements`.
|
|
303
|
+
*/
|
|
304
|
+
visitIf(stmt) {
|
|
305
|
+
const header = this.builder.newBlock(startLineOf(stmt), this.conditionEndLine(stmt), this.conditionText(stmt), 'normal', this.harvest.conditionFacts(stmt, false));
|
|
306
|
+
const exits = [];
|
|
307
|
+
const thenRes = this.visitBody(this.thenStatements(stmt));
|
|
308
|
+
if (thenRes) {
|
|
309
|
+
this.builder.edge(header, thenRes.entry, 'cond-true');
|
|
310
|
+
exits.push(...thenRes.exits);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
exits.push(header); // empty then — true path falls through
|
|
314
|
+
}
|
|
315
|
+
const elseNode = this.elseNodeOf(stmt);
|
|
316
|
+
if (elseNode) {
|
|
317
|
+
const elseRes = this.visitBody(elseNode);
|
|
318
|
+
if (elseRes) {
|
|
319
|
+
this.builder.edge(header, elseRes.entry, 'cond-false');
|
|
320
|
+
exits.push(...elseRes.exits);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
exits.push(header);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
exits.push(header); // no else — false path falls through to the join
|
|
328
|
+
}
|
|
329
|
+
return { entry: header, exits: [...new Set(exits)] };
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* `guard COND else { … }` — the inverse of `if`. The `else` body runs when COND
|
|
333
|
+
* is false and (per Swift) MUST diverge (return/throw/break/continue); it is
|
|
334
|
+
* branched with a `cond-false` edge. The guard body CONTINUES straight-line on
|
|
335
|
+
* the true path (`cond-true`).
|
|
336
|
+
*/
|
|
337
|
+
visitGuard(stmt) {
|
|
338
|
+
const header = this.builder.newBlock(startLineOf(stmt), this.conditionEndLine(stmt), this.conditionText(stmt), 'normal', this.harvest.conditionFacts(stmt, false));
|
|
339
|
+
// The else body is the guard's `statements` (after the `else` keyword).
|
|
340
|
+
const elseNode = this.elseNodeOf(stmt) ?? this.bodyStatements(stmt);
|
|
341
|
+
if (elseNode) {
|
|
342
|
+
const elseRes = this.visitBody(elseNode);
|
|
343
|
+
if (elseRes) {
|
|
344
|
+
this.builder.edge(header, elseRes.entry, 'cond-false');
|
|
345
|
+
// The else MUST diverge; any normal exit it leaves rejoins EXIT-bound
|
|
346
|
+
// flow conservatively (Swift forbids fall-through, but stay robust).
|
|
347
|
+
this.builder.connect(elseRes.exits, this.builder.exitIndex, 'seq');
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
this.builder.edge(header, this.builder.exitIndex, 'cond-false');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
this.builder.edge(header, this.builder.exitIndex, 'cond-false');
|
|
355
|
+
}
|
|
356
|
+
// The true path continues straight-line after the guard.
|
|
357
|
+
return { entry: header, exits: [header] };
|
|
358
|
+
}
|
|
359
|
+
/** The THEN body `statements` of an `if`/`guard` (the first `statements` child). */
|
|
360
|
+
thenStatements(stmt) {
|
|
361
|
+
return stmt.namedChildren.find((c) => c.type === 'statements');
|
|
362
|
+
}
|
|
363
|
+
/** The node after the `else` keyword: a nested `if_statement` or the else `statements`. */
|
|
364
|
+
elseNodeOf(stmt) {
|
|
365
|
+
let sawElse = false;
|
|
366
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
367
|
+
const c = stmt.child(i);
|
|
368
|
+
if (!c)
|
|
369
|
+
continue;
|
|
370
|
+
if (sawElse && c.isNamed)
|
|
371
|
+
return c;
|
|
372
|
+
if (c.type === 'else')
|
|
373
|
+
sawElse = true;
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
/** End line of a construct's condition (the last `condition`-field child). */
|
|
378
|
+
conditionEndLine(stmt) {
|
|
379
|
+
let line = startLineOf(stmt);
|
|
380
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
381
|
+
const field = stmt.fieldNameForChild(i);
|
|
382
|
+
const c = stmt.child(i);
|
|
383
|
+
if (c && (field === 'condition' || field === 'bound_identifier'))
|
|
384
|
+
line = endLineOf(c);
|
|
385
|
+
}
|
|
386
|
+
return line;
|
|
387
|
+
}
|
|
388
|
+
/** Display text for a construct's condition (joined `condition`-field children). */
|
|
389
|
+
conditionText(stmt) {
|
|
390
|
+
const parts = [];
|
|
391
|
+
const kw = stmt.child(0);
|
|
392
|
+
if (kw && !kw.isNamed)
|
|
393
|
+
parts.push(kw.text);
|
|
394
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
395
|
+
const field = stmt.fieldNameForChild(i);
|
|
396
|
+
const c = stmt.child(i);
|
|
397
|
+
if (c && (field === 'condition' || field === 'bound_identifier'))
|
|
398
|
+
parts.push(c.text);
|
|
399
|
+
}
|
|
400
|
+
return parts.join(' ') || stmt.text;
|
|
401
|
+
}
|
|
402
|
+
// ── loops ───────────────────────────────────────────────────────────────
|
|
403
|
+
/** `for item in COLLECTION [where …] { … }`. */
|
|
404
|
+
visitFor(stmt) {
|
|
405
|
+
const labels = this.takeLabels();
|
|
406
|
+
const collection = stmt.childForFieldName('collection');
|
|
407
|
+
const header = this.builder.newBlock(startLineOf(stmt), collection ? endLineOf(collection) : startLineOf(stmt), this.forHeaderText(stmt), 'normal', this.harvest.forHeadFacts(stmt));
|
|
408
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
409
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
410
|
+
const body = this.visitBody(this.bodyStatements(stmt));
|
|
411
|
+
this.cfc.pop();
|
|
412
|
+
if (body) {
|
|
413
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
414
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-iterates
|
|
418
|
+
}
|
|
419
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
420
|
+
return { entry: header, exits: [loopExit] };
|
|
421
|
+
}
|
|
422
|
+
forHeaderText(stmt) {
|
|
423
|
+
const item = stmt.childForFieldName('item')?.text ?? '';
|
|
424
|
+
const collection = stmt.childForFieldName('collection')?.text ?? '';
|
|
425
|
+
return item || collection ? `for ${item} in ${collection}` : 'for';
|
|
426
|
+
}
|
|
427
|
+
/** `while COND { … }` (and `while let PAT = e { … }`). */
|
|
428
|
+
visitWhile(stmt) {
|
|
429
|
+
const labels = this.takeLabels();
|
|
430
|
+
const header = this.builder.newBlock(startLineOf(stmt), this.conditionEndLine(stmt), this.conditionText(stmt), 'normal', this.harvest.conditionFacts(stmt, true));
|
|
431
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
432
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
433
|
+
const body = this.visitBody(this.bodyStatements(stmt));
|
|
434
|
+
this.cfc.pop();
|
|
435
|
+
if (body) {
|
|
436
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
437
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
441
|
+
}
|
|
442
|
+
// Structural exit edge — even `while true {}` keeps EXIT reverse-reachable.
|
|
443
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
444
|
+
return { entry: header, exits: [loopExit] };
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* `repeat { … } while COND` — BOTTOM-TEST: the body runs at least once, THEN
|
|
448
|
+
* the condition decides whether to loop back. The body entry is the loop entry;
|
|
449
|
+
* the condition block is the loop-back / exit decision.
|
|
450
|
+
*/
|
|
451
|
+
visitRepeatWhile(stmt) {
|
|
452
|
+
const labels = this.takeLabels();
|
|
453
|
+
const cond = stmt.childForFieldName('condition');
|
|
454
|
+
const condBlock = this.builder.newBlock(cond ? startLineOf(cond) : endLineOf(stmt), cond ? endLineOf(cond) : endLineOf(stmt), cond ? `while ${cond.text}` : 'while', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
455
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
456
|
+
// `continue` re-tests the condition; `break` leaves the loop.
|
|
457
|
+
this.cfc.pushLoop(condBlock, loopExit, labels);
|
|
458
|
+
const body = this.visitBody(this.bodyStatements(stmt));
|
|
459
|
+
this.cfc.pop();
|
|
460
|
+
const backTarget = body ? body.entry : condBlock;
|
|
461
|
+
if (body)
|
|
462
|
+
this.builder.connect(body.exits, condBlock, 'seq');
|
|
463
|
+
this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
|
|
464
|
+
// Structural exit edge — even `repeat {} while true` keeps EXIT reachable.
|
|
465
|
+
this.builder.edge(condBlock, loopExit, 'cond-false');
|
|
466
|
+
return { entry: backTarget, exits: [loopExit] };
|
|
467
|
+
}
|
|
468
|
+
// ── switch ───────────────────────────────────────────────────────────────
|
|
469
|
+
/**
|
|
470
|
+
* `switch EXPR { case … : … fallthrough? … default: … }`. Cases do NOT fall
|
|
471
|
+
* through implicitly (the opposite of C); an explicit `fallthrough` keyword at
|
|
472
|
+
* the end of a `switch_entry` spills into the NEXT entry's body. A `where` guard
|
|
473
|
+
* on a case is harvested conditionally onto the dispatch block.
|
|
474
|
+
*/
|
|
475
|
+
visitSwitch(stmt) {
|
|
476
|
+
const labels = this.takeLabels();
|
|
477
|
+
const value = stmt.childForFieldName('expr');
|
|
478
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `switch ${value.text}` : 'switch', 'normal', value ? this.harvest.facts(value) : undefined);
|
|
479
|
+
const switchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
480
|
+
this.cfc.pushSwitch(switchExit, labels);
|
|
481
|
+
const entries = stmt.namedChildren.filter((c) => c.type === 'switch_entry');
|
|
482
|
+
// A case `where` guard evaluates conditionally before the body — harvest its
|
|
483
|
+
// uses onto the dispatch block (a later case tests only when earlier ones
|
|
484
|
+
// didn't match; any def there is a may-def).
|
|
485
|
+
for (const entry of entries) {
|
|
486
|
+
// `case let n` value bindings are may-defs on the dispatch block (the case
|
|
487
|
+
// may not match) — propagated into the body where the name is read.
|
|
488
|
+
const pat = this.entryPattern(entry);
|
|
489
|
+
if (pat)
|
|
490
|
+
this.builder.attachFacts(dispatch, this.harvest.switchPatternFacts(pat));
|
|
491
|
+
const guard = this.entryGuard(entry);
|
|
492
|
+
if (guard)
|
|
493
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(guard));
|
|
494
|
+
}
|
|
495
|
+
const entryResults = entries.map((e) => this.visitBody(this.entryBody(e)));
|
|
496
|
+
const hasDefault = entries.some((e) => this.entryIsDefault(e));
|
|
497
|
+
const entryOf = new Array(entries.length);
|
|
498
|
+
let after = switchExit;
|
|
499
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
500
|
+
entryOf[i] = entryResults[i]?.entry ?? after;
|
|
501
|
+
after = entryOf[i];
|
|
502
|
+
}
|
|
503
|
+
for (let i = 0; i < entries.length; i++) {
|
|
504
|
+
this.builder.edge(dispatch, entryOf[i], 'switch-case');
|
|
505
|
+
}
|
|
506
|
+
if (!hasDefault)
|
|
507
|
+
this.builder.edge(dispatch, switchExit, 'switch-case'); // no-match path
|
|
508
|
+
// A case body rejoins AFTER the switch (no implicit fallthrough) UNLESS the
|
|
509
|
+
// entry ends in an explicit `fallthrough`, which spills into the next entry.
|
|
510
|
+
for (let i = 0; i < entries.length; i++) {
|
|
511
|
+
const res = entryResults[i];
|
|
512
|
+
if (!res)
|
|
513
|
+
continue;
|
|
514
|
+
const fallsThrough = this.entryFallsThrough(entries[i]);
|
|
515
|
+
const fallTarget = i + 1 < entries.length ? entryOf[i + 1] : switchExit;
|
|
516
|
+
if (fallsThrough)
|
|
517
|
+
this.builder.connect(res.exits, fallTarget, 'fallthrough');
|
|
518
|
+
else
|
|
519
|
+
this.builder.connect(res.exits, switchExit, 'seq');
|
|
520
|
+
}
|
|
521
|
+
this.cfc.pop();
|
|
522
|
+
return { entry: dispatch, exits: [switchExit] };
|
|
523
|
+
}
|
|
524
|
+
/** The `switch_pattern` of a `switch_entry` (absent for `default:`). */
|
|
525
|
+
entryPattern(entry) {
|
|
526
|
+
return entry.namedChildren.find((c) => c.type === 'switch_pattern');
|
|
527
|
+
}
|
|
528
|
+
/** The `where` guard expression of a `switch_entry`, if any. */
|
|
529
|
+
entryGuard(entry) {
|
|
530
|
+
let sawWhere = false;
|
|
531
|
+
for (let i = 0; i < entry.childCount; i++) {
|
|
532
|
+
const c = entry.child(i);
|
|
533
|
+
if (!c)
|
|
534
|
+
continue;
|
|
535
|
+
if (sawWhere && c.isNamed)
|
|
536
|
+
return c;
|
|
537
|
+
if (c.type === 'where_keyword')
|
|
538
|
+
sawWhere = true;
|
|
539
|
+
}
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
/** The body `statements` of a `switch_entry`. */
|
|
543
|
+
entryBody(entry) {
|
|
544
|
+
return entry.namedChildren.find((c) => c.type === 'statements');
|
|
545
|
+
}
|
|
546
|
+
entryIsDefault(entry) {
|
|
547
|
+
return (entry.namedChildren.some((c) => c.type === 'default_keyword') ||
|
|
548
|
+
entry.children.some((c) => c.type === 'default_keyword'));
|
|
549
|
+
}
|
|
550
|
+
/** A `switch_entry` ends in an explicit `fallthrough` keyword child. */
|
|
551
|
+
entryFallsThrough(entry) {
|
|
552
|
+
for (let i = 0; i < entry.childCount; i++) {
|
|
553
|
+
if (entry.child(i)?.type === 'fallthrough')
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
// ── do / catch (error handling) ──────────────────────────────────────────
|
|
559
|
+
/**
|
|
560
|
+
* `do { … } catch [pat] { … }`. Conservative exceptional flow (mirrors the C++
|
|
561
|
+
* `try`/`catch` over-approximation): every block created while walking the
|
|
562
|
+
* protected `do` body edges to the FIRST catch handler — a `try`/`throw` may
|
|
563
|
+
* fire mid-block. Each `catch_block`'s normal completion joins the post-`do`
|
|
564
|
+
* continuation.
|
|
565
|
+
*/
|
|
566
|
+
visitDo(stmt) {
|
|
567
|
+
const bodyNode = stmt.namedChildren.find((c) => c.type === 'statements');
|
|
568
|
+
const catchBlocks = stmt.namedChildren.filter((c) => c.type === 'catch_block');
|
|
569
|
+
// Build each catch handler. The handler entry is the error-binding block (a
|
|
570
|
+
// facts-only block) in front of the body, so the binding happens once on entry.
|
|
571
|
+
const handlerEntries = [];
|
|
572
|
+
const handlerExits = [];
|
|
573
|
+
for (const clause of catchBlocks) {
|
|
574
|
+
const clauseBody = clause.namedChildren.find((c) => c.type === 'statements');
|
|
575
|
+
let res = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
|
|
576
|
+
if (res === null) {
|
|
577
|
+
// Empty catch body still CATCHES — synthesize one block so the error
|
|
578
|
+
// lands somewhere and post-`do` code stays reachable.
|
|
579
|
+
const idx = this.builder.newBlock(startLineOf(clause), endLineOf(clause), '');
|
|
580
|
+
res = { entry: idx, exits: [idx] };
|
|
581
|
+
}
|
|
582
|
+
const errFacts = this.harvest.catchErrorFacts(clause);
|
|
583
|
+
if (errFacts) {
|
|
584
|
+
const errBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', errFacts);
|
|
585
|
+
this.builder.edge(errBlock, res.entry, 'seq');
|
|
586
|
+
res = { entry: errBlock, exits: res.exits };
|
|
587
|
+
}
|
|
588
|
+
handlerEntries.push(res.entry);
|
|
589
|
+
handlerExits.push(...res.exits);
|
|
590
|
+
}
|
|
591
|
+
// The protected body's handler is the FIRST catch (if any), else the outer.
|
|
592
|
+
const doHandler = handlerEntries[0] ?? this.currentHandler();
|
|
593
|
+
const protectedStart = this.builder.blockCount;
|
|
594
|
+
this.handlers.push(doHandler);
|
|
595
|
+
const bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
|
|
596
|
+
this.handlers.pop();
|
|
597
|
+
// Conservative exceptional edges: every protected-region block → EACH catch
|
|
598
|
+
// handler. Swift tries the catch clauses in order until one matches; the
|
|
599
|
+
// thrown type is unknown at CFG time, so any protected block may reach ANY
|
|
600
|
+
// clause. Edging only the first handler orphaned the 2nd..Nth catch blocks
|
|
601
|
+
// (unreachable from ENTRY, stranding their error bindings + def/use facts).
|
|
602
|
+
if (handlerEntries.length > 0) {
|
|
603
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
604
|
+
for (const handler of handlerEntries)
|
|
605
|
+
this.builder.edge(b, handler, 'throw');
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const exits = [];
|
|
609
|
+
if (bodyRes)
|
|
610
|
+
exits.push(...bodyRes.exits);
|
|
611
|
+
exits.push(...handlerExits);
|
|
612
|
+
// No catch at all — a `do {}` without `catch` is just a scope: the body
|
|
613
|
+
// flows straight through to its own normal exits (no extra edge needed).
|
|
614
|
+
const entry = bodyRes?.entry ?? handlerEntries[0];
|
|
615
|
+
if (entry === undefined)
|
|
616
|
+
return null;
|
|
617
|
+
return { entry, exits: [...new Set(exits)] };
|
|
618
|
+
}
|
|
619
|
+
/** Nearest enclosing `catch` handler, or the function EXIT. */
|
|
620
|
+
currentHandler() {
|
|
621
|
+
return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Emit a `throw` edge to the nearest handler (or EXIT) for every `try`/`try?`/
|
|
625
|
+
* `try!` operator inside a straight-line statement's subtree (excluding nested
|
|
626
|
+
* function bodies). A `try` can propagate an error mid-statement; routing it
|
|
627
|
+
* keeps the error path represented while the success path falls through.
|
|
628
|
+
* Deduped by the builder, so repeated `try` in a statement emit one edge.
|
|
629
|
+
*/
|
|
630
|
+
wireTryExits(stmt, fromBlock) {
|
|
631
|
+
if (this.containsTry(stmt))
|
|
632
|
+
this.builder.edge(fromBlock, this.currentHandler(), 'throw');
|
|
633
|
+
}
|
|
634
|
+
containsTry(node) {
|
|
635
|
+
if (node.type === 'try_expression') {
|
|
636
|
+
// `try?` / `try!` handle the error in-place (optional / trap), so only a
|
|
637
|
+
// bare `try` propagates — but conservatively any `try` may error in a
|
|
638
|
+
// throwing context; route all to the handler (deduped, low cost).
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (SWIFT_FUNCTION_TYPES.has(node.type))
|
|
642
|
+
return false; // opaque nested fn
|
|
643
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
644
|
+
const c = node.namedChild(i);
|
|
645
|
+
if (c && this.containsTry(c))
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
// ── defer ──────────────────────────────────────────────────────────────
|
|
651
|
+
/**
|
|
652
|
+
* `defer { … }` is parsed as a `call_expression` whose callee is a bare
|
|
653
|
+
* `defer` identifier carrying a trailing-closure `lambda_literal`. Detect that
|
|
654
|
+
* exact shape so the deferred body threads the function-exit completion chain.
|
|
655
|
+
*/
|
|
656
|
+
isDeferCall(stmt) {
|
|
657
|
+
if (stmt.type !== 'call_expression')
|
|
658
|
+
return false;
|
|
659
|
+
const callee = stmt.namedChild(0);
|
|
660
|
+
if (!callee || callee.type !== 'simple_identifier' || callee.text !== 'defer')
|
|
661
|
+
return false;
|
|
662
|
+
return this.deferClosure(stmt) !== undefined;
|
|
663
|
+
}
|
|
664
|
+
/** The trailing-closure `lambda_literal` body of a `defer { … }` call. */
|
|
665
|
+
deferClosure(stmt) {
|
|
666
|
+
for (let i = 0; i < stmt.namedChildCount; i++) {
|
|
667
|
+
const c = stmt.namedChild(i);
|
|
668
|
+
if (c?.type === 'call_suffix') {
|
|
669
|
+
const lambda = c.namedChildren.find((x) => x.type === 'lambda_literal');
|
|
670
|
+
if (lambda)
|
|
671
|
+
return lambda;
|
|
672
|
+
}
|
|
673
|
+
if (c?.type === 'lambda_literal')
|
|
674
|
+
return c;
|
|
675
|
+
}
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* `defer { … }` — register the deferred body as a finalizer frame that stays
|
|
680
|
+
* active for the rest of the function tail (mirrors the Go visitor). Every later
|
|
681
|
+
* `return` AND the normal fall-off thread through it; LIFO across multiple
|
|
682
|
+
* defers falls out of `finalizersForReturn()` yielding innermost-first. The
|
|
683
|
+
* deferred block carries the closure body's def/use facts. The `defer` itself is
|
|
684
|
+
* a no-op at its source position — control falls straight through.
|
|
685
|
+
*/
|
|
686
|
+
visitDefer(stmt) {
|
|
687
|
+
const lambda = this.deferClosure(stmt);
|
|
688
|
+
const deferBlock = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', lambda ? this.harvest.facts(lambda) : undefined);
|
|
689
|
+
const frame = this.cfc.pushFinalizer(deferBlock);
|
|
690
|
+
this.deferFrames.push(frame);
|
|
691
|
+
// The `defer` statement is a no-op inline — return a SEPARATE marker block as
|
|
692
|
+
// the source position so the deferred block stays out of straight-line flow.
|
|
693
|
+
const marker = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '');
|
|
694
|
+
return { entry: marker, exits: [marker] };
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Drain the active `defer` chain at function end (mirrors the Go visitor). The
|
|
698
|
+
* chain runs innermost-first (LIFO). Returns the block set control reaches AFTER
|
|
699
|
+
* the whole defer chain runs (to be wired to EXIT by the caller), or
|
|
700
|
+
* `normalExits` unchanged when there are no defers.
|
|
701
|
+
*/
|
|
702
|
+
finishDefers(normalExits) {
|
|
703
|
+
if (this.deferFrames.length === 0)
|
|
704
|
+
return normalExits;
|
|
705
|
+
const lifo = [...this.deferFrames].reverse();
|
|
706
|
+
for (let i = 0; i < lifo.length; i++)
|
|
707
|
+
this.cfc.pop();
|
|
708
|
+
for (const frame of lifo)
|
|
709
|
+
drainFinalizerPending(this.builder, frame, [frame.entry]);
|
|
710
|
+
if (normalExits.length > 0) {
|
|
711
|
+
this.builder.connect(normalExits, lifo[0].entry, 'return');
|
|
712
|
+
for (let i = 0; i + 1 < lifo.length; i++) {
|
|
713
|
+
this.builder.edge(lifo[i].entry, lifo[i + 1].entry, 'finally-return');
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return [lifo[lifo.length - 1].entry];
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/** Build the CFG for one Swift function node, or `undefined` if not modelable. */
|
|
720
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
721
|
+
try {
|
|
722
|
+
if (!SWIFT_FUNCTION_TYPES.has(fnNode.type))
|
|
723
|
+
return undefined;
|
|
724
|
+
const startLine = startLineOf(fnNode);
|
|
725
|
+
const endLine = endLineOf(fnNode);
|
|
726
|
+
const startColumn = fnNode.startPosition.column;
|
|
727
|
+
// A function/init/deinit must own a `function_body`; a lambda owns its
|
|
728
|
+
// `statements` directly. Absence ⇒ a protocol requirement / signature-only
|
|
729
|
+
// declaration with no body to model (return undefined). A PRESENT-but-empty
|
|
730
|
+
// body is a valid empty function (ENTRY → EXIT), distinct from "no body".
|
|
731
|
+
const hasBodyContainer = fnNode.childForFieldName('body') !== null ||
|
|
732
|
+
fnNode.namedChildren.some((c) => c.type === 'function_body') ||
|
|
733
|
+
(fnNode.type === 'lambda_literal' &&
|
|
734
|
+
fnNode.namedChildren.some((c) => c.type === 'statements')) ||
|
|
735
|
+
fnNode.type === 'lambda_literal';
|
|
736
|
+
if (!hasBodyContainer)
|
|
737
|
+
return undefined;
|
|
738
|
+
const body = bodyStatementsOf(fnNode); // may be undefined for an empty body
|
|
739
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
740
|
+
const harvest = new SwiftHarvester(fnNode);
|
|
741
|
+
const paramFacts = harvest.paramFacts();
|
|
742
|
+
if (paramFacts)
|
|
743
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
744
|
+
const walk = new SwiftCfgWalk(builder, harvest);
|
|
745
|
+
const res = body
|
|
746
|
+
? walk.visitSeq(body.namedChildren.filter((c) => c.type !== 'comment' && c.type !== 'multiline_comment'))
|
|
747
|
+
: null;
|
|
748
|
+
builder.edge(builder.entryIndex, res ? res.entry : builder.exitIndex, 'seq');
|
|
749
|
+
// Normal fall-off threads through the active `defer` chain (LIFO) → EXIT.
|
|
750
|
+
const normalExits = res ? res.exits : [builder.entryIndex];
|
|
751
|
+
const afterDefers = walk.finishDefers(normalExits);
|
|
752
|
+
builder.connect(afterDefers, builder.exitIndex, 'seq');
|
|
753
|
+
return builder.finish(harvest.bindingTable());
|
|
754
|
+
}
|
|
755
|
+
catch (err) {
|
|
756
|
+
// Never throw out of buildFunctionCfg — a malformed AST shape must skip only
|
|
757
|
+
// this one function's CFG, never drop the whole file's language group (R4).
|
|
758
|
+
// eslint-disable-next-line no-console
|
|
759
|
+
console.warn(`[cfg] Swift buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* The function's body `statements` node. A `function_declaration` / `init_` /
|
|
765
|
+
* `deinit_declaration` wraps it in a `function_body`; a `lambda_literal` carries
|
|
766
|
+
* the `statements` directly (after an optional `lambda_function_type` + `in`).
|
|
767
|
+
*/
|
|
768
|
+
function bodyStatementsOf(fnNode) {
|
|
769
|
+
const fb = fnNode.childForFieldName('body') ??
|
|
770
|
+
fnNode.namedChildren.find((c) => c.type === 'function_body');
|
|
771
|
+
if (fb && fb.type === 'function_body') {
|
|
772
|
+
return fb.namedChildren.find((c) => c.type === 'statements');
|
|
773
|
+
}
|
|
774
|
+
if (fnNode.type === 'lambda_literal') {
|
|
775
|
+
return fnNode.namedChildren.find((c) => c.type === 'statements');
|
|
776
|
+
}
|
|
777
|
+
return undefined;
|
|
778
|
+
}
|
|
779
|
+
/** Whether a node is a Swift function/closure this visitor builds a CFG for. */
|
|
780
|
+
function isFunction(node) {
|
|
781
|
+
return SWIFT_FUNCTION_TYPES.has(node.type);
|
|
782
|
+
}
|
|
783
|
+
/** The Swift CFG visitor. */
|
|
784
|
+
export function createSwiftCfgVisitor() {
|
|
785
|
+
return { buildFunctionCfg, isFunction };
|
|
786
|
+
}
|
|
787
|
+
export { SWIFT_FUNCTION_TYPES };
|