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,756 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
|
|
3
|
+
import { RubyHarvester } from './ruby-harvest.js';
|
|
4
|
+
/** Ruby node types that own a CFG-bearing function/closure body. */
|
|
5
|
+
const RUBY_FUNCTION_TYPES = new Set(['method', 'singleton_method', 'do_block', 'block', 'lambda']);
|
|
6
|
+
/** Statement node types that break a basic block (everything else coalesces). */
|
|
7
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
8
|
+
'if',
|
|
9
|
+
'unless',
|
|
10
|
+
'if_modifier',
|
|
11
|
+
'unless_modifier',
|
|
12
|
+
'while',
|
|
13
|
+
'until',
|
|
14
|
+
'while_modifier',
|
|
15
|
+
'until_modifier',
|
|
16
|
+
'for',
|
|
17
|
+
'case',
|
|
18
|
+
'case_match',
|
|
19
|
+
'begin',
|
|
20
|
+
'return',
|
|
21
|
+
'break',
|
|
22
|
+
'next',
|
|
23
|
+
'redo',
|
|
24
|
+
'retry',
|
|
25
|
+
'body_statement',
|
|
26
|
+
'block_body',
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* Value-position branch constructs (#2205). When one of these is an `assignment`
|
|
30
|
+
* RHS (`x = if … / case …`) or a `return` value, it is modeled as control flow
|
|
31
|
+
* (its arms carry control dependence) instead of coalescing inline. `if`/`unless`
|
|
32
|
+
* always branch (the condition gates the then-arm even without `else`).
|
|
33
|
+
*/
|
|
34
|
+
const VALUE_BRANCH_EXPR_TYPES = new Set(['if', 'unless', 'case', 'case_match']);
|
|
35
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
36
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
37
|
+
/**
|
|
38
|
+
* Per-function Ruby walk state. One instance per function so the
|
|
39
|
+
* {@link ControlFlowContext}, the exception-handler stack, and the `begin`
|
|
40
|
+
* re-enter stack (for `retry`) are scoped to that function and never leak.
|
|
41
|
+
*/
|
|
42
|
+
class RubyCfgWalk {
|
|
43
|
+
builder;
|
|
44
|
+
harvest;
|
|
45
|
+
cfc = new ControlFlowContext();
|
|
46
|
+
/** Stack of exception-handler entry blocks (rescue / ensure) a raise jumps to. */
|
|
47
|
+
handlers = [];
|
|
48
|
+
/** Stack of nearest-enclosing `begin` protected-body entry blocks (for `retry`). */
|
|
49
|
+
beginEntries = [];
|
|
50
|
+
/**
|
|
51
|
+
* For a block / lambda function (`do … end`, `{ … }`, `->(){}`), the body entry
|
|
52
|
+
* a `redo` re-enters when there is no enclosing real loop. A bare block is
|
|
53
|
+
* implicitly re-runnable by `redo`; set once the body's first block is known.
|
|
54
|
+
*/
|
|
55
|
+
blockRedoTarget;
|
|
56
|
+
/** Record the block-function body entry that a bare `redo` re-enters. */
|
|
57
|
+
setBlockRedoTarget(entry) {
|
|
58
|
+
this.blockRedoTarget = entry;
|
|
59
|
+
}
|
|
60
|
+
constructor(builder, harvest) {
|
|
61
|
+
this.builder = builder;
|
|
62
|
+
this.harvest = harvest;
|
|
63
|
+
}
|
|
64
|
+
/** Statements of a body node (`then`/`do`/`else`/`ensure`/`body_statement`/…), no comments. */
|
|
65
|
+
statementsOf(body) {
|
|
66
|
+
return body.namedChildren.filter((c) => c.type !== 'comment');
|
|
67
|
+
}
|
|
68
|
+
/** Visit a body that may be a wrapper (`then`/`do`/`block_body`/`body_statement`) or a single statement. */
|
|
69
|
+
visitBody(node) {
|
|
70
|
+
return this.builder.withNesting(() => {
|
|
71
|
+
if (!node)
|
|
72
|
+
return null;
|
|
73
|
+
if (this.isBodyWrapper(node))
|
|
74
|
+
return this.visitSeq(this.statementsOf(node));
|
|
75
|
+
return this.visitStmt(node);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
isBodyWrapper(node) {
|
|
79
|
+
return (node.type === 'then' ||
|
|
80
|
+
node.type === 'do' ||
|
|
81
|
+
node.type === 'else' ||
|
|
82
|
+
node.type === 'ensure' ||
|
|
83
|
+
node.type === 'body_statement' ||
|
|
84
|
+
node.type === 'block_body');
|
|
85
|
+
}
|
|
86
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
87
|
+
visitSeq(stmts) {
|
|
88
|
+
return this.builder.withNesting(() => {
|
|
89
|
+
let entry;
|
|
90
|
+
let dangling = [];
|
|
91
|
+
let openSimple;
|
|
92
|
+
for (const stmt of stmts) {
|
|
93
|
+
// A value-position branch on an assignment RHS (`x = if …`) is NOT in
|
|
94
|
+
// CONTROL_FLOW_TYPES (its node is `assignment`), so route it out of the
|
|
95
|
+
// coalescing path explicitly so visitStmt models its arms (#2205).
|
|
96
|
+
if (CONTROL_FLOW_TYPES.has(stmt.type) || this.isValueBranchAssignment(stmt)) {
|
|
97
|
+
openSimple = undefined; // close any open straight-line block
|
|
98
|
+
const res = this.visitStmt(stmt);
|
|
99
|
+
if (res === null)
|
|
100
|
+
continue; // transparent (empty nested block)
|
|
101
|
+
if (entry === undefined)
|
|
102
|
+
entry = res.entry;
|
|
103
|
+
else
|
|
104
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
105
|
+
dangling = [...res.exits];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
if (openSimple === undefined) {
|
|
109
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
110
|
+
if (entry === undefined)
|
|
111
|
+
entry = idx;
|
|
112
|
+
else
|
|
113
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
114
|
+
openSimple = idx;
|
|
115
|
+
dangling = [idx];
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (entry === undefined)
|
|
123
|
+
return null;
|
|
124
|
+
return { entry, exits: dangling };
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Dispatch one statement to its handler. Non-null except for empty blocks. */
|
|
128
|
+
visitStmt(stmt) {
|
|
129
|
+
switch (stmt.type) {
|
|
130
|
+
case 'if':
|
|
131
|
+
case 'unless':
|
|
132
|
+
return this.visitIf(stmt);
|
|
133
|
+
case 'if_modifier':
|
|
134
|
+
case 'unless_modifier':
|
|
135
|
+
return this.visitIfModifier(stmt);
|
|
136
|
+
case 'while':
|
|
137
|
+
case 'until':
|
|
138
|
+
return this.visitWhile(stmt);
|
|
139
|
+
case 'while_modifier':
|
|
140
|
+
case 'until_modifier':
|
|
141
|
+
return this.visitWhileModifier(stmt);
|
|
142
|
+
case 'for':
|
|
143
|
+
return this.visitFor(stmt);
|
|
144
|
+
case 'case':
|
|
145
|
+
return this.visitCase(stmt);
|
|
146
|
+
case 'case_match':
|
|
147
|
+
return this.visitCaseMatch(stmt);
|
|
148
|
+
case 'begin':
|
|
149
|
+
return this.visitBegin(stmt);
|
|
150
|
+
case 'return':
|
|
151
|
+
return this.visitReturn(stmt);
|
|
152
|
+
case 'break':
|
|
153
|
+
return this.visitBreak(stmt);
|
|
154
|
+
case 'next':
|
|
155
|
+
return this.visitNext(stmt);
|
|
156
|
+
case 'redo':
|
|
157
|
+
return this.visitRedo(stmt);
|
|
158
|
+
case 'retry':
|
|
159
|
+
return this.visitRetry(stmt);
|
|
160
|
+
case 'body_statement':
|
|
161
|
+
return this.visitImplicitBegin(stmt);
|
|
162
|
+
case 'block_body':
|
|
163
|
+
return this.visitSeq(this.statementsOf(stmt));
|
|
164
|
+
case 'assignment': {
|
|
165
|
+
// `x = if c then a else b end` / `x = case … end` (#2205): model the RHS
|
|
166
|
+
// branch as control flow and bind the LHS on the rejoin.
|
|
167
|
+
const branch = this.assignmentBranch(stmt);
|
|
168
|
+
if (branch)
|
|
169
|
+
return this.visitBindBranch(stmt, branch);
|
|
170
|
+
return this.visitSimple(stmt);
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
return this.visitSimple(stmt);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
visitSimple(stmt) {
|
|
177
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
178
|
+
return { entry: idx, exits: [idx] };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* `return [expr]` — threads through EVERY active `ensure` before EXIT.
|
|
182
|
+
* (A value-position branch RETURN is handled the idiomatic Ruby way already:
|
|
183
|
+
* `def f; if c then a else b end; end` is the implicit last-expression and is
|
|
184
|
+
* modeled as statement position. Bare `return if … end` is not a clean
|
|
185
|
+
* grammar shape — tree-sitter-ruby drops the value — so it stays a plain
|
|
186
|
+
* return; the assignment carrier is the value-branch fix here, #2205.)
|
|
187
|
+
*/
|
|
188
|
+
visitReturn(stmt) {
|
|
189
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
190
|
+
wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
|
|
191
|
+
return { entry: idx, exits: [] };
|
|
192
|
+
}
|
|
193
|
+
/** `break [expr]` — exits the nearest loop/block; threads through any `ensure`. */
|
|
194
|
+
visitBreak(stmt) {
|
|
195
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
196
|
+
const res = this.cfc.resolveBreak();
|
|
197
|
+
const { target, finalizers } = res ?? {
|
|
198
|
+
target: this.builder.exitIndex,
|
|
199
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
200
|
+
};
|
|
201
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
202
|
+
return { entry: idx, exits: [] };
|
|
203
|
+
}
|
|
204
|
+
/** `next [expr]` ≈ continue — re-tests the nearest loop header; threads `ensure`. */
|
|
205
|
+
visitNext(stmt) {
|
|
206
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
207
|
+
const res = this.cfc.resolveContinue();
|
|
208
|
+
const { target, finalizers } = res ?? {
|
|
209
|
+
target: this.builder.exitIndex,
|
|
210
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
211
|
+
};
|
|
212
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
213
|
+
return { entry: idx, exits: [] };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* `redo` — re-run the current loop/block body WITHOUT re-evaluating the
|
|
217
|
+
* condition. Modeled as a `loop-back` to the nearest loop's continue target
|
|
218
|
+
* (the loop header in the visitor's model). Outside a loop it routes to EXIT.
|
|
219
|
+
*/
|
|
220
|
+
visitRedo(stmt) {
|
|
221
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
222
|
+
// A `redo` inside a real loop re-runs that loop's body (its continue target);
|
|
223
|
+
// a `redo` inside a bare block re-enters the block body. Falls back to EXIT
|
|
224
|
+
// when neither exists (single-exit preserved).
|
|
225
|
+
const target = this.cfc.continueTarget() ?? this.blockRedoTarget;
|
|
226
|
+
if (target !== undefined)
|
|
227
|
+
this.builder.edge(idx, target, 'loop-back');
|
|
228
|
+
else
|
|
229
|
+
this.builder.edge(idx, this.builder.exitIndex, 'seq');
|
|
230
|
+
return { entry: idx, exits: [] };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* `retry` — re-enter the nearest enclosing `begin`'s protected body. Modeled as
|
|
234
|
+
* a `loop-back` to that begin's entry block. Outside any begin it routes to
|
|
235
|
+
* EXIT (single-exit preserved) — a documented approximation.
|
|
236
|
+
*/
|
|
237
|
+
visitRetry(stmt) {
|
|
238
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
239
|
+
const begin = this.beginEntries[this.beginEntries.length - 1];
|
|
240
|
+
if (begin !== undefined)
|
|
241
|
+
this.builder.edge(idx, begin, 'loop-back');
|
|
242
|
+
else
|
|
243
|
+
this.builder.edge(idx, this.builder.exitIndex, 'seq');
|
|
244
|
+
return { entry: idx, exits: [] };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* `if cond … elsif cond … else …` and `unless cond … else …`. Ruby has no
|
|
248
|
+
* nested-if else chain: an `if`/`unless` carries `condition`/`consequence`(then)
|
|
249
|
+
* plus an optional `alternative` that is an `elsif` (its own
|
|
250
|
+
* condition/consequence/alternative) or a trailing `else`. `unless` inverts the
|
|
251
|
+
* sense — its consequence is the cond-FALSE arm.
|
|
252
|
+
*/
|
|
253
|
+
/** The value-position branch on an `assignment` RHS (`x = if … / case …`), or
|
|
254
|
+
* undefined when the RHS is anything else (#2205). */
|
|
255
|
+
assignmentBranch(stmt) {
|
|
256
|
+
if (stmt.type !== 'assignment')
|
|
257
|
+
return undefined;
|
|
258
|
+
const right = stmt.childForFieldName('right');
|
|
259
|
+
return right && VALUE_BRANCH_EXPR_TYPES.has(right.type) ? right : undefined;
|
|
260
|
+
}
|
|
261
|
+
/** Whether a statement is an `assignment` with a value-position-branch RHS —
|
|
262
|
+
* routes it out of `visitSeq`'s coalescing path so its arms are modeled. */
|
|
263
|
+
isValueBranchAssignment(stmt) {
|
|
264
|
+
return this.assignmentBranch(stmt) !== undefined;
|
|
265
|
+
}
|
|
266
|
+
/** Model a value-position `if`/`unless`/`case`/`case_match` as control flow,
|
|
267
|
+
* bypassing the statement/value-position distinction (#2205). */
|
|
268
|
+
visitBranchExpr(node) {
|
|
269
|
+
switch (node.type) {
|
|
270
|
+
case 'if':
|
|
271
|
+
case 'unless':
|
|
272
|
+
return this.visitIf(node);
|
|
273
|
+
case 'case':
|
|
274
|
+
return this.visitCase(node);
|
|
275
|
+
case 'case_match':
|
|
276
|
+
return this.visitCaseMatch(node);
|
|
277
|
+
default:
|
|
278
|
+
return this.visitSimple(node); // unreachable — guarded by VALUE_BRANCH_EXPR_TYPES
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* `x = if … / case …` (#2205): visit the RHS branch as control flow, then rejoin
|
|
283
|
+
* its arms at a facts-only continuation carrying ONLY the LHS target def (the
|
|
284
|
+
* condition + arm-value uses are already harvested onto the branch's blocks).
|
|
285
|
+
* The arms are now control-dependent on the branch condition, and `x` is defined
|
|
286
|
+
* at the join — mirrors the Rust visitor's value-position `let` handling.
|
|
287
|
+
*/
|
|
288
|
+
visitBindBranch(stmt, branch) {
|
|
289
|
+
const res = this.visitBranchExpr(branch);
|
|
290
|
+
const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.assignmentDefFacts(stmt));
|
|
291
|
+
this.builder.connect(res.exits, cont, 'seq');
|
|
292
|
+
return { entry: res.entry, exits: [cont] };
|
|
293
|
+
}
|
|
294
|
+
visitIf(stmt) {
|
|
295
|
+
const inverted = stmt.type === 'unless';
|
|
296
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
297
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
298
|
+
const thenKind = inverted ? 'cond-false' : 'cond-true';
|
|
299
|
+
const elseKind = inverted ? 'cond-true' : 'cond-false';
|
|
300
|
+
const exits = [];
|
|
301
|
+
const thenRes = this.visitBody(stmt.childForFieldName('consequence'));
|
|
302
|
+
if (thenRes) {
|
|
303
|
+
this.builder.edge(header, thenRes.entry, thenKind);
|
|
304
|
+
exits.push(...thenRes.exits);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
exits.push(header); // empty consequence — that path falls through
|
|
308
|
+
}
|
|
309
|
+
const alt = stmt.childForFieldName('alternative');
|
|
310
|
+
if (alt) {
|
|
311
|
+
const { exits: altExits } = this.visitIfAlternative(alt, header, elseKind);
|
|
312
|
+
exits.push(...altExits);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
exits.push(header); // no else — the complement path falls through to the join
|
|
316
|
+
}
|
|
317
|
+
return { entry: header, exits: [...new Set(exits)] };
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Thread an `if`/`unless` alternative: an `elsif` (its own condition chained on
|
|
321
|
+
* the parent's false edge) or a trailing `else`. Returns the dangling exits.
|
|
322
|
+
*/
|
|
323
|
+
visitIfAlternative(alt, falseFrom, falseKind) {
|
|
324
|
+
const exits = [];
|
|
325
|
+
if (alt.type === 'elsif') {
|
|
326
|
+
const cond = alt.childForFieldName('condition') ?? alt;
|
|
327
|
+
const elifHeader = this.builder.newBlock(startLineOf(alt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
328
|
+
this.builder.edge(falseFrom, elifHeader, falseKind);
|
|
329
|
+
const thenRes = this.visitBody(alt.childForFieldName('consequence'));
|
|
330
|
+
if (thenRes) {
|
|
331
|
+
this.builder.edge(elifHeader, thenRes.entry, 'cond-true');
|
|
332
|
+
exits.push(...thenRes.exits);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
exits.push(elifHeader);
|
|
336
|
+
}
|
|
337
|
+
const nested = alt.childForFieldName('alternative');
|
|
338
|
+
if (nested) {
|
|
339
|
+
exits.push(...this.visitIfAlternative(nested, elifHeader, 'cond-false').exits);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
exits.push(elifHeader); // elsif with no further alternative falls through
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// a trailing `else` (consumes the false path entirely).
|
|
347
|
+
const elseRes = this.visitBody(alt);
|
|
348
|
+
if (elseRes) {
|
|
349
|
+
this.builder.edge(falseFrom, elseRes.entry, falseKind);
|
|
350
|
+
exits.push(...elseRes.exits);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
exits.push(falseFrom);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return { exits };
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* `expr if cond` / `expr unless cond` — the modifier statement-form. The body
|
|
360
|
+
* runs on the taken branch; the complement falls straight through to the join.
|
|
361
|
+
*/
|
|
362
|
+
visitIfModifier(stmt) {
|
|
363
|
+
const inverted = stmt.type === 'unless_modifier';
|
|
364
|
+
const cond = stmt.childForFieldName('condition');
|
|
365
|
+
const body = stmt.childForFieldName('body');
|
|
366
|
+
const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? cond.text : stmt.text, 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
367
|
+
const takenKind = inverted ? 'cond-false' : 'cond-true';
|
|
368
|
+
const exits = [header]; // the complement path falls through
|
|
369
|
+
const bodyRes = body ? this.visitStmt(body) : null;
|
|
370
|
+
if (bodyRes) {
|
|
371
|
+
this.builder.edge(header, bodyRes.entry, takenKind);
|
|
372
|
+
exits.push(...bodyRes.exits);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
this.builder.edge(header, header, takenKind === 'cond-true' ? 'cond-true' : 'cond-false');
|
|
376
|
+
}
|
|
377
|
+
return { entry: header, exits: [...new Set(exits)] };
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* `while cond … end` / `until cond … end`. `until` inverts (its body runs while
|
|
381
|
+
* the condition is FALSE); both are modeled with `cond-true` to the body and
|
|
382
|
+
* `cond-false` to the loop exit (the inversion is a semantic note, not a
|
|
383
|
+
* structural change — the header is a single control point either way).
|
|
384
|
+
*/
|
|
385
|
+
visitWhile(stmt) {
|
|
386
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
387
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
388
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
389
|
+
this.cfc.pushLoop(header, loopExit, []);
|
|
390
|
+
const body = this.visitBody(stmt.childForFieldName('body'));
|
|
391
|
+
this.cfc.pop();
|
|
392
|
+
if (body) {
|
|
393
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
394
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
398
|
+
}
|
|
399
|
+
// Always emit the structural exit edge — even `while true` keeps EXIT
|
|
400
|
+
// reverse-reachable for the post-dominator / CDG pass.
|
|
401
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
402
|
+
return { entry: header, exits: [loopExit] };
|
|
403
|
+
}
|
|
404
|
+
/** `expr while cond` / `expr until cond` — the modifier loop-form. */
|
|
405
|
+
visitWhileModifier(stmt) {
|
|
406
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
407
|
+
const body = stmt.childForFieldName('body');
|
|
408
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
409
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
410
|
+
this.cfc.pushLoop(header, loopExit, []);
|
|
411
|
+
const bodyRes = body ? this.visitStmt(body) : null;
|
|
412
|
+
this.cfc.pop();
|
|
413
|
+
if (bodyRes) {
|
|
414
|
+
this.builder.edge(header, bodyRes.entry, 'cond-true');
|
|
415
|
+
this.builder.connect(bodyRes.exits, header, 'loop-back');
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
this.builder.edge(header, header, 'loop-back');
|
|
419
|
+
}
|
|
420
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
421
|
+
return { entry: header, exits: [loopExit] };
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* `for PATTERN in VALUE … end`. Header = the iteration test (a def of the
|
|
425
|
+
* pattern target(s) + a use of the iterable).
|
|
426
|
+
*/
|
|
427
|
+
visitFor(stmt) {
|
|
428
|
+
const value = stmt.childForFieldName('value');
|
|
429
|
+
const header = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), this.forHeaderText(stmt), 'normal', this.harvest.loopHeadFacts(stmt));
|
|
430
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
431
|
+
this.cfc.pushLoop(header, loopExit, []);
|
|
432
|
+
const body = this.visitBody(stmt.childForFieldName('body'));
|
|
433
|
+
this.cfc.pop();
|
|
434
|
+
if (body) {
|
|
435
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
436
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
this.builder.edge(header, header, 'loop-back');
|
|
440
|
+
}
|
|
441
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
442
|
+
return { entry: header, exits: [loopExit] };
|
|
443
|
+
}
|
|
444
|
+
forHeaderText(stmt) {
|
|
445
|
+
const pattern = stmt.childForFieldName('pattern')?.text ?? '';
|
|
446
|
+
const value = stmt.childForFieldName('value')?.text ?? '';
|
|
447
|
+
return pattern || value ? `for ${pattern} ${value}` : stmt.text.split('\n')[0];
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* `case VALUE; when P; … else; … end`. Cases do NOT fall through (like Go /
|
|
451
|
+
* Python match). The subject block fans a `switch-case` edge to each `when`
|
|
452
|
+
* body; a `case` with no `else` also reaches the join directly (no-match path),
|
|
453
|
+
* keeping EXIT reverse-reachable.
|
|
454
|
+
*/
|
|
455
|
+
visitCase(stmt) {
|
|
456
|
+
const value = stmt.childForFieldName('value');
|
|
457
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `case ${value.text}` : 'case', 'normal', value ? this.harvest.facts(value) : undefined);
|
|
458
|
+
const caseExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
459
|
+
const whenClauses = stmt.namedChildren.filter((c) => c.type === 'when');
|
|
460
|
+
const elseClause = stmt.namedChildren.find((c) => c.type === 'else');
|
|
461
|
+
// Each `when` pattern evaluates conditionally before its body — harvest its
|
|
462
|
+
// uses onto the dispatch block (a later case only tests when earlier
|
|
463
|
+
// patterns did not match).
|
|
464
|
+
for (const w of whenClauses) {
|
|
465
|
+
for (const pat of this.whenPatterns(w)) {
|
|
466
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(pat));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
this.cfc.pushSwitch(caseExit, []);
|
|
470
|
+
for (const w of whenClauses) {
|
|
471
|
+
const bodyRes = this.visitBody(w.childForFieldName('body'));
|
|
472
|
+
const entry = bodyRes?.entry ?? caseExit;
|
|
473
|
+
this.builder.edge(dispatch, entry, 'switch-case');
|
|
474
|
+
if (bodyRes)
|
|
475
|
+
this.builder.connect(bodyRes.exits, caseExit, 'seq');
|
|
476
|
+
}
|
|
477
|
+
if (elseClause) {
|
|
478
|
+
const elseRes = this.visitBody(elseClause);
|
|
479
|
+
const entry = elseRes?.entry ?? caseExit;
|
|
480
|
+
this.builder.edge(dispatch, entry, 'switch-case');
|
|
481
|
+
if (elseRes)
|
|
482
|
+
this.builder.connect(elseRes.exits, caseExit, 'seq');
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
// No `else` → a no-match path reaches the exit directly (keeps EXIT
|
|
486
|
+
// reverse-reachable even when every when body jumps).
|
|
487
|
+
this.builder.edge(dispatch, caseExit, 'switch-case');
|
|
488
|
+
}
|
|
489
|
+
this.cfc.pop();
|
|
490
|
+
return { entry: dispatch, exits: [caseExit] };
|
|
491
|
+
}
|
|
492
|
+
/** The `pattern`-field children of a `when` clause (a `when` may list several). */
|
|
493
|
+
whenPatterns(whenClause) {
|
|
494
|
+
const out = [];
|
|
495
|
+
for (let i = 0; i < whenClause.childCount; i++) {
|
|
496
|
+
if (whenClause.fieldNameForChild(i) === 'pattern') {
|
|
497
|
+
const c = whenClause.child(i);
|
|
498
|
+
if (c)
|
|
499
|
+
out.push(c);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* `case VALUE; in PATTERN [if guard]; … else; … end` — the pattern-matching
|
|
506
|
+
* form (`case_match`). Same no-fallthrough dispatch as `case`/`when`; each
|
|
507
|
+
* `in_clause` body is dispatched with a `switch-case` edge and an `in_clause`
|
|
508
|
+
* guard is harvested as a conditional use on the dispatch.
|
|
509
|
+
*/
|
|
510
|
+
visitCaseMatch(stmt) {
|
|
511
|
+
const value = stmt.childForFieldName('value');
|
|
512
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), value ? endLineOf(value) : startLineOf(stmt), value ? `case ${value.text}` : 'case', 'normal', value ? this.harvest.facts(value) : undefined);
|
|
513
|
+
const caseExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
514
|
+
const clauses = stmt.namedChildren.filter((c) => c.type === 'in_clause');
|
|
515
|
+
const elseClause = stmt.childForFieldName('else') ?? stmt.namedChildren.find((c) => c.type === 'else');
|
|
516
|
+
// An `in`-clause guard (`in P if g`) evaluates conditionally on the dispatch.
|
|
517
|
+
for (const c of clauses) {
|
|
518
|
+
const guard = c.childForFieldName('guard');
|
|
519
|
+
if (guard)
|
|
520
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(guard));
|
|
521
|
+
}
|
|
522
|
+
this.cfc.pushSwitch(caseExit, []);
|
|
523
|
+
for (const c of clauses) {
|
|
524
|
+
const bodyRes = this.visitBody(c.childForFieldName('body'));
|
|
525
|
+
const entry = bodyRes?.entry ?? caseExit;
|
|
526
|
+
this.builder.edge(dispatch, entry, 'switch-case');
|
|
527
|
+
if (bodyRes)
|
|
528
|
+
this.builder.connect(bodyRes.exits, caseExit, 'seq');
|
|
529
|
+
}
|
|
530
|
+
if (elseClause) {
|
|
531
|
+
const elseRes = this.visitBody(elseClause);
|
|
532
|
+
const entry = elseRes?.entry ?? caseExit;
|
|
533
|
+
this.builder.edge(dispatch, entry, 'switch-case');
|
|
534
|
+
if (elseRes)
|
|
535
|
+
this.builder.connect(elseRes.exits, caseExit, 'seq');
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
// A `case … in` with no `else` raises NoMatchingPatternError on no match,
|
|
539
|
+
// but the structural no-match edge keeps EXIT reverse-reachable.
|
|
540
|
+
this.builder.edge(dispatch, caseExit, 'switch-case');
|
|
541
|
+
}
|
|
542
|
+
this.cfc.pop();
|
|
543
|
+
return { entry: dispatch, exits: [caseExit] };
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* `begin … [rescue …]* [else …] [ensure …] end`. Mirrors the Python
|
|
547
|
+
* try-route-through:
|
|
548
|
+
* - `ensure` runs on every exit (normal, exception, early jumps) — a finalizer
|
|
549
|
+
* frame for early-exit threading + a normal/exceptional join.
|
|
550
|
+
* - each `rescue` handler catches from the protected body.
|
|
551
|
+
* - `else` runs only if the body completed with no exception.
|
|
552
|
+
*/
|
|
553
|
+
visitBegin(stmt) {
|
|
554
|
+
const children = this.statementsOf(stmt);
|
|
555
|
+
return this.buildProtected(stmt, children);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* A method `body_statement` is an implicit `begin` — its trailing
|
|
559
|
+
* `rescue`/`else`/`ensure` children behave exactly like a `begin`'s. When there
|
|
560
|
+
* are none, this is just a straight statement sequence.
|
|
561
|
+
*/
|
|
562
|
+
visitImplicitBegin(stmt) {
|
|
563
|
+
const children = this.statementsOf(stmt);
|
|
564
|
+
const hasHandlers = children.some((c) => c.type === 'rescue' || c.type === 'else' || c.type === 'ensure');
|
|
565
|
+
if (!hasHandlers)
|
|
566
|
+
return this.visitSeq(children);
|
|
567
|
+
return this.buildProtected(stmt, children);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Shared begin/rescue/else/ensure builder. `children` is the construct's
|
|
571
|
+
* ordered named children: leading protected statements, then the typed
|
|
572
|
+
* `rescue` / `else` / `ensure` clauses.
|
|
573
|
+
*/
|
|
574
|
+
buildProtected(span, children) {
|
|
575
|
+
const protectedStmts = [];
|
|
576
|
+
const rescueClauses = [];
|
|
577
|
+
let elseClause;
|
|
578
|
+
let ensureClause;
|
|
579
|
+
for (const c of children) {
|
|
580
|
+
if (c.type === 'rescue')
|
|
581
|
+
rescueClauses.push(c);
|
|
582
|
+
else if (c.type === 'else')
|
|
583
|
+
elseClause = c;
|
|
584
|
+
else if (c.type === 'ensure')
|
|
585
|
+
ensureClause = c;
|
|
586
|
+
else
|
|
587
|
+
protectedStmts.push(c);
|
|
588
|
+
}
|
|
589
|
+
// Build ensure first — it is both a normal join and a handler target. It runs
|
|
590
|
+
// OUTSIDE this begin's finalizer frame (a return inside ensure threads only
|
|
591
|
+
// OUTER ensures).
|
|
592
|
+
const ensureRes = ensureClause ? this.visitSeq(this.statementsOf(ensureClause)) : null;
|
|
593
|
+
const finFrame = ensureRes ? this.cfc.pushFinalizer(ensureRes.entry) : null;
|
|
594
|
+
// Pre-create each rescue's facts-only HEAD block (`rescue Exc => e` binds `e`
|
|
595
|
+
// once, on handler entry) so the protected body knows its handler target
|
|
596
|
+
// BEFORE either body is walked — this lets a `retry` inside a rescue handler
|
|
597
|
+
// re-enter the protected body's real entry (which is walked next).
|
|
598
|
+
const handlerEntries = rescueClauses.map((clause) => this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', this.harvest.rescueHeadFacts(clause)));
|
|
599
|
+
// Handler for the protected body: the first rescue if present, else ensure,
|
|
600
|
+
// else the outer handler.
|
|
601
|
+
const tryHandler = handlerEntries[0] ?? ensureRes?.entry ?? this.currentHandler();
|
|
602
|
+
const protectedStart = this.builder.blockCount;
|
|
603
|
+
this.handlers.push(tryHandler);
|
|
604
|
+
const bodyRes = this.visitSeq(protectedStmts);
|
|
605
|
+
this.handlers.pop();
|
|
606
|
+
// Block range covering ONLY the protected body (rescue handler bodies are
|
|
607
|
+
// walked afterward and must NOT get the conservative per-block throw edge).
|
|
608
|
+
const protectedEnd = this.builder.blockCount;
|
|
609
|
+
// The protected body's entry is where a `retry` re-enters (a `loop-back`).
|
|
610
|
+
// Push it for BOTH the rescue-handler bodies AND already-walked body (already
|
|
611
|
+
// done above — a `retry` in the body itself is a non-idiom, but harmless).
|
|
612
|
+
const beginEntry = bodyRes?.entry ?? handlerEntries[0] ?? this.builder.exitIndex;
|
|
613
|
+
this.beginEntries.push(beginEntry);
|
|
614
|
+
// Walk each rescue handler body now (within the begin-entry scope so a
|
|
615
|
+
// `retry` re-enters the protected body). A raise inside a handler propagates
|
|
616
|
+
// to ensure (if any), else the outer handler.
|
|
617
|
+
const handlerExits = [];
|
|
618
|
+
rescueClauses.forEach((clause, i) => {
|
|
619
|
+
if (ensureRes)
|
|
620
|
+
this.handlers.push(ensureRes.entry);
|
|
621
|
+
const headBlock = handlerEntries[i];
|
|
622
|
+
const handlerBodyRes = this.visitBody(clause.childForFieldName('body'));
|
|
623
|
+
if (handlerBodyRes) {
|
|
624
|
+
this.builder.edge(headBlock, handlerBodyRes.entry, 'seq');
|
|
625
|
+
handlerExits.push(...handlerBodyRes.exits);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
handlerExits.push(headBlock); // empty handler — header is the exit
|
|
629
|
+
}
|
|
630
|
+
if (ensureRes)
|
|
631
|
+
this.handlers.pop();
|
|
632
|
+
});
|
|
633
|
+
this.beginEntries.pop();
|
|
634
|
+
// Conservative exceptional edges: ANY protected-region block may raise to
|
|
635
|
+
// EACH handler (an unmatched exception type tries the next handler).
|
|
636
|
+
if (rescueClauses.length > 0 || ensureClause) {
|
|
637
|
+
const targets = handlerEntries.length > 0 ? handlerEntries : ensureRes ? [ensureRes.entry] : [];
|
|
638
|
+
for (let b = protectedStart; b < protectedEnd; b++) {
|
|
639
|
+
for (const h of targets)
|
|
640
|
+
this.builder.edge(b, h, 'throw');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// The `else` runs only on no-exception normal completion of the body.
|
|
644
|
+
let normalAfterBody = bodyRes ? [...bodyRes.exits] : [];
|
|
645
|
+
if (elseClause) {
|
|
646
|
+
const elseRes = this.visitBody(elseClause);
|
|
647
|
+
if (elseRes && bodyRes) {
|
|
648
|
+
this.builder.connect(bodyRes.exits, elseRes.entry, 'seq');
|
|
649
|
+
normalAfterBody = [...elseRes.exits];
|
|
650
|
+
}
|
|
651
|
+
else if (elseRes) {
|
|
652
|
+
normalAfterBody = [...elseRes.exits];
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// Close the finalizer frame; wire crossing-jump completion legs.
|
|
656
|
+
if (finFrame && ensureRes) {
|
|
657
|
+
this.cfc.pop();
|
|
658
|
+
drainFinalizerPending(this.builder, finFrame, ensureRes.exits);
|
|
659
|
+
}
|
|
660
|
+
const exits = [];
|
|
661
|
+
if (ensureRes) {
|
|
662
|
+
// Normal completion of (body→else) AND each handler flows through ensure.
|
|
663
|
+
this.builder.connect(normalAfterBody, ensureRes.entry, 'seq');
|
|
664
|
+
this.builder.connect(handlerExits, ensureRes.entry, 'seq');
|
|
665
|
+
exits.push(...ensureRes.exits);
|
|
666
|
+
// A begin with no rescue → an uncaught exception re-propagates after ensure.
|
|
667
|
+
if (handlerEntries.length === 0) {
|
|
668
|
+
this.builder.connect(ensureRes.exits, this.currentHandler(), 'throw');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
exits.push(...normalAfterBody);
|
|
673
|
+
exits.push(...handlerExits);
|
|
674
|
+
}
|
|
675
|
+
const entry = bodyRes?.entry ?? ensureRes?.entry ?? handlerEntries[0];
|
|
676
|
+
if (entry === undefined) {
|
|
677
|
+
void span;
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
return { entry, exits: [...new Set(exits)] };
|
|
681
|
+
}
|
|
682
|
+
/** Nearest enclosing exception handler, or the function EXIT. */
|
|
683
|
+
currentHandler() {
|
|
684
|
+
return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/** The body node of a Ruby function/block/lambda (a `block` for `lambda` unwraps once). */
|
|
688
|
+
function functionBody(fnNode) {
|
|
689
|
+
const body = fnNode.childForFieldName('body');
|
|
690
|
+
if (!body)
|
|
691
|
+
return undefined;
|
|
692
|
+
// A `lambda`'s `body` is a `block` wrapping a `block_body` — unwrap to the
|
|
693
|
+
// inner body so the walk sees the statement sequence.
|
|
694
|
+
if (fnNode.type === 'lambda' && body.type === 'block') {
|
|
695
|
+
return body.childForFieldName('body') ?? body;
|
|
696
|
+
}
|
|
697
|
+
return body;
|
|
698
|
+
}
|
|
699
|
+
/** Build the CFG for one Ruby function/block/lambda node, or `undefined` if not modelable. */
|
|
700
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
701
|
+
try {
|
|
702
|
+
if (!RUBY_FUNCTION_TYPES.has(fnNode.type))
|
|
703
|
+
return undefined;
|
|
704
|
+
const startLine = startLineOf(fnNode);
|
|
705
|
+
const endLine = endLineOf(fnNode);
|
|
706
|
+
const startColumn = fnNode.startPosition.column;
|
|
707
|
+
const body = functionBody(fnNode);
|
|
708
|
+
if (!body) {
|
|
709
|
+
// No body — an empty `def f; end` still gets a trivial ENTRY → EXIT CFG so
|
|
710
|
+
// it is not silently dropped.
|
|
711
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
712
|
+
const harvest = new RubyHarvester(fnNode);
|
|
713
|
+
const paramFacts = harvest.paramFacts();
|
|
714
|
+
if (paramFacts)
|
|
715
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
716
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq');
|
|
717
|
+
return builder.finish(harvest.bindingTable());
|
|
718
|
+
}
|
|
719
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
720
|
+
const harvest = new RubyHarvester(fnNode);
|
|
721
|
+
const paramFacts = harvest.paramFacts();
|
|
722
|
+
if (paramFacts)
|
|
723
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
724
|
+
const walk = new RubyCfgWalk(builder, harvest);
|
|
725
|
+
// For a bare block / lambda, a `redo` re-enters the block body. The body's
|
|
726
|
+
// first block is the NEXT index the builder will allocate (entry + exit are
|
|
727
|
+
// already taken). Set it so a top-level `redo` inside the block loops back.
|
|
728
|
+
if (fnNode.type !== 'method' && fnNode.type !== 'singleton_method') {
|
|
729
|
+
walk.setBlockRedoTarget(builder.blockCount);
|
|
730
|
+
}
|
|
731
|
+
const res = walk.visitStmt(body);
|
|
732
|
+
if (!res) {
|
|
733
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
|
734
|
+
return builder.finish(harvest.bindingTable());
|
|
735
|
+
}
|
|
736
|
+
builder.edge(builder.entryIndex, res.entry, 'seq');
|
|
737
|
+
builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
|
|
738
|
+
return builder.finish(harvest.bindingTable());
|
|
739
|
+
}
|
|
740
|
+
catch (err) {
|
|
741
|
+
// Never throw out of buildFunctionCfg — a malformed AST shape must skip only
|
|
742
|
+
// this one function's CFG, never drop the whole file's language group (R4).
|
|
743
|
+
// eslint-disable-next-line no-console
|
|
744
|
+
console.warn(`[cfg] Ruby buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
|
|
745
|
+
return undefined;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/** Whether a node is a Ruby function/closure this visitor builds a CFG for. */
|
|
749
|
+
function isFunction(node) {
|
|
750
|
+
return RUBY_FUNCTION_TYPES.has(node.type);
|
|
751
|
+
}
|
|
752
|
+
/** The Ruby CFG visitor. */
|
|
753
|
+
export function createRubyCfgVisitor() {
|
|
754
|
+
return { buildFunctionCfg, isFunction };
|
|
755
|
+
}
|
|
756
|
+
export { RUBY_FUNCTION_TYPES };
|