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,812 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
|
|
3
|
+
import { JavaHarvester } from './java-harvest.js';
|
|
4
|
+
/** Java node types that own a CFG-bearing function body. */
|
|
5
|
+
const JAVA_FUNCTION_TYPES = new Set([
|
|
6
|
+
'method_declaration',
|
|
7
|
+
'constructor_declaration',
|
|
8
|
+
'compact_constructor_declaration',
|
|
9
|
+
'lambda_expression',
|
|
10
|
+
]);
|
|
11
|
+
/** Statement node types that break a basic block (everything else coalesces). */
|
|
12
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
13
|
+
'if_statement',
|
|
14
|
+
'while_statement',
|
|
15
|
+
'do_statement',
|
|
16
|
+
'for_statement',
|
|
17
|
+
'enhanced_for_statement',
|
|
18
|
+
'switch_expression',
|
|
19
|
+
'try_statement',
|
|
20
|
+
'try_with_resources_statement',
|
|
21
|
+
'synchronized_statement',
|
|
22
|
+
'return_statement',
|
|
23
|
+
'break_statement',
|
|
24
|
+
'continue_statement',
|
|
25
|
+
'throw_statement',
|
|
26
|
+
'labeled_statement',
|
|
27
|
+
'yield_statement',
|
|
28
|
+
'block',
|
|
29
|
+
]);
|
|
30
|
+
/** Comment node types tree-sitter-java surfaces (NOT `comment`). */
|
|
31
|
+
const COMMENT_TYPES = new Set(['line_comment', 'block_comment']);
|
|
32
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
33
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
34
|
+
const isComment = (n) => COMMENT_TYPES.has(n.type);
|
|
35
|
+
/**
|
|
36
|
+
* Per-function Java walk state. One instance per function so the
|
|
37
|
+
* {@link ControlFlowContext}, exception-handler stack, and labeled-block frame
|
|
38
|
+
* bookkeeping are scoped to that function and never leak across functions.
|
|
39
|
+
*/
|
|
40
|
+
class JavaCfgWalk {
|
|
41
|
+
builder;
|
|
42
|
+
harvest;
|
|
43
|
+
cfc = new ControlFlowContext();
|
|
44
|
+
/** Stack of exception-handler entry blocks (catch/finally) a `throw` jumps to. */
|
|
45
|
+
handlers = [];
|
|
46
|
+
/** Label(s) pending attachment to the NEXT pushed loop/switch frame. */
|
|
47
|
+
pendingLabels = [];
|
|
48
|
+
constructor(builder, harvest) {
|
|
49
|
+
this.builder = builder;
|
|
50
|
+
this.harvest = harvest;
|
|
51
|
+
}
|
|
52
|
+
/** Statements of a block node, ignoring comments. */
|
|
53
|
+
statementsOf(block) {
|
|
54
|
+
return block.namedChildren.filter((c) => !isComment(c));
|
|
55
|
+
}
|
|
56
|
+
/** The `body` block of a node (field, or the first `block` child). */
|
|
57
|
+
bodyBlockOf(node) {
|
|
58
|
+
return node.childForFieldName('body') ?? node.namedChildren.find((c) => c.type === 'block');
|
|
59
|
+
}
|
|
60
|
+
/** Strip a `parenthesized_expression` wrapper (Java `if`/`while` conditions). */
|
|
61
|
+
unwrapParen(node) {
|
|
62
|
+
if (node.type === 'parenthesized_expression') {
|
|
63
|
+
const inner = node.namedChildren.find((c) => !isComment(c));
|
|
64
|
+
if (inner)
|
|
65
|
+
return inner;
|
|
66
|
+
}
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
/** Visit a body that may be a `block` or a single statement. */
|
|
70
|
+
visitBody(node) {
|
|
71
|
+
return this.builder.withNesting(() => {
|
|
72
|
+
if (!node)
|
|
73
|
+
return null;
|
|
74
|
+
if (node.type === 'block')
|
|
75
|
+
return this.visitSeq(this.statementsOf(node));
|
|
76
|
+
return this.visitStmt(node);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
80
|
+
visitSeq(stmts) {
|
|
81
|
+
return this.builder.withNesting(() => {
|
|
82
|
+
let entry;
|
|
83
|
+
let dangling = [];
|
|
84
|
+
let openSimple;
|
|
85
|
+
for (const stmt of stmts) {
|
|
86
|
+
if (this.breaksBlock(stmt)) {
|
|
87
|
+
openSimple = undefined; // close any open straight-line block
|
|
88
|
+
const res = this.visitStmt(stmt);
|
|
89
|
+
if (res === null)
|
|
90
|
+
continue; // transparent (empty nested block)
|
|
91
|
+
if (entry === undefined)
|
|
92
|
+
entry = res.entry;
|
|
93
|
+
else
|
|
94
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
95
|
+
dangling = [...res.exits];
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (openSimple === undefined) {
|
|
99
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
100
|
+
if (entry === undefined)
|
|
101
|
+
entry = idx;
|
|
102
|
+
else
|
|
103
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
104
|
+
openSimple = idx;
|
|
105
|
+
dangling = [idx];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (entry === undefined)
|
|
113
|
+
return null;
|
|
114
|
+
return { entry, exits: dangling };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Whether a statement breaks the current straight-line block. A
|
|
119
|
+
* `switch_expression` breaks only when it is a STATEMENT switch (a value-
|
|
120
|
+
* position switch used directly inside a `block` coalesces). A
|
|
121
|
+
* `local_variable_declaration` whose value is a modelable value-position switch
|
|
122
|
+
* (`var x = switch (…) {…}`, #2207) also breaks — `visitStmt` then models the
|
|
123
|
+
* arms as control flow instead of collapsing the decl to one inline block.
|
|
124
|
+
*/
|
|
125
|
+
breaksBlock(stmt) {
|
|
126
|
+
if (stmt.type === 'local_variable_declaration') {
|
|
127
|
+
const v = this.directValue(stmt);
|
|
128
|
+
return v !== undefined && this.isModelableValueBranch(v);
|
|
129
|
+
}
|
|
130
|
+
if (!CONTROL_FLOW_TYPES.has(stmt.type))
|
|
131
|
+
return false;
|
|
132
|
+
if (stmt.type === 'switch_expression')
|
|
133
|
+
return this.isStatementSwitch(stmt);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
/** Dispatch one statement to its handler. Non-null except for empty blocks. */
|
|
137
|
+
visitStmt(stmt) {
|
|
138
|
+
switch (stmt.type) {
|
|
139
|
+
case 'local_variable_declaration': {
|
|
140
|
+
// `var x = switch (k) { … }` (#2207): the value is a value-position
|
|
141
|
+
// branch — model it as control flow and bind the result on the rejoin,
|
|
142
|
+
// instead of collapsing the whole decl to one block.
|
|
143
|
+
const value = this.directValue(stmt);
|
|
144
|
+
if (value && this.isModelableValueBranch(value))
|
|
145
|
+
return this.visitBindBranch(stmt, value);
|
|
146
|
+
return this.visitSimple(stmt);
|
|
147
|
+
}
|
|
148
|
+
case 'if_statement':
|
|
149
|
+
return this.visitIf(stmt);
|
|
150
|
+
case 'while_statement':
|
|
151
|
+
return this.visitWhile(stmt);
|
|
152
|
+
case 'do_statement':
|
|
153
|
+
return this.visitDoWhile(stmt);
|
|
154
|
+
case 'for_statement':
|
|
155
|
+
return this.visitFor(stmt);
|
|
156
|
+
case 'enhanced_for_statement':
|
|
157
|
+
return this.visitForEach(stmt);
|
|
158
|
+
case 'switch_expression':
|
|
159
|
+
return this.visitSwitch(stmt);
|
|
160
|
+
case 'try_statement':
|
|
161
|
+
case 'try_with_resources_statement':
|
|
162
|
+
return this.visitTry(stmt);
|
|
163
|
+
case 'synchronized_statement':
|
|
164
|
+
return this.visitSynchronized(stmt);
|
|
165
|
+
case 'return_statement':
|
|
166
|
+
return this.visitReturn(stmt);
|
|
167
|
+
case 'throw_statement':
|
|
168
|
+
return this.visitThrow(stmt);
|
|
169
|
+
case 'break_statement':
|
|
170
|
+
return this.visitBreak(stmt);
|
|
171
|
+
case 'continue_statement':
|
|
172
|
+
return this.visitContinue(stmt);
|
|
173
|
+
case 'labeled_statement':
|
|
174
|
+
return this.visitLabeled(stmt);
|
|
175
|
+
case 'yield_statement':
|
|
176
|
+
return this.visitYield(stmt);
|
|
177
|
+
case 'block':
|
|
178
|
+
return this.visitSeq(this.statementsOf(stmt));
|
|
179
|
+
default:
|
|
180
|
+
return this.visitSimple(stmt);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
visitSimple(stmt) {
|
|
184
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
185
|
+
return { entry: idx, exits: [idx] };
|
|
186
|
+
}
|
|
187
|
+
visitReturn(stmt) {
|
|
188
|
+
// `return switch (k) { … };` (#2207): the returned value is a value-position
|
|
189
|
+
// branch — model it as control flow, with each arm returning (its value IS
|
|
190
|
+
// the function result), threading every active finalizer per arm.
|
|
191
|
+
const branch = stmt.namedChildren.find((c) => !isComment(c));
|
|
192
|
+
if (branch && this.isModelableValueBranch(branch)) {
|
|
193
|
+
const res = this.visitBranchExpr(branch);
|
|
194
|
+
const finalizers = this.cfc.finalizersForReturn();
|
|
195
|
+
for (const ex of res.exits) {
|
|
196
|
+
wireJumpThroughFinalizers(this.builder, ex, finalizers, this.builder.exitIndex, 'return');
|
|
197
|
+
}
|
|
198
|
+
return { entry: res.entry, exits: [] };
|
|
199
|
+
}
|
|
200
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
201
|
+
// A return crosses EVERY active finalizer (finally / try-with-resources /
|
|
202
|
+
// synchronized) before EXIT.
|
|
203
|
+
wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
|
|
204
|
+
return { entry: idx, exits: [] };
|
|
205
|
+
}
|
|
206
|
+
visitThrow(stmt) {
|
|
207
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
208
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
209
|
+
return { entry: idx, exits: [] };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `yield e;` (switch-expression arm value) — produces the switch-expression's
|
|
213
|
+
* value and EXITS the enclosing switch (it does NOT fall through to the next
|
|
214
|
+
* colon group). Modeled as a terminator that jumps to the switch exit, threading
|
|
215
|
+
* any finalizer it crosses — exactly like a `break` out of the switch but
|
|
216
|
+
* carrying the yielded value's def/use facts. (Reusing the statement `visitSwitch`
|
|
217
|
+
* for a value-position colon switch would otherwise wire a spurious `fallthrough`
|
|
218
|
+
* edge between yield-terminated arms — #2211 review.)
|
|
219
|
+
*/
|
|
220
|
+
visitYield(stmt) {
|
|
221
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
222
|
+
const res = this.cfc.resolveYield();
|
|
223
|
+
const { target, finalizers } = res ?? {
|
|
224
|
+
target: this.builder.exitIndex,
|
|
225
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
226
|
+
};
|
|
227
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
228
|
+
return { entry: idx, exits: [] };
|
|
229
|
+
}
|
|
230
|
+
visitBreak(stmt) {
|
|
231
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
232
|
+
const label = this.jumpLabel(stmt);
|
|
233
|
+
const res = this.cfc.resolveBreak(label);
|
|
234
|
+
const { target, finalizers } = res ?? {
|
|
235
|
+
target: this.builder.exitIndex,
|
|
236
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
237
|
+
};
|
|
238
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
239
|
+
return { entry: idx, exits: [] };
|
|
240
|
+
}
|
|
241
|
+
visitContinue(stmt) {
|
|
242
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
243
|
+
const label = this.jumpLabel(stmt);
|
|
244
|
+
const res = this.cfc.resolveContinue(label);
|
|
245
|
+
const { target, finalizers } = res ?? {
|
|
246
|
+
target: this.builder.exitIndex,
|
|
247
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
248
|
+
};
|
|
249
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
250
|
+
return { entry: idx, exits: [] };
|
|
251
|
+
}
|
|
252
|
+
/** The trailing label `identifier` of a `break`/`continue`, if any. */
|
|
253
|
+
jumpLabel(stmt) {
|
|
254
|
+
const id = stmt.namedChildren.find((c) => c.type === 'identifier');
|
|
255
|
+
return id?.text;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* `label: <statement>` — the label names the construct it directly wraps. For a
|
|
259
|
+
* loop/switch we forward the label so its pushed frame carries it (`break
|
|
260
|
+
* outer;` then resolves to it); for any other labeled statement we push a
|
|
261
|
+
* labeled-block frame around its body so `break label;` reaches the join after.
|
|
262
|
+
*/
|
|
263
|
+
visitLabeled(stmt) {
|
|
264
|
+
const labelNode = stmt.namedChildren.find((c) => c.type === 'identifier');
|
|
265
|
+
const label = labelNode?.text;
|
|
266
|
+
const body = stmt.namedChildren.find((c) => c.id !== labelNode?.id && !isComment(c)) ?? null;
|
|
267
|
+
if (!body || label === undefined)
|
|
268
|
+
return this.visitBody(body);
|
|
269
|
+
if (this.isLoopOrSwitchStatement(body)) {
|
|
270
|
+
// Forward the label to the loop/switch frame this statement pushes.
|
|
271
|
+
this.pendingLabels = [...this.pendingLabels, label];
|
|
272
|
+
return this.visitStmt(body);
|
|
273
|
+
}
|
|
274
|
+
// Labeled non-loop (`blk: { … break blk; … }`) — a break-to-label join after
|
|
275
|
+
// the body; an unlabeled break never matches it.
|
|
276
|
+
const joinBlock = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
277
|
+
this.cfc.pushLabeledBlock(joinBlock, [label]);
|
|
278
|
+
const res = this.visitBody(body);
|
|
279
|
+
this.cfc.pop();
|
|
280
|
+
if (res)
|
|
281
|
+
this.builder.connect(res.exits, joinBlock, 'seq');
|
|
282
|
+
const entry = res?.entry ?? joinBlock;
|
|
283
|
+
return { entry, exits: [joinBlock] };
|
|
284
|
+
}
|
|
285
|
+
isLoopOrSwitchStatement(node) {
|
|
286
|
+
return (node.type === 'for_statement' ||
|
|
287
|
+
node.type === 'enhanced_for_statement' ||
|
|
288
|
+
node.type === 'while_statement' ||
|
|
289
|
+
node.type === 'do_statement' ||
|
|
290
|
+
(node.type === 'switch_expression' && this.isStatementSwitch(node)));
|
|
291
|
+
}
|
|
292
|
+
/** Take and clear the labels queued by an enclosing `labeled_statement`. */
|
|
293
|
+
takeLabels() {
|
|
294
|
+
const labels = this.pendingLabels;
|
|
295
|
+
this.pendingLabels = [];
|
|
296
|
+
return labels;
|
|
297
|
+
}
|
|
298
|
+
visitIf(stmt) {
|
|
299
|
+
const cond = this.condOf(stmt) ?? stmt;
|
|
300
|
+
const condBlock = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
301
|
+
const exits = [];
|
|
302
|
+
const thenRes = this.visitBody(stmt.childForFieldName('consequence'));
|
|
303
|
+
if (thenRes) {
|
|
304
|
+
this.builder.edge(condBlock, thenRes.entry, 'cond-true');
|
|
305
|
+
exits.push(...thenRes.exits);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
exits.push(condBlock); // empty then — true path falls through
|
|
309
|
+
}
|
|
310
|
+
// No `else_clause` wrapper in Java: `alternative` is the else body or the
|
|
311
|
+
// nested `if_statement` of an `else if` chain directly.
|
|
312
|
+
const elseNode = stmt.childForFieldName('alternative');
|
|
313
|
+
if (elseNode) {
|
|
314
|
+
const elseRes = this.visitBody(elseNode);
|
|
315
|
+
if (elseRes) {
|
|
316
|
+
this.builder.edge(condBlock, elseRes.entry, 'cond-false');
|
|
317
|
+
exits.push(...elseRes.exits);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
exits.push(condBlock);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
exits.push(condBlock); // no else — false path falls through to the join
|
|
325
|
+
}
|
|
326
|
+
return { entry: condBlock, exits: [...new Set(exits)] };
|
|
327
|
+
}
|
|
328
|
+
/** The (paren-unwrapped) condition expression of an if/while/do/synchronized. */
|
|
329
|
+
condOf(stmt) {
|
|
330
|
+
const cond = stmt.childForFieldName('condition');
|
|
331
|
+
return cond ? this.unwrapParen(cond) : undefined;
|
|
332
|
+
}
|
|
333
|
+
visitWhile(stmt) {
|
|
334
|
+
const labels = this.takeLabels();
|
|
335
|
+
const cond = this.condOf(stmt) ?? stmt;
|
|
336
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
337
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
338
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
339
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
340
|
+
this.cfc.pop();
|
|
341
|
+
if (body) {
|
|
342
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
343
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
347
|
+
}
|
|
348
|
+
// Always emit the structural exit edge — even `while (true)` keeps EXIT
|
|
349
|
+
// reverse-reachable for the post-dominator / CDG pass.
|
|
350
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
351
|
+
return { entry: header, exits: [loopExit] };
|
|
352
|
+
}
|
|
353
|
+
visitDoWhile(stmt) {
|
|
354
|
+
const labels = this.takeLabels();
|
|
355
|
+
const cond = this.condOf(stmt) ?? stmt;
|
|
356
|
+
const condBlock = this.builder.newBlock(startLineOf(cond), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
357
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
358
|
+
this.cfc.pushLoop(condBlock, loopExit, labels);
|
|
359
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
360
|
+
this.cfc.pop();
|
|
361
|
+
const backTarget = body ? body.entry : condBlock;
|
|
362
|
+
if (body)
|
|
363
|
+
this.builder.connect(body.exits, condBlock, 'seq');
|
|
364
|
+
this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
|
|
365
|
+
this.builder.edge(condBlock, loopExit, 'cond-false');
|
|
366
|
+
return { entry: backTarget, exits: [loopExit] };
|
|
367
|
+
}
|
|
368
|
+
visitFor(stmt) {
|
|
369
|
+
const labels = this.takeLabels();
|
|
370
|
+
const init = stmt.childForFieldName('init');
|
|
371
|
+
const cond = stmt.childForFieldName('condition');
|
|
372
|
+
const incr = stmt.childForFieldName('update');
|
|
373
|
+
const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? cond.text : 'for(;;)', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
374
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
375
|
+
let incrBlock = header;
|
|
376
|
+
if (incr) {
|
|
377
|
+
incrBlock = this.builder.newBlock(startLineOf(incr), endLineOf(incr), incr.text, 'normal', this.harvest.facts(incr));
|
|
378
|
+
this.builder.edge(incrBlock, header, 'loop-back');
|
|
379
|
+
}
|
|
380
|
+
this.cfc.pushLoop(incrBlock, loopExit, labels);
|
|
381
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
382
|
+
this.cfc.pop();
|
|
383
|
+
if (body) {
|
|
384
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
385
|
+
this.builder.connect(body.exits, incrBlock, incr ? 'seq' : 'loop-back');
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
this.builder.edge(header, incrBlock, 'cond-true');
|
|
389
|
+
if (!incr)
|
|
390
|
+
this.builder.edge(header, header, 'loop-back');
|
|
391
|
+
}
|
|
392
|
+
// Structural exit edge — `for (;;) {}` (no condition) still keeps EXIT
|
|
393
|
+
// reverse-reachable so CDG is not silently skipped for the function.
|
|
394
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
395
|
+
let entry = header;
|
|
396
|
+
if (init) {
|
|
397
|
+
const initBlock = this.builder.newBlock(startLineOf(init), endLineOf(init), init.text, 'normal', this.harvest.facts(init));
|
|
398
|
+
this.builder.edge(initBlock, header, 'seq');
|
|
399
|
+
entry = initBlock;
|
|
400
|
+
}
|
|
401
|
+
return { entry, exits: [loopExit] };
|
|
402
|
+
}
|
|
403
|
+
visitForEach(stmt) {
|
|
404
|
+
const labels = this.takeLabels();
|
|
405
|
+
// Header text is SYNTHESIZED, so facts come from the name/value nodes
|
|
406
|
+
// directly (the loop variable is a def, the iterated expression a use).
|
|
407
|
+
const header = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), this.forEachHeaderText(stmt), 'normal', this.harvest.forEachHeadFacts(stmt));
|
|
408
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
409
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
410
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
411
|
+
this.cfc.pop();
|
|
412
|
+
if (body) {
|
|
413
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
414
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
this.builder.edge(header, header, 'loop-back');
|
|
418
|
+
}
|
|
419
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
420
|
+
return { entry: header, exits: [loopExit] };
|
|
421
|
+
}
|
|
422
|
+
forEachHeaderText(stmt) {
|
|
423
|
+
const name = stmt.childForFieldName('name')?.text ?? '';
|
|
424
|
+
const value = stmt.childForFieldName('value')?.text ?? '';
|
|
425
|
+
return name || value ? `for(${name} : ${value})` : 'for(… : …)';
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* `synchronized (obj) body` — the monitor release runs on BOTH normal AND
|
|
429
|
+
* exception exit (deterministic finalizer), exactly like a `finally`. Modeled
|
|
430
|
+
* as a synthesized finalizer block so a `return`/`break`/`continue` crossing it
|
|
431
|
+
* threads through and gets a `finally-*` completion edge.
|
|
432
|
+
*/
|
|
433
|
+
visitSynchronized(stmt) {
|
|
434
|
+
const bodyNode = this.bodyBlockOf(stmt) ?? null;
|
|
435
|
+
const lockExpr = this.synchronizedLock(stmt);
|
|
436
|
+
const releaseFacts = lockExpr ? this.harvest.facts(lockExpr) : undefined;
|
|
437
|
+
return this.buildProtectedSynthetic(bodyNode, stmt, 'release', releaseFacts);
|
|
438
|
+
}
|
|
439
|
+
/** The locked expression of a `synchronized (expr) {…}` (paren-unwrapped). */
|
|
440
|
+
synchronizedLock(stmt) {
|
|
441
|
+
const body = stmt.childForFieldName('body');
|
|
442
|
+
const expr = stmt.namedChildren.find((c) => c.id !== body?.id && !isComment(c));
|
|
443
|
+
return expr ? this.unwrapParen(expr) : undefined;
|
|
444
|
+
}
|
|
445
|
+
/** Whether a `switch_expression` is used as a STATEMENT (vs an expression value). */
|
|
446
|
+
isStatementSwitch(node) {
|
|
447
|
+
const p = node.parent;
|
|
448
|
+
if (!p)
|
|
449
|
+
return false;
|
|
450
|
+
// A statement-position switch is a direct child of a `block`, a classic
|
|
451
|
+
// group, or an arrow rule body; an expression-value switch is nested under a
|
|
452
|
+
// declaration / return / assignment / argument and is NOT one of these.
|
|
453
|
+
return (p.type === 'block' ||
|
|
454
|
+
p.type === 'switch_block_statement_group' ||
|
|
455
|
+
p.type === 'switch_rule' ||
|
|
456
|
+
p.type === 'labeled_statement');
|
|
457
|
+
}
|
|
458
|
+
visitSwitch(stmt) {
|
|
459
|
+
const labels = this.takeLabels();
|
|
460
|
+
const value = this.condOf(stmt) ?? stmt;
|
|
461
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), endLineOf(value), value.text, 'normal', this.harvest.facts(value));
|
|
462
|
+
const switchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
463
|
+
this.cfc.pushSwitch(switchExit, labels);
|
|
464
|
+
const body = stmt.childForFieldName('body');
|
|
465
|
+
// A `switch_block` holds `switch_block_statement_group`s (classic, fall
|
|
466
|
+
// through) OR `switch_rule`s (arrow, no fallthrough); never both.
|
|
467
|
+
const groups = body
|
|
468
|
+
? body.namedChildren.filter((c) => c.type === 'switch_block_statement_group' || c.type === 'switch_rule')
|
|
469
|
+
: [];
|
|
470
|
+
// Each group's case-test expression(s) evaluate before the body runs —
|
|
471
|
+
// harvest their uses (and any pattern binding as a may-def) onto the dispatch
|
|
472
|
+
// block, one record per test, CONDITIONALLY (a later case test only runs when
|
|
473
|
+
// earlier cases didn't match).
|
|
474
|
+
for (const g of groups) {
|
|
475
|
+
for (const test of this.caseTests(g)) {
|
|
476
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(test));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const groupResults = groups.map((g) => this.visitSeq(this.caseStatements(g)));
|
|
480
|
+
const hasDefault = groups.some((g) => this.caseIsDefault(g));
|
|
481
|
+
const arrowForm = groups.some((g) => g.type === 'switch_rule');
|
|
482
|
+
const entryOf = new Array(groups.length);
|
|
483
|
+
let after = switchExit;
|
|
484
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
485
|
+
entryOf[i] = groupResults[i]?.entry ?? after;
|
|
486
|
+
after = entryOf[i];
|
|
487
|
+
}
|
|
488
|
+
for (let i = 0; i < groups.length; i++) {
|
|
489
|
+
this.builder.edge(dispatch, entryOf[i], 'switch-case');
|
|
490
|
+
}
|
|
491
|
+
if (!hasDefault)
|
|
492
|
+
this.builder.edge(dispatch, switchExit, 'switch-case'); // no-match path
|
|
493
|
+
// Classic colon groups FALL THROUGH to the next group when not break/return-
|
|
494
|
+
// terminated; arrow rules NEVER fall through (each body rejoins after).
|
|
495
|
+
for (let i = 0; i < groups.length; i++) {
|
|
496
|
+
const res = groupResults[i];
|
|
497
|
+
if (!res)
|
|
498
|
+
continue;
|
|
499
|
+
if (groups[i].type === 'switch_rule') {
|
|
500
|
+
this.builder.connect(res.exits, switchExit, 'seq');
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
const fallTarget = i + 1 < groups.length ? entryOf[i + 1] : switchExit;
|
|
504
|
+
this.builder.connect(res.exits, fallTarget, 'fallthrough');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
void arrowForm;
|
|
508
|
+
this.cfc.pop();
|
|
509
|
+
return { entry: dispatch, exits: [switchExit] };
|
|
510
|
+
}
|
|
511
|
+
/** A switch group/rule's body statements (everything but its `switch_label`). */
|
|
512
|
+
caseStatements(group) {
|
|
513
|
+
return group.namedChildren.filter((c) => c.type !== 'switch_label' && !isComment(c));
|
|
514
|
+
}
|
|
515
|
+
/** The case-test value expressions of a group (a `switch_label`'s values). */
|
|
516
|
+
caseTests(group) {
|
|
517
|
+
const label = group.namedChildren.find((c) => c.type === 'switch_label');
|
|
518
|
+
if (!label)
|
|
519
|
+
return [];
|
|
520
|
+
return label.namedChildren.filter((c) => !isComment(c));
|
|
521
|
+
}
|
|
522
|
+
/** A `default:` group has a `switch_label` with no value children. */
|
|
523
|
+
caseIsDefault(group) {
|
|
524
|
+
const label = group.namedChildren.find((c) => c.type === 'switch_label');
|
|
525
|
+
if (!label)
|
|
526
|
+
return false;
|
|
527
|
+
return label.namedChildren.filter((c) => !isComment(c)).length === 0;
|
|
528
|
+
}
|
|
529
|
+
// ── value-position branches (#2207) ─────────────────────────────────────────
|
|
530
|
+
/**
|
|
531
|
+
* The direct value of a `local_variable_declaration` with a SINGLE declarator:
|
|
532
|
+
* its `variable_declarator`'s `value` field (`var x = <value>`). Returns
|
|
533
|
+
* undefined for a multi-declarator decl (`int a = …, b = …;`) — modeling those
|
|
534
|
+
* arm-by-arm is out of scope, so they coalesce inline. The DIRECT value only:
|
|
535
|
+
* `var x = f(switch …)` yields the call, not the nested switch, so an
|
|
536
|
+
* argument-position switch stays inline.
|
|
537
|
+
*/
|
|
538
|
+
directValue(stmt) {
|
|
539
|
+
const declarators = stmt.namedChildren.filter((c) => c.type === 'variable_declarator');
|
|
540
|
+
if (declarators.length !== 1)
|
|
541
|
+
return undefined;
|
|
542
|
+
return declarators[0].childForFieldName('value') ?? undefined;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Whether `node` is a value-position branch worth modeling as control flow
|
|
546
|
+
* (#2207): a `switch_expression` with ≥2 case groups (a real dispatch). Java has
|
|
547
|
+
* no value-position `if` (the ternary `?:` is deliberately excluded, like elvis
|
|
548
|
+
* in Kotlin), so `switch` is the only carrier.
|
|
549
|
+
*/
|
|
550
|
+
isModelableValueBranch(node) {
|
|
551
|
+
if (node.type !== 'switch_expression')
|
|
552
|
+
return false;
|
|
553
|
+
const body = node.childForFieldName('body');
|
|
554
|
+
if (!body)
|
|
555
|
+
return false;
|
|
556
|
+
const groups = body.namedChildren.filter((c) => c.type === 'switch_block_statement_group' || c.type === 'switch_rule');
|
|
557
|
+
return groups.length >= 2;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Model a value-position `switch` as control flow regardless of position —
|
|
561
|
+
* {@link visitSeq}'s `isStatementSwitch` gate keeps value-position switches
|
|
562
|
+
* inline, so call {@link visitSwitch} directly here.
|
|
563
|
+
*/
|
|
564
|
+
visitBranchExpr(node) {
|
|
565
|
+
return this.visitSwitch(node);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* `var x = switch (k) { … }` (#2207): visit the switch as control flow, then
|
|
569
|
+
* rejoin its arms at a facts-only continuation carrying ONLY the bound name's
|
|
570
|
+
* def (the subject + arm-value uses are already harvested onto the switch's
|
|
571
|
+
* blocks). The arms are now control-dependent on the dispatch, and `x` is
|
|
572
|
+
* defined at the join — mirrors the Kotlin / Rust value-position binding.
|
|
573
|
+
*/
|
|
574
|
+
visitBindBranch(stmt, branch) {
|
|
575
|
+
const res = this.visitBranchExpr(branch);
|
|
576
|
+
const cont = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '', 'normal', this.harvest.bindingDefFacts(stmt));
|
|
577
|
+
this.builder.connect(res.exits, cont, 'seq');
|
|
578
|
+
return { entry: res.entry, exits: [cont] };
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* try / catch / finally / try-with-resources. The `resources` of a
|
|
582
|
+
* try-with-resources auto-close on BOTH normal and exception exit — exactly
|
|
583
|
+
* `finally`-shaped — so we synthesize a close finalizer that wraps an explicit
|
|
584
|
+
* `finally` (if any).
|
|
585
|
+
*/
|
|
586
|
+
visitTry(stmt) {
|
|
587
|
+
const bodyNode = stmt.childForFieldName('body');
|
|
588
|
+
const catchClauses = [];
|
|
589
|
+
let finallyClause;
|
|
590
|
+
for (let i = 0; i < stmt.namedChildCount; i++) {
|
|
591
|
+
const c = stmt.namedChild(i);
|
|
592
|
+
if (c?.type === 'catch_clause')
|
|
593
|
+
catchClauses.push(c);
|
|
594
|
+
else if (c?.type === 'finally_clause')
|
|
595
|
+
finallyClause = c;
|
|
596
|
+
}
|
|
597
|
+
const finallyBody = finallyClause?.namedChildren.find((c) => c.type === 'block');
|
|
598
|
+
// try-with-resources: the resource close is a deterministic finalizer that
|
|
599
|
+
// runs after the body and (if present) before the explicit finally. Model it
|
|
600
|
+
// as a synthesized close block prepended to the finalizer chain.
|
|
601
|
+
const resources = this.tryResources(stmt);
|
|
602
|
+
return this.buildProtected(bodyNode ?? null, catchClauses, finallyBody ?? null, resources, stmt);
|
|
603
|
+
}
|
|
604
|
+
/** The `resource` nodes of a try-with-resources, or [] for a plain try. */
|
|
605
|
+
tryResources(stmt) {
|
|
606
|
+
const spec = stmt.childForFieldName('resources');
|
|
607
|
+
if (!spec)
|
|
608
|
+
return [];
|
|
609
|
+
return spec.namedChildren.filter((c) => c.type === 'resource');
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Shared try/catch/finally builder. `catchClauses` may be empty; `finallyBody`
|
|
613
|
+
* is the explicit finally's `block` (or null); `resources` are try-with-
|
|
614
|
+
* resources resources (auto-close = an implicit finally that runs first).
|
|
615
|
+
*
|
|
616
|
+
* Models the TS/C# `visitTry` semantics: normal completion of try AND catch
|
|
617
|
+
* flow through the finalizers; a throw in the protected region routes to the
|
|
618
|
+
* handler; early exits crossing a finalizer thread through it (`finally-*`
|
|
619
|
+
* completion edges). The resource close finalizer (synthesized) and the
|
|
620
|
+
* explicit finally are chained so a crossing jump threads BOTH.
|
|
621
|
+
*/
|
|
622
|
+
buildProtected(bodyNode, catchClauses, finallyBody, resources, span) {
|
|
623
|
+
// Innermost finalizer first on the active stack = outermost lexically. The
|
|
624
|
+
// resource close runs BEFORE the explicit finally, so push the explicit
|
|
625
|
+
// finally first (deeper) then the close (shallower / nearer the jump).
|
|
626
|
+
const explicitFinRes = finallyBody ? this.visitSeq(this.statementsOf(finallyBody)) : null;
|
|
627
|
+
const explicitFrame = explicitFinRes ? this.cfc.pushFinalizer(explicitFinRes.entry) : null;
|
|
628
|
+
// Synthesized resource-close finalizer (a single block; close()/AutoCloseable
|
|
629
|
+
// has no AST node), carrying the resource bindings' facts as uses.
|
|
630
|
+
let closeRes = null;
|
|
631
|
+
let closeFrame = null;
|
|
632
|
+
if (resources.length > 0) {
|
|
633
|
+
const closeFacts = this.harvest.resourceCloseFacts(resources);
|
|
634
|
+
const closeBlock = this.builder.newBlock(endLineOf(span), endLineOf(span), 'close', 'normal', closeFacts);
|
|
635
|
+
closeRes = { entry: closeBlock, exits: [closeBlock] };
|
|
636
|
+
// The close's normal exit flows into the explicit finally (if any).
|
|
637
|
+
if (explicitFinRes)
|
|
638
|
+
this.builder.edge(closeBlock, explicitFinRes.entry, 'seq');
|
|
639
|
+
closeFrame = this.cfc.pushFinalizer(closeRes.entry);
|
|
640
|
+
}
|
|
641
|
+
// The entry of the finalizer chain (what normal/exception completion runs
|
|
642
|
+
// first): close (if any) else explicit finally.
|
|
643
|
+
const finalizerEntry = closeRes?.entry ?? explicitFinRes?.entry;
|
|
644
|
+
// The set of normal exits AFTER the whole finalizer chain runs.
|
|
645
|
+
const finalizerExits = explicitFinRes?.exits ?? closeRes?.exits ?? null;
|
|
646
|
+
// Build each catch handler.
|
|
647
|
+
const catchEntries = [];
|
|
648
|
+
const catchExits = [];
|
|
649
|
+
let firstCatchEntry;
|
|
650
|
+
for (const clause of catchClauses) {
|
|
651
|
+
const clauseBody = clause.childForFieldName('body');
|
|
652
|
+
if (finalizerEntry !== undefined)
|
|
653
|
+
this.handlers.push(finalizerEntry);
|
|
654
|
+
let res = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
|
|
655
|
+
if (finalizerEntry !== undefined)
|
|
656
|
+
this.handlers.pop();
|
|
657
|
+
if (res === null) {
|
|
658
|
+
// Empty `catch {}` still catches — synthesize one block so exception flow
|
|
659
|
+
// lands somewhere and the post-try code stays reachable.
|
|
660
|
+
const idx = this.builder.newBlock(startLineOf(clause), endLineOf(clause), '');
|
|
661
|
+
res = { entry: idx, exits: [idx] };
|
|
662
|
+
}
|
|
663
|
+
const paramFacts = this.harvest.catchParamFacts(clause);
|
|
664
|
+
if (paramFacts) {
|
|
665
|
+
const paramBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', paramFacts);
|
|
666
|
+
this.builder.edge(paramBlock, res.entry, 'seq');
|
|
667
|
+
res = { entry: paramBlock, exits: res.exits };
|
|
668
|
+
}
|
|
669
|
+
catchEntries.push(res.entry);
|
|
670
|
+
catchExits.push(...res.exits);
|
|
671
|
+
if (firstCatchEntry === undefined)
|
|
672
|
+
firstCatchEntry = res.entry;
|
|
673
|
+
}
|
|
674
|
+
// Handler for the try body: first catch if present, else the finalizer
|
|
675
|
+
// chain, else the outer handler.
|
|
676
|
+
const tryHandler = firstCatchEntry ?? finalizerEntry ?? this.currentHandler();
|
|
677
|
+
const protectedStart = this.builder.blockCount;
|
|
678
|
+
this.handlers.push(tryHandler);
|
|
679
|
+
const bodyRes = bodyNode
|
|
680
|
+
? bodyNode.type === 'block'
|
|
681
|
+
? this.visitSeq(this.statementsOf(bodyNode))
|
|
682
|
+
: this.visitStmt(bodyNode)
|
|
683
|
+
: null;
|
|
684
|
+
this.handlers.pop();
|
|
685
|
+
if (catchClauses.length > 0 || finalizerEntry !== undefined) {
|
|
686
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
687
|
+
this.builder.edge(b, tryHandler, 'throw');
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// Pop the finalizer frames (inner→outer) and drain their pending legs.
|
|
691
|
+
if (closeFrame && closeRes) {
|
|
692
|
+
this.cfc.pop();
|
|
693
|
+
drainFinalizerPending(this.builder, closeFrame, closeRes.exits);
|
|
694
|
+
}
|
|
695
|
+
if (explicitFrame && explicitFinRes) {
|
|
696
|
+
this.cfc.pop();
|
|
697
|
+
drainFinalizerPending(this.builder, explicitFrame, explicitFinRes.exits);
|
|
698
|
+
}
|
|
699
|
+
const exits = [];
|
|
700
|
+
if (finalizerEntry !== undefined) {
|
|
701
|
+
if (bodyRes)
|
|
702
|
+
this.builder.connect(bodyRes.exits, finalizerEntry, 'seq');
|
|
703
|
+
for (const e of catchExits)
|
|
704
|
+
this.builder.edge(e, finalizerEntry, 'seq');
|
|
705
|
+
if (finalizerExits)
|
|
706
|
+
exits.push(...finalizerExits);
|
|
707
|
+
// No catch → an exception re-propagates out after the finalizers run.
|
|
708
|
+
if (catchClauses.length === 0 && finalizerExits) {
|
|
709
|
+
this.builder.connect(finalizerExits, this.currentHandler(), 'throw');
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
if (bodyRes)
|
|
714
|
+
exits.push(...bodyRes.exits);
|
|
715
|
+
exits.push(...catchExits);
|
|
716
|
+
}
|
|
717
|
+
const entry = bodyRes?.entry ?? finalizerEntry ?? catchEntries[0];
|
|
718
|
+
if (entry === undefined) {
|
|
719
|
+
void span;
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
return { entry, exits: [...new Set(exits)] };
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* `synchronized`: a protected body whose finalizer is a SYNTHESIZED single
|
|
726
|
+
* block (Monitor exit has no AST node). The finalizer runs on both normal and
|
|
727
|
+
* exception exit, and crossing jumps thread through it.
|
|
728
|
+
*/
|
|
729
|
+
buildProtectedSynthetic(bodyNode, span, text, finalizerFacts) {
|
|
730
|
+
const finalizerBlock = this.builder.newBlock(endLineOf(span), endLineOf(span), text, 'normal', finalizerFacts ?? undefined);
|
|
731
|
+
const finRes = { entry: finalizerBlock, exits: [finalizerBlock] };
|
|
732
|
+
const finFrame = this.cfc.pushFinalizer(finRes.entry);
|
|
733
|
+
const protectedStart = this.builder.blockCount;
|
|
734
|
+
// The finalizer IS the handler — an exception in the body still runs the
|
|
735
|
+
// release, which then re-propagates to the outer handler.
|
|
736
|
+
this.handlers.push(finRes.entry);
|
|
737
|
+
const bodyRes = bodyNode
|
|
738
|
+
? bodyNode.type === 'block'
|
|
739
|
+
? this.visitSeq(this.statementsOf(bodyNode))
|
|
740
|
+
: this.visitStmt(bodyNode)
|
|
741
|
+
: null;
|
|
742
|
+
this.handlers.pop();
|
|
743
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
744
|
+
this.builder.edge(b, finRes.entry, 'throw');
|
|
745
|
+
}
|
|
746
|
+
this.cfc.pop();
|
|
747
|
+
drainFinalizerPending(this.builder, finFrame, finRes.exits);
|
|
748
|
+
// Normal completion of the body flows through the finalizer; the finalizer's
|
|
749
|
+
// exit re-propagates an exception to the outer handler (it had no catch).
|
|
750
|
+
if (bodyRes)
|
|
751
|
+
this.builder.connect(bodyRes.exits, finRes.entry, 'seq');
|
|
752
|
+
this.builder.connect(finRes.exits, this.currentHandler(), 'throw');
|
|
753
|
+
const entry = bodyRes?.entry ?? finRes.entry;
|
|
754
|
+
return { entry, exits: [...finRes.exits] };
|
|
755
|
+
}
|
|
756
|
+
/** Nearest enclosing exception handler, or the function EXIT. */
|
|
757
|
+
currentHandler() {
|
|
758
|
+
return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/** Build the CFG for one Java function node, or `undefined` if not modelable. */
|
|
762
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
763
|
+
try {
|
|
764
|
+
if (!JAVA_FUNCTION_TYPES.has(fnNode.type))
|
|
765
|
+
return undefined;
|
|
766
|
+
const startLine = startLineOf(fnNode);
|
|
767
|
+
const endLine = endLineOf(fnNode);
|
|
768
|
+
const startColumn = fnNode.startPosition.column;
|
|
769
|
+
// The body is a `block` (field `body`) OR an expression (single-expression
|
|
770
|
+
// lambda `x -> expr`).
|
|
771
|
+
const body = fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'block');
|
|
772
|
+
if (!body)
|
|
773
|
+
return undefined; // abstract / interface method — no body
|
|
774
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
775
|
+
const harvest = new JavaHarvester(fnNode);
|
|
776
|
+
const paramFacts = harvest.paramFacts();
|
|
777
|
+
if (paramFacts)
|
|
778
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
779
|
+
if (body.type !== 'block') {
|
|
780
|
+
// Single-expression lambda (`x -> expr`): one block whose value is returned.
|
|
781
|
+
const blk = builder.newBlock(startLineOf(body), endLineOf(body), body.text, 'normal', harvest.facts(body));
|
|
782
|
+
builder.edge(builder.entryIndex, blk, 'seq');
|
|
783
|
+
builder.edge(blk, builder.exitIndex, 'return');
|
|
784
|
+
return builder.finish(harvest.bindingTable());
|
|
785
|
+
}
|
|
786
|
+
const walk = new JavaCfgWalk(builder, harvest);
|
|
787
|
+
const res = walk.visitSeq(body.namedChildren.filter((c) => !isComment(c)));
|
|
788
|
+
if (!res) {
|
|
789
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
|
790
|
+
return builder.finish(harvest.bindingTable());
|
|
791
|
+
}
|
|
792
|
+
builder.edge(builder.entryIndex, res.entry, 'seq');
|
|
793
|
+
builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
|
|
794
|
+
return builder.finish(harvest.bindingTable());
|
|
795
|
+
}
|
|
796
|
+
catch (err) {
|
|
797
|
+
// Never throw out of buildFunctionCfg — a malformed AST shape must skip only
|
|
798
|
+
// this one function's CFG, never drop the whole file's language group (R4).
|
|
799
|
+
// eslint-disable-next-line no-console
|
|
800
|
+
console.warn(`[cfg] Java buildFunctionCfg skipped a function in ${filePath}: ${String(err)}`);
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/** Whether a node is a Java function this visitor builds a CFG for. */
|
|
805
|
+
function isFunction(node) {
|
|
806
|
+
return JAVA_FUNCTION_TYPES.has(node.type);
|
|
807
|
+
}
|
|
808
|
+
/** The Java CFG visitor. */
|
|
809
|
+
export function createJavaCfgVisitor() {
|
|
810
|
+
return { buildFunctionCfg, isFunction };
|
|
811
|
+
}
|
|
812
|
+
export { JAVA_FUNCTION_TYPES };
|