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,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TS/JS CfgVisitor (issue #2081, M1).
|
|
3
|
+
*
|
|
4
|
+
* Walks a TypeScript/JavaScript function's tree-sitter AST and drives the
|
|
5
|
+
* language-agnostic {@link CfgBuilder} to produce a serializable
|
|
6
|
+
* {@link FunctionCfg}. TS and JS share a grammar family (tree-sitter-typescript
|
|
7
|
+
* reuses tree-sitter-javascript's statement nodes), so one visitor covers both.
|
|
8
|
+
*
|
|
9
|
+
* Design — a `visit_<node_type>` dispatch over the statement taxonomy. The
|
|
10
|
+
* classic CFG hazards (R10) are handled explicitly:
|
|
11
|
+
* - loops allocate a dedicated **loop-exit** block so `break` has a concrete
|
|
12
|
+
* target before the loop's successor is known; `continue` targets the
|
|
13
|
+
* header/increment; the back-edge closes the loop.
|
|
14
|
+
* - `switch` cases fall through naturally: a case body that does not `break`
|
|
15
|
+
* yields non-empty `exits`, which we wire to the next case as `fallthrough`;
|
|
16
|
+
* a case that `break`s wires to the switch exit (via {@link ControlFlowContext})
|
|
17
|
+
* and yields no fall-out.
|
|
18
|
+
* - `try/catch/finally` routes both normal completion AND a `throw` in the try
|
|
19
|
+
* through `finally` (the finally block post-dominates the try/catch); a
|
|
20
|
+
* `throw` with no catch propagates through finally to the enclosing handler.
|
|
21
|
+
* - EARLY EXITS THROUGH FINALLY (#2082 M2 U2, closes the M1 soundness gap): a
|
|
22
|
+
* `break`/`continue`/`return` whose jump CROSSES a `finally` is re-routed to
|
|
23
|
+
* the finally entry (keeping its bare jump kind), and the finally's exits
|
|
24
|
+
* gain a `finally-return`/`finally-break`/`finally-continue` completion edge
|
|
25
|
+
* to the resumed target. Threading is TARGET-RELATIVE via finalizer frames
|
|
26
|
+
* interleaved on the {@link ControlFlowContext} stack: only the finallys
|
|
27
|
+
* lexically between the jump and its target thread (a `break` whose loop is
|
|
28
|
+
* wholly inside the try keeps its direct edge — re-routing it would let a
|
|
29
|
+
* finally redefinition falsely kill in-loop defs for reaching-defs). Nested
|
|
30
|
+
* finallys chain inner→outer; finally-as-shared-join conflates exit paths
|
|
31
|
+
* (sound over-approximation; duplication-per-exit-path was rejected). An
|
|
32
|
+
* empty/comment-only finally pushes no frame — jumps keep direct edges.
|
|
33
|
+
* - labeled `break`/`continue` resolve against the labeled construct's frame:
|
|
34
|
+
* loops/switches carry their full label LIST (`outer: inner: for` resolves
|
|
35
|
+
* both), and a labeled NON-loop statement (`blk: { … break blk; … }`) gets
|
|
36
|
+
* a break-target frame whose target is a synthesized join after the body —
|
|
37
|
+
* the M1 route-to-EXIT fallback removed the real continuation and falsely
|
|
38
|
+
* killed defs for reaching-defs (tri-review P1).
|
|
39
|
+
*
|
|
40
|
+
* Known limitations:
|
|
41
|
+
* - A jump whose label STILL fails to resolve (malformed source) keeps the
|
|
42
|
+
* conservative route-to-EXIT + thread-all-finallys fallback in
|
|
43
|
+
* visitBreak/visitContinue — single-exit preserved, no finally bypassed,
|
|
44
|
+
* but the continuation path is approximate.
|
|
45
|
+
* - Exceptional flow stays the sound over-approximation: EVERY protected-region
|
|
46
|
+
* block edges to the handler (an exception may fire mid-block), which
|
|
47
|
+
* over-supplies reaching-defs facts into `catch` — extra facts, never false
|
|
48
|
+
* kills. Per-leader throw precision is deliberately deferred (M3 decides).
|
|
49
|
+
* - Def/use harvest scope (#2082 M2, see typescript-harvest.ts for the full
|
|
50
|
+
* v1 semantics table): member/property writes are not scalar defs; nested
|
|
51
|
+
* function bodies are opaque in BOTH directions (writes to and reads of
|
|
52
|
+
* captured outer variables are invisible — callback flows are M4 territory);
|
|
53
|
+
* `case x:` test uses attach to the switch dispatch block (sound
|
|
54
|
+
* over-approximation of in-order case evaluation).
|
|
55
|
+
*
|
|
56
|
+
* Block/edge accounting and reachability are pinned in
|
|
57
|
+
* `test/unit/cfg/cfg-builder.test.ts` (core) and
|
|
58
|
+
* `test/unit/cfg/typescript-visitor.test.ts` (this visitor, per hazard).
|
|
59
|
+
*/
|
|
60
|
+
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
|
61
|
+
import type { CfgVisitor } from '../types.js';
|
|
62
|
+
/** TS/JS node types that own a CFG-bearing function body. */
|
|
63
|
+
declare const TS_FUNCTION_TYPES: Set<string>;
|
|
64
|
+
/** The TS/JS CFG visitor (shared by TypeScript and JavaScript). */
|
|
65
|
+
export declare function createTypeScriptCfgVisitor(): CfgVisitor<SyntaxNode>;
|
|
66
|
+
export { TS_FUNCTION_TYPES };
|
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import { CfgBuilder } from '../cfg-builder.js';
|
|
2
|
+
import { ControlFlowContext, drainFinalizerPending, wireJumpThroughFinalizers, } from '../control-flow-context.js';
|
|
3
|
+
import { TsHarvester } from './typescript-harvest.js';
|
|
4
|
+
/** TS/JS node types that own a CFG-bearing function body. */
|
|
5
|
+
const TS_FUNCTION_TYPES = new Set([
|
|
6
|
+
'function_declaration',
|
|
7
|
+
'function_expression',
|
|
8
|
+
'arrow_function',
|
|
9
|
+
'method_definition',
|
|
10
|
+
'generator_function_declaration',
|
|
11
|
+
'generator_function',
|
|
12
|
+
]);
|
|
13
|
+
/** Statement node types that break a basic block (everything else coalesces). */
|
|
14
|
+
const CONTROL_FLOW_TYPES = new Set([
|
|
15
|
+
'if_statement',
|
|
16
|
+
'while_statement',
|
|
17
|
+
'do_statement',
|
|
18
|
+
'for_statement',
|
|
19
|
+
'for_in_statement',
|
|
20
|
+
'switch_statement',
|
|
21
|
+
'try_statement',
|
|
22
|
+
'return_statement',
|
|
23
|
+
'break_statement',
|
|
24
|
+
'continue_statement',
|
|
25
|
+
'throw_statement',
|
|
26
|
+
'labeled_statement',
|
|
27
|
+
'statement_block',
|
|
28
|
+
]);
|
|
29
|
+
const LOOP_OR_SWITCH_TYPES = new Set([
|
|
30
|
+
'while_statement',
|
|
31
|
+
'do_statement',
|
|
32
|
+
'for_statement',
|
|
33
|
+
'for_in_statement',
|
|
34
|
+
'switch_statement',
|
|
35
|
+
]);
|
|
36
|
+
const startLineOf = (n) => n.startPosition.row + 1;
|
|
37
|
+
const endLineOf = (n) => n.endPosition.row + 1;
|
|
38
|
+
/**
|
|
39
|
+
* Per-function walk state. One instance is created per function so the
|
|
40
|
+
* {@link ControlFlowContext}, exception-handler stack, and pending label are
|
|
41
|
+
* scoped to that function and never leak across functions.
|
|
42
|
+
*/
|
|
43
|
+
class TsCfgWalk {
|
|
44
|
+
builder;
|
|
45
|
+
harvest;
|
|
46
|
+
cfc = new ControlFlowContext();
|
|
47
|
+
/** Stack of exception-handler entry blocks (catch/finally) a `throw` jumps to. */
|
|
48
|
+
handlers = [];
|
|
49
|
+
/** Labels awaiting the construct they precede (`outer: inner: for` = both). */
|
|
50
|
+
pendingLabels = [];
|
|
51
|
+
constructor(builder,
|
|
52
|
+
/** Def/use fact extractor (#2082 M2 U1) — phase-2 only; its scope tree is
|
|
53
|
+
* already complete, so any walk order resolves names correctly. */
|
|
54
|
+
harvest) {
|
|
55
|
+
this.builder = builder;
|
|
56
|
+
this.harvest = harvest;
|
|
57
|
+
}
|
|
58
|
+
/** Statements of a block node, ignoring comments. */
|
|
59
|
+
statementsOf(block) {
|
|
60
|
+
return block.namedChildren.filter((c) => c.type !== 'comment');
|
|
61
|
+
}
|
|
62
|
+
/** The `body` block of a node (field, or the first statement_block child). */
|
|
63
|
+
bodyBlockOf(node) {
|
|
64
|
+
return (node.childForFieldName('body') ?? node.namedChildren.find((c) => c.type === 'statement_block'));
|
|
65
|
+
}
|
|
66
|
+
/** Visit a body that may be a `statement_block` or a single statement. */
|
|
67
|
+
visitBody(node) {
|
|
68
|
+
return this.builder.withNesting(() => {
|
|
69
|
+
if (!node)
|
|
70
|
+
return null;
|
|
71
|
+
if (node.type === 'statement_block')
|
|
72
|
+
return this.visitSeq(this.statementsOf(node));
|
|
73
|
+
return this.visitStmt(node);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Wire a sequence of statements, coalescing straight-line runs into blocks. */
|
|
77
|
+
visitSeq(stmts) {
|
|
78
|
+
return this.builder.withNesting(() => {
|
|
79
|
+
let entry;
|
|
80
|
+
let dangling = [];
|
|
81
|
+
let openSimple;
|
|
82
|
+
for (const stmt of stmts) {
|
|
83
|
+
if (CONTROL_FLOW_TYPES.has(stmt.type)) {
|
|
84
|
+
openSimple = undefined; // close any open straight-line block
|
|
85
|
+
const res = this.visitStmt(stmt);
|
|
86
|
+
if (res === null)
|
|
87
|
+
continue; // transparent (empty nested block)
|
|
88
|
+
if (entry === undefined)
|
|
89
|
+
entry = res.entry;
|
|
90
|
+
else
|
|
91
|
+
this.builder.connect(dangling, res.entry, 'seq');
|
|
92
|
+
dangling = [...res.exits];
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Simple statement — coalesce into the current straight-line block.
|
|
96
|
+
if (openSimple === undefined) {
|
|
97
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
98
|
+
if (entry === undefined)
|
|
99
|
+
entry = idx;
|
|
100
|
+
else
|
|
101
|
+
this.builder.connect(dangling, idx, 'seq');
|
|
102
|
+
openSimple = idx;
|
|
103
|
+
dangling = [idx];
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (entry === undefined)
|
|
111
|
+
return null;
|
|
112
|
+
return { entry, exits: dangling };
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Dispatch one statement to its handler. Non-null except for empty blocks. */
|
|
116
|
+
visitStmt(stmt) {
|
|
117
|
+
switch (stmt.type) {
|
|
118
|
+
case 'if_statement':
|
|
119
|
+
return this.visitIf(stmt);
|
|
120
|
+
case 'while_statement':
|
|
121
|
+
return this.visitWhile(stmt);
|
|
122
|
+
case 'do_statement':
|
|
123
|
+
return this.visitDoWhile(stmt);
|
|
124
|
+
case 'for_statement':
|
|
125
|
+
return this.visitFor(stmt);
|
|
126
|
+
case 'for_in_statement':
|
|
127
|
+
return this.visitForIn(stmt);
|
|
128
|
+
case 'switch_statement':
|
|
129
|
+
return this.visitSwitch(stmt);
|
|
130
|
+
case 'try_statement':
|
|
131
|
+
return this.visitTry(stmt);
|
|
132
|
+
case 'return_statement':
|
|
133
|
+
return this.visitReturn(stmt);
|
|
134
|
+
case 'throw_statement':
|
|
135
|
+
return this.visitThrow(stmt);
|
|
136
|
+
case 'break_statement':
|
|
137
|
+
return this.visitBreak(stmt);
|
|
138
|
+
case 'continue_statement':
|
|
139
|
+
return this.visitContinue(stmt);
|
|
140
|
+
case 'labeled_statement':
|
|
141
|
+
return this.visitLabeled(stmt);
|
|
142
|
+
case 'statement_block':
|
|
143
|
+
return this.visitSeq(this.statementsOf(stmt));
|
|
144
|
+
default:
|
|
145
|
+
return this.visitSimple(stmt);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
visitSimple(stmt) {
|
|
149
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
150
|
+
return { entry: idx, exits: [idx] };
|
|
151
|
+
}
|
|
152
|
+
visitReturn(stmt) {
|
|
153
|
+
// Harvest the argument expression's uses — `return x` blocks live in this
|
|
154
|
+
// dedicated handler, not visitSeq, and were a silently-missed site once.
|
|
155
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
156
|
+
// A return crosses EVERY active finally before reaching EXIT.
|
|
157
|
+
wireJumpThroughFinalizers(this.builder, idx, this.cfc.finalizersForReturn(), this.builder.exitIndex, 'return');
|
|
158
|
+
return { entry: idx, exits: [] };
|
|
159
|
+
}
|
|
160
|
+
visitThrow(stmt) {
|
|
161
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text, 'normal', this.harvest.facts(stmt));
|
|
162
|
+
this.builder.edge(idx, this.currentHandler(), 'throw');
|
|
163
|
+
return { entry: idx, exits: [] };
|
|
164
|
+
}
|
|
165
|
+
visitBreak(stmt) {
|
|
166
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
167
|
+
const res = this.cfc.resolveBreak(this.labelOf(stmt));
|
|
168
|
+
// An unresolved target — a label this visitor doesn't model (a stacked
|
|
169
|
+
// outer label like `outer: inner: for`, or a labeled non-loop block) —
|
|
170
|
+
// would otherwise leave this block with NO out-edge, stranding it and
|
|
171
|
+
// breaking the single-exit invariant a downstream post-dominator / PDG pass
|
|
172
|
+
// relies on. Conservatively route an unresolved jump to the function EXIT
|
|
173
|
+
// ("escapes the function") and thread ALL active finallys — a superset of
|
|
174
|
+
// the truly-crossed set (the real target is somewhere in the function, so
|
|
175
|
+
// execution provably runs every finally between the jump and wherever it
|
|
176
|
+
// lands... up to the ones the conservative EXIT routing over-includes).
|
|
177
|
+
// Sound for dataflow either way: extra paths, never a bypassed finally.
|
|
178
|
+
const { target, finalizers } = res ?? {
|
|
179
|
+
target: this.builder.exitIndex,
|
|
180
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
181
|
+
};
|
|
182
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'break');
|
|
183
|
+
return { entry: idx, exits: [] };
|
|
184
|
+
}
|
|
185
|
+
visitContinue(stmt) {
|
|
186
|
+
const idx = this.builder.newBlock(startLineOf(stmt), endLineOf(stmt), stmt.text);
|
|
187
|
+
const res = this.cfc.resolveContinue(this.labelOf(stmt));
|
|
188
|
+
// See visitBreak: an unresolved label routes to EXIT (threading all
|
|
189
|
+
// active finallys) to preserve single-exit without bypassing a finally.
|
|
190
|
+
const { target, finalizers } = res ?? {
|
|
191
|
+
target: this.builder.exitIndex,
|
|
192
|
+
finalizers: this.cfc.finalizersForReturn(),
|
|
193
|
+
};
|
|
194
|
+
wireJumpThroughFinalizers(this.builder, idx, finalizers, target, 'continue');
|
|
195
|
+
return { entry: idx, exits: [] };
|
|
196
|
+
}
|
|
197
|
+
visitLabeled(stmt) {
|
|
198
|
+
const body = stmt.childForFieldName('body') ?? stmt.namedChildren[stmt.namedChildren.length - 1];
|
|
199
|
+
const label = this.labelOf(stmt);
|
|
200
|
+
if (body && (LOOP_OR_SWITCH_TYPES.has(body.type) || body.type === 'labeled_statement')) {
|
|
201
|
+
// Loop/switch consumes the accumulated labels via takeLabels(); a nested
|
|
202
|
+
// labeled_statement keeps accumulating (`outer: inner: for` → both
|
|
203
|
+
// labels land on the loop frame).
|
|
204
|
+
if (label)
|
|
205
|
+
this.pendingLabels.push(label);
|
|
206
|
+
const res = this.visitStmt(body);
|
|
207
|
+
this.pendingLabels = []; // clear leftovers if the construct didn't consume
|
|
208
|
+
return res;
|
|
209
|
+
}
|
|
210
|
+
// Labeled NON-loop statement (`blk: { … break blk; … }`): break-to-label
|
|
211
|
+
// targets a synthesized join after the body. Routing it to EXIT instead
|
|
212
|
+
// (the M1 behavior) removed the real continuation and falsely killed
|
|
213
|
+
// every def live at the jump for post-construct uses (tri-review P1).
|
|
214
|
+
const labels = [...this.pendingLabels, ...(label ? [label] : [])];
|
|
215
|
+
this.pendingLabels = [];
|
|
216
|
+
const join = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
217
|
+
this.cfc.pushLabeledBlock(join, labels);
|
|
218
|
+
const res = this.visitBody(body);
|
|
219
|
+
this.cfc.pop();
|
|
220
|
+
if (res)
|
|
221
|
+
this.builder.connect(res.exits, join, 'seq');
|
|
222
|
+
return { entry: res?.entry ?? join, exits: [join] };
|
|
223
|
+
}
|
|
224
|
+
visitIf(stmt) {
|
|
225
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
226
|
+
const condBlock = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
227
|
+
const exits = [];
|
|
228
|
+
const thenRes = this.visitBody(stmt.childForFieldName('consequence'));
|
|
229
|
+
if (thenRes) {
|
|
230
|
+
this.builder.edge(condBlock, thenRes.entry, 'cond-true');
|
|
231
|
+
exits.push(...thenRes.exits);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
exits.push(condBlock); // empty then — true path falls through
|
|
235
|
+
}
|
|
236
|
+
const elseNode = this.elseBodyOf(stmt);
|
|
237
|
+
if (elseNode) {
|
|
238
|
+
const elseRes = this.visitBody(elseNode);
|
|
239
|
+
if (elseRes) {
|
|
240
|
+
this.builder.edge(condBlock, elseRes.entry, 'cond-false');
|
|
241
|
+
exits.push(...elseRes.exits);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
exits.push(condBlock); // empty else block
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
exits.push(condBlock); // no else — false path falls through to the join
|
|
249
|
+
}
|
|
250
|
+
return { entry: condBlock, exits: [...new Set(exits)] };
|
|
251
|
+
}
|
|
252
|
+
/** The else body node (unwraps an `else_clause` wrapper if present). */
|
|
253
|
+
elseBodyOf(ifStmt) {
|
|
254
|
+
const alt = ifStmt.childForFieldName('alternative');
|
|
255
|
+
if (!alt)
|
|
256
|
+
return undefined;
|
|
257
|
+
if (alt.type === 'else_clause') {
|
|
258
|
+
return alt.childForFieldName('body') ?? alt.namedChildren[0];
|
|
259
|
+
}
|
|
260
|
+
return alt;
|
|
261
|
+
}
|
|
262
|
+
visitWhile(stmt) {
|
|
263
|
+
const labels = this.takeLabels();
|
|
264
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
265
|
+
const header = this.builder.newBlock(startLineOf(stmt), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
266
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
267
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
268
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
269
|
+
this.cfc.pop();
|
|
270
|
+
if (body) {
|
|
271
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
272
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
this.builder.edge(header, header, 'loop-back'); // empty body re-tests
|
|
276
|
+
}
|
|
277
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
278
|
+
return { entry: header, exits: [loopExit] };
|
|
279
|
+
}
|
|
280
|
+
visitDoWhile(stmt) {
|
|
281
|
+
const labels = this.takeLabels();
|
|
282
|
+
const cond = stmt.childForFieldName('condition') ?? stmt;
|
|
283
|
+
const condBlock = this.builder.newBlock(startLineOf(cond), endLineOf(cond), cond.text, 'normal', this.harvest.facts(cond));
|
|
284
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
285
|
+
this.cfc.pushLoop(condBlock, loopExit, labels);
|
|
286
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
287
|
+
this.cfc.pop();
|
|
288
|
+
const backTarget = body ? body.entry : condBlock;
|
|
289
|
+
if (body)
|
|
290
|
+
this.builder.connect(body.exits, condBlock, 'seq');
|
|
291
|
+
this.builder.edge(condBlock, backTarget, 'loop-back'); // cond true → run body again
|
|
292
|
+
this.builder.edge(condBlock, loopExit, 'cond-false');
|
|
293
|
+
return { entry: backTarget, exits: [loopExit] };
|
|
294
|
+
}
|
|
295
|
+
visitFor(stmt) {
|
|
296
|
+
const labels = this.takeLabels();
|
|
297
|
+
const init = stmt.childForFieldName('initializer');
|
|
298
|
+
const cond = stmt.childForFieldName('condition');
|
|
299
|
+
const incr = stmt.childForFieldName('increment');
|
|
300
|
+
const header = this.builder.newBlock(startLineOf(stmt), cond ? endLineOf(cond) : startLineOf(stmt), cond ? cond.text : 'for(;;)', 'normal', cond ? this.harvest.facts(cond) : undefined);
|
|
301
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
302
|
+
let incrBlock = header;
|
|
303
|
+
if (incr) {
|
|
304
|
+
incrBlock = this.builder.newBlock(startLineOf(incr), endLineOf(incr), incr.text, 'normal', this.harvest.facts(incr));
|
|
305
|
+
this.builder.edge(incrBlock, header, 'loop-back');
|
|
306
|
+
}
|
|
307
|
+
this.cfc.pushLoop(incrBlock, loopExit, labels);
|
|
308
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
309
|
+
this.cfc.pop();
|
|
310
|
+
if (body) {
|
|
311
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
312
|
+
// With no increment clause the body's exits ARE the back-edge — carry
|
|
313
|
+
// the loop-back kind on them (mirroring visitWhile/visitForIn) instead
|
|
314
|
+
// of a phantom header→header self-loop that models a path which never
|
|
315
|
+
// executes the body. With an increment, the body falls through to the
|
|
316
|
+
// increment (`seq`) and the increment carries the loop-back (:338).
|
|
317
|
+
this.builder.connect(body.exits, incrBlock, incr ? 'seq' : 'loop-back');
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this.builder.edge(header, incrBlock, 'cond-true');
|
|
321
|
+
// Empty body with no increment: the header genuinely re-tests itself.
|
|
322
|
+
if (!incr)
|
|
323
|
+
this.builder.edge(header, header, 'loop-back');
|
|
324
|
+
}
|
|
325
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
326
|
+
let entry = header;
|
|
327
|
+
if (init) {
|
|
328
|
+
const initBlock = this.builder.newBlock(startLineOf(init), endLineOf(init), init.text, 'normal', this.harvest.facts(init));
|
|
329
|
+
this.builder.edge(initBlock, header, 'seq');
|
|
330
|
+
entry = initBlock;
|
|
331
|
+
}
|
|
332
|
+
return { entry, exits: [loopExit] };
|
|
333
|
+
}
|
|
334
|
+
visitForIn(stmt) {
|
|
335
|
+
const labels = this.takeLabels();
|
|
336
|
+
// Header text is SYNTHESIZED, so facts come from the left/right AST nodes
|
|
337
|
+
// directly (the loop variable is a def, the iterated expression a use).
|
|
338
|
+
const header = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), this.forInHeaderText(stmt), 'normal', this.harvest.forInHeadFacts(stmt));
|
|
339
|
+
const loopExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
340
|
+
this.cfc.pushLoop(header, loopExit, labels);
|
|
341
|
+
const body = this.visitBody(this.bodyBlockOf(stmt));
|
|
342
|
+
this.cfc.pop();
|
|
343
|
+
if (body) {
|
|
344
|
+
this.builder.edge(header, body.entry, 'cond-true');
|
|
345
|
+
this.builder.connect(body.exits, header, 'loop-back');
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
this.builder.edge(header, header, 'loop-back');
|
|
349
|
+
}
|
|
350
|
+
this.builder.edge(header, loopExit, 'cond-false');
|
|
351
|
+
return { entry: header, exits: [loopExit] };
|
|
352
|
+
}
|
|
353
|
+
forInHeaderText(stmt) {
|
|
354
|
+
const left = stmt.childForFieldName('left')?.text ?? '';
|
|
355
|
+
const right = stmt.childForFieldName('right')?.text ?? '';
|
|
356
|
+
return left || right ? `for(${left} … ${right})` : 'for(… in/of …)';
|
|
357
|
+
}
|
|
358
|
+
visitSwitch(stmt) {
|
|
359
|
+
const labels = this.takeLabels();
|
|
360
|
+
const value = stmt.childForFieldName('value') ?? stmt;
|
|
361
|
+
const dispatch = this.builder.newBlock(startLineOf(stmt), endLineOf(value), value.text, 'normal', this.harvest.facts(value));
|
|
362
|
+
const switchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
|
|
363
|
+
this.cfc.pushSwitch(switchExit, labels);
|
|
364
|
+
const body = stmt.childForFieldName('body');
|
|
365
|
+
const cases = body
|
|
366
|
+
? body.namedChildren.filter((c) => c.type === 'switch_case' || c.type === 'switch_default')
|
|
367
|
+
: [];
|
|
368
|
+
// `case x:` test expressions live in no block (caseStatements filters the
|
|
369
|
+
// value node out) — harvest their uses onto the dispatch block, one record
|
|
370
|
+
// per case in source order (a sound over-approximation of JS's in-order
|
|
371
|
+
// case evaluation). Conditionally: a later case test only evaluates when
|
|
372
|
+
// earlier cases didn't match, so any def inside one is a may-def — as a
|
|
373
|
+
// must-def on the always-executed dispatch block it would falsely kill
|
|
374
|
+
// prior defs for earlier-matching arms (tri-review).
|
|
375
|
+
for (const c of cases) {
|
|
376
|
+
const caseValue = c.childForFieldName('value');
|
|
377
|
+
if (caseValue)
|
|
378
|
+
this.builder.attachFacts(dispatch, this.harvest.factsConditional(caseValue));
|
|
379
|
+
}
|
|
380
|
+
const caseResults = cases.map((c) => this.visitSeq(this.caseStatements(c)));
|
|
381
|
+
const hasDefault = cases.some((c) => c.type === 'switch_default');
|
|
382
|
+
// entryOf[i] = block a dispatch/fallthrough INTO case i lands on (empty
|
|
383
|
+
// cases are transparent — they resolve to the next case, or the exit).
|
|
384
|
+
const entryOf = new Array(cases.length);
|
|
385
|
+
let after = switchExit;
|
|
386
|
+
for (let i = cases.length - 1; i >= 0; i--) {
|
|
387
|
+
entryOf[i] = caseResults[i]?.entry ?? after;
|
|
388
|
+
after = entryOf[i];
|
|
389
|
+
}
|
|
390
|
+
for (let i = 0; i < cases.length; i++) {
|
|
391
|
+
this.builder.edge(dispatch, entryOf[i], 'switch-case');
|
|
392
|
+
}
|
|
393
|
+
if (!hasDefault)
|
|
394
|
+
this.builder.edge(dispatch, switchExit, 'switch-case'); // no-match path
|
|
395
|
+
for (let i = 0; i < cases.length; i++) {
|
|
396
|
+
const res = caseResults[i];
|
|
397
|
+
if (!res)
|
|
398
|
+
continue;
|
|
399
|
+
const fallTarget = i + 1 < cases.length ? entryOf[i + 1] : switchExit;
|
|
400
|
+
this.builder.connect(res.exits, fallTarget, 'fallthrough');
|
|
401
|
+
}
|
|
402
|
+
this.cfc.pop();
|
|
403
|
+
return { entry: dispatch, exits: [switchExit] };
|
|
404
|
+
}
|
|
405
|
+
caseStatements(caseNode) {
|
|
406
|
+
const value = caseNode.childForFieldName('value');
|
|
407
|
+
return caseNode.namedChildren.filter((c) => c.id !== value?.id && c.type !== 'comment');
|
|
408
|
+
}
|
|
409
|
+
visitTry(stmt) {
|
|
410
|
+
const bodyNode = stmt.childForFieldName('body');
|
|
411
|
+
// Single pass over named children — tree-sitter's `namedChildren` getter
|
|
412
|
+
// allocates a fresh array on every access, so avoid the double `.find`.
|
|
413
|
+
let catchClause;
|
|
414
|
+
let finallyClause;
|
|
415
|
+
for (let i = 0; i < stmt.namedChildCount; i++) {
|
|
416
|
+
const c = stmt.namedChild(i);
|
|
417
|
+
if (c?.type === 'catch_clause')
|
|
418
|
+
catchClause = c;
|
|
419
|
+
else if (c?.type === 'finally_clause')
|
|
420
|
+
finallyClause = c;
|
|
421
|
+
}
|
|
422
|
+
// Build finally first so its entry is known as both a normal join and a
|
|
423
|
+
// handler target. The finally body runs in the OUTER handler context — and
|
|
424
|
+
// OUTSIDE this try's finalizer frame: a return inside the finally must not
|
|
425
|
+
// thread itself (it threads only outer finallys, matching JS semantics).
|
|
426
|
+
const finallyRes = finallyClause
|
|
427
|
+
? this.visitSeq(this.statementsOf(this.bodyBlockOf(finallyClause)))
|
|
428
|
+
: null;
|
|
429
|
+
// Finalizer frame for early-exit threading (#2082 M2 U2): active while the
|
|
430
|
+
// catch and protected bodies are walked, so a crossing `return`/`break`/
|
|
431
|
+
// `continue` inside either routes through the finally. An empty/comment-only
|
|
432
|
+
// finally (`finallyRes` null — the #2099-F2 empty-catch bug shape) pushes
|
|
433
|
+
// NO frame: it can define nothing, so jumps soundly keep direct edges.
|
|
434
|
+
const finFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
|
|
435
|
+
// A throw inside catch propagates to finally (if any), else the outer handler.
|
|
436
|
+
let catchRes = null;
|
|
437
|
+
if (catchClause) {
|
|
438
|
+
if (finallyRes)
|
|
439
|
+
this.handlers.push(finallyRes.entry);
|
|
440
|
+
catchRes = this.visitSeq(this.statementsOf(this.bodyBlockOf(catchClause)));
|
|
441
|
+
if (finallyRes)
|
|
442
|
+
this.handlers.pop();
|
|
443
|
+
if (catchRes === null) {
|
|
444
|
+
// Empty (or comment-only) catch body — `catch {}`. The clause still
|
|
445
|
+
// CATCHES: handler semantics key off the syntactic clause, not the
|
|
446
|
+
// traversal result. Treating it as "no catch" sent the swallowed
|
|
447
|
+
// exception to the outer handler/EXIT and left post-try code
|
|
448
|
+
// unreachable when the body always throws — a hard false-negative
|
|
449
|
+
// for downstream taint. Synthesize one empty block spanning the
|
|
450
|
+
// clause (entry == sole exit) so exception flow lands in it and
|
|
451
|
+
// rejoins the normal continuation. Created BEFORE the protected
|
|
452
|
+
// region is walked, so it never receives a spurious throw edge.
|
|
453
|
+
const idx = this.builder.newBlock(startLineOf(catchClause), endLineOf(catchClause), '');
|
|
454
|
+
catchRes = { entry: idx, exits: [idx] };
|
|
455
|
+
}
|
|
456
|
+
// `catch (e)` has no header block — the param def gets its OWN
|
|
457
|
+
// facts-only block in front of the body entry. It must NOT be prepended
|
|
458
|
+
// into the body's entry block: when the catch body STARTS with a loop,
|
|
459
|
+
// that entry is the loop HEADER, re-entered on every iteration — the
|
|
460
|
+
// param def would re-gen there and falsely KILL loop-carried
|
|
461
|
+
// redefinitions of the param (`catch (e) { while (c) { e = fix(e); }
|
|
462
|
+
// sink(e); }` would lose the fix→sink fact, a taint false negative).
|
|
463
|
+
// The param block becomes the handler entry, which is also semantically
|
|
464
|
+
// right: the binding happens exactly once, on handler entry.
|
|
465
|
+
const paramFacts = this.harvest.catchParamFacts(catchClause);
|
|
466
|
+
if (paramFacts) {
|
|
467
|
+
const paramBlock = this.builder.newBlock(startLineOf(catchClause), startLineOf(catchClause), '', 'normal', paramFacts);
|
|
468
|
+
this.builder.edge(paramBlock, catchRes.entry, 'seq');
|
|
469
|
+
catchRes = { entry: paramBlock, exits: catchRes.exits };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Handler for the try body: catch if present, else finally, else outer.
|
|
473
|
+
const tryHandler = catchRes?.entry ?? finallyRes?.entry ?? this.currentHandler();
|
|
474
|
+
const protectedStart = this.builder.blockCount;
|
|
475
|
+
this.handlers.push(tryHandler);
|
|
476
|
+
const bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
|
|
477
|
+
this.handlers.pop();
|
|
478
|
+
// Conservative exceptional edges: ANY block in the protected region may raise
|
|
479
|
+
// to the handler — not just an explicit `throw`, and not just the body ENTRY.
|
|
480
|
+
// Edging every block created during the try-body walk keeps exception flow
|
|
481
|
+
// sound when the body BRANCHES: an `if` / nested-try / post-branch block whose
|
|
482
|
+
// interior blocks would otherwise have no path to the handler — i.e. a taint
|
|
483
|
+
// false-negative into `catch` for the downstream PDG analysis. The
|
|
484
|
+
// per-function edge cap bounds the count; explicit `throw`s add their own
|
|
485
|
+
// (idempotent) edge to the same handler.
|
|
486
|
+
if (catchClause || finallyClause) {
|
|
487
|
+
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
|
488
|
+
this.builder.edge(b, tryHandler, 'throw');
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// The finalizer frame closes once the protected/catch walks are done; any
|
|
492
|
+
// jumps that crossed it left their completion legs on `pending`, wired
|
|
493
|
+
// here from the finally's exits (see drainFinalizerPending for the
|
|
494
|
+
// finally-override semantics of an always-jumping finally).
|
|
495
|
+
if (finFrame && finallyRes) {
|
|
496
|
+
this.cfc.pop();
|
|
497
|
+
drainFinalizerPending(this.builder, finFrame, finallyRes.exits);
|
|
498
|
+
}
|
|
499
|
+
const exits = [];
|
|
500
|
+
if (finallyRes) {
|
|
501
|
+
// Normal completion of try AND catch both flow through finally.
|
|
502
|
+
if (bodyRes)
|
|
503
|
+
this.builder.connect(bodyRes.exits, finallyRes.entry, 'seq');
|
|
504
|
+
if (catchRes)
|
|
505
|
+
this.builder.connect(catchRes.exits, finallyRes.entry, 'seq');
|
|
506
|
+
exits.push(...finallyRes.exits);
|
|
507
|
+
// No catch → an exception re-propagates out after finally runs.
|
|
508
|
+
if (!catchRes)
|
|
509
|
+
this.builder.connect(finallyRes.exits, this.currentHandler(), 'throw');
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
if (bodyRes)
|
|
513
|
+
exits.push(...bodyRes.exits);
|
|
514
|
+
if (catchRes)
|
|
515
|
+
exits.push(...catchRes.exits);
|
|
516
|
+
}
|
|
517
|
+
const entry = bodyRes?.entry ?? finallyRes?.entry ?? catchRes?.entry;
|
|
518
|
+
if (entry === undefined)
|
|
519
|
+
return null;
|
|
520
|
+
return { entry, exits: [...new Set(exits)] };
|
|
521
|
+
}
|
|
522
|
+
/** Nearest enclosing exception handler, or the function EXIT. */
|
|
523
|
+
currentHandler() {
|
|
524
|
+
return this.handlers.length ? this.handlers[this.handlers.length - 1] : this.builder.exitIndex;
|
|
525
|
+
}
|
|
526
|
+
/** Consume the labels awaiting the loop/switch this call is building. */
|
|
527
|
+
takeLabels() {
|
|
528
|
+
const labels = this.pendingLabels;
|
|
529
|
+
this.pendingLabels = [];
|
|
530
|
+
return labels;
|
|
531
|
+
}
|
|
532
|
+
labelOf(stmt) {
|
|
533
|
+
const id = stmt.childForFieldName('label') ??
|
|
534
|
+
stmt.namedChildren.find((c) => c.type === 'statement_identifier');
|
|
535
|
+
return id?.text;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/** Build the CFG for one TS/JS function node (or `undefined` if not a function). */
|
|
539
|
+
function buildFunctionCfg(fnNode, filePath) {
|
|
540
|
+
if (!TS_FUNCTION_TYPES.has(fnNode.type))
|
|
541
|
+
return undefined;
|
|
542
|
+
const startLine = startLineOf(fnNode);
|
|
543
|
+
const endLine = endLineOf(fnNode);
|
|
544
|
+
const startColumn = fnNode.startPosition.column;
|
|
545
|
+
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
|
|
546
|
+
const body = fnNode.childForFieldName('body');
|
|
547
|
+
if (!body)
|
|
548
|
+
return undefined; // overload signature / abstract method — no body
|
|
549
|
+
// Phase-1 declaration pre-scan (#2082 M2 U1) — must complete before any
|
|
550
|
+
// facts are extracted; the CFG walk below is not source-order.
|
|
551
|
+
const harvest = new TsHarvester(fnNode);
|
|
552
|
+
// Parameters define at ENTRY (facts only — never touch the entry block's
|
|
553
|
+
// text or span: bench fingerprints and CFG snapshots include block text).
|
|
554
|
+
const paramFacts = harvest.paramFacts();
|
|
555
|
+
if (paramFacts)
|
|
556
|
+
builder.attachFacts(builder.entryIndex, paramFacts);
|
|
557
|
+
if (body.type !== 'statement_block') {
|
|
558
|
+
// Expression-bodied arrow: `() => expr` — one block whose value is returned.
|
|
559
|
+
// Lives outside the walk class, so it harvests explicitly.
|
|
560
|
+
const blk = builder.newBlock(startLineOf(body), endLineOf(body), body.text, 'normal', harvest.facts(body));
|
|
561
|
+
builder.edge(builder.entryIndex, blk, 'seq');
|
|
562
|
+
builder.edge(blk, builder.exitIndex, 'return');
|
|
563
|
+
return builder.finish(harvest.table());
|
|
564
|
+
}
|
|
565
|
+
const walk = new TsCfgWalk(builder, harvest);
|
|
566
|
+
const res = walk.visitSeq(body.namedChildren.filter((c) => c.type !== 'comment'));
|
|
567
|
+
if (!res) {
|
|
568
|
+
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body
|
|
569
|
+
return builder.finish(harvest.table());
|
|
570
|
+
}
|
|
571
|
+
builder.edge(builder.entryIndex, res.entry, 'seq');
|
|
572
|
+
builder.connect(res.exits, builder.exitIndex, 'seq'); // normal fall-off → EXIT
|
|
573
|
+
return builder.finish(harvest.table());
|
|
574
|
+
}
|
|
575
|
+
/** Whether a node is a TS/JS function this visitor builds a CFG for. */
|
|
576
|
+
function isFunction(node) {
|
|
577
|
+
return TS_FUNCTION_TYPES.has(node.type);
|
|
578
|
+
}
|
|
579
|
+
/** The TS/JS CFG visitor (shared by TypeScript and JavaScript). */
|
|
580
|
+
export function createTypeScriptCfgVisitor() {
|
|
581
|
+
return { buildFunctionCfg, isFunction };
|
|
582
|
+
}
|
|
583
|
+
export { TS_FUNCTION_TYPES };
|