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,1056 @@
|
|
|
1
|
+
/** Node types that own a nested CFG — their subtrees are opaque to harvesting. */
|
|
2
|
+
const NESTED_FUNCTION_TYPES = new Set([
|
|
3
|
+
'function_declaration',
|
|
4
|
+
'function_expression',
|
|
5
|
+
'arrow_function',
|
|
6
|
+
'method_definition',
|
|
7
|
+
'generator_function_declaration',
|
|
8
|
+
'generator_function',
|
|
9
|
+
'async_function_declaration',
|
|
10
|
+
'async_arrow_function',
|
|
11
|
+
]);
|
|
12
|
+
/** Function-ish declaration statements whose NAME still binds in the enclosing scope. */
|
|
13
|
+
const FUNCTION_DECL_TYPES = new Set([
|
|
14
|
+
'function_declaration',
|
|
15
|
+
'generator_function_declaration',
|
|
16
|
+
'async_function_declaration',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Nodes that open a lexical scope for `let`/`const`/`class`/catch bindings.
|
|
20
|
+
* A `switch` BODY is deliberately ONE scope shared by all case arms (JS
|
|
21
|
+
* semantics: `case 1: let x = 1; case 2: use(x)` is the same binding).
|
|
22
|
+
*/
|
|
23
|
+
const SCOPE_TYPES = new Set([
|
|
24
|
+
'statement_block',
|
|
25
|
+
'for_statement',
|
|
26
|
+
'for_in_statement',
|
|
27
|
+
'for_of_statement',
|
|
28
|
+
'catch_clause',
|
|
29
|
+
'switch_body',
|
|
30
|
+
]);
|
|
31
|
+
/** Type-position subtrees — identifiers inside them are not value uses. */
|
|
32
|
+
const TYPE_CONTEXT_TYPES = new Set([
|
|
33
|
+
'type_annotation',
|
|
34
|
+
'type_arguments',
|
|
35
|
+
'type_parameters',
|
|
36
|
+
'type_predicate_annotation',
|
|
37
|
+
'asserts_annotation',
|
|
38
|
+
]);
|
|
39
|
+
/**
|
|
40
|
+
* Wrappers that don't change which VALUE flows through them (#2083 M3 U1) —
|
|
41
|
+
* unwrapped when resolving call-result attribution (`const b = (await
|
|
42
|
+
* escape(t))!` still attaches `resultDefs: [b]` to the escape site) and
|
|
43
|
+
* member-chain roots. Distinct from {@link TsHarvester.unwrapLvalue}, which is
|
|
44
|
+
* the narrower LVALUE set.
|
|
45
|
+
*/
|
|
46
|
+
const VALUE_WRAPPER_TYPES = new Set([
|
|
47
|
+
'parenthesized_expression',
|
|
48
|
+
'non_null_expression',
|
|
49
|
+
'as_expression',
|
|
50
|
+
'satisfies_expression',
|
|
51
|
+
'await_expression',
|
|
52
|
+
]);
|
|
53
|
+
/** Literal text of a `string` node (concatenated fragments; raw escapes kept). */
|
|
54
|
+
const stringLiteralText = (node) => {
|
|
55
|
+
let out = '';
|
|
56
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
57
|
+
const c = node.namedChild(i);
|
|
58
|
+
if (c?.type === 'string_fragment' || c?.type === 'escape_sequence')
|
|
59
|
+
out += c.text;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
63
|
+
export class TsHarvester {
|
|
64
|
+
fnNode;
|
|
65
|
+
bindings = [];
|
|
66
|
+
/** Scope-opening node id → its scope. */
|
|
67
|
+
scopeByNode = new Map();
|
|
68
|
+
root = { parent: null, table: new Map() };
|
|
69
|
+
/** name → synthetic binding index (implicit global / import / captured). */
|
|
70
|
+
synthetic = new Map();
|
|
71
|
+
fnId;
|
|
72
|
+
/**
|
|
73
|
+
* Innermost enclosing scope per visited node id, filled during the prescan
|
|
74
|
+
* (which already touches every named node once). Makes phase-2 resolution
|
|
75
|
+
* O(scope-chain) instead of O(AST-depth) per identifier — a deeply-chained
|
|
76
|
+
* single-statement expression (generated code) otherwise turns the
|
|
77
|
+
* parent-chain walk quadratic (tri-review perf finding).
|
|
78
|
+
*/
|
|
79
|
+
nearestScopeCache = new Map();
|
|
80
|
+
/**
|
|
81
|
+
* >0 while walking a conditionally-evaluated subexpression (short-circuit
|
|
82
|
+
* right operand, ternary arm, logical-assignment target, case test). Defs
|
|
83
|
+
* found there are MAY-defs — gen without kill (tri-review P1: a must-def
|
|
84
|
+
* here falsely kills the prior def on the not-taken path).
|
|
85
|
+
*/
|
|
86
|
+
conditionalDepth = 0;
|
|
87
|
+
/**
|
|
88
|
+
* Call/new node id → bindings whose declarator/assignment VALUE is exactly
|
|
89
|
+
* that call (#2083 M3 U1). Registered by the declarator/assignment handlers
|
|
90
|
+
* BEFORE the value walk, consumed by {@link visitCall} when it reaches the
|
|
91
|
+
* node — the indirection keeps result-def attribution per-declarator
|
|
92
|
+
* (`const a = t, b = escape(t)` attaches `[b]` to the escape site only) and
|
|
93
|
+
* top-level-only (`const c = cond ? escape(b) : b` attaches nothing — the
|
|
94
|
+
* bypass occurrence must keep `c` taintable, plan KTD4a).
|
|
95
|
+
*/
|
|
96
|
+
resultDefTargets = new Map();
|
|
97
|
+
constructor(fnNode) {
|
|
98
|
+
this.fnNode = fnNode;
|
|
99
|
+
this.fnId = fnNode.id;
|
|
100
|
+
this.scopeByNode.set(fnNode.id, this.root);
|
|
101
|
+
this.declareParams(fnNode);
|
|
102
|
+
const body = fnNode.childForFieldName('body');
|
|
103
|
+
if (body)
|
|
104
|
+
this.prescan(body, body.type === 'statement_block' ? this.openScope(body) : this.root);
|
|
105
|
+
}
|
|
106
|
+
/** The completed binding table — pass to `CfgBuilder.finish`. */
|
|
107
|
+
table() {
|
|
108
|
+
return this.bindings;
|
|
109
|
+
}
|
|
110
|
+
// ── phase 1: declaration pre-scan ────────────────────────────────────────
|
|
111
|
+
openScope(node) {
|
|
112
|
+
const existing = this.scopeByNode.get(node.id);
|
|
113
|
+
if (existing)
|
|
114
|
+
return existing;
|
|
115
|
+
const scope = { parent: this.nearestScopeOf(node), table: new Map() };
|
|
116
|
+
this.scopeByNode.set(node.id, scope);
|
|
117
|
+
return scope;
|
|
118
|
+
}
|
|
119
|
+
nearestScopeOf(node) {
|
|
120
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
121
|
+
const s = this.scopeByNode.get(p.id);
|
|
122
|
+
if (s)
|
|
123
|
+
return s;
|
|
124
|
+
if (p.id === this.fnId)
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
return this.root;
|
|
128
|
+
}
|
|
129
|
+
declare(nameNode, kind, scope, hoistToRoot, formalIndex) {
|
|
130
|
+
const target = hoistToRoot ? this.root : scope;
|
|
131
|
+
const name = nameNode.text;
|
|
132
|
+
// `var` multi-declaration (and a param + `var` of the same name) is ONE
|
|
133
|
+
// binding — first declaration in source order is canonical. The dedup is
|
|
134
|
+
// scoped to the single target table, so an inner `let x` shadowing a root
|
|
135
|
+
// `var x` still gets its own entry in its own scope.
|
|
136
|
+
if (target.table.has(name))
|
|
137
|
+
return;
|
|
138
|
+
target.table.set(name, this.bindings.length);
|
|
139
|
+
this.bindings.push({
|
|
140
|
+
name,
|
|
141
|
+
declLine: nameNode.startPosition.row + 1,
|
|
142
|
+
declColumn: nameNode.startPosition.column,
|
|
143
|
+
kind,
|
|
144
|
+
// Carry the enclosing formal position for params so the PDG call-summary
|
|
145
|
+
// consumer joins by FORMAL slot, never the flattened binding ordinal. A
|
|
146
|
+
// destructured/rest formal hands the SAME index to every inner name.
|
|
147
|
+
...(formalIndex !== undefined ? { formalIndex } : {}),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
declareParams(fnNode) {
|
|
151
|
+
const params = fnNode.childForFieldName('parameters') ?? fnNode.childForFieldName('parameter');
|
|
152
|
+
if (!params)
|
|
153
|
+
return;
|
|
154
|
+
if (params.type === 'identifier') {
|
|
155
|
+
this.declare(params, 'param', this.root, true, 0); // `x => …` single-param arrow ⇒ formal 0
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Each NAMED child of the parameter list is one top-level formal — its index
|
|
159
|
+
// here is the 0-based formal position. Destructured/rest formals fan out to
|
|
160
|
+
// several inner names, but ALL inherit this one formal index, so the PDG
|
|
161
|
+
// call-summary never misattributes an inner name to a later formal's slot.
|
|
162
|
+
let formalIndex = 0;
|
|
163
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
164
|
+
const p = params.namedChild(i);
|
|
165
|
+
if (!p)
|
|
166
|
+
continue;
|
|
167
|
+
// TS wraps each param (required_parameter/optional_parameter, field
|
|
168
|
+
// `pattern`); plain JS puts the pattern directly in formal_parameters.
|
|
169
|
+
const pattern = p.childForFieldName('pattern') ?? p;
|
|
170
|
+
this.declarePattern(pattern, 'param', this.root, true, formalIndex);
|
|
171
|
+
formalIndex++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Declare every name bound by a (possibly destructuring) pattern. When
|
|
176
|
+
* `formalIndex` is supplied (param patterns), EVERY name the pattern binds
|
|
177
|
+
* carries that one enclosing-formal position (the recursion never reassigns
|
|
178
|
+
* it), so `function f({a, b}, c)` records a:0, b:0, c:1.
|
|
179
|
+
*/
|
|
180
|
+
declarePattern(node, kind, scope, hoistToRoot, formalIndex) {
|
|
181
|
+
switch (node.type) {
|
|
182
|
+
case 'identifier':
|
|
183
|
+
case 'shorthand_property_identifier_pattern':
|
|
184
|
+
this.declare(node, kind, scope, hoistToRoot, formalIndex);
|
|
185
|
+
return;
|
|
186
|
+
case 'rest_pattern':
|
|
187
|
+
case 'object_pattern':
|
|
188
|
+
case 'array_pattern':
|
|
189
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
190
|
+
const c = node.namedChild(i);
|
|
191
|
+
if (c)
|
|
192
|
+
this.declarePattern(c, kind, scope, hoistToRoot, formalIndex);
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
case 'pair_pattern': {
|
|
196
|
+
const value = node.childForFieldName('value');
|
|
197
|
+
if (value)
|
|
198
|
+
this.declarePattern(value, kind, scope, hoistToRoot, formalIndex);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
case 'assignment_pattern':
|
|
202
|
+
case 'object_assignment_pattern': {
|
|
203
|
+
const left = node.childForFieldName('left');
|
|
204
|
+
if (left)
|
|
205
|
+
this.declarePattern(left, kind, scope, hoistToRoot, formalIndex);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
default:
|
|
209
|
+
// Type annotations / unknown wrappers — descend defensively.
|
|
210
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
211
|
+
const c = node.namedChild(i);
|
|
212
|
+
if (c && !TYPE_CONTEXT_TYPES.has(c.type)) {
|
|
213
|
+
this.declarePattern(c, kind, scope, hoistToRoot, formalIndex);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
prescan(node, scope) {
|
|
219
|
+
this.nearestScopeCache.set(node.id, scope);
|
|
220
|
+
const t = node.type;
|
|
221
|
+
if (NESTED_FUNCTION_TYPES.has(t) && node.id !== this.fnId) {
|
|
222
|
+
// A nested function's NAME binds in the enclosing scope; its body is opaque.
|
|
223
|
+
if (FUNCTION_DECL_TYPES.has(t)) {
|
|
224
|
+
const name = node.childForFieldName('name');
|
|
225
|
+
if (name)
|
|
226
|
+
this.declare(name, 'function', scope, false);
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let childScope = scope;
|
|
231
|
+
if (SCOPE_TYPES.has(t))
|
|
232
|
+
childScope = this.openScope(node);
|
|
233
|
+
switch (t) {
|
|
234
|
+
case 'lexical_declaration': {
|
|
235
|
+
const kind = node.child(0)?.type === 'const' ? 'const' : 'let';
|
|
236
|
+
this.declareDeclarators(node, kind, childScope, false);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 'variable_declaration':
|
|
240
|
+
this.declareDeclarators(node, 'var', childScope, true);
|
|
241
|
+
break;
|
|
242
|
+
case 'class_declaration': {
|
|
243
|
+
const name = node.childForFieldName('name');
|
|
244
|
+
if (name)
|
|
245
|
+
this.declare(name, 'class', childScope, false);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
case 'catch_clause': {
|
|
249
|
+
const param = node.childForFieldName('parameter');
|
|
250
|
+
if (param)
|
|
251
|
+
this.declarePattern(param, 'catch', childScope, false);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case 'for_in_statement':
|
|
255
|
+
case 'for_of_statement': {
|
|
256
|
+
// `for (const x of xs)` — the `kind` keyword marks a declaration; a bare
|
|
257
|
+
// `for (x of xs)` left is an assignment, resolved at use time instead.
|
|
258
|
+
const kindNode = node.childForFieldName('kind');
|
|
259
|
+
const left = node.childForFieldName('left');
|
|
260
|
+
if (kindNode && left) {
|
|
261
|
+
const k = kindNode.type === 'var' ? 'var' : kindNode.type === 'const' ? 'const' : 'let';
|
|
262
|
+
this.declarePattern(left, k, childScope, k === 'var');
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
default:
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
270
|
+
const c = node.namedChild(i);
|
|
271
|
+
if (c)
|
|
272
|
+
this.prescan(c, childScope);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
declareDeclarators(declNode, kind, scope, hoistToRoot) {
|
|
276
|
+
for (let i = 0; i < declNode.namedChildCount; i++) {
|
|
277
|
+
const d = declNode.namedChild(i);
|
|
278
|
+
if (d?.type !== 'variable_declarator')
|
|
279
|
+
continue;
|
|
280
|
+
const name = d.childForFieldName('name');
|
|
281
|
+
if (name)
|
|
282
|
+
this.declarePattern(name, kind, scope, hoistToRoot);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// ── phase 2: per-statement fact extraction ───────────────────────────────
|
|
286
|
+
/**
|
|
287
|
+
* Def/use facts for one statement (or construct-header expression) node.
|
|
288
|
+
* Safe from any walk order — resolution consults the completed scope tree.
|
|
289
|
+
*/
|
|
290
|
+
facts(node) {
|
|
291
|
+
const acc = new FactAccumulator(node.startPosition.row + 1);
|
|
292
|
+
this.walkValue(node, acc);
|
|
293
|
+
return acc.finish();
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Facts for an expression whose WHOLE evaluation is conditional (switch
|
|
297
|
+
* case tests, which only run when earlier cases didn't match) — every def
|
|
298
|
+
* inside becomes a may-def.
|
|
299
|
+
*/
|
|
300
|
+
factsConditional(node) {
|
|
301
|
+
const acc = new FactAccumulator(node.startPosition.row + 1);
|
|
302
|
+
this.conditional(() => this.walkValue(node, acc));
|
|
303
|
+
return acc.finish();
|
|
304
|
+
}
|
|
305
|
+
/** Facts for a `for (left in/of right)` head: left binds/assigns, right is used. */
|
|
306
|
+
forInHeadFacts(stmt) {
|
|
307
|
+
const acc = new FactAccumulator(stmt.startPosition.row + 1);
|
|
308
|
+
const left = stmt.childForFieldName('left');
|
|
309
|
+
const right = stmt.childForFieldName('right');
|
|
310
|
+
if (left)
|
|
311
|
+
this.walkDefPattern(left, acc);
|
|
312
|
+
if (right)
|
|
313
|
+
this.walkValue(right, acc);
|
|
314
|
+
return acc.finish();
|
|
315
|
+
}
|
|
316
|
+
/** ENTRY-block facts for the function's parameters (defs + default-value uses). */
|
|
317
|
+
paramFacts() {
|
|
318
|
+
const fnNode = this.fnNode;
|
|
319
|
+
const params = fnNode.childForFieldName('parameters') ?? fnNode.childForFieldName('parameter');
|
|
320
|
+
if (!params)
|
|
321
|
+
return undefined;
|
|
322
|
+
const acc = new FactAccumulator(fnNode.startPosition.row + 1);
|
|
323
|
+
if (params.type === 'identifier') {
|
|
324
|
+
this.def(params, acc);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
328
|
+
const p = params.namedChild(i);
|
|
329
|
+
if (!p)
|
|
330
|
+
continue;
|
|
331
|
+
const pattern = p.childForFieldName('pattern') ?? p;
|
|
332
|
+
this.walkDefPattern(pattern, acc);
|
|
333
|
+
const dflt = p.childForFieldName('value');
|
|
334
|
+
if (dflt)
|
|
335
|
+
this.walkValue(dflt, acc);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return acc.defCount() || acc.useCount() ? acc.finish() : undefined;
|
|
339
|
+
}
|
|
340
|
+
/** Def fact for a `catch (e)` parameter — prepend to the handler entry block. */
|
|
341
|
+
catchParamFacts(catchClause) {
|
|
342
|
+
const param = catchClause.childForFieldName('parameter');
|
|
343
|
+
if (!param)
|
|
344
|
+
return undefined;
|
|
345
|
+
const acc = new FactAccumulator(catchClause.startPosition.row + 1);
|
|
346
|
+
this.walkDefPattern(param, acc);
|
|
347
|
+
return acc.defCount() ? acc.finish() : undefined;
|
|
348
|
+
}
|
|
349
|
+
resolve(nameNode) {
|
|
350
|
+
const name = nameNode.text;
|
|
351
|
+
// Fast path: the prescan cached every visited node's innermost scope, so
|
|
352
|
+
// resolution walks the SCOPE chain (shallow), not the AST parent chain
|
|
353
|
+
// (arbitrarily deep in chained expressions). The parent-chain walk remains
|
|
354
|
+
// as fallback for the few nodes the prescan never visits (e.g. a nested
|
|
355
|
+
// function declaration's own name node).
|
|
356
|
+
const cached = this.nearestScopeCache.get(nameNode.id);
|
|
357
|
+
let startScope = cached ?? null;
|
|
358
|
+
if (!startScope) {
|
|
359
|
+
for (let p = nameNode; p; p = p.parent) {
|
|
360
|
+
const scope = this.scopeByNode.get(p.id) ?? this.nearestScopeCache.get(p.id);
|
|
361
|
+
if (scope) {
|
|
362
|
+
startScope = scope;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
if (p.id === this.fnId) {
|
|
366
|
+
startScope = this.root;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
for (let s = startScope; s; s = s.parent) {
|
|
372
|
+
const idx = s.table.get(name);
|
|
373
|
+
if (idx !== undefined)
|
|
374
|
+
return idx;
|
|
375
|
+
}
|
|
376
|
+
// No in-function declaration — synthetic module-level binding, shared by
|
|
377
|
+
// defs and uses so `notDeclared = 1; use(notDeclared)` still forms a fact.
|
|
378
|
+
let idx = this.synthetic.get(name);
|
|
379
|
+
if (idx === undefined) {
|
|
380
|
+
idx = this.bindings.length;
|
|
381
|
+
this.synthetic.set(name, idx);
|
|
382
|
+
this.bindings.push({ name, declLine: 0, declColumn: 0, kind: 'module', synthetic: true });
|
|
383
|
+
}
|
|
384
|
+
return idx;
|
|
385
|
+
}
|
|
386
|
+
def(nameNode, acc) {
|
|
387
|
+
if (this.conditionalDepth > 0)
|
|
388
|
+
acc.addMayDef(this.resolve(nameNode));
|
|
389
|
+
else
|
|
390
|
+
acc.addDef(this.resolve(nameNode));
|
|
391
|
+
}
|
|
392
|
+
/** Run `fn` with defs demoted to may-defs (conditionally-evaluated context). */
|
|
393
|
+
conditional(fn) {
|
|
394
|
+
this.conditionalDepth++;
|
|
395
|
+
try {
|
|
396
|
+
fn();
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
this.conditionalDepth--;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/** Strip wrappers that don't change the lvalue (`(x) += 1`, `x! ++`). */
|
|
403
|
+
unwrapLvalue(node) {
|
|
404
|
+
let n = node;
|
|
405
|
+
while (n.type === 'parenthesized_expression' || n.type === 'non_null_expression') {
|
|
406
|
+
const inner = n.namedChild(0);
|
|
407
|
+
if (!inner)
|
|
408
|
+
break;
|
|
409
|
+
n = inner;
|
|
410
|
+
}
|
|
411
|
+
return n;
|
|
412
|
+
}
|
|
413
|
+
use(nameNode, acc) {
|
|
414
|
+
acc.addUse(this.resolve(nameNode));
|
|
415
|
+
}
|
|
416
|
+
/** Value-position walk: collect uses; route def positions to the pattern walk. */
|
|
417
|
+
walkValue(node, acc) {
|
|
418
|
+
const t = node.type;
|
|
419
|
+
if (TYPE_CONTEXT_TYPES.has(t))
|
|
420
|
+
return;
|
|
421
|
+
if (NESTED_FUNCTION_TYPES.has(t) && node.id !== this.fnId) {
|
|
422
|
+
// Opaque nested function: its NAME (function declaration) is a def in
|
|
423
|
+
// the enclosing scope; captured reads/writes inside are invisible (KTD4).
|
|
424
|
+
if (FUNCTION_DECL_TYPES.has(t)) {
|
|
425
|
+
const name = node.childForFieldName('name');
|
|
426
|
+
if (name)
|
|
427
|
+
this.def(name, acc);
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
switch (t) {
|
|
432
|
+
case 'identifier':
|
|
433
|
+
case 'shorthand_property_identifier':
|
|
434
|
+
this.use(node, acc);
|
|
435
|
+
return;
|
|
436
|
+
case 'lexical_declaration':
|
|
437
|
+
case 'variable_declaration':
|
|
438
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
439
|
+
const d = node.namedChild(i);
|
|
440
|
+
if (d?.type !== 'variable_declarator')
|
|
441
|
+
continue;
|
|
442
|
+
const name = d.childForFieldName('name');
|
|
443
|
+
const value = d.childForFieldName('value');
|
|
444
|
+
// A bare `var x;` mid-function is hoisted and writes NOTHING at
|
|
445
|
+
// runtime — harvesting it as a def would fabricate a kill of the
|
|
446
|
+
// live def (`x = source(); var x; sink(x)` must keep source→sink;
|
|
447
|
+
// tri-review P2). `let`/`const` declarators genuinely initialize.
|
|
448
|
+
if (name && (value || t === 'lexical_declaration')) {
|
|
449
|
+
const snap = acc.defSnapshot();
|
|
450
|
+
this.walkDefPattern(name, acc);
|
|
451
|
+
if (value)
|
|
452
|
+
this.registerResultDefs(value, acc.defsSince(snap));
|
|
453
|
+
}
|
|
454
|
+
if (value)
|
|
455
|
+
this.walkValue(value, acc);
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
case 'assignment_expression': {
|
|
459
|
+
const left = node.childForFieldName('left');
|
|
460
|
+
const right = node.childForFieldName('right');
|
|
461
|
+
if (left) {
|
|
462
|
+
const snap = acc.defSnapshot();
|
|
463
|
+
this.walkDefPattern(this.unwrapLvalue(left), acc);
|
|
464
|
+
if (right)
|
|
465
|
+
this.registerResultDefs(right, acc.defsSince(snap));
|
|
466
|
+
}
|
|
467
|
+
if (right)
|
|
468
|
+
this.walkValue(right, acc);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
case 'augmented_assignment_expression': {
|
|
472
|
+
// `x += y` both defines and uses x. The logical-assignment operators
|
|
473
|
+
// (`||=`, `&&=`, `??=`) only WRITE conditionally — their def is a
|
|
474
|
+
// may-def (the read always happens).
|
|
475
|
+
const left = node.childForFieldName('left')
|
|
476
|
+
? this.unwrapLvalue(node.childForFieldName('left'))
|
|
477
|
+
: null;
|
|
478
|
+
const right = node.childForFieldName('right');
|
|
479
|
+
const op = node.childForFieldName('operator')?.type ?? '';
|
|
480
|
+
const logical = op === '||=' || op === '&&=' || op === '??=';
|
|
481
|
+
if (left?.type === 'identifier') {
|
|
482
|
+
if (logical)
|
|
483
|
+
this.conditional(() => this.def(left, acc));
|
|
484
|
+
else
|
|
485
|
+
this.def(left, acc);
|
|
486
|
+
this.use(left, acc);
|
|
487
|
+
}
|
|
488
|
+
else if (left) {
|
|
489
|
+
this.walkValue(left, acc); // member/subscript target — uses only
|
|
490
|
+
}
|
|
491
|
+
// The RHS of a logical assignment is itself conditionally evaluated.
|
|
492
|
+
if (right) {
|
|
493
|
+
if (logical)
|
|
494
|
+
this.conditional(() => this.walkValue(right, acc));
|
|
495
|
+
else
|
|
496
|
+
this.walkValue(right, acc);
|
|
497
|
+
}
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
case 'update_expression': {
|
|
501
|
+
const rawArg = node.childForFieldName('argument');
|
|
502
|
+
const arg = rawArg ? this.unwrapLvalue(rawArg) : null;
|
|
503
|
+
if (arg?.type === 'identifier') {
|
|
504
|
+
this.def(arg, acc);
|
|
505
|
+
this.use(arg, acc);
|
|
506
|
+
}
|
|
507
|
+
else if (arg) {
|
|
508
|
+
this.walkValue(arg, acc);
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
case 'binary_expression': {
|
|
513
|
+
// Short-circuit operators evaluate their RIGHT operand conditionally:
|
|
514
|
+
// a def inside it (`a && (x = clean())`, `c ?? (c = load())`) must be
|
|
515
|
+
// a may-def or the not-taken path's prior def is falsely killed
|
|
516
|
+
// (tri-review P1). Other binary operators evaluate both sides.
|
|
517
|
+
const left = node.childForFieldName('left');
|
|
518
|
+
const right = node.childForFieldName('right');
|
|
519
|
+
const op = node.childForFieldName('operator')?.type ?? '';
|
|
520
|
+
if (left)
|
|
521
|
+
this.walkValue(left, acc);
|
|
522
|
+
if (right) {
|
|
523
|
+
if (op === '&&' || op === '||' || op === '??') {
|
|
524
|
+
this.conditional(() => this.walkValue(right, acc));
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
this.walkValue(right, acc);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
case 'ternary_expression': {
|
|
533
|
+
// Each arm is conditionally evaluated — defs inside are may-defs.
|
|
534
|
+
const cond = node.childForFieldName('condition');
|
|
535
|
+
const consequence = node.childForFieldName('consequence');
|
|
536
|
+
const alternative = node.childForFieldName('alternative');
|
|
537
|
+
if (cond)
|
|
538
|
+
this.walkValue(cond, acc);
|
|
539
|
+
if (consequence)
|
|
540
|
+
this.conditional(() => this.walkValue(consequence, acc));
|
|
541
|
+
if (alternative)
|
|
542
|
+
this.conditional(() => this.walkValue(alternative, acc));
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
case 'class_declaration': {
|
|
546
|
+
// The class NAME is a def (prescan declared the binding) — without
|
|
547
|
+
// this case the default walk would record it as a bogus USE in plain
|
|
548
|
+
// JS (the name is an `identifier` there; in TS it's a type_identifier
|
|
549
|
+
// and would be silently skipped, losing the def either way). The body
|
|
550
|
+
// walk picks up field-initializer uses; methods are opaque nested fns.
|
|
551
|
+
const name = node.childForFieldName('name');
|
|
552
|
+
if (name)
|
|
553
|
+
this.def(name, acc);
|
|
554
|
+
const body = node.childForFieldName('body');
|
|
555
|
+
if (body)
|
|
556
|
+
this.walkValue(body, acc);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
case 'class': {
|
|
560
|
+
// Class EXPRESSION: its name (if any) binds only inside the class —
|
|
561
|
+
// not a def in the enclosing function. Walk only the body.
|
|
562
|
+
const body = node.childForFieldName('body');
|
|
563
|
+
if (body)
|
|
564
|
+
this.walkValue(body, acc);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
case 'call_expression':
|
|
568
|
+
// #2083 M3 U1: explicit case (previously default-descended) — same
|
|
569
|
+
// uses, plus a taint-site record. MUST keep defs/uses byte-identical.
|
|
570
|
+
this.visitCall(node, acc, 'call');
|
|
571
|
+
return;
|
|
572
|
+
case 'new_expression':
|
|
573
|
+
this.visitCall(node, acc, 'new');
|
|
574
|
+
return;
|
|
575
|
+
case 'member_expression':
|
|
576
|
+
case 'subscript_expression':
|
|
577
|
+
// #2083 M3 U1: value-position member chain — same uses as the old
|
|
578
|
+
// default descent (root identifier + dynamic subscript indices), plus
|
|
579
|
+
// a member-read site for the innermost identifier-rooted access.
|
|
580
|
+
this.walkChain(node, acc, false);
|
|
581
|
+
return;
|
|
582
|
+
case 'sequence_expression': {
|
|
583
|
+
// Comma operator: only the LAST operand's value flows. Earlier operands
|
|
584
|
+
// are evaluated for side effects — record their uses but suppress
|
|
585
|
+
// occurrence fan-out so `exec((log(x), 'safe'))` does not taint exec's
|
|
586
|
+
// arg 0 with `x` (review fix). Defs/uses stay byte-identical to the old
|
|
587
|
+
// default descent; only the sites layer narrows.
|
|
588
|
+
const operands = [];
|
|
589
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
590
|
+
const c = node.namedChild(i);
|
|
591
|
+
if (c)
|
|
592
|
+
operands.push(c);
|
|
593
|
+
}
|
|
594
|
+
const last = operands.length - 1;
|
|
595
|
+
operands.forEach((op, i) => {
|
|
596
|
+
if (i === last)
|
|
597
|
+
this.walkValue(op, acc);
|
|
598
|
+
else
|
|
599
|
+
acc.suppressOccurrences(() => this.walkValue(op, acc));
|
|
600
|
+
});
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
default:
|
|
604
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
605
|
+
const c = node.namedChild(i);
|
|
606
|
+
if (c)
|
|
607
|
+
this.walkValue(c, acc);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
/** Assignment-target walk: identifiers bind; member/subscript targets are uses. */
|
|
612
|
+
walkDefPattern(node, acc) {
|
|
613
|
+
switch (node.type) {
|
|
614
|
+
case 'identifier':
|
|
615
|
+
case 'shorthand_property_identifier_pattern':
|
|
616
|
+
this.def(node, acc);
|
|
617
|
+
return;
|
|
618
|
+
case 'rest_pattern':
|
|
619
|
+
case 'object_pattern':
|
|
620
|
+
case 'array_pattern':
|
|
621
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
622
|
+
const c = node.namedChild(i);
|
|
623
|
+
if (c)
|
|
624
|
+
this.walkDefPattern(c, acc);
|
|
625
|
+
}
|
|
626
|
+
return;
|
|
627
|
+
case 'pair_pattern': {
|
|
628
|
+
const key = node.childForFieldName('key');
|
|
629
|
+
const value = node.childForFieldName('value');
|
|
630
|
+
if (key?.type === 'computed_property_name')
|
|
631
|
+
this.walkValue(key, acc);
|
|
632
|
+
if (value)
|
|
633
|
+
this.walkDefPattern(value, acc);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
case 'assignment_pattern':
|
|
637
|
+
case 'object_assignment_pattern': {
|
|
638
|
+
const left = node.childForFieldName('left');
|
|
639
|
+
const right = node.childForFieldName('right');
|
|
640
|
+
if (left)
|
|
641
|
+
this.walkDefPattern(left, acc);
|
|
642
|
+
if (right)
|
|
643
|
+
this.walkValue(right, acc);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
case 'member_expression':
|
|
647
|
+
case 'subscript_expression':
|
|
648
|
+
// Property/element write — NOT a scalar def (KTD4); its identifiers
|
|
649
|
+
// (object, computed key) are uses. WRITE position (#2083 M3 U1): the
|
|
650
|
+
// written access itself is not a value read — no member-read site for
|
|
651
|
+
// it (`obj.p = q` records nothing; `req.body.x = v`'s mid-chain LOAD
|
|
652
|
+
// of `req.body` still does).
|
|
653
|
+
this.walkChain(node, acc, true);
|
|
654
|
+
return;
|
|
655
|
+
default:
|
|
656
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
657
|
+
const c = node.namedChild(i);
|
|
658
|
+
if (c && !TYPE_CONTEXT_TYPES.has(c.type))
|
|
659
|
+
this.walkDefPattern(c, acc);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
// ── taint-site harvest (#2083 M3 U1) ────────────────────────────────────
|
|
664
|
+
/** Strip value-transparent wrappers (`(x)`, `x!`, `x as T`, `await x`). */
|
|
665
|
+
unwrapValueWrappers(node) {
|
|
666
|
+
let n = node;
|
|
667
|
+
while (VALUE_WRAPPER_TYPES.has(n.type)) {
|
|
668
|
+
const inner = n.namedChild(0);
|
|
669
|
+
if (!inner)
|
|
670
|
+
break;
|
|
671
|
+
n = inner;
|
|
672
|
+
}
|
|
673
|
+
return n;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* When `value`'s root (after unwrapping) is a call/new node, remember that
|
|
677
|
+
* its site should carry `resultDefs: defs` — consumed by {@link visitCall}
|
|
678
|
+
* once the value walk reaches the node.
|
|
679
|
+
*/
|
|
680
|
+
registerResultDefs(value, defs) {
|
|
681
|
+
if (defs.length === 0)
|
|
682
|
+
return;
|
|
683
|
+
const root = this.unwrapValueWrappers(value);
|
|
684
|
+
if (root.type === 'call_expression' || root.type === 'new_expression') {
|
|
685
|
+
this.resultDefTargets.set(root.id, [...defs]);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Explicit call/new handler: records a call site (callee path, receiver,
|
|
690
|
+
* per-arg occurrence entries, spread/template markers, require literal,
|
|
691
|
+
* result defs) while reproducing EXACTLY the uses the old default descent
|
|
692
|
+
* recorded — callee chain root + dynamic subscript indices + arguments.
|
|
693
|
+
*/
|
|
694
|
+
visitCall(node, acc, kind) {
|
|
695
|
+
const calleeNode = node.childForFieldName(kind === 'new' ? 'constructor' : 'function');
|
|
696
|
+
const argsNode = node.childForFieldName('arguments');
|
|
697
|
+
// `node` IS the call_expression/new_expression — the SAME node the
|
|
698
|
+
// scope-extractor anchors `@reference.call.*` (its `atRange`) on (KTD7).
|
|
699
|
+
const siteIdx = acc.openCallSite(kind, [node.startPosition.row + 1, node.startPosition.column]);
|
|
700
|
+
acc.pushFrame(siteIdx);
|
|
701
|
+
let calleePath;
|
|
702
|
+
if (calleeNode) {
|
|
703
|
+
const callee = this.unwrapValueWrappers(calleeNode);
|
|
704
|
+
if (callee.type === 'identifier') {
|
|
705
|
+
// The callee NAME is a statement-level use but NOT a value occurrence
|
|
706
|
+
// flowing into any enclosing argument — `exec(escape(x))` must not
|
|
707
|
+
// put the `escape` binding itself into exec's arg 0 (only x, tagged
|
|
708
|
+
// via the escape site). Receiver-chain roots DO fan out (KTD5 TITO).
|
|
709
|
+
acc.addUseWithoutOccurrence(this.resolve(callee));
|
|
710
|
+
calleePath = callee.text;
|
|
711
|
+
}
|
|
712
|
+
else if (callee.type === 'member_expression' || callee.type === 'subscript_expression') {
|
|
713
|
+
// skipFinalRead: the final access IS the callee, carried by the
|
|
714
|
+
// dotted path — recording it as a member read would double-count.
|
|
715
|
+
// Mid-chain reads (`req.body` inside `req.body.toString()`) ARE
|
|
716
|
+
// recorded (plan KTD2).
|
|
717
|
+
const chain = this.walkChain(callee, acc, true);
|
|
718
|
+
calleePath = chain.path;
|
|
719
|
+
if (chain.rootIdx !== undefined)
|
|
720
|
+
acc.setSiteReceiver(siteIdx, chain.rootIdx);
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
// Call-rooted chains, IIFEs, function expressions — no dotted path;
|
|
724
|
+
// the walk still records uses and nested sites.
|
|
725
|
+
this.walkValue(callee, acc);
|
|
726
|
+
}
|
|
727
|
+
if (calleePath !== undefined)
|
|
728
|
+
acc.setSiteCallee(siteIdx, calleePath);
|
|
729
|
+
}
|
|
730
|
+
const resultDefs = this.resultDefTargets.get(node.id);
|
|
731
|
+
if (resultDefs !== undefined)
|
|
732
|
+
acc.setSiteResultDefs(siteIdx, resultDefs);
|
|
733
|
+
if (argsNode?.type === 'template_string') {
|
|
734
|
+
// Tagged template (`sql\`…${id}\``): the `arguments` field is a
|
|
735
|
+
// template_string, not an arguments node — substitution occurrences
|
|
736
|
+
// aggregate at position 0 and the site is marked non-positional.
|
|
737
|
+
acc.setSiteTemplate(siteIdx);
|
|
738
|
+
acc.setFrameArg(0);
|
|
739
|
+
this.walkValue(argsNode, acc);
|
|
740
|
+
}
|
|
741
|
+
else if (argsNode) {
|
|
742
|
+
let pos = 0;
|
|
743
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
744
|
+
const arg = argsNode.namedChild(i);
|
|
745
|
+
if (!arg || arg.type === 'comment')
|
|
746
|
+
continue;
|
|
747
|
+
acc.setFrameArg(pos);
|
|
748
|
+
if (arg.type === 'spread_element') {
|
|
749
|
+
acc.setSiteSpread(siteIdx, pos);
|
|
750
|
+
const inner = arg.namedChild(0);
|
|
751
|
+
if (inner)
|
|
752
|
+
this.walkValue(inner, acc);
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
if (kind === 'call' && pos === 0 && calleePath === 'require' && arg.type === 'string') {
|
|
756
|
+
// CommonJS `require('lit')` — record the literal so the matcher
|
|
757
|
+
// resolves require'd aliases like ESM imports (plan KTD7).
|
|
758
|
+
acc.setSiteRequireArg(siteIdx, stringLiteralText(arg));
|
|
759
|
+
}
|
|
760
|
+
this.walkValue(arg, acc);
|
|
761
|
+
}
|
|
762
|
+
pos++;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
acc.popFrame();
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Member/subscript chain walk shared by value position, write position, and
|
|
769
|
+
* callee position. Use-recording is identical to the old default descent
|
|
770
|
+
* (chain-root identifier once, dynamic subscript index expressions, full
|
|
771
|
+
* walk of non-identifier roots) — NO double-recording. Member-read sites:
|
|
772
|
+
* at most ONE per chain — the INNERMOST access — and only when the chain
|
|
773
|
+
* root is an identifier and the access's key is static (`.prop` or a
|
|
774
|
+
* string-literal subscript); `skipFinalRead` suppresses it when that access
|
|
775
|
+
* is the final one (callee / write target). Optional chaining (`?.`) never
|
|
776
|
+
* appears in the output (field-based traversal normalizes it); dynamic
|
|
777
|
+
* computed keys record nothing (documented KTD10 FN).
|
|
778
|
+
*/
|
|
779
|
+
walkChain(node, acc, skipFinalRead) {
|
|
780
|
+
// Collect accesses outer→inner (unshift), then resolve the root.
|
|
781
|
+
const accesses = [];
|
|
782
|
+
let cur = this.unwrapValueWrappers(node);
|
|
783
|
+
for (;;) {
|
|
784
|
+
if (cur.type === 'member_expression') {
|
|
785
|
+
const prop = cur.childForFieldName('property');
|
|
786
|
+
accesses.unshift({ prop: prop?.text });
|
|
787
|
+
const obj = cur.childForFieldName('object');
|
|
788
|
+
if (!obj)
|
|
789
|
+
break;
|
|
790
|
+
cur = this.unwrapValueWrappers(obj);
|
|
791
|
+
}
|
|
792
|
+
else if (cur.type === 'subscript_expression') {
|
|
793
|
+
const index = cur.childForFieldName('index');
|
|
794
|
+
if (index?.type === 'string') {
|
|
795
|
+
accesses.unshift({ prop: stringLiteralText(index) });
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
accesses.unshift({ dynamicIndex: index ?? undefined });
|
|
799
|
+
}
|
|
800
|
+
const obj = cur.childForFieldName('object');
|
|
801
|
+
if (!obj)
|
|
802
|
+
break;
|
|
803
|
+
cur = this.unwrapValueWrappers(obj);
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
let rootIdx;
|
|
810
|
+
let rootSegment;
|
|
811
|
+
if (cur.type === 'identifier') {
|
|
812
|
+
rootIdx = this.resolve(cur);
|
|
813
|
+
acc.addUse(rootIdx);
|
|
814
|
+
rootSegment = cur.text;
|
|
815
|
+
}
|
|
816
|
+
else if (cur.type === 'this' || cur.type === 'super') {
|
|
817
|
+
rootSegment = cur.text; // path segment only — `this`/`super` never bind
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
this.walkValue(cur, acc); // call-rooted etc. — uses + nested sites
|
|
821
|
+
}
|
|
822
|
+
// Dynamic subscript index expressions are real value reads (old default
|
|
823
|
+
// descent walked them) — inner→outer matches the old recording order.
|
|
824
|
+
for (const a of accesses) {
|
|
825
|
+
if (a.dynamicIndex)
|
|
826
|
+
this.walkValue(a.dynamicIndex, acc);
|
|
827
|
+
}
|
|
828
|
+
const innermost = accesses[0];
|
|
829
|
+
if (rootIdx !== undefined &&
|
|
830
|
+
innermost?.prop !== undefined &&
|
|
831
|
+
!(skipFinalRead && accesses.length === 1)) {
|
|
832
|
+
acc.addMemberRead(rootIdx, innermost.prop);
|
|
833
|
+
}
|
|
834
|
+
const path = rootSegment !== undefined && accesses.every((a) => a.prop !== undefined)
|
|
835
|
+
? [rootSegment, ...accesses.map((a) => a.prop)].join('.')
|
|
836
|
+
: undefined;
|
|
837
|
+
return { path, rootIdx };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
/** Ordered, deduplicating def/use collector for one statement record. */
|
|
841
|
+
class FactAccumulator {
|
|
842
|
+
line;
|
|
843
|
+
defs = [];
|
|
844
|
+
uses = [];
|
|
845
|
+
mayDefs = [];
|
|
846
|
+
defSeen = new Set();
|
|
847
|
+
useSeen = new Set();
|
|
848
|
+
mayDefSeen = new Set();
|
|
849
|
+
/** Taint sites recorded for this statement (#2083 M3 U1). */
|
|
850
|
+
sites = [];
|
|
851
|
+
/** Composite (object|property|parent) keys of recorded member-read sites, so
|
|
852
|
+
* dedup is O(1) instead of a rescan of `sites` per read. */
|
|
853
|
+
memberReadKeys = new Set();
|
|
854
|
+
/** Stack of open call/new sites — the occurrence fan-out targets. */
|
|
855
|
+
frames = [];
|
|
856
|
+
constructor(line) {
|
|
857
|
+
this.line = line;
|
|
858
|
+
}
|
|
859
|
+
addDef(idx) {
|
|
860
|
+
if (this.defSeen.has(idx))
|
|
861
|
+
return;
|
|
862
|
+
this.defSeen.add(idx);
|
|
863
|
+
this.defs.push(idx);
|
|
864
|
+
}
|
|
865
|
+
/** A def that may not execute (conditional context) — gen without kill. */
|
|
866
|
+
addMayDef(idx) {
|
|
867
|
+
if (this.mayDefSeen.has(idx))
|
|
868
|
+
return;
|
|
869
|
+
this.mayDefSeen.add(idx);
|
|
870
|
+
this.mayDefs.push(idx);
|
|
871
|
+
}
|
|
872
|
+
addUse(idx) {
|
|
873
|
+
// Occurrence fan-out happens BEFORE the statement-level dedup: `exec(x, x)`
|
|
874
|
+
// records x at BOTH arg positions even though `uses` lists it once.
|
|
875
|
+
this.recordOccurrence(idx);
|
|
876
|
+
this.addUseWithoutOccurrence(idx);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Statement-level use that is NOT a value occurrence in any open site
|
|
880
|
+
* argument — bare callee names only (#2083 M3 U1, see visitCall).
|
|
881
|
+
*/
|
|
882
|
+
addUseWithoutOccurrence(idx) {
|
|
883
|
+
if (this.useSeen.has(idx))
|
|
884
|
+
return;
|
|
885
|
+
this.useSeen.add(idx);
|
|
886
|
+
this.uses.push(idx);
|
|
887
|
+
}
|
|
888
|
+
defCount() {
|
|
889
|
+
return this.defs.length + this.mayDefs.length;
|
|
890
|
+
}
|
|
891
|
+
useCount() {
|
|
892
|
+
return this.uses.length;
|
|
893
|
+
}
|
|
894
|
+
// ── site machinery (#2083 M3 U1) ─────────────────────────────────────────
|
|
895
|
+
/** `[defs.length, mayDefs.length]` marker for {@link defsSince}. */
|
|
896
|
+
defSnapshot() {
|
|
897
|
+
return [this.defs.length, this.mayDefs.length];
|
|
898
|
+
}
|
|
899
|
+
/** Binding indices def'd (must- OR may-) since the snapshot was taken. */
|
|
900
|
+
defsSince(snap) {
|
|
901
|
+
return [...this.defs.slice(snap[0]), ...this.mayDefs.slice(snap[1])];
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Open a call/new site; parent = innermost enclosing argument position. `at`
|
|
905
|
+
* is the call/new node's anchor position `[line (1-based), col (0-based)]` —
|
|
906
|
+
* the SAME position the CALLS-edge resolution keys on (KTD7; see
|
|
907
|
+
* {@link SiteRecord.at}).
|
|
908
|
+
*/
|
|
909
|
+
openCallSite(kind, at) {
|
|
910
|
+
const site = { kind };
|
|
911
|
+
const parent = this.innermostArgPosition();
|
|
912
|
+
if (parent)
|
|
913
|
+
site.parent = parent;
|
|
914
|
+
if (at)
|
|
915
|
+
site.at = [at[0], at[1]];
|
|
916
|
+
this.sites.push(site);
|
|
917
|
+
return this.sites.length - 1;
|
|
918
|
+
}
|
|
919
|
+
pushFrame(siteIdx) {
|
|
920
|
+
this.frames.push({ siteIdx, argIdx: -1 });
|
|
921
|
+
}
|
|
922
|
+
popFrame() {
|
|
923
|
+
this.frames.pop();
|
|
924
|
+
}
|
|
925
|
+
/** Set the argument position the top frame is currently walking. */
|
|
926
|
+
setFrameArg(argIdx) {
|
|
927
|
+
const top = this.frames[this.frames.length - 1];
|
|
928
|
+
if (top)
|
|
929
|
+
top.argIdx = argIdx;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Run `fn` with all open arg frames temporarily detached (argIdx = -1), so
|
|
933
|
+
* identifier reads inside still record USES but do NOT fan occurrences into
|
|
934
|
+
* the enclosing sink-argument position. Used for the non-value operands of a
|
|
935
|
+
* sequence (comma) expression — only the final operand's value flows.
|
|
936
|
+
*/
|
|
937
|
+
suppressOccurrences(fn) {
|
|
938
|
+
const saved = this.frames.map((f) => f.argIdx);
|
|
939
|
+
for (const f of this.frames)
|
|
940
|
+
f.argIdx = -1;
|
|
941
|
+
try {
|
|
942
|
+
fn();
|
|
943
|
+
}
|
|
944
|
+
finally {
|
|
945
|
+
this.frames.forEach((f, i) => {
|
|
946
|
+
f.argIdx = saved[i];
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
setSiteCallee(siteIdx, callee) {
|
|
951
|
+
this.sites[siteIdx].callee = callee;
|
|
952
|
+
}
|
|
953
|
+
setSiteReceiver(siteIdx, receiver) {
|
|
954
|
+
this.sites[siteIdx].receiver = receiver;
|
|
955
|
+
}
|
|
956
|
+
setSiteResultDefs(siteIdx, resultDefs) {
|
|
957
|
+
this.sites[siteIdx].resultDefs = [...resultDefs];
|
|
958
|
+
}
|
|
959
|
+
setSiteSpread(siteIdx, firstSpreadArg) {
|
|
960
|
+
const site = this.sites[siteIdx];
|
|
961
|
+
if (site.spread === undefined)
|
|
962
|
+
site.spread = firstSpreadArg;
|
|
963
|
+
}
|
|
964
|
+
setSiteTemplate(siteIdx) {
|
|
965
|
+
this.sites[siteIdx].template = true;
|
|
966
|
+
}
|
|
967
|
+
setSiteRequireArg(siteIdx, literal) {
|
|
968
|
+
this.sites[siteIdx].requireArg = literal;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Record a value-position member read. Exact duplicates within the
|
|
972
|
+
* statement (same object/property/parent position) dedup; reads at
|
|
973
|
+
* DIFFERENT argument positions stay distinct (`exec(req.body, req.body)`
|
|
974
|
+
* is two occurrences — KTD6 finding identity needs both).
|
|
975
|
+
*/
|
|
976
|
+
addMemberRead(object, property) {
|
|
977
|
+
const parent = this.innermostArgPosition();
|
|
978
|
+
const dedupKey = `${object}|${property}|${parent ? `${parent[0]}:${parent[1]}` : 'top'}`;
|
|
979
|
+
if (this.memberReadKeys.has(dedupKey))
|
|
980
|
+
return;
|
|
981
|
+
this.memberReadKeys.add(dedupKey);
|
|
982
|
+
const site = { kind: 'member-read' };
|
|
983
|
+
if (parent)
|
|
984
|
+
site.parent = parent;
|
|
985
|
+
site.object = object;
|
|
986
|
+
site.property = property;
|
|
987
|
+
this.sites.push(site);
|
|
988
|
+
}
|
|
989
|
+
innermostArgPosition() {
|
|
990
|
+
for (let i = this.frames.length - 1; i >= 0; i--) {
|
|
991
|
+
const f = this.frames[i];
|
|
992
|
+
if (f.argIdx >= 0)
|
|
993
|
+
return [f.siteIdx, f.argIdx];
|
|
994
|
+
}
|
|
995
|
+
return undefined;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Fan a binding occurrence out to every arg-active open frame. The entry is
|
|
999
|
+
* via-tagged with the site of the IMMEDIATELY nested frame when one exists:
|
|
1000
|
+
* `exec(escape(x))` puts a plain `x` in escape's arg 0 and `[x, escapeIdx]`
|
|
1001
|
+
* in exec's arg 0 — the KTD4a interposition substrate.
|
|
1002
|
+
*/
|
|
1003
|
+
recordOccurrence(idx) {
|
|
1004
|
+
for (let i = this.frames.length - 1; i >= 0; i--) {
|
|
1005
|
+
const f = this.frames[i];
|
|
1006
|
+
if (f.argIdx < 0)
|
|
1007
|
+
continue;
|
|
1008
|
+
const via = i + 1 < this.frames.length ? this.frames[i + 1].siteIdx : undefined;
|
|
1009
|
+
this.pushArgEntry(f.siteIdx, f.argIdx, idx, via);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
pushArgEntry(siteIdx, argIdx, bindingIdx, via) {
|
|
1013
|
+
const site = this.sites[siteIdx];
|
|
1014
|
+
const args = (site.args ??= []);
|
|
1015
|
+
while (args.length <= argIdx)
|
|
1016
|
+
args.push([]);
|
|
1017
|
+
const list = args[argIdx];
|
|
1018
|
+
// Dedup exact (binding, via) pairs per position — `f(x + x)` is one entry;
|
|
1019
|
+
// `f(x + g(x))` keeps the plain AND the via-tagged entry (distinct paths).
|
|
1020
|
+
for (const e of list) {
|
|
1021
|
+
const match = typeof e === 'number'
|
|
1022
|
+
? via === undefined && e === bindingIdx
|
|
1023
|
+
: via !== undefined && e[0] === bindingIdx && e[1] === via;
|
|
1024
|
+
if (match)
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
list.push(via === undefined ? bindingIdx : [bindingIdx, via]);
|
|
1028
|
+
}
|
|
1029
|
+
finish() {
|
|
1030
|
+
return {
|
|
1031
|
+
line: this.line,
|
|
1032
|
+
defs: this.defs,
|
|
1033
|
+
uses: this.uses,
|
|
1034
|
+
// Optional field stays absent when empty — keeps the serialized
|
|
1035
|
+
// side-channel payload lean (most statements have no may-defs).
|
|
1036
|
+
...(this.mayDefs.length > 0 ? { mayDefs: this.mayDefs } : {}),
|
|
1037
|
+
// Sites likewise omit-when-empty (#2083 M3 U1): flag-off runs never
|
|
1038
|
+
// harvest, and most fact-bearing statements carry no calls.
|
|
1039
|
+
...(this.sites.length > 0 ? { sites: this.sites.map(finalizeSite) } : {}),
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/** Trim trailing empty arg positions; drop `args` entirely when all-empty. */
|
|
1044
|
+
const finalizeSite = (site) => {
|
|
1045
|
+
const args = site.args;
|
|
1046
|
+
if (args !== undefined) {
|
|
1047
|
+
let end = args.length;
|
|
1048
|
+
while (end > 0 && args[end - 1].length === 0)
|
|
1049
|
+
end--;
|
|
1050
|
+
if (end === 0)
|
|
1051
|
+
delete site.args;
|
|
1052
|
+
else if (end < args.length)
|
|
1053
|
+
site.args = args.slice(0, end);
|
|
1054
|
+
}
|
|
1055
|
+
return site;
|
|
1056
|
+
};
|