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,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust CfgVisitor (#2195 U7) — the EXPRESSION-ORIENTED CFG target. Unlike the
|
|
3
|
+
* C-family / Go / Python visitors (statement languages), Rust control flow is
|
|
4
|
+
* built from EXPRESSIONS: `if` / `loop` / `while` / `for` / `match` / `block`
|
|
5
|
+
* are all expressions that produce a value. The visitor still drives the
|
|
6
|
+
* language-agnostic {@link CfgBuilder} to produce a serializable
|
|
7
|
+
* {@link FunctionCfg} plus a def/use harvest ({@link RustHarvester}) for the
|
|
8
|
+
* reaching-defs / CDG solvers, structured like the sibling visitors — a
|
|
9
|
+
* `visit_<node_type>` dispatch over the control-flow taxonomy driving a
|
|
10
|
+
* per-function {@link ControlFlowContext} for labeled break/continue.
|
|
11
|
+
*
|
|
12
|
+
* Every node type and field literal below was grammar-validated against
|
|
13
|
+
* tree-sitter-rust via the introspection probe before use (mandatory pre-step).
|
|
14
|
+
* Rust shapes pre-empted (verified by a real parse):
|
|
15
|
+
* - functions: `function_item` (fields `name`/`parameters`/`return_type`/`body`;
|
|
16
|
+
* a method is a `function_item` inside an `impl_item`'s `declaration_list`)
|
|
17
|
+
* and `closure_expression` (field `parameters`=`closure_parameters`; `body` is
|
|
18
|
+
* a `block` OR a bare expression — `|x| x + 1`).
|
|
19
|
+
* - `if_expression` fields `condition` / `consequence` (a `block`) /
|
|
20
|
+
* `alternative` (an `else_clause` wrapping a `block` or a nested
|
|
21
|
+
* `if_expression` of an `else if`). The condition can be a `let_condition`
|
|
22
|
+
* (`if let PAT = e`) or a `let_chain` (`if let PAT = e && cond`).
|
|
23
|
+
* - `loop_expression` field `body` — the INFINITE loop (NO `condition` field);
|
|
24
|
+
* an optional `label` NAMED CHILD (label is NOT a field). The key
|
|
25
|
+
* non-terminating case.
|
|
26
|
+
* - `while_expression` fields `condition` (may be a `let_condition` for
|
|
27
|
+
* `while let`) / `body`; optional `label` named child.
|
|
28
|
+
* - `for_expression` fields `pattern` / `value` / `body`; optional `label`
|
|
29
|
+
* named child.
|
|
30
|
+
* - `match_expression` fields `value` / `body` (a `match_block` of `match_arm`s).
|
|
31
|
+
* A `match_arm` has field `pattern` (a `match_pattern`, which may carry an `if`
|
|
32
|
+
* guard with field `condition`) and field `value` (the arm body — an
|
|
33
|
+
* expression or a `block`). Arms do NOT fall through.
|
|
34
|
+
* - `break_expression` — optional `label` named child AND an optional value
|
|
35
|
+
* expression (`break 'a 42`). `continue_expression` — optional `label`.
|
|
36
|
+
* - `return_expression` — optional value child. `try_expression` (`expr?`) —
|
|
37
|
+
* the early-return operator.
|
|
38
|
+
* - a `label` node's bare name is its `identifier` child's text (`'outer`'s name
|
|
39
|
+
* is `outer`), matching `break 'outer` / `continue 'outer`.
|
|
40
|
+
*
|
|
41
|
+
* Edge-kind contract (matches the existing visitors — RD/CDG consume these):
|
|
42
|
+
* - if / else (incl. `if let`) → `cond-true` / `cond-false`
|
|
43
|
+
* - `loop {}` (no condition) → `loop-back` (body re-enters) PLUS a structural
|
|
44
|
+
* `cond-false` escape edge (header → loopExit) so EXIT stays reverse-reachable
|
|
45
|
+
* - `while` / `while let` / `for` → `cond-true` / `loop-back` / `cond-false`
|
|
46
|
+
* - `match` dispatch → `switch-case` (NO fallthrough — like Go / Python)
|
|
47
|
+
* - `break` / `continue` / `return` → the matching terminator kind; a labeled
|
|
48
|
+
* `break 'outer` / `continue 'outer` targets the labeled loop frame; a
|
|
49
|
+
* `break value` is still a `break` edge
|
|
50
|
+
* - the `?` operator (`try_expression`) → `throw` — an early error-return edge to
|
|
51
|
+
* EXIT from the `?` site (a conservative throw-like edge; the Err/None path)
|
|
52
|
+
* - straight-line → `seq`
|
|
53
|
+
*
|
|
54
|
+
* Rust-specific modeling decisions (documented approximations):
|
|
55
|
+
* - `loop {}` is the canonical Rust infinite loop with NO condition. We ALWAYS
|
|
56
|
+
* emit a structural `header → loopExit` escape edge (exactly as the C-family /
|
|
57
|
+
* Go visitors do for `while(true)` / `for {}`), so EXIT stays reverse-reachable
|
|
58
|
+
* and the post-dominator / CDG pass is not silently skipped for the function.
|
|
59
|
+
* This is the single highest-risk correctness property. A `loop {}` that
|
|
60
|
+
* DOES `break` also reaches EXIT via the break; the structural escape edge is
|
|
61
|
+
* emitted either way.
|
|
62
|
+
* - the `?` operator desugars to a `match` that returns the Err/None early. We
|
|
63
|
+
* model it conservatively as a `throw` edge to the function EXIT from the block
|
|
64
|
+
* that contains the `?` — so the post-`?` continuation stays reachable (the Ok
|
|
65
|
+
* path) AND the early-exit path is represented. Multiple `?` in one block emit
|
|
66
|
+
* one early-exit edge per `?`-bearing block (deduped by the builder).
|
|
67
|
+
* - a closure (`closure_expression`) is collected as its OWN function by
|
|
68
|
+
* `isFunction`, so its body gets a standalone CFG; in the ENCLOSING function it
|
|
69
|
+
* is an opaque straight-line value (its body is not followed inline), exactly
|
|
70
|
+
* as the Go visitor treats a spawned closure and the TS visitor an arrow body.
|
|
71
|
+
* - the trailing tail expression of a `block` (no `;`) is the block's value; for
|
|
72
|
+
* control-flow purposes it just falls off normally to the block's successor.
|
|
73
|
+
*
|
|
74
|
+
* Known limitations:
|
|
75
|
+
* - panic: a `panic!()` (or an out-of-bounds index, an `unwrap` on `None`) aborts
|
|
76
|
+
* the function abnormally, but tree-sitter sees only a normal macro/method call.
|
|
77
|
+
* The panic-unwind path is NOT modeled (documented gap, not faked) — Rust has
|
|
78
|
+
* no try/catch, so there is no handler structure to route to.
|
|
79
|
+
* - async (`async fn`, `.await`): the suspension points are modeled as normal
|
|
80
|
+
* straight-line control flow (no scheduler edges).
|
|
81
|
+
* - block scope + shadowing in the def/use harvest is flattened to one function
|
|
82
|
+
* table (see rust-harvest.ts) — a documented v1 over-approximation.
|
|
83
|
+
* - macro bodies (`println!`, `vec!`, custom macros) are opaque token trees — any
|
|
84
|
+
* control flow expanded by a macro is invisible.
|
|
85
|
+
*
|
|
86
|
+
* Returns `undefined` (never throws) for an AST shape it cannot model, so a
|
|
87
|
+
* malformed function never drops the whole file's CFG group (R4).
|
|
88
|
+
*/
|
|
89
|
+
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
|
90
|
+
import type { CfgVisitor } from '../types.js';
|
|
91
|
+
/** Rust node types that own a CFG-bearing function body. */
|
|
92
|
+
declare const RUST_FUNCTION_TYPES: Set<string>;
|
|
93
|
+
/** The Rust CFG visitor. */
|
|
94
|
+
export declare function createRustCfgVisitor(): CfgVisitor<SyntaxNode>;
|
|
95
|
+
export { RUST_FUNCTION_TYPES };
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, wireJumpThroughFinalizers } from '../control-flow-context.js';
|
|
3
|
+
import { RustHarvester } from './rust-harvest.js';
|
|
4
|
+
/** Rust node types that own a CFG-bearing function body. */
|
|
5
|
+
const RUST_FUNCTION_TYPES = new Set(['function_item', 'closure_expression']);
|
|
6
|
+
/**
|
|
7
|
+
* Expression / statement node types that break a basic block (everything else
|
|
8
|
+
* coalesces). `expression_statement` is the `<expr>;` wrapper; the control-flow
|
|
9
|
+
* EXPRESSIONS inside it are unwrapped by {@link visitStmt}.
|
|
10
|
+
*/
|
|
11
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
12
|
+
'expression_statement',
|
|
13
|
+
'if_expression',
|
|
14
|
+
'loop_expression',
|
|
15
|
+
'while_expression',
|
|
16
|
+
'for_expression',
|
|
17
|
+
'match_expression',
|
|
18
|
+
'return_expression',
|
|
19
|
+
'break_expression',
|
|
20
|
+
'continue_expression',
|
|
21
|
+
'block',
|
|
22
|
+
]);
|
|
23
|
+
/** Expression node types that the statement walker treats as control-flow. */
|
|
24
|
+
const CONTROL_FLOW_EXPR_TYPES = new Set([
|
|
25
|
+
'if_expression',
|
|
26
|
+
'loop_expression',
|
|
27
|
+
'while_expression',
|
|
28
|
+
'for_expression',
|
|
29
|
+
'match_expression',
|
|
30
|
+
'return_expression',
|
|
31
|
+
'break_expression',
|
|
32
|
+
'continue_expression',
|
|
33
|
+
'block',
|
|
34
|
+
]);
|
|
35
|
+
/** Node types whose subtrees are opaque (a nested function owns its own CFG). */
|
|
36
|
+
const NESTED_FUNCTION_TYPES = new Set(['function_item', 'closure_expression']);
|
|
37
|
+
/** Rust comment node types — line (`//`) and block (slash-star) comments. */
|
|
38
|
+
const COMMENT_TYPES = new Set(['line_comment', 'block_comment']);
|
|
39
|
+
const isNotComment = (n) => !COMMENT_TYPES.has(n.type);
|
|
40
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
41
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
42
|
+
/**
|
|
43
|
+
* Per-function Rust walk state. One instance per function so the
|
|
44
|
+
* {@link ControlFlowContext} and label tables are scoped to that function and
|
|
45
|
+
* never leak across functions.
|
|
46
|
+
*/
|
|
47
|
+
class RustCfgWalk {
|
|
48
|
+
builder;
|
|
49
|
+
harvest;
|
|
50
|
+
cfc = new ControlFlowContext();
|
|
51
|
+
constructor(builder, harvest) {
|
|
52
|
+
this.builder = builder;
|
|
53
|
+
this.harvest = harvest;
|
|
54
|
+
}
|
|
55
|
+
/** Statements of a `block`, ignoring comments. */
|
|
56
|
+
statementsOf(block) {
|
|
57
|
+
return block.namedChildren.filter(isNotComment);
|
|
58
|
+
}
|
|
59
|
+
/** The `body` of a node (a `block`, or a bare expression for a closure / arm). */
|
|
60
|
+
bodyOf(node) {
|
|
61
|
+
return node.childForFieldName('body') ?? undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Visit a body that may be a `block` or a single expression. */
|
|
64
|
+
visitBody(node) {
|
|
65
|
+
return this.builder.withNesting(() => {
|
|
66
|
+
if (!node)
|
|
67
|
+
return null;
|
|
68
|
+
if (node.type === 'block')
|
|
69
|
+
return this.visitSeq(this.statementsOf(node));
|
|
70
|
+
return this.visitStmt(node);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
74
|
+
visitSeq(stmts) {
|
|
75
|
+
return this.builder.withNesting(() => {
|
|
76
|
+
let entry;
|
|
77
|
+
let dangling = [];
|
|
78
|
+
let openSimple;
|
|
79
|
+
for (const stmt of stmts) {
|
|
80
|
+
if (this.isControlFlow(stmt)) {
|
|
81
|
+
openSimple = undefined; // close any open straight-line block
|
|
82
|
+
const res = this.visitStmt(stmt);
|
|
83
|
+
if (res === null)
|
|
84
|
+
continue; // transparent (empty nested block)
|
|
85
|
+
if (entry === undefined)
|
|
86
|
+
entry = res.entry;
|
|
87
|
+
else
|
|
88
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
89
|
+
dangling = [...res.exits];
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const idx = openSimple === undefined ? this.openBlock(stmt) : this.extendOpen(openSimple, stmt);
|
|
93
|
+
if (openSimple === undefined) {
|
|
94
|
+
if (entry === undefined)
|
|
95
|
+
entry = idx;
|
|
96
|
+
else
|
|
97
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
98
|
+
dangling = [idx];
|
|
99
|
+
}
|
|
100
|
+
openSimple = idx;
|
|
101
|
+
// A straight-line statement that contains a `?` early-returns to EXIT.
|
|
102
|
+
this.wireTryExits(stmt, idx);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (entry === undefined)
|
|
106
|
+
return null;
|
|
107
|
+
return { entry, exits: dangling };
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
openBlock(stmt) {
|
|
111
|
+
return this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
112
|
+
}
|
|
113
|
+
extendOpen(open, stmt) {
|
|
114
|
+
this.builder.extendBlock(open, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
115
|
+
return open;
|
|
116
|
+
}
|
|
117
|
+
/** Whether a statement node breaks the current straight-line block. */
|
|
118
|
+
isControlFlow(stmt) {
|
|
119
|
+
if (stmt.type === 'expression_statement') {
|
|
120
|
+
const inner = this.exprStmtInner(stmt);
|
|
121
|
+
return inner ? CONTROL_FLOW_EXPR_TYPES.has(inner.type) : false;
|
|
122
|
+
}
|
|
123
|
+
if (stmt.type === 'let_declaration') {
|
|
124
|
+
// `let v = loop {…}` / `let w = if c {…} else {…}` / `let PAT = e else {…}`
|
|
125
|
+
// — the value is a control-flow EXPRESSION, or the let-else alternative
|
|
126
|
+
// block is divergent; either way the let must be modeled structurally.
|
|
127
|
+
const value = stmt.childForFieldName('value');
|
|
128
|
+
const alt = stmt.childForFieldName('alternative');
|
|
129
|
+
return (value !== null && CONTROL_FLOW_EXPR_TYPES.has(value.type)) || alt !== null;
|
|
130
|
+
}
|
|
131
|
+
return CONTROL_FLOW_TYPES.has(stmt.type);
|
|
132
|
+
}
|
|
133
|
+
/** The inner expression of an `expression_statement` (`if x {…};`). */
|
|
134
|
+
exprStmtInner(stmt) {
|
|
135
|
+
return stmt.namedChildren.find(isNotComment);
|
|
136
|
+
}
|
|
137
|
+
/** Dispatch one statement to its handler. Non-null except for empty blocks. */
|
|
138
|
+
visitStmt(stmt) {
|
|
139
|
+
// Unwrap an `expression_statement` to its control-flow expression.
|
|
140
|
+
if (stmt.type === 'expression_statement') {
|
|
141
|
+
const inner = this.exprStmtInner(stmt);
|
|
142
|
+
if (inner && CONTROL_FLOW_EXPR_TYPES.has(inner.type))
|
|
143
|
+
return this.visitStmt(inner);
|
|
144
|
+
return this.visitSimple(stmt);
|
|
145
|
+
}
|
|
146
|
+
if (stmt.type === 'let_declaration')
|
|
147
|
+
return this.visitLet(stmt);
|
|
148
|
+
switch (stmt.type) {
|
|
149
|
+
case 'if_expression':
|
|
150
|
+
return this.visitIf(stmt);
|
|
151
|
+
case 'loop_expression':
|
|
152
|
+
return this.visitLoop(stmt);
|
|
153
|
+
case 'while_expression':
|
|
154
|
+
return this.visitWhile(stmt);
|
|
155
|
+
case 'for_expression':
|
|
156
|
+
return this.visitFor(stmt);
|
|
157
|
+
case 'match_expression':
|
|
158
|
+
return this.visitMatch(stmt);
|
|
159
|
+
case 'return_expression':
|
|
160
|
+
return this.visitReturn(stmt);
|
|
161
|
+
case 'break_expression':
|
|
162
|
+
return this.visitBreak(stmt);
|
|
163
|
+
case 'continue_expression':
|
|
164
|
+
return this.visitContinue(stmt);
|
|
165
|
+
case 'block':
|
|
166
|
+
return this.visitSeq(this.statementsOf(stmt));
|
|
167
|
+
default:
|
|
168
|
+
return this.visitSimple(stmt);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
visitSimple(stmt) {
|
|
172
|
+
const idx = this.openBlock(stmt);
|
|
173
|
+
this.wireTryExits(stmt, idx);
|
|
174
|
+
return { entry: idx, exits: [idx] };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* `let PAT = VALUE [else { ALT }]` whose VALUE is a control-flow expression
|
|
178
|
+
* (`let v = loop {…}`, `let w = if c {…} else {…}`, `let m = match …`) or which
|
|
179
|
+
* has a divergent let-else ALT block. A plain `let x = e;` never reaches here
|
|
180
|
+
* (it coalesces into a straight-line block in {@link visitSeq}).
|
|
181
|
+
*
|
|
182
|
+
* The value's control-flow construct is visited as a sub-CFG; the let-pattern's
|
|
183
|
+
* bindings are defs that happen on the value's NORMAL completion — attached to a
|
|
184
|
+
* facts-only continuation block the value's exits feed. For a `let … else`, the
|
|
185
|
+
* ALT block runs on the refutable-failure path and (per Rust's rules) MUST
|
|
186
|
+
* diverge (return/break/continue/panic); it is visited as control flow, so its
|
|
187
|
+
* own jumps wire directly to their targets and it contributes no normal exit.
|
|
188
|
+
*/
|
|
189
|
+
visitLet(stmt) {
|
|
190
|
+
const value = stmt.childForFieldName('value');
|
|
191
|
+
const alt = stmt.childForFieldName('alternative');
|
|
192
|
+
const cfValue = value !== null && CONTROL_FLOW_EXPR_TYPES.has(value.type);
|
|
193
|
+
let entry;
|
|
194
|
+
let normalExits;
|
|
195
|
+
if (cfValue && value) {
|
|
196
|
+
// `let v = loop {…}` — the value is a control-flow construct: visit it, then
|
|
197
|
+
// attach ONLY the pattern's binding defs to a facts-only continuation (the
|
|
198
|
+
// value's uses are already harvested onto its own blocks).
|
|
199
|
+
const valueRes = this.visitStmt(value);
|
|
200
|
+
const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.letPatternFacts(stmt));
|
|
201
|
+
this.builder.connect(valueRes?.exits ?? [], cont, 'seq');
|
|
202
|
+
entry = valueRes ? valueRes.entry : cont;
|
|
203
|
+
normalExits = [cont];
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// A simple-value `let PAT = e [else {…}]` — ONE block with the whole let's
|
|
207
|
+
// facts (value uses + pattern defs). It reaches here only via the let-else
|
|
208
|
+
// alternative (a plain `let x = e;` coalesces in visitSeq).
|
|
209
|
+
const idx = this.openBlock(stmt);
|
|
210
|
+
this.wireTryExits(stmt, idx);
|
|
211
|
+
entry = idx;
|
|
212
|
+
normalExits = [idx];
|
|
213
|
+
}
|
|
214
|
+
// `let … else { … }` — the else block runs on the binding-FAILURE path and
|
|
215
|
+
// (per Rust) MUST diverge (return/break/continue/panic); visit it as control
|
|
216
|
+
// flow so its jumps wire themselves to their targets. It is NOT on the normal
|
|
217
|
+
// continuation — branched from the binding site with a `cond-false` (refute)
|
|
218
|
+
// edge.
|
|
219
|
+
if (alt) {
|
|
220
|
+
const altRes = this.visitBody(alt);
|
|
221
|
+
if (altRes)
|
|
222
|
+
this.builder.connect(normalExits, altRes.entry, 'cond-false');
|
|
223
|
+
}
|
|
224
|
+
return { entry, exits: normalExits };
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Emit a `throw` (early-return) edge to EXIT for every `?` operator inside a
|
|
228
|
+
* straight-line statement's subtree (excluding nested function bodies). The
|
|
229
|
+
* `?` desugars to "return Err(...) early"; modeling it as a throw-like edge to
|
|
230
|
+
* EXIT keeps the early-exit path represented while the Ok path falls through
|
|
231
|
+
* normally. Deduped by the builder, so repeated `?` in a block emit one edge.
|
|
232
|
+
*/
|
|
233
|
+
wireTryExits(stmt, fromBlock) {
|
|
234
|
+
if (this.containsTry(stmt))
|
|
235
|
+
this.builder.edge(fromBlock, this.builder.exitIndex, 'throw');
|
|
236
|
+
}
|
|
237
|
+
containsTry(node) {
|
|
238
|
+
if (node.type === 'try_expression')
|
|
239
|
+
return true;
|
|
240
|
+
if (NESTED_FUNCTION_TYPES.has(node.type))
|
|
241
|
+
return false; // opaque
|
|
242
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
243
|
+
const c = node.namedChild(i);
|
|
244
|
+
if (c && this.containsTry(c))
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
/** `return [expr]` — direct edge to the function EXIT. */
|
|
250
|
+
visitReturn(stmt) {
|
|
251
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
252
|
+
// A `return f()?;` early-returns on the `?` path too.
|
|
253
|
+
this.wireTryExits(stmt, idx);
|
|
254
|
+
this.builder.edge(idx, this.builder.exitIndex, 'return');
|
|
255
|
+
return { entry: idx, exits: [] };
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* `break ['label] [value]` — targets the labeled loop frame if labeled, else the
|
|
259
|
+
* nearest enclosing loop. `break value` is still a `break` edge (the value is a
|
|
260
|
+
* normal use harvested onto the block).
|
|
261
|
+
*/
|
|
262
|
+
visitBreak(stmt) {
|
|
263
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
264
|
+
this.wireTryExits(stmt, idx);
|
|
265
|
+
const label = this.jumpLabel(stmt);
|
|
266
|
+
const res = this.cfc.resolveBreak(label);
|
|
267
|
+
const { target, finalizers } = res ?? {
|
|
268
|
+
target: this.builder.exitIndex,
|
|
269
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
270
|
+
};
|
|
271
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
272
|
+
return { entry: idx, exits: [] };
|
|
273
|
+
}
|
|
274
|
+
/** `continue ['label]` — re-tests the labeled (or nearest) loop header. */
|
|
275
|
+
visitContinue(stmt) {
|
|
276
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
277
|
+
const label = this.jumpLabel(stmt);
|
|
278
|
+
const res = this.cfc.resolveContinue(label);
|
|
279
|
+
const { target, finalizers } = res ?? {
|
|
280
|
+
target: this.builder.exitIndex,
|
|
281
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
282
|
+
};
|
|
283
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
284
|
+
return { entry: idx, exits: [] };
|
|
285
|
+
}
|
|
286
|
+
/** The bare name (`outer`) of a `break`/`continue`'s `'label`, if any. */
|
|
287
|
+
jumpLabel(stmt) {
|
|
288
|
+
const label = stmt.namedChildren.find((c) => c.type === 'label');
|
|
289
|
+
return this.labelName(label);
|
|
290
|
+
}
|
|
291
|
+
/** The bare identifier name of a `label` node (`'outer` ⇒ `outer`). */
|
|
292
|
+
labelName(label) {
|
|
293
|
+
if (!label)
|
|
294
|
+
return undefined;
|
|
295
|
+
const id = label.namedChildren.find((c) => c.type === 'identifier');
|
|
296
|
+
return id?.text ?? label.text.replace(/^'/, '');
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The optional `'label` of a loop expression (a NAMED CHILD, not a field — Rust
|
|
300
|
+
* attaches the label directly to the loop, unlike Go's `labeled_statement`).
|
|
301
|
+
*/
|
|
302
|
+
loopLabels(stmt) {
|
|
303
|
+
const label = stmt.namedChildren.find((c) => c.type === 'label');
|
|
304
|
+
const name = this.labelName(label);
|
|
305
|
+
return name !== undefined ? [name] : [];
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* `if COND { … } [else { … } | else if …]`. The condition can be a plain
|
|
309
|
+
* expression, a `let_condition` (`if let PAT = e`), or a `let_chain`. The header
|
|
310
|
+
* carries the condition's def/use facts (an `if let` pattern is a def, its value
|
|
311
|
+
* a use). The else `alternative` is an `else_clause` wrapping a `block` or a
|
|
312
|
+
* nested `if_expression` (the `else if` chain).
|
|
313
|
+
*/
|
|
314
|
+
visitIf(stmt) {
|
|
315
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
316
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.condFacts(cond, false));
|
|
317
|
+
this.wireTryExits(cond, header);
|
|
318
|
+
const exits = [];
|
|
319
|
+
const thenRes = this.visitBody(stmt.childForFieldName('consequence'));
|
|
320
|
+
if (thenRes) {
|
|
321
|
+
this.builder.edge(header, thenRes.entry, 'cond-true');
|
|
322
|
+
exits.push(...thenRes.exits);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
exits.push(header); // empty then — true path falls through
|
|
326
|
+
}
|
|
327
|
+
const elseNode = this.elseBodyOf(stmt);
|
|
328
|
+
if (elseNode) {
|
|
329
|
+
const elseRes = this.visitBody(elseNode);
|
|
330
|
+
if (elseRes) {
|
|
331
|
+
this.builder.edge(header, elseRes.entry, 'cond-false');
|
|
332
|
+
exits.push(...elseRes.exits);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
exits.push(header);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
exits.push(header); // no else — false path falls through to the join
|
|
340
|
+
}
|
|
341
|
+
return { entry: header, exits: [...new Set(exits)] };
|
|
342
|
+
}
|
|
343
|
+
/** The else body of an `if_expression` (unwraps the `else_clause` wrapper). */
|
|
344
|
+
elseBodyOf(stmt) {
|
|
345
|
+
const alt = stmt.childForFieldName('alternative');
|
|
346
|
+
if (!alt)
|
|
347
|
+
return undefined;
|
|
348
|
+
if (alt.type === 'else_clause') {
|
|
349
|
+
// The clause wraps a `block` or a nested `if_expression` (`else if`).
|
|
350
|
+
return alt.namedChildren.find(isNotComment);
|
|
351
|
+
}
|
|
352
|
+
return alt;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* `loop { … }` — Rust's INFINITE loop (NO condition). Body exits re-enter the
|
|
356
|
+
* header (`loop-back`); a `break` reaches `loopExit`. We ALWAYS emit a
|
|
357
|
+
* structural `header → loopExit` `cond-false` escape edge so EXIT stays
|
|
358
|
+
* reverse-reachable (a `loop {}` with no break never reaches EXIT otherwise, and
|
|
359
|
+
* the CDG pass would be silently skipped for the whole function).
|
|
360
|
+
*/
|
|
361
|
+
visitLoop(stmt) {
|
|
362
|
+
const labels = this.loopLabels(stmt);
|
|
363
|
+
const header = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), 'loop');
|
|
364
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
365
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
366
|
+
const body = this.visitBody(this.bodyOf(stmt));
|
|
367
|
+
this.cfc.pop();
|
|
368
|
+
if (body) {
|
|
369
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
370
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
this.builder.edge(header, header, 'loop-back'); // empty `loop {}` re-enters
|
|
374
|
+
}
|
|
375
|
+
// Structural escape edge — keeps EXIT reverse-reachable even for `loop {}`
|
|
376
|
+
// with no `break` (the canonical Rust non-terminating case).
|
|
377
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
378
|
+
return { entry: header, exits: [loopExit] };
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* `while COND { … }` (and `while let PAT = e { … }`). Standard loop: header
|
|
382
|
+
* tests, true → body → loop-back, false → loop exit. The `while let` pattern is
|
|
383
|
+
* a may-def on the header (the binding does not happen on the exit iteration).
|
|
384
|
+
*/
|
|
385
|
+
visitWhile(stmt) {
|
|
386
|
+
const labels = this.loopLabels(stmt);
|
|
387
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
388
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.condFacts(cond, true));
|
|
389
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
390
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
391
|
+
const body = this.visitBody(this.bodyOf(stmt));
|
|
392
|
+
this.cfc.pop();
|
|
393
|
+
if (body) {
|
|
394
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
395
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
399
|
+
}
|
|
400
|
+
// Structural exit edge — even `while true {}` keeps EXIT reverse-reachable.
|
|
401
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
402
|
+
return { entry: header, exits: [loopExit] };
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* `for PAT in ITER { … }`. The header binds the loop pattern (a def) and uses
|
|
406
|
+
* the iterated expression. Standard loop topology.
|
|
407
|
+
*/
|
|
408
|
+
visitFor(stmt) {
|
|
409
|
+
const labels = this.loopLabels(stmt);
|
|
410
|
+
const value = stmt.childForFieldName('value');
|
|
411
|
+
const headEnd = value ? endLineOf(value) : startLineOf(stmt);
|
|
412
|
+
const header = this.builder.newBlock(startLineOf(stmt), headEnd, this.forHeaderText(stmt), 'normal', this.harvest.forHeadFacts(stmt));
|
|
413
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
414
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
415
|
+
const body = this.visitBody(this.bodyOf(stmt));
|
|
416
|
+
this.cfc.pop();
|
|
417
|
+
if (body) {
|
|
418
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
419
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-iterates
|
|
423
|
+
}
|
|
424
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
425
|
+
return { entry: header, exits: [loopExit] };
|
|
426
|
+
}
|
|
427
|
+
forHeaderText(stmt) {
|
|
428
|
+
const pat = stmt.childForFieldName('pattern')?.text ?? '';
|
|
429
|
+
const value = stmt.childForFieldName('value')?.text ?? '';
|
|
430
|
+
return pat || value ? `for ${pat} in ${value}` : 'for';
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* `match VALUE { PAT [if guard] => ARM, … }`. Arms do NOT fall through (like
|
|
434
|
+
* Go / Python). Each arm body is dispatched from the subject block with a
|
|
435
|
+
* `switch-case` edge; arm bodies rejoin AFTER the match. A `match` with no
|
|
436
|
+
* irrefutable `_` arm also reaches the join directly (no-match path), keeping
|
|
437
|
+
* EXIT reverse-reachable.
|
|
438
|
+
*/
|
|
439
|
+
visitMatch(stmt) {
|
|
440
|
+
const value = stmt.childForFieldName('value');
|
|
441
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `match ${value.text}` : 'match', 'normal', value ? this.harvest.facts(value) : undefined);
|
|
442
|
+
if (value)
|
|
443
|
+
this.wireTryExits(value, dispatch);
|
|
444
|
+
const matchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
445
|
+
const body = stmt.childForFieldName('body') ?? stmt.namedChildren.find((c) => c.type === 'match_block');
|
|
446
|
+
const arms = body ? body.namedChildren.filter((c) => c.type === 'match_arm') : [];
|
|
447
|
+
// Each arm's pattern bindings (`Some(n) =>`) are MAY-defs from the matched
|
|
448
|
+
// subject, and a guarded arm (`PAT if g`) evaluates `g` conditionally — both
|
|
449
|
+
// are harvested onto the dispatch block (co-located with the subject's use, so
|
|
450
|
+
// a tainted subject reaches the binding), as may-defs (a later arm binds/tests
|
|
451
|
+
// only when earlier ones didn't match). #2206.
|
|
452
|
+
for (const arm of arms) {
|
|
453
|
+
const patFacts = this.harvest.matchArmPatternFacts(arm);
|
|
454
|
+
if (patFacts)
|
|
455
|
+
this.builder.attachFacts(dispatch, patFacts);
|
|
456
|
+
const guard = this.armGuard(arm);
|
|
457
|
+
if (guard)
|
|
458
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(guard));
|
|
459
|
+
}
|
|
460
|
+
this.cfc.pushSwitch(matchExit, []);
|
|
461
|
+
let hasIrrefutable = false;
|
|
462
|
+
for (const arm of arms) {
|
|
463
|
+
// The arm body may be an expr or block; its pattern bindings were harvested
|
|
464
|
+
// onto the dispatch above (#2206).
|
|
465
|
+
const armBody = this.visitBody(arm.childForFieldName('value'));
|
|
466
|
+
const entry = armBody?.entry ?? matchExit;
|
|
467
|
+
this.builder.edge(dispatch, entry, 'switch-case');
|
|
468
|
+
if (armBody)
|
|
469
|
+
this.builder.connect(armBody.exits, matchExit, 'seq');
|
|
470
|
+
if (this.isIrrefutableArm(arm))
|
|
471
|
+
hasIrrefutable = true;
|
|
472
|
+
}
|
|
473
|
+
this.cfc.pop();
|
|
474
|
+
// No catch-all arm → a no-match path reaches the exit directly. (A real Rust
|
|
475
|
+
// match is exhaustive, but a non-`_`-tailed match keeps EXIT reverse-reachable
|
|
476
|
+
// even when every arm body jumps.)
|
|
477
|
+
if (!hasIrrefutable)
|
|
478
|
+
this.builder.edge(dispatch, matchExit, 'switch-case');
|
|
479
|
+
return { entry: dispatch, exits: [matchExit] };
|
|
480
|
+
}
|
|
481
|
+
/** The guard condition of a `match_arm` (`PAT if g`), if any. */
|
|
482
|
+
armGuard(arm) {
|
|
483
|
+
const pat = arm.childForFieldName('pattern');
|
|
484
|
+
return pat?.childForFieldName('condition') ?? undefined;
|
|
485
|
+
}
|
|
486
|
+
/** A `_ =>` arm with no guard is the unconditional catch-all. */
|
|
487
|
+
isIrrefutableArm(arm) {
|
|
488
|
+
if (this.armGuard(arm))
|
|
489
|
+
return false;
|
|
490
|
+
const pat = arm.childForFieldName('pattern');
|
|
491
|
+
return pat?.text.trim() === '_';
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Def/use facts for an `if`/`while` condition. A `let_condition` binds a pattern
|
|
495
|
+
* (a def — a may-def for `while let`, which re-tests) and uses its value; a
|
|
496
|
+
* `let_chain` threads through each `let_condition`. A plain expression is walked
|
|
497
|
+
* for uses.
|
|
498
|
+
*/
|
|
499
|
+
condFacts(cond, loopCond) {
|
|
500
|
+
if (cond.type === 'let_condition') {
|
|
501
|
+
return this.harvest.letConditionFacts(cond, loopCond);
|
|
502
|
+
}
|
|
503
|
+
// A `let_chain` (`let PAT = e && cond`) — harvest the whole chain. The let
|
|
504
|
+
// bindings inside it are defs (may-defs for a while-let chain).
|
|
505
|
+
return this.harvest.facts(cond);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/** Build the CFG for one Rust function / closure node, or `undefined`. */
|
|
509
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
510
|
+
try {
|
|
511
|
+
if (!RUST_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; // trait method signature / no body
|
|
519
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
520
|
+
const harvest = new RustHarvester(fnNode);
|
|
521
|
+
const paramFacts = harvest.paramFacts();
|
|
522
|
+
if (paramFacts)
|
|
523
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
524
|
+
const walk = new RustCfgWalk(builder, harvest);
|
|
525
|
+
if (body.type !== 'block') {
|
|
526
|
+
// A closure with an expression body (`|x| x + 1`): one block whose value is
|
|
527
|
+
// the returned expression. A `?` inside it early-returns to EXIT.
|
|
528
|
+
const res = walk.visitStmt(body);
|
|
529
|
+
builder.edge(builder.entryIndex, res ? res.entry : builder.exitIndex, 'seq');
|
|
530
|
+
builder.connect(res ? res.exits : [builder.entryIndex], builder.exitIndex, 'seq');
|
|
531
|
+
return builder.finish(harvest.bindingTable());
|
|
532
|
+
}
|
|
533
|
+
const res = walk.visitSeq(body.namedChildren.filter(isNotComment));
|
|
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] Rust buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/** Whether a node is a Rust function/closure this visitor builds a CFG for. */
|
|
551
|
+
function isFunction(node) {
|
|
552
|
+
return RUST_FUNCTION_TYPES.has(node.type);
|
|
553
|
+
}
|
|
554
|
+
/** The Rust CFG visitor. */
|
|
555
|
+
export function createRustCfgVisitor() {
|
|
556
|
+
return { buildFunctionCfg, isFunction };
|
|
557
|
+
}
|
|
558
|
+
export { RUST_FUNCTION_TYPES };
|