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,809 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
|
|
3
|
+
import { KotlinHarvester } from './kotlin-harvest.js';
|
|
4
|
+
/** Kotlin node types that own a CFG-bearing function body. */
|
|
5
|
+
const KOTLIN_FUNCTION_TYPES = new Set([
|
|
6
|
+
'function_declaration',
|
|
7
|
+
'anonymous_function',
|
|
8
|
+
'lambda_literal',
|
|
9
|
+
]);
|
|
10
|
+
/** Statement node types that break a basic block (everything else coalesces). */
|
|
11
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
12
|
+
'if_expression',
|
|
13
|
+
'when_expression',
|
|
14
|
+
'for_statement',
|
|
15
|
+
'while_statement',
|
|
16
|
+
'do_while_statement',
|
|
17
|
+
'try_expression',
|
|
18
|
+
'jump_expression',
|
|
19
|
+
'label',
|
|
20
|
+
]);
|
|
21
|
+
/** Comment / non-code node types tree-sitter-kotlin surfaces (NOT `comment`). */
|
|
22
|
+
const COMMENT_TYPES = new Set(['line_comment', 'multiline_comment', 'shebang_line']);
|
|
23
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
24
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
25
|
+
const isComment = (n) => COMMENT_TYPES.has(n.type);
|
|
26
|
+
/**
|
|
27
|
+
* Per-function Kotlin walk state. One instance per function so the
|
|
28
|
+
* {@link ControlFlowContext}, exception-handler stack, and labeled-frame
|
|
29
|
+
* bookkeeping are scoped to that function and never leak across functions.
|
|
30
|
+
*/
|
|
31
|
+
class KotlinCfgWalk {
|
|
32
|
+
builder;
|
|
33
|
+
harvest;
|
|
34
|
+
cfc = new ControlFlowContext();
|
|
35
|
+
/** Stack of exception-handler entry blocks (catch/finally) a `throw` jumps to. */
|
|
36
|
+
handlers = [];
|
|
37
|
+
/** Label(s) pending attachment to the NEXT pushed loop frame. */
|
|
38
|
+
pendingLabels = [];
|
|
39
|
+
constructor(builder, harvest) {
|
|
40
|
+
this.builder = builder;
|
|
41
|
+
this.harvest = harvest;
|
|
42
|
+
}
|
|
43
|
+
/** Named statements of a `statements` node, ignoring comments. */
|
|
44
|
+
statementsOf(block) {
|
|
45
|
+
return block.namedChildren.filter((c) => !isComment(c));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Unwrap a `control_structure_body`: a `{ statements }` block yields its
|
|
49
|
+
* `statements` node; a bare single statement (`if (c) a()`) yields itself.
|
|
50
|
+
*/
|
|
51
|
+
bodyOf(csb) {
|
|
52
|
+
if (!csb)
|
|
53
|
+
return undefined;
|
|
54
|
+
if (csb.type === 'control_structure_body') {
|
|
55
|
+
const stmts = csb.namedChildren.find((c) => c.type === 'statements');
|
|
56
|
+
if (stmts)
|
|
57
|
+
return stmts;
|
|
58
|
+
const single = csb.namedChildren.find((c) => !isComment(c));
|
|
59
|
+
return single;
|
|
60
|
+
}
|
|
61
|
+
return csb;
|
|
62
|
+
}
|
|
63
|
+
/** Visit a `control_structure_body` (block or single statement). */
|
|
64
|
+
visitBody(csb) {
|
|
65
|
+
return this.builder.withNesting(() => {
|
|
66
|
+
const inner = this.bodyOf(csb);
|
|
67
|
+
if (!inner)
|
|
68
|
+
return null;
|
|
69
|
+
if (inner.type === 'statements')
|
|
70
|
+
return this.visitSeq(this.statementsOf(inner));
|
|
71
|
+
return this.visitStmt(inner);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
75
|
+
visitSeq(stmts) {
|
|
76
|
+
return this.builder.withNesting(() => {
|
|
77
|
+
let entry;
|
|
78
|
+
let dangling = [];
|
|
79
|
+
let openSimple;
|
|
80
|
+
for (const stmt of stmts) {
|
|
81
|
+
if (this.isControlFlow(stmt)) {
|
|
82
|
+
openSimple = undefined; // close any open straight-line block
|
|
83
|
+
const res = this.visitStmt(stmt);
|
|
84
|
+
if (res === null)
|
|
85
|
+
continue; // transparent (empty nested block / label-only)
|
|
86
|
+
if (entry === undefined)
|
|
87
|
+
entry = res.entry;
|
|
88
|
+
else
|
|
89
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
90
|
+
dangling = [...res.exits];
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (openSimple === undefined) {
|
|
94
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
95
|
+
if (entry === undefined)
|
|
96
|
+
entry = idx;
|
|
97
|
+
else
|
|
98
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
99
|
+
openSimple = idx;
|
|
100
|
+
dangling = [idx];
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (entry === undefined)
|
|
108
|
+
return null;
|
|
109
|
+
return { entry, exits: dangling };
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Whether a statement breaks the current straight-line block. `if` / `when` /
|
|
114
|
+
* `try` are EXPRESSIONS in Kotlin — they break a block when used as a STATEMENT
|
|
115
|
+
* (a direct child of a `statements` list), OR when they are the value of a
|
|
116
|
+
* `val/var x = <branch>` binding or an `x = <branch>` assignment (#2205) —
|
|
117
|
+
* `visitStmt`'s `property_declaration` / `assignment` case then models the arms
|
|
118
|
+
* as control flow. A call argument value position still coalesces (a remaining
|
|
119
|
+
* gap — the branch is nested in a call, harder to bind).
|
|
120
|
+
*/
|
|
121
|
+
isControlFlow(stmt) {
|
|
122
|
+
if (stmt.type === 'label')
|
|
123
|
+
return true; // queue label, emit no block
|
|
124
|
+
if (stmt.type === 'property_declaration') {
|
|
125
|
+
const v = this.directValue(stmt);
|
|
126
|
+
return v !== undefined && this.isModelableValueBranch(v);
|
|
127
|
+
}
|
|
128
|
+
if (stmt.type === 'assignment')
|
|
129
|
+
return this.assignmentBranch(stmt) !== undefined;
|
|
130
|
+
if (!CONTROL_FLOW_TYPES.has(stmt.type))
|
|
131
|
+
return false;
|
|
132
|
+
if (this.isExpressionConstruct(stmt.type))
|
|
133
|
+
return this.isStatementPosition(stmt);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
isExpressionConstruct(type) {
|
|
137
|
+
return type === 'if_expression' || type === 'when_expression' || type === 'try_expression';
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Whether an expression-construct (`if`/`when`/`try`) is in STATEMENT position
|
|
141
|
+
* (a direct `statements` child, or a `control_structure_body` that is itself a
|
|
142
|
+
* bare statement) vs an expression VALUE (nested under a declaration / jump /
|
|
143
|
+
* assignment / argument). Statement-position constructs are modeled as
|
|
144
|
+
* dispatch/branch; value-position ones stay inline.
|
|
145
|
+
*/
|
|
146
|
+
isStatementPosition(node) {
|
|
147
|
+
const p = node.parent;
|
|
148
|
+
if (!p)
|
|
149
|
+
return false;
|
|
150
|
+
return p.type === 'statements' || p.type === 'control_structure_body';
|
|
151
|
+
}
|
|
152
|
+
/** Dispatch one statement to its handler. Non-null except for empty / label-only. */
|
|
153
|
+
visitStmt(stmt) {
|
|
154
|
+
if (stmt.type === 'label') {
|
|
155
|
+
// A label preceding its loop — queue it; the loop construct picks it up.
|
|
156
|
+
const name = this.labelName(stmt);
|
|
157
|
+
if (name !== undefined)
|
|
158
|
+
this.pendingLabels = [...this.pendingLabels, name];
|
|
159
|
+
return null; // emits no block of its own
|
|
160
|
+
}
|
|
161
|
+
switch (stmt.type) {
|
|
162
|
+
case 'if_expression':
|
|
163
|
+
return this.isStatementPosition(stmt) ? this.visitIf(stmt) : this.visitSimple(stmt);
|
|
164
|
+
case 'when_expression':
|
|
165
|
+
return this.isStatementPosition(stmt) ? this.visitWhen(stmt) : this.visitSimple(stmt);
|
|
166
|
+
case 'try_expression':
|
|
167
|
+
return this.isStatementPosition(stmt) ? this.visitTry(stmt) : this.visitSimple(stmt);
|
|
168
|
+
case 'for_statement':
|
|
169
|
+
return this.visitFor(stmt);
|
|
170
|
+
case 'while_statement':
|
|
171
|
+
return this.visitWhile(stmt);
|
|
172
|
+
case 'do_while_statement':
|
|
173
|
+
return this.visitDoWhile(stmt);
|
|
174
|
+
case 'jump_expression':
|
|
175
|
+
return this.visitJump(stmt);
|
|
176
|
+
case 'property_declaration': {
|
|
177
|
+
// `val x = when (k) { … }` / `val x = if (c) a else b` (#2205): the value
|
|
178
|
+
// is a value-position branch — model it as control flow and bind the
|
|
179
|
+
// result on the rejoin, instead of collapsing the whole decl to one block.
|
|
180
|
+
const value = this.directValue(stmt);
|
|
181
|
+
if (value && this.isModelableValueBranch(value))
|
|
182
|
+
return this.visitBindBranch(stmt, value);
|
|
183
|
+
return this.visitSimple(stmt);
|
|
184
|
+
}
|
|
185
|
+
case 'assignment': {
|
|
186
|
+
// `x = when (k) { … }` / `x = if (c) a else b` / `x = try { … }` (#2205):
|
|
187
|
+
// model the RHS branch as control flow and bind the target on the rejoin.
|
|
188
|
+
const branch = this.assignmentBranch(stmt);
|
|
189
|
+
if (branch)
|
|
190
|
+
return this.visitBindAssign(stmt, branch);
|
|
191
|
+
return this.visitSimple(stmt);
|
|
192
|
+
}
|
|
193
|
+
default:
|
|
194
|
+
return this.visitSimple(stmt);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
visitSimple(stmt) {
|
|
198
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
199
|
+
return { entry: idx, exits: [idx] };
|
|
200
|
+
}
|
|
201
|
+
// ── jump expressions (return / throw / break / continue) ──────────────────
|
|
202
|
+
/** The leading anonymous keyword of a `jump_expression` decides its kind. */
|
|
203
|
+
jumpKeyword(stmt) {
|
|
204
|
+
const first = stmt.child(0);
|
|
205
|
+
return first?.text ?? '';
|
|
206
|
+
}
|
|
207
|
+
visitJump(stmt) {
|
|
208
|
+
const kw = this.jumpKeyword(stmt);
|
|
209
|
+
if (kw === 'return' || kw === 'return@')
|
|
210
|
+
return this.visitReturn(stmt);
|
|
211
|
+
if (kw === 'throw')
|
|
212
|
+
return this.visitThrow(stmt);
|
|
213
|
+
if (kw === 'break' || kw === 'break@')
|
|
214
|
+
return this.visitBreak(stmt);
|
|
215
|
+
if (kw === 'continue' || kw === 'continue@')
|
|
216
|
+
return this.visitContinue(stmt);
|
|
217
|
+
// Unknown jump — straight through (defensive; the grammar emits only the above).
|
|
218
|
+
return this.visitSimple(stmt);
|
|
219
|
+
}
|
|
220
|
+
/** `return [expr]` / `return@label` — threads through every active finalizer. */
|
|
221
|
+
visitReturn(stmt) {
|
|
222
|
+
// `return when (k) { … }` / `return if (c) a else b` / `return try { … }`
|
|
223
|
+
// (#2205): the returned value is a value-position branch — model it as control
|
|
224
|
+
// flow, with each arm returning (its value IS the function result), threading
|
|
225
|
+
// finalizers per arm.
|
|
226
|
+
const branch = stmt.namedChildren.find((c) => c.type === 'when_expression' || c.type === 'if_expression' || c.type === 'try_expression');
|
|
227
|
+
if (branch && this.isModelableValueBranch(branch)) {
|
|
228
|
+
const res = this.visitBranchExpr(branch);
|
|
229
|
+
const finalizers = this.cfc.finalizersForReturn();
|
|
230
|
+
for (const ex of res.exits) {
|
|
231
|
+
wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
|
|
232
|
+
}
|
|
233
|
+
return { entry: res.entry, exits: [] };
|
|
234
|
+
}
|
|
235
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
236
|
+
wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
|
|
237
|
+
return { entry: idx, exits: [] };
|
|
238
|
+
}
|
|
239
|
+
/** `throw e` — routes to the nearest enclosing handler (catch/finally), else EXIT. */
|
|
240
|
+
visitThrow(stmt) {
|
|
241
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
242
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
243
|
+
return { entry: idx, exits: [] };
|
|
244
|
+
}
|
|
245
|
+
visitBreak(stmt) {
|
|
246
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
247
|
+
const label = this.jumpLabel(stmt);
|
|
248
|
+
const res = this.cfc.resolveBreak(label);
|
|
249
|
+
const { target, finalizers } = res ?? {
|
|
250
|
+
target: this.builder.exitIndex,
|
|
251
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
252
|
+
};
|
|
253
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
254
|
+
return { entry: idx, exits: [] };
|
|
255
|
+
}
|
|
256
|
+
visitContinue(stmt) {
|
|
257
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
258
|
+
const label = this.jumpLabel(stmt);
|
|
259
|
+
const res = this.cfc.resolveContinue(label);
|
|
260
|
+
const { target, finalizers } = res ?? {
|
|
261
|
+
target: this.builder.exitIndex,
|
|
262
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
263
|
+
};
|
|
264
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
265
|
+
return { entry: idx, exits: [] };
|
|
266
|
+
}
|
|
267
|
+
/** The target `label` of a `break@outer` / `continue@outer` / `return@x`, if any. */
|
|
268
|
+
jumpLabel(stmt) {
|
|
269
|
+
const label = stmt.namedChildren.find((c) => c.type === 'label');
|
|
270
|
+
return label ? this.stripLabel(label.text) : undefined;
|
|
271
|
+
}
|
|
272
|
+
/** The name of a `label` sibling (`outer@` ⇒ `outer`; jump target `outer` ⇒ `outer`). */
|
|
273
|
+
labelName(label) {
|
|
274
|
+
const id = label.namedChildren.find((c) => c.type === 'simple_identifier');
|
|
275
|
+
if (id?.text)
|
|
276
|
+
return this.stripLabel(id.text);
|
|
277
|
+
return this.stripLabel(label.text) || undefined;
|
|
278
|
+
}
|
|
279
|
+
stripLabel(text) {
|
|
280
|
+
return text.replace(/@$/, '').replace(/^@/, '').trim();
|
|
281
|
+
}
|
|
282
|
+
/** Take and clear the labels queued by a preceding `label` sibling. */
|
|
283
|
+
takeLabels() {
|
|
284
|
+
const labels = this.pendingLabels;
|
|
285
|
+
this.pendingLabels = [];
|
|
286
|
+
return labels;
|
|
287
|
+
}
|
|
288
|
+
// ── branches ──────────────────────────────────────────────────────────────
|
|
289
|
+
/**
|
|
290
|
+
* The direct value expression of a `= VALUE` carrier (`property_declaration`,
|
|
291
|
+
* a `function_body` expression body): the first named child after the `=`
|
|
292
|
+
* token. Returns the DIRECT value only — `val x = f(when …)` yields the call,
|
|
293
|
+
* not the nested `when`, so an argument-position branch is left inline (#2205).
|
|
294
|
+
*/
|
|
295
|
+
directValue(stmt) {
|
|
296
|
+
let sawEq = false;
|
|
297
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
298
|
+
const c = stmt.child(i);
|
|
299
|
+
if (!c)
|
|
300
|
+
continue;
|
|
301
|
+
if (c.type === '=') {
|
|
302
|
+
sawEq = true;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (sawEq && c.isNamed && !isComment(c))
|
|
306
|
+
return c;
|
|
307
|
+
}
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Whether `node` is a value-position branch worth modeling as control flow
|
|
312
|
+
* (#2205): a `when` with ≥2 arms, or an `if` that has an `else` (a value-position
|
|
313
|
+
* `if` always does). A single-arm `when` / else-less `if` carries no real
|
|
314
|
+
* control dependence, so it stays an inline {@link visitSimple} block.
|
|
315
|
+
*/
|
|
316
|
+
isModelableValueBranch(node) {
|
|
317
|
+
if (node.type === 'when_expression') {
|
|
318
|
+
return node.namedChildren.filter((c) => c.type === 'when_entry').length >= 2;
|
|
319
|
+
}
|
|
320
|
+
if (node.type === 'if_expression')
|
|
321
|
+
return this.elseNodeOf(node) !== undefined;
|
|
322
|
+
// `val x = try { … } catch { … }` / `try { … } finally { … }` (#2205): a
|
|
323
|
+
// value-position `try` with a `catch` OR a `finally` is a real branch — its
|
|
324
|
+
// value is the body's value, a catch's value, or the body's value threaded
|
|
325
|
+
// through a finalizer — so model it as control flow.
|
|
326
|
+
if (node.type === 'try_expression') {
|
|
327
|
+
return node.namedChildren.some((c) => c.type === 'catch_block' || c.type === 'finally_block');
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Model a value-position `when`/`if`/`try` as control flow regardless of its
|
|
333
|
+
* statement/value position — {@link visitStmt}'s `isStatementPosition` gate keeps
|
|
334
|
+
* value-position branches inline, so call the branch handlers directly here.
|
|
335
|
+
*/
|
|
336
|
+
visitBranchExpr(node) {
|
|
337
|
+
if (node.type === 'when_expression')
|
|
338
|
+
return this.visitWhen(node);
|
|
339
|
+
if (node.type === 'try_expression')
|
|
340
|
+
return this.visitTry(node) ?? this.visitSimple(node);
|
|
341
|
+
return this.visitIf(node);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* `val x = <branch>` (#2205): visit the branch as control flow, then rejoin its
|
|
345
|
+
* arms at a facts-only continuation carrying ONLY the bound name's def (the
|
|
346
|
+
* subject + arm-value uses are already harvested onto the branch's blocks). The
|
|
347
|
+
* arms are now control-dependent on the branch condition, and `x` is defined at
|
|
348
|
+
* the join — mirrors the Rust visitor's value-position `let` handling.
|
|
349
|
+
*/
|
|
350
|
+
visitBindBranch(stmt, branch) {
|
|
351
|
+
const res = this.visitBranchExpr(branch);
|
|
352
|
+
const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.bindingDefFacts(stmt));
|
|
353
|
+
this.builder.connect(res.exits, cont, 'seq');
|
|
354
|
+
return { entry: res.entry, exits: [cont] };
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The value-position branch on a plain `=` assignment RHS (`x = when (k) {…}` /
|
|
358
|
+
* `x = if (c) a else b` / `x = try {…}`, #2205), or undefined. Only a plain `=`
|
|
359
|
+
* (not a compound `+=`) with a modelable-branch RHS qualifies.
|
|
360
|
+
*/
|
|
361
|
+
assignmentBranch(stmt) {
|
|
362
|
+
if (stmt.type !== 'assignment')
|
|
363
|
+
return undefined;
|
|
364
|
+
const eq = stmt.children.find((c) => !c.isNamed && c.text === '=');
|
|
365
|
+
if (!eq)
|
|
366
|
+
return undefined; // compound assignment (`+=` etc.) is not a carrier
|
|
367
|
+
const rhs = stmt.namedChildren.find((c) => c.type !== 'directly_assignable_expression' && !isComment(c));
|
|
368
|
+
return rhs && this.isModelableValueBranch(rhs) ? rhs : undefined;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* `x = <branch>` (#2205): visit the RHS branch as control flow, then rejoin its
|
|
372
|
+
* arms at a facts-only continuation carrying ONLY the LHS target def (the branch
|
|
373
|
+
* subject + arm-value uses are already on the branch's blocks). The arms are now
|
|
374
|
+
* control-dependent on the branch — mirrors the Ruby value-branch assignment.
|
|
375
|
+
*/
|
|
376
|
+
visitBindAssign(stmt, branch) {
|
|
377
|
+
const res = this.visitBranchExpr(branch);
|
|
378
|
+
const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.assignmentDefFacts(stmt));
|
|
379
|
+
this.builder.connect(res.exits, cont, 'seq');
|
|
380
|
+
return { entry: res.entry, exits: [cont] };
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* A `fun f() = EXPR` expression body (#2205). A value-position branch is modeled
|
|
384
|
+
* as control flow (each arm yields the returned function result); any other
|
|
385
|
+
* expression stays one block. The caller wires entry ← ENTRY and exits → EXIT
|
|
386
|
+
* with a `return` edge (the body's value is the function's result).
|
|
387
|
+
*/
|
|
388
|
+
visitExprBody(expr) {
|
|
389
|
+
if (this.isModelableValueBranch(expr))
|
|
390
|
+
return this.visitBranchExpr(expr);
|
|
391
|
+
const blk = this.builder.newBlock(startLineOf(expr), endLineOf(expr), expr.text, 'normal', this.harvest.facts(expr));
|
|
392
|
+
return { entry: blk, exits: [blk] };
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* `if ( COND ) control_structure_body [ else (control_structure_body |
|
|
396
|
+
* if_expression) ]`. The else child after the `else` keyword is either the
|
|
397
|
+
* else body (`control_structure_body`) or a nested `if_expression` (`else if`).
|
|
398
|
+
*/
|
|
399
|
+
visitIf(stmt) {
|
|
400
|
+
const cond = this.parenCondition(stmt);
|
|
401
|
+
const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? `if (${cond.text})` : 'if', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
402
|
+
const bodies = stmt.namedChildren.filter((c) => c.type === 'control_structure_body');
|
|
403
|
+
const elseNode = this.elseNodeOf(stmt);
|
|
404
|
+
const exits = [];
|
|
405
|
+
const thenRes = this.visitBody(bodies[0]);
|
|
406
|
+
if (thenRes) {
|
|
407
|
+
this.builder.edge(header, thenRes.entry, 'cond-true');
|
|
408
|
+
exits.push(...thenRes.exits);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
exits.push(header); // empty then — true path falls through
|
|
412
|
+
}
|
|
413
|
+
if (elseNode) {
|
|
414
|
+
const elseRes = this.visitBody(elseNode);
|
|
415
|
+
if (elseRes) {
|
|
416
|
+
this.builder.edge(header, elseRes.entry, 'cond-false');
|
|
417
|
+
exits.push(...elseRes.exits);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
exits.push(header);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
exits.push(header); // no else — false path falls through to the join
|
|
425
|
+
}
|
|
426
|
+
return { entry: header, exits: [...new Set(exits)] };
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* The node after the `else` keyword: a nested `if_expression` (`else if`) or the
|
|
430
|
+
* else-body `control_structure_body`.
|
|
431
|
+
*/
|
|
432
|
+
elseNodeOf(stmt) {
|
|
433
|
+
let sawElse = false;
|
|
434
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
435
|
+
const c = stmt.child(i);
|
|
436
|
+
if (!c)
|
|
437
|
+
continue;
|
|
438
|
+
if (sawElse && c.isNamed)
|
|
439
|
+
return c;
|
|
440
|
+
if (c.type === 'else')
|
|
441
|
+
sawElse = true;
|
|
442
|
+
}
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
/** The condition expression of an `if`/`while` (the named child between `(` and `)`). */
|
|
446
|
+
parenCondition(stmt) {
|
|
447
|
+
let sawOpen = false;
|
|
448
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
449
|
+
const c = stmt.child(i);
|
|
450
|
+
if (!c)
|
|
451
|
+
continue;
|
|
452
|
+
if (c.type === '(') {
|
|
453
|
+
sawOpen = true;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (c.type === ')')
|
|
457
|
+
return undefined;
|
|
458
|
+
if (sawOpen && c.isNamed && !isComment(c))
|
|
459
|
+
return c;
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
// ── when (no fallthrough) ──────────────────────────────────────────────────
|
|
464
|
+
/**
|
|
465
|
+
* `when when_subject? { when_entry* }`. Arms do NOT fall through — each
|
|
466
|
+
* `when_entry` body rejoins after the `when`. The subject (and each entry's
|
|
467
|
+
* `when_condition` tests) evaluate before the body; their uses are harvested
|
|
468
|
+
* onto the dispatch block (a later entry's test runs only when earlier ones
|
|
469
|
+
* didn't match, so any binding there is a may-def).
|
|
470
|
+
*/
|
|
471
|
+
visitWhen(stmt) {
|
|
472
|
+
const labels = this.takeLabels();
|
|
473
|
+
const subject = stmt.namedChildren.find((c) => c.type === 'when_subject');
|
|
474
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), subject ? endLineOf(subject) : startLineOf(stmt), subject ? `when ${subject.text}` : 'when', 'normal', subject ? this.harvest.whenSubjectFacts(subject) : undefined);
|
|
475
|
+
const whenExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
476
|
+
this.cfc.pushSwitch(whenExit, labels);
|
|
477
|
+
const entries = stmt.namedChildren.filter((c) => c.type === 'when_entry');
|
|
478
|
+
// Each entry's case tests evaluate conditionally before its body.
|
|
479
|
+
for (const entry of entries) {
|
|
480
|
+
for (const test of this.entryConditions(entry)) {
|
|
481
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(test));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const entryResults = entries.map((e) => this.visitBody(this.entryBody(e)));
|
|
485
|
+
const hasElse = entries.some((e) => this.entryIsElse(e));
|
|
486
|
+
for (const res of entryResults) {
|
|
487
|
+
// An EMPTY-body arm still dispatches — it falls straight to the join. Wiring
|
|
488
|
+
// it (rather than skipping) keeps the dispatch from ending up with ZERO
|
|
489
|
+
// successors, which would orphan whenExit and break EXIT reverse-reachability
|
|
490
|
+
// (so the whole function's CDG gets dropped). The canonical trigger is an
|
|
491
|
+
// all-empty `when` with an `else` arm — `when(k){0->{};else->{}}` — where the
|
|
492
|
+
// no-match edge below is suppressed. The builder dedups, so this coexists
|
|
493
|
+
// with the no-match edge.
|
|
494
|
+
this.builder.edge(dispatch, res ? res.entry : whenExit, 'switch-case');
|
|
495
|
+
}
|
|
496
|
+
// A `when` with no `else` (statement position) may match no arm — the no-match
|
|
497
|
+
// path falls straight to the join.
|
|
498
|
+
if (!hasElse)
|
|
499
|
+
this.builder.edge(dispatch, whenExit, 'switch-case');
|
|
500
|
+
const exits = [whenExit];
|
|
501
|
+
// Each non-empty arm rejoins after the when (no fallthrough); an empty arm's
|
|
502
|
+
// dispatch edge already targets whenExit above.
|
|
503
|
+
for (const res of entryResults) {
|
|
504
|
+
if (!res)
|
|
505
|
+
continue;
|
|
506
|
+
this.builder.connect(res.exits, whenExit, 'seq');
|
|
507
|
+
}
|
|
508
|
+
this.cfc.pop();
|
|
509
|
+
return { entry: dispatch, exits };
|
|
510
|
+
}
|
|
511
|
+
/** The `when_condition` test(s) of a `when_entry` (empty for an `else` arm). */
|
|
512
|
+
entryConditions(entry) {
|
|
513
|
+
return entry.namedChildren.filter((c) => c.type === 'when_condition');
|
|
514
|
+
}
|
|
515
|
+
/** The body `control_structure_body` of a `when_entry`. */
|
|
516
|
+
entryBody(entry) {
|
|
517
|
+
return entry.namedChildren.find((c) => c.type === 'control_structure_body');
|
|
518
|
+
}
|
|
519
|
+
/** An `else ->` arm has an `else` keyword child and no `when_condition`. */
|
|
520
|
+
entryIsElse(entry) {
|
|
521
|
+
return entry.children.some((c) => c.type === 'else');
|
|
522
|
+
}
|
|
523
|
+
// ── loops ───────────────────────────────────────────────────────────────
|
|
524
|
+
/** `for ( PAT in COLLECTION ) control_structure_body`. */
|
|
525
|
+
visitFor(stmt) {
|
|
526
|
+
const labels = this.takeLabels();
|
|
527
|
+
const collection = this.forCollection(stmt);
|
|
528
|
+
const header = this.builder.newBlock(startLineOf(stmt), collection ? endLineOf(collection) : startLineOf(stmt), this.forHeaderText(stmt), 'normal', this.harvest.forHeadFacts(stmt));
|
|
529
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
530
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
531
|
+
const body = this.visitBody(this.loopBody(stmt));
|
|
532
|
+
this.cfc.pop();
|
|
533
|
+
if (body) {
|
|
534
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
535
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-iterates
|
|
539
|
+
}
|
|
540
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
541
|
+
return { entry: header, exits: [loopExit] };
|
|
542
|
+
}
|
|
543
|
+
/** The iterated collection of a `for` — the named child after `in` before `)`. */
|
|
544
|
+
forCollection(stmt) {
|
|
545
|
+
let sawIn = false;
|
|
546
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
547
|
+
const c = stmt.child(i);
|
|
548
|
+
if (!c)
|
|
549
|
+
continue;
|
|
550
|
+
if (c.type === 'in') {
|
|
551
|
+
sawIn = true;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (c.type === ')')
|
|
555
|
+
return undefined;
|
|
556
|
+
if (sawIn && c.isNamed && !isComment(c))
|
|
557
|
+
return c;
|
|
558
|
+
}
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
forHeaderText(stmt) {
|
|
562
|
+
const pat = stmt.namedChildren.find((c) => c.type === 'variable_declaration' || c.type === 'multi_variable_declaration');
|
|
563
|
+
const collection = this.forCollection(stmt);
|
|
564
|
+
const p = pat?.text ?? '';
|
|
565
|
+
const col = collection?.text ?? '';
|
|
566
|
+
return p || col ? `for (${p} in ${col})` : 'for';
|
|
567
|
+
}
|
|
568
|
+
/** The loop body `control_structure_body` (the LAST one — for/while/do). */
|
|
569
|
+
loopBody(stmt) {
|
|
570
|
+
const all = stmt.namedChildren.filter((c) => c.type === 'control_structure_body');
|
|
571
|
+
return all.length ? all[all.length - 1] : undefined;
|
|
572
|
+
}
|
|
573
|
+
/** `while ( COND ) control_structure_body`. */
|
|
574
|
+
visitWhile(stmt) {
|
|
575
|
+
const labels = this.takeLabels();
|
|
576
|
+
const cond = this.parenCondition(stmt);
|
|
577
|
+
const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? `while (${cond.text})` : 'while', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
578
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
579
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
580
|
+
const body = this.visitBody(this.loopBody(stmt));
|
|
581
|
+
this.cfc.pop();
|
|
582
|
+
if (body) {
|
|
583
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
584
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
588
|
+
}
|
|
589
|
+
// Structural exit edge — even `while (true) {}` keeps EXIT reverse-reachable.
|
|
590
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
591
|
+
return { entry: header, exits: [loopExit] };
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* `do control_structure_body while ( COND )` — BOTTOM-TEST: the body runs at
|
|
595
|
+
* least once, THEN the condition decides whether to loop back.
|
|
596
|
+
*/
|
|
597
|
+
visitDoWhile(stmt) {
|
|
598
|
+
const labels = this.takeLabels();
|
|
599
|
+
const cond = this.doWhileCondition(stmt);
|
|
600
|
+
const condBlock = this.builder.newBlock(cond ? startLineOf(cond) : endLineOf(stmt), cond ? endLineOf(cond) : endLineOf(stmt), cond ? `while (${cond.text})` : 'while', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
601
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
602
|
+
// `continue` re-tests the condition; `break` leaves the loop.
|
|
603
|
+
this.cfc.pushLoop(condBlock, loopExit, labels);
|
|
604
|
+
const body = this.visitBody(this.loopBody(stmt));
|
|
605
|
+
this.cfc.pop();
|
|
606
|
+
const backTarget = body ? body.entry : condBlock;
|
|
607
|
+
if (body)
|
|
608
|
+
this.builder.connect(body.exits, condBlock, 'seq');
|
|
609
|
+
this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
|
|
610
|
+
// Structural exit edge — even `do {} while (true)` keeps EXIT reachable.
|
|
611
|
+
this.builder.edge(condBlock, loopExit, 'cond-false');
|
|
612
|
+
return { entry: backTarget, exits: [loopExit] };
|
|
613
|
+
}
|
|
614
|
+
/** The condition of a `do … while ( COND )` — the named child after the trailing `while`. */
|
|
615
|
+
doWhileCondition(stmt) {
|
|
616
|
+
let sawWhile = false;
|
|
617
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
618
|
+
const c = stmt.child(i);
|
|
619
|
+
if (!c)
|
|
620
|
+
continue;
|
|
621
|
+
if (c.type === 'while') {
|
|
622
|
+
sawWhile = true;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (sawWhile && c.type === ')')
|
|
626
|
+
return undefined;
|
|
627
|
+
if (sawWhile && c.isNamed && !isComment(c))
|
|
628
|
+
return c;
|
|
629
|
+
}
|
|
630
|
+
return undefined;
|
|
631
|
+
}
|
|
632
|
+
// ── try / catch / finally ──────────────────────────────────────────────────
|
|
633
|
+
/**
|
|
634
|
+
* `try { statements } catch_block* finally_block?`. The `finally` runs on BOTH
|
|
635
|
+
* normal and exception exit (finally semantics) — a `return`/`break`/`continue`
|
|
636
|
+
* crossing it threads through and gets a `finally-*` completion edge. Mirrors
|
|
637
|
+
* the Java `visitTry`.
|
|
638
|
+
*/
|
|
639
|
+
visitTry(stmt) {
|
|
640
|
+
const bodyNode = stmt.namedChildren.find((c) => c.type === 'statements');
|
|
641
|
+
const catchBlocks = stmt.namedChildren.filter((c) => c.type === 'catch_block');
|
|
642
|
+
const finallyBlock = stmt.namedChildren.find((c) => c.type === 'finally_block');
|
|
643
|
+
const finallyBody = finallyBlock?.namedChildren.find((c) => c.type === 'statements');
|
|
644
|
+
// The explicit finally is a finalizer the whole protected region threads through.
|
|
645
|
+
const finallyRes = finallyBody ? this.visitSeq(this.statementsOf(finallyBody)) : null;
|
|
646
|
+
const finallyFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
|
|
647
|
+
const finalizerEntry = finallyRes?.entry;
|
|
648
|
+
const finalizerExits = finallyRes?.exits ?? null;
|
|
649
|
+
// Build each catch handler.
|
|
650
|
+
const catchEntries = [];
|
|
651
|
+
const catchExits = [];
|
|
652
|
+
let firstCatchEntry;
|
|
653
|
+
for (const clause of catchBlocks) {
|
|
654
|
+
const clauseBody = clause.namedChildren.find((c) => c.type === 'statements');
|
|
655
|
+
if (finalizerEntry !== undefined)
|
|
656
|
+
this.handlers.push(finalizerEntry);
|
|
657
|
+
let res = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
|
|
658
|
+
if (finalizerEntry !== undefined)
|
|
659
|
+
this.handlers.pop();
|
|
660
|
+
if (res === null) {
|
|
661
|
+
// Empty `catch {}` still catches — synthesize one block so exception flow
|
|
662
|
+
// lands somewhere and post-try code stays reachable.
|
|
663
|
+
const idx = this.builder.newBlock(startLineOf(clause), endLineOf(clause), '');
|
|
664
|
+
res = { entry: idx, exits: [idx] };
|
|
665
|
+
}
|
|
666
|
+
const paramFacts = this.harvest.catchParamFacts(clause);
|
|
667
|
+
if (paramFacts) {
|
|
668
|
+
const paramBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', paramFacts);
|
|
669
|
+
this.builder.edge(paramBlock, res.entry, 'seq');
|
|
670
|
+
res = { entry: paramBlock, exits: res.exits };
|
|
671
|
+
}
|
|
672
|
+
catchEntries.push(res.entry);
|
|
673
|
+
catchExits.push(...res.exits);
|
|
674
|
+
if (firstCatchEntry === undefined)
|
|
675
|
+
firstCatchEntry = res.entry;
|
|
676
|
+
}
|
|
677
|
+
// Handler for the try body: first catch if present, else the finally, else outer.
|
|
678
|
+
const tryHandler = firstCatchEntry ?? finalizerEntry ?? this.currentHandler();
|
|
679
|
+
const protectedStart = this.builder.blockCount;
|
|
680
|
+
this.handlers.push(tryHandler);
|
|
681
|
+
let bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
|
|
682
|
+
this.handlers.pop();
|
|
683
|
+
if (bodyRes === null && (catchBlocks.length > 0 || finalizerEntry !== undefined)) {
|
|
684
|
+
// An empty `try {}` body still establishes a protected region. Synthesize
|
|
685
|
+
// one block (like the empty-`catch` case above) so the throw-edge loop
|
|
686
|
+
// wires the catch handler(s) and the try's entry is the body — otherwise
|
|
687
|
+
// the catch handler block + its error binding are orphaned (unreachable
|
|
688
|
+
// from ENTRY) and control routes straight to the finally, bypassing catch.
|
|
689
|
+
const idx = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '');
|
|
690
|
+
bodyRes = { entry: idx, exits: [idx] };
|
|
691
|
+
}
|
|
692
|
+
if (catchBlocks.length > 0 || finalizerEntry !== undefined) {
|
|
693
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
694
|
+
this.builder.edge(b, tryHandler, 'throw');
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// Pop the finalizer frame and drain its pending completion legs.
|
|
698
|
+
if (finallyFrame && finallyRes) {
|
|
699
|
+
this.cfc.pop();
|
|
700
|
+
drainFinalizerPending(this.builder, finallyFrame, finallyRes.exits);
|
|
701
|
+
}
|
|
702
|
+
const exits = [];
|
|
703
|
+
if (finalizerEntry !== undefined) {
|
|
704
|
+
if (bodyRes)
|
|
705
|
+
this.builder.connect(bodyRes.exits, finalizerEntry, 'seq');
|
|
706
|
+
for (const e of catchExits)
|
|
707
|
+
this.builder.edge(e, finalizerEntry, 'seq');
|
|
708
|
+
if (finalizerExits)
|
|
709
|
+
exits.push(...finalizerExits);
|
|
710
|
+
// No catch → an exception re-propagates out after the finally runs.
|
|
711
|
+
if (catchBlocks.length === 0 && finalizerExits) {
|
|
712
|
+
this.builder.connect(finalizerExits, this.currentHandler(), 'throw');
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
if (bodyRes)
|
|
717
|
+
exits.push(...bodyRes.exits);
|
|
718
|
+
exits.push(...catchExits);
|
|
719
|
+
}
|
|
720
|
+
const entry = bodyRes?.entry ?? finalizerEntry ?? catchEntries[0];
|
|
721
|
+
if (entry === undefined)
|
|
722
|
+
return null;
|
|
723
|
+
return { entry, exits: [...new Set(exits)] };
|
|
724
|
+
}
|
|
725
|
+
/** Nearest enclosing exception handler, or the function EXIT. */
|
|
726
|
+
currentHandler() {
|
|
727
|
+
return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/** The function/lambda body `statements`, or an expression body, or undefined. */
|
|
731
|
+
function bodyStatementsOf(fnNode) {
|
|
732
|
+
if (fnNode.type === 'lambda_literal') {
|
|
733
|
+
return fnNode.namedChildren.find((c) => c.type === 'statements');
|
|
734
|
+
}
|
|
735
|
+
const fb = fnNode.namedChildren.find((c) => c.type === 'function_body');
|
|
736
|
+
if (!fb)
|
|
737
|
+
return undefined;
|
|
738
|
+
// `function_body` is `{ statements }` OR an expression body `= expr`.
|
|
739
|
+
const stmts = fb.namedChildren.find((c) => c.type === 'statements');
|
|
740
|
+
if (stmts)
|
|
741
|
+
return stmts;
|
|
742
|
+
// Expression body — return the body itself so the caller treats it as one block.
|
|
743
|
+
return fb;
|
|
744
|
+
}
|
|
745
|
+
/** Build the CFG for one Kotlin function node, or `undefined` if not modelable. */
|
|
746
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
747
|
+
try {
|
|
748
|
+
if (!KOTLIN_FUNCTION_TYPES.has(fnNode.type))
|
|
749
|
+
return undefined;
|
|
750
|
+
const startLine = startLineOf(fnNode);
|
|
751
|
+
const endLine = endLineOf(fnNode);
|
|
752
|
+
const startColumn = fnNode.startPosition.column;
|
|
753
|
+
// A `function_declaration` / `anonymous_function` needs a `function_body`; a
|
|
754
|
+
// `lambda_literal` carries its `statements` directly. Absence of a body
|
|
755
|
+
// container ⇒ an abstract / interface-member / signature-only declaration with
|
|
756
|
+
// nothing to model (return undefined).
|
|
757
|
+
const hasBody = fnNode.type === 'lambda_literal' ||
|
|
758
|
+
fnNode.namedChildren.some((c) => c.type === 'function_body');
|
|
759
|
+
if (!hasBody)
|
|
760
|
+
return undefined;
|
|
761
|
+
const body = bodyStatementsOf(fnNode);
|
|
762
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
763
|
+
const harvest = new KotlinHarvester(fnNode);
|
|
764
|
+
const paramFacts = harvest.paramFacts();
|
|
765
|
+
if (paramFacts)
|
|
766
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
767
|
+
// Expression body (`fun f() = expr` / a `function_body` that is `= expr`):
|
|
768
|
+
// the body's value is returned. A value-position branch (`= when (k) { … }`)
|
|
769
|
+
// is modeled as control flow so each arm is control-dependent on the
|
|
770
|
+
// condition (#2205); any other expression is one block.
|
|
771
|
+
if (body && body.type === 'function_body') {
|
|
772
|
+
const expr = body.namedChildren.find((c) => !isComment(c) && c.type !== 'statements');
|
|
773
|
+
if (expr) {
|
|
774
|
+
const res = new KotlinCfgWalk(builder, harvest).visitExprBody(expr);
|
|
775
|
+
builder.edge(builder.entryIndex, res.entry, 'seq');
|
|
776
|
+
builder.connect(res.exits, builder.exitIndex, 'return');
|
|
777
|
+
return builder.finish(harvest.bindingTable());
|
|
778
|
+
}
|
|
779
|
+
// `function_body` with neither statements nor an expression — empty.
|
|
780
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq');
|
|
781
|
+
return builder.finish(harvest.bindingTable());
|
|
782
|
+
}
|
|
783
|
+
const walk = new KotlinCfgWalk(builder, harvest);
|
|
784
|
+
const res = body ? walk.visitSeq(body.namedChildren.filter((c) => !isComment(c))) : null;
|
|
785
|
+
if (!res) {
|
|
786
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
|
787
|
+
return builder.finish(harvest.bindingTable());
|
|
788
|
+
}
|
|
789
|
+
builder.edge(builder.entryIndex, res.entry, 'seq');
|
|
790
|
+
builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
|
|
791
|
+
return builder.finish(harvest.bindingTable());
|
|
792
|
+
}
|
|
793
|
+
catch (err) {
|
|
794
|
+
// Never throw out of buildFunctionCfg — a malformed AST shape must skip only
|
|
795
|
+
// this one function's CFG, never drop the whole file's language group (R4).
|
|
796
|
+
// eslint-disable-next-line no-console
|
|
797
|
+
console.warn(`[cfg] Kotlin buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
|
|
798
|
+
return undefined;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/** Whether a node is a Kotlin function/lambda this visitor builds a CFG for. */
|
|
802
|
+
function isFunction(node) {
|
|
803
|
+
return KOTLIN_FUNCTION_TYPES.has(node.type);
|
|
804
|
+
}
|
|
805
|
+
/** The Kotlin CFG visitor. */
|
|
806
|
+
export function createKotlinCfgVisitor() {
|
|
807
|
+
return { buildFunctionCfg, isFunction };
|
|
808
|
+
}
|
|
809
|
+
export { KOTLIN_FUNCTION_TYPES };
|