homegraph 1.1.3 → 1.4.1

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.
Files changed (412) hide show
  1. package/README.md +43 -18
  2. package/dist/arkts/ohos-api-index.d.ts +15 -0
  3. package/dist/arkts/ohos-api-index.d.ts.map +1 -0
  4. package/dist/arkts/ohos-api-index.js +190 -0
  5. package/dist/arkts/ohos-api-index.js.map +1 -0
  6. package/dist/arkts/ohos-sdk-input.d.ts +36 -0
  7. package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
  8. package/dist/arkts/ohos-sdk-input.js +214 -0
  9. package/dist/arkts/ohos-sdk-input.js.map +1 -0
  10. package/dist/bin/command-supervision.d.ts +12 -0
  11. package/dist/bin/command-supervision.d.ts.map +1 -0
  12. package/dist/bin/command-supervision.js +86 -0
  13. package/dist/bin/command-supervision.js.map +1 -0
  14. package/dist/bin/homegraph.d.ts +1 -1
  15. package/dist/bin/homegraph.js +709 -168
  16. package/dist/bin/homegraph.js.map +1 -1
  17. package/dist/context/index.d.ts.map +1 -1
  18. package/dist/context/index.js +85 -0
  19. package/dist/context/index.js.map +1 -1
  20. package/dist/db/index.d.ts +84 -5
  21. package/dist/db/index.d.ts.map +1 -1
  22. package/dist/db/index.js +217 -14
  23. package/dist/db/index.js.map +1 -1
  24. package/dist/db/migrations.d.ts +1 -1
  25. package/dist/db/migrations.d.ts.map +1 -1
  26. package/dist/db/migrations.js +58 -1
  27. package/dist/db/migrations.js.map +1 -1
  28. package/dist/db/queries.d.ts +131 -2
  29. package/dist/db/queries.d.ts.map +1 -1
  30. package/dist/db/queries.js +473 -9
  31. package/dist/db/queries.js.map +1 -1
  32. package/dist/db/schema.sql +52 -1
  33. package/dist/db/wal-valve.d.ts +106 -0
  34. package/dist/db/wal-valve.d.ts.map +1 -0
  35. package/dist/db/wal-valve.js +208 -0
  36. package/dist/db/wal-valve.js.map +1 -0
  37. package/dist/directory.d.ts +36 -0
  38. package/dist/directory.d.ts.map +1 -1
  39. package/dist/directory.js +274 -0
  40. package/dist/directory.js.map +1 -1
  41. package/dist/extraction/arkts-batch-worker.d.ts +2 -0
  42. package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
  43. package/dist/extraction/arkts-batch-worker.js +28 -0
  44. package/dist/extraction/arkts-batch-worker.js.map +1 -0
  45. package/dist/extraction/cfml-extractor.d.ts +107 -0
  46. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  47. package/dist/extraction/cfml-extractor.js +494 -0
  48. package/dist/extraction/cfml-extractor.js.map +1 -0
  49. package/dist/extraction/context.d.ts +11 -0
  50. package/dist/extraction/context.d.ts.map +1 -1
  51. package/dist/extraction/context.js +20 -0
  52. package/dist/extraction/context.js.map +1 -1
  53. package/dist/extraction/grammars.d.ts +25 -1
  54. package/dist/extraction/grammars.d.ts.map +1 -1
  55. package/dist/extraction/grammars.js +177 -24
  56. package/dist/extraction/grammars.js.map +1 -1
  57. package/dist/extraction/index.d.ts +55 -2
  58. package/dist/extraction/index.d.ts.map +1 -1
  59. package/dist/extraction/index.js +923 -381
  60. package/dist/extraction/index.js.map +1 -1
  61. package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
  62. package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
  63. package/dist/extraction/languages/arkts-state-decorators.js +26 -0
  64. package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
  65. package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
  66. package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
  67. package/dist/extraction/languages/arkts-viewtree.js +21 -6
  68. package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
  69. package/dist/extraction/languages/arkts.d.ts +98 -2
  70. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  71. package/dist/extraction/languages/arkts.js +1290 -61
  72. package/dist/extraction/languages/arkts.js.map +1 -1
  73. package/dist/extraction/languages/c-cpp.d.ts +98 -0
  74. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  75. package/dist/extraction/languages/c-cpp.js +563 -1
  76. package/dist/extraction/languages/c-cpp.js.map +1 -1
  77. package/dist/extraction/languages/cfquery.d.ts +12 -0
  78. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  79. package/dist/extraction/languages/cfquery.js +28 -0
  80. package/dist/extraction/languages/cfquery.js.map +1 -0
  81. package/dist/extraction/languages/cfscript.d.ts +3 -0
  82. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  83. package/dist/extraction/languages/cfscript.js +73 -0
  84. package/dist/extraction/languages/cfscript.js.map +1 -0
  85. package/dist/extraction/languages/cobol.d.ts +33 -0
  86. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  87. package/dist/extraction/languages/cobol.js +499 -0
  88. package/dist/extraction/languages/cobol.js.map +1 -0
  89. package/dist/extraction/languages/erlang.d.ts +3 -0
  90. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  91. package/dist/extraction/languages/erlang.js +350 -0
  92. package/dist/extraction/languages/erlang.js.map +1 -0
  93. package/dist/extraction/languages/index.d.ts.map +1 -1
  94. package/dist/extraction/languages/index.js +16 -0
  95. package/dist/extraction/languages/index.js.map +1 -1
  96. package/dist/extraction/languages/nix.d.ts +3 -0
  97. package/dist/extraction/languages/nix.d.ts.map +1 -0
  98. package/dist/extraction/languages/nix.js +294 -0
  99. package/dist/extraction/languages/nix.js.map +1 -0
  100. package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
  101. package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
  102. package/dist/extraction/languages/ohos-api-consumer.js +283 -0
  103. package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
  104. package/dist/extraction/languages/solidity.d.ts +3 -0
  105. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  106. package/dist/extraction/languages/solidity.js +293 -0
  107. package/dist/extraction/languages/solidity.js.map +1 -0
  108. package/dist/extraction/languages/terraform.d.ts +3 -0
  109. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  110. package/dist/extraction/languages/terraform.js +641 -0
  111. package/dist/extraction/languages/terraform.js.map +1 -0
  112. package/dist/extraction/languages/vbnet.d.ts +11 -0
  113. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  114. package/dist/extraction/languages/vbnet.js +141 -0
  115. package/dist/extraction/languages/vbnet.js.map +1 -0
  116. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  117. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  118. package/dist/extraction/mybatis-extractor.js +140 -38
  119. package/dist/extraction/mybatis-extractor.js.map +1 -1
  120. package/dist/extraction/parse-pool.d.ts +152 -0
  121. package/dist/extraction/parse-pool.d.ts.map +1 -0
  122. package/dist/extraction/parse-pool.js +387 -0
  123. package/dist/extraction/parse-pool.js.map +1 -0
  124. package/dist/extraction/parse-worker.js +9 -2
  125. package/dist/extraction/parse-worker.js.map +1 -1
  126. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  127. package/dist/extraction/tree-sitter-helpers.js +1 -0
  128. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  129. package/dist/extraction/tree-sitter-types.d.ts +20 -1
  130. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  131. package/dist/extraction/tree-sitter.d.ts +59 -0
  132. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  133. package/dist/extraction/tree-sitter.js +952 -37
  134. package/dist/extraction/tree-sitter.js.map +1 -1
  135. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  136. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  137. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  138. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  139. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  140. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  141. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  142. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  143. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  144. package/dist/graph/traversal.d.ts.map +1 -1
  145. package/dist/graph/traversal.js +77 -18
  146. package/dist/graph/traversal.js.map +1 -1
  147. package/dist/index.d.ts +85 -2
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +426 -9
  150. package/dist/index.js.map +1 -1
  151. package/dist/installer/index.d.ts +42 -0
  152. package/dist/installer/index.d.ts.map +1 -1
  153. package/dist/installer/index.js +97 -2
  154. package/dist/installer/index.js.map +1 -1
  155. package/dist/mcp/daemon-paths.d.ts +30 -3
  156. package/dist/mcp/daemon-paths.d.ts.map +1 -1
  157. package/dist/mcp/daemon-paths.js +50 -10
  158. package/dist/mcp/daemon-paths.js.map +1 -1
  159. package/dist/mcp/daemon-registry.d.ts.map +1 -1
  160. package/dist/mcp/daemon-registry.js +7 -3
  161. package/dist/mcp/daemon-registry.js.map +1 -1
  162. package/dist/mcp/daemon.d.ts +73 -3
  163. package/dist/mcp/daemon.d.ts.map +1 -1
  164. package/dist/mcp/daemon.js +258 -40
  165. package/dist/mcp/daemon.js.map +1 -1
  166. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  167. package/dist/mcp/dynamic-boundaries.js +2 -1
  168. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  169. package/dist/mcp/early-ppid.d.ts +26 -0
  170. package/dist/mcp/early-ppid.d.ts.map +1 -0
  171. package/dist/mcp/early-ppid.js +29 -0
  172. package/dist/mcp/early-ppid.js.map +1 -0
  173. package/dist/mcp/engine.d.ts +16 -0
  174. package/dist/mcp/engine.d.ts.map +1 -1
  175. package/dist/mcp/engine.js +100 -2
  176. package/dist/mcp/engine.js.map +1 -1
  177. package/dist/mcp/index.d.ts.map +1 -1
  178. package/dist/mcp/index.js +66 -49
  179. package/dist/mcp/index.js.map +1 -1
  180. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  181. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  182. package/dist/mcp/liveness-watchdog.js +73 -13
  183. package/dist/mcp/liveness-watchdog.js.map +1 -1
  184. package/dist/mcp/memory-budget.d.ts +30 -0
  185. package/dist/mcp/memory-budget.d.ts.map +1 -0
  186. package/dist/mcp/memory-budget.js +123 -0
  187. package/dist/mcp/memory-budget.js.map +1 -0
  188. package/dist/mcp/ppid-watchdog.d.ts +18 -0
  189. package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
  190. package/dist/mcp/ppid-watchdog.js +37 -0
  191. package/dist/mcp/ppid-watchdog.js.map +1 -1
  192. package/dist/mcp/proxy.d.ts.map +1 -1
  193. package/dist/mcp/proxy.js +88 -4
  194. package/dist/mcp/proxy.js.map +1 -1
  195. package/dist/mcp/query-cache.d.ts +56 -0
  196. package/dist/mcp/query-cache.d.ts.map +1 -0
  197. package/dist/mcp/query-cache.js +261 -0
  198. package/dist/mcp/query-cache.js.map +1 -0
  199. package/dist/mcp/query-pool.d.ts +128 -0
  200. package/dist/mcp/query-pool.d.ts.map +1 -0
  201. package/dist/mcp/query-pool.js +374 -0
  202. package/dist/mcp/query-pool.js.map +1 -0
  203. package/dist/mcp/query-worker.d.ts +24 -0
  204. package/dist/mcp/query-worker.d.ts.map +1 -0
  205. package/dist/mcp/query-worker.js +87 -0
  206. package/dist/mcp/query-worker.js.map +1 -0
  207. package/dist/mcp/server-instructions.d.ts +4 -29
  208. package/dist/mcp/server-instructions.d.ts.map +1 -1
  209. package/dist/mcp/server-instructions.js +39 -86
  210. package/dist/mcp/server-instructions.js.map +1 -1
  211. package/dist/mcp/session.d.ts +14 -0
  212. package/dist/mcp/session.d.ts.map +1 -1
  213. package/dist/mcp/session.js +28 -1
  214. package/dist/mcp/session.js.map +1 -1
  215. package/dist/mcp/startup-handshake.d.ts +44 -0
  216. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  217. package/dist/mcp/startup-handshake.js +73 -0
  218. package/dist/mcp/startup-handshake.js.map +1 -0
  219. package/dist/mcp/tools.d.ts +179 -2
  220. package/dist/mcp/tools.d.ts.map +1 -1
  221. package/dist/mcp/tools.js +3027 -192
  222. package/dist/mcp/tools.js.map +1 -1
  223. package/dist/mcp/transport.d.ts.map +1 -1
  224. package/dist/mcp/transport.js +18 -0
  225. package/dist/mcp/transport.js.map +1 -1
  226. package/dist/project-config.d.ts +58 -0
  227. package/dist/project-config.d.ts.map +1 -1
  228. package/dist/project-config.js +141 -2
  229. package/dist/project-config.js.map +1 -1
  230. package/dist/reasoning/login.js +1 -1
  231. package/dist/reasoning/login.js.map +1 -1
  232. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -29
  233. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  234. package/dist/resolution/c-fnptr-synthesizer.js +866 -148
  235. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  236. package/dist/resolution/callback-synthesizer.d.ts +9 -1
  237. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  238. package/dist/resolution/callback-synthesizer.js +1232 -183
  239. package/dist/resolution/callback-synthesizer.js.map +1 -1
  240. package/dist/resolution/cooperative-yield.d.ts +32 -0
  241. package/dist/resolution/cooperative-yield.d.ts.map +1 -0
  242. package/dist/resolution/cooperative-yield.js +42 -0
  243. package/dist/resolution/cooperative-yield.js.map +1 -0
  244. package/dist/resolution/frameworks/cics.d.ts +20 -0
  245. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  246. package/dist/resolution/frameworks/cics.js +90 -0
  247. package/dist/resolution/frameworks/cics.js.map +1 -0
  248. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  249. package/dist/resolution/frameworks/index.js +3 -0
  250. package/dist/resolution/frameworks/index.js.map +1 -1
  251. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  252. package/dist/resolution/frameworks/java.js +14 -6
  253. package/dist/resolution/frameworks/java.js.map +1 -1
  254. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  255. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  256. package/dist/resolution/frameworks/terraform.js +277 -0
  257. package/dist/resolution/frameworks/terraform.js.map +1 -0
  258. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  259. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  260. package/dist/resolution/goframe-synthesizer.js +8 -3
  261. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  262. package/dist/resolution/import-resolver.d.ts +7 -0
  263. package/dist/resolution/import-resolver.d.ts.map +1 -1
  264. package/dist/resolution/import-resolver.js +129 -4
  265. package/dist/resolution/import-resolver.js.map +1 -1
  266. package/dist/resolution/index.d.ts +65 -3
  267. package/dist/resolution/index.d.ts.map +1 -1
  268. package/dist/resolution/index.js +477 -59
  269. package/dist/resolution/index.js.map +1 -1
  270. package/dist/resolution/name-matcher.d.ts +22 -3
  271. package/dist/resolution/name-matcher.d.ts.map +1 -1
  272. package/dist/resolution/name-matcher.js +657 -39
  273. package/dist/resolution/name-matcher.js.map +1 -1
  274. package/dist/resolution/strip-comments.d.ts +1 -1
  275. package/dist/resolution/strip-comments.d.ts.map +1 -1
  276. package/dist/resolution/strip-comments.js +49 -0
  277. package/dist/resolution/strip-comments.js.map +1 -1
  278. package/dist/resolution/types.d.ts +29 -0
  279. package/dist/resolution/types.d.ts.map +1 -1
  280. package/dist/resolution/workspace-packages.d.ts +10 -0
  281. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  282. package/dist/resolution/workspace-packages.js +142 -4
  283. package/dist/resolution/workspace-packages.js.map +1 -1
  284. package/dist/search/identifier-segments.d.ts +60 -0
  285. package/dist/search/identifier-segments.d.ts.map +1 -0
  286. package/dist/search/identifier-segments.js +176 -0
  287. package/dist/search/identifier-segments.js.map +1 -0
  288. package/dist/search/query-utils.d.ts +201 -0
  289. package/dist/search/query-utils.d.ts.map +1 -1
  290. package/dist/search/query-utils.js +919 -2
  291. package/dist/search/query-utils.js.map +1 -1
  292. package/dist/spec/config.d.ts +7 -11
  293. package/dist/spec/config.d.ts.map +1 -1
  294. package/dist/spec/config.js +77 -48
  295. package/dist/spec/config.js.map +1 -1
  296. package/dist/spec/db/fragment-node.d.ts +1 -0
  297. package/dist/spec/db/fragment-node.d.ts.map +1 -1
  298. package/dist/spec/db/fragment-node.js +8 -0
  299. package/dist/spec/db/fragment-node.js.map +1 -1
  300. package/dist/spec/db/fts.d.ts +14 -0
  301. package/dist/spec/db/fts.d.ts.map +1 -1
  302. package/dist/spec/db/fts.js +43 -4
  303. package/dist/spec/db/fts.js.map +1 -1
  304. package/dist/spec/db/schema.d.ts +6 -3
  305. package/dist/spec/db/schema.d.ts.map +1 -1
  306. package/dist/spec/db/schema.js +36 -4
  307. package/dist/spec/db/schema.js.map +1 -1
  308. package/dist/spec/db/schema.sql +11 -1
  309. package/dist/spec/evolve/logic-checker.d.ts +2 -2
  310. package/dist/spec/evolve/logic-checker.d.ts.map +1 -1
  311. package/dist/spec/evolve/logic-checker.js +3 -27
  312. package/dist/spec/evolve/logic-checker.js.map +1 -1
  313. package/dist/spec/evolve/pipeline.d.ts +21 -19
  314. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  315. package/dist/spec/evolve/pipeline.js +284 -31
  316. package/dist/spec/evolve/pipeline.js.map +1 -1
  317. package/dist/spec/evolve/spec-rewriter.d.ts +2 -2
  318. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
  319. package/dist/spec/evolve/spec-rewriter.js +7 -31
  320. package/dist/spec/evolve/spec-rewriter.js.map +1 -1
  321. package/dist/spec/graph/queries.d.ts +97 -0
  322. package/dist/spec/graph/queries.d.ts.map +1 -1
  323. package/dist/spec/graph/queries.js +225 -0
  324. package/dist/spec/graph/queries.js.map +1 -1
  325. package/dist/spec/llm/client.d.ts +29 -0
  326. package/dist/spec/llm/client.d.ts.map +1 -0
  327. package/dist/spec/llm/client.js +123 -0
  328. package/dist/spec/llm/client.js.map +1 -0
  329. package/dist/spec/llm/index.d.ts +3 -0
  330. package/dist/spec/llm/index.d.ts.map +1 -0
  331. package/dist/spec/llm/index.js +11 -0
  332. package/dist/spec/llm/index.js.map +1 -0
  333. package/dist/spec/llm/prompts.d.ts +13 -0
  334. package/dist/spec/llm/prompts.d.ts.map +1 -0
  335. package/dist/spec/llm/prompts.js +75 -0
  336. package/dist/spec/llm/prompts.js.map +1 -0
  337. package/dist/spec/mining/git-scanner.d.ts +12 -0
  338. package/dist/spec/mining/git-scanner.d.ts.map +1 -1
  339. package/dist/spec/mining/git-scanner.js +41 -0
  340. package/dist/spec/mining/git-scanner.js.map +1 -1
  341. package/dist/spec/mining/pipeline.d.ts.map +1 -1
  342. package/dist/spec/mining/pipeline.js +14 -1
  343. package/dist/spec/mining/pipeline.js.map +1 -1
  344. package/dist/spec/utils.d.ts +2 -1
  345. package/dist/spec/utils.d.ts.map +1 -1
  346. package/dist/spec/utils.js +3 -1
  347. package/dist/spec/utils.js.map +1 -1
  348. package/dist/sync/git-hooks.d.ts.map +1 -1
  349. package/dist/sync/git-hooks.js +2 -0
  350. package/dist/sync/git-hooks.js.map +1 -1
  351. package/dist/sync/watcher.d.ts +10 -5
  352. package/dist/sync/watcher.d.ts.map +1 -1
  353. package/dist/sync/watcher.js +51 -14
  354. package/dist/sync/watcher.js.map +1 -1
  355. package/dist/sync/worktree.d.ts +9 -0
  356. package/dist/sync/worktree.d.ts.map +1 -1
  357. package/dist/sync/worktree.js +45 -0
  358. package/dist/sync/worktree.js.map +1 -1
  359. package/dist/types.d.ts +25 -2
  360. package/dist/types.d.ts.map +1 -1
  361. package/dist/types.js +10 -0
  362. package/dist/types.js.map +1 -1
  363. package/dist/ui/shimmer-progress.d.ts +2 -0
  364. package/dist/ui/shimmer-progress.d.ts.map +1 -1
  365. package/dist/ui/shimmer-progress.js +19 -2
  366. package/dist/ui/shimmer-progress.js.map +1 -1
  367. package/dist/upgrade/index.d.ts +32 -0
  368. package/dist/upgrade/index.d.ts.map +1 -1
  369. package/dist/upgrade/index.js +163 -15
  370. package/dist/upgrade/index.js.map +1 -1
  371. package/dist/upgrade/remove-binary.d.ts +87 -0
  372. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  373. package/dist/upgrade/remove-binary.js +289 -0
  374. package/dist/upgrade/remove-binary.js.map +1 -0
  375. package/dist/upgrade/update-check.d.ts +92 -0
  376. package/dist/upgrade/update-check.d.ts.map +1 -0
  377. package/dist/upgrade/update-check.js +258 -0
  378. package/dist/upgrade/update-check.js.map +1 -0
  379. package/package.json +4 -4
  380. package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
  381. package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
  382. package/scripts/agent-eval/run-all.sh +6 -0
  383. package/scripts/build-bundle.sh +5 -0
  384. package/scripts/exp_boundary_eval/README.md +247 -0
  385. package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
  386. package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
  387. package/scripts/exp_boundary_eval/_utils.py +1116 -0
  388. package/scripts/exp_boundary_eval/analyze.py +1313 -0
  389. package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
  390. package/scripts/exp_boundary_eval/run_all.py +378 -0
  391. package/scripts/exp_boundary_eval/run_one.py +165 -0
  392. package/scripts/exp_boundary_eval/run_session.py +158 -0
  393. package/scripts/exp_boundary_eval/setup.py +120 -0
  394. package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
  395. package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
  396. package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
  397. package/scripts/npm-shim.js +32 -3
  398. package/scripts/ohos-sdk-publish.mjs +133 -0
  399. package/scripts/qa_eval/README.md +0 -407
  400. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  401. package/scripts/qa_eval/agent_runner.py +0 -526
  402. package/scripts/qa_eval/data/.gitignore +0 -4
  403. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  404. package/scripts/qa_eval/eval_metrics.py +0 -274
  405. package/scripts/qa_eval/external_agent.py +0 -976
  406. package/scripts/qa_eval/llm_config.py +0 -92
  407. package/scripts/qa_eval/memory_monitor.py +0 -132
  408. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  409. package/scripts/qa_eval/requirements.txt +0 -2
  410. package/scripts/qa_eval/run_pipeline.py +0 -804
  411. package/scripts/qa_eval/stats_efficiency.py +0 -279
  412. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -1,6 +1,91 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.cFnPointerDispatchEdges = cFnPointerDispatchEdges;
37
+ /**
38
+ * C/C++ function-pointer dispatch synthesis (#932).
39
+ *
40
+ * C/C++ polymorphism is the function pointer: a struct carries a fn-pointer
41
+ * field (`int (*fn)(int)`, or a fn-pointer-typedef field `hook_func func`),
42
+ * concrete functions are *registered* into it through a table
43
+ * (`static struct cmd cmds[] = {{"add", cmd_add}, …}`, a designated
44
+ * `.fn = cmd_add`, or `x->fn = cmd_add`), and the dispatcher calls through it
45
+ * indirectly (`p->fn(argv)`). Static extraction captures neither the
46
+ * registration→field binding nor the indirect call, so the dispatcher→handler
47
+ * edge is missing and `git`'s `run_builtin` looks like it calls nothing, the
48
+ * hooks in `hook_demo.c` are unreachable, etc.
49
+ *
50
+ * This bridges it, keyed by **(struct type, fn-pointer field)**:
51
+ * • registrations — a function bound to `S.field` via a positional
52
+ * initializer (matched by field index), a designated `.field = fn`, or a
53
+ * direct `x.field = fn` / `x->field = fn` assignment;
54
+ * • dispatch — `recv->field(…)` / `recv.field(…)` where `recv` resolves to a
55
+ * value of struct type `S` (from the enclosing function's params / locals,
56
+ * or by walking a chained/array receiver `c->cmd->proc` across field types),
57
+ * falling back to the field name when it is unique to one struct;
58
+ * • field←field propagation — `a->f = b->g` merges `B.g`'s handlers into
59
+ * `A.f`, so a generic single-slot hook that is reassigned from a registry
60
+ * (the `hook_demo.c` shape: `h->func = found->fn`) still resolves.
61
+ *
62
+ * Also handles **macro-built tables** (#991) — the dominant real-world shape,
63
+ * e.g. redis' command table, sqlite's builtin functions, and vim's `:ex` /
64
+ * normal-mode commands. The fn-pointer arg lives inside a macro call
65
+ * (`MAKE_CMD(…,proc,…)` / `FUNCTION(…,xFunc)` / `EXCMD(…,fn,…)`) in a generated
66
+ * or `#include`-d file; the table's struct type may itself be an object-macro
67
+ * alias; the field may use a function-TYPE typedef; the struct may be defined
68
+ * INLINE with the array; and the whole thing may sit behind `#ifdef` switched on
69
+ * by the includer. The registration pass reads each `#include`-d file as a unit
70
+ * with the includer's effective macro env (own + headers) in scope, evaluates
71
+ * its `#ifdef`s against the includer's defined set, expands object/function
72
+ * macros, peels a brace-wrapped element, and parses an inline struct in place —
73
+ * then reads the positional/designated bindings. Dispatch additionally resolves
74
+ * an array subscript through a file-scope table (`(cmdnames[i].cmd_func)(…)`).
75
+ *
76
+ * Also bridges **bare arrays of function pointers** (no struct, no field) —
77
+ * `opcode_t *opcodes[256] = {nop,…}` dispatched `opcodes[op](…)` (SameBoy's CPU),
78
+ * `zend_rc_dtor_func_t t[] = {[IS_STRING]=(cast)fn,…}` dispatched `t[GC_TYPE(p)](…)`
79
+ * (php's Zend) — keyed by the array VARIABLE name. The element type must be a
80
+ * function typedef (the precision gate), entries are literal function names, and
81
+ * the same-file table wins on a name collision (two file-local `opcodes[256]`).
82
+ *
83
+ * Whole-graph pass after base resolution; all edges are `provenance:'heuristic'`
84
+ * (`synthesizedBy:'fn-pointer-dispatch'`). High precision via the (type, field)
85
+ * key + a real-function gate; a project with no fn-pointer dispatch is a no-op.
86
+ */
87
+ const path = __importStar(require("node:path"));
88
+ const lru_cache_1 = require("./lru-cache");
4
89
  const strip_comments_1 = require("./strip-comments");
5
90
  const C_CPP_EXT = /\.(c|h|cc|cpp|cxx|hpp|hh|hxx|cppm|ipp|inl|tcc)$/i;
6
91
  const FN_KINDS = new Set(['function', 'method']);
@@ -44,27 +129,280 @@ function splitTopLevel(body, sep) {
44
129
  out.push(body.slice(start));
45
130
  return out;
46
131
  }
47
- /** A fn-pointer field looks like `… (*name)(…)` — capture `name`. */
48
- const FNPTR_DECL_RE = /\(\s*\*\s*(\w+)\s*\)\s*\(/;
49
- /** `typedef RET (*NAME)(…)` — a function-pointer typedef. */
50
- const FNPTR_TYPEDEF_RE = /\btypedef\b[^;{}]*?\(\s*\*\s*(\w+)\s*\)\s*\(/g;
51
- function cFnPointerDispatchEdges(queries, ctx) {
132
+ /** Index of the `)` matching the `(` at `open` (which must point at a `(`). -1 if unbalanced. */
133
+ function matchParen(src, open) {
134
+ let depth = 0;
135
+ for (let i = open; i < src.length; i++) {
136
+ const c = src[i];
137
+ if (c === '(')
138
+ depth++;
139
+ else if (c === ')') {
140
+ depth--;
141
+ if (depth === 0)
142
+ return i;
143
+ }
144
+ }
145
+ return -1;
146
+ }
147
+ /**
148
+ * Collect function-like macros from (comment-stripped) source, joining
149
+ * `\`-continuations first. Only object/positional table macros matter here, so
150
+ * variadic macros are skipped. Used to expand registration tables built through
151
+ * a macro (redis' `MAKE_CMD(…)`) before reading the struct-field bindings.
152
+ */
153
+ function parseFunctionMacros(stripped) {
154
+ const out = new Map();
155
+ if (!stripped.includes('#define') && !stripped.includes('# define'))
156
+ return out;
157
+ const joined = stripped.replace(/\\\r?\n/g, ' ');
158
+ const RE = /^[ \t]*#[ \t]*define[ \t]+(\w+)\(([^)]*)\)\s+(.+)$/gm;
159
+ let m;
160
+ while ((m = RE.exec(joined))) {
161
+ const params = m[2].split(',').map((p) => p.trim()).filter(Boolean);
162
+ if (params.some((p) => p === '...' || p.endsWith('...')))
163
+ continue; // variadic — skip
164
+ out.set(m[1], { params, expansion: m[3].trim() });
165
+ }
166
+ return out;
167
+ }
168
+ /**
169
+ * Collect object-like macros `#define NAME value` (NAME not immediately followed
170
+ * by `(`). redis aliases the table's struct type this way:
171
+ * `#define COMMAND_STRUCT redisCommand`, used as `struct COMMAND_STRUCT table[]`.
172
+ */
173
+ function parseObjectMacros(stripped) {
174
+ const out = new Map();
175
+ if (!stripped.includes('#define') && !stripped.includes('# define'))
176
+ return out;
177
+ const joined = stripped.replace(/\\\r?\n/g, ' ');
178
+ const RE = /^[ \t]*#[ \t]*define[ \t]+(\w+)[ \t]+(\S[^\n]*)$/gm;
179
+ let m;
180
+ while ((m = RE.exec(joined)))
181
+ out.set(m[1], m[2].trim());
182
+ return out;
183
+ }
184
+ /** All macro names a file `#define`s (value-ful or not) — the "defined" set for #ifdef. */
185
+ function parseDefinedNames(stripped) {
186
+ const out = new Set();
187
+ if (!stripped.includes('#define') && !stripped.includes('# define'))
188
+ return out;
189
+ const RE = /^[ \t]*#[ \t]*define[ \t]+(\w+)/gm;
190
+ let m;
191
+ while ((m = RE.exec(stripped)))
192
+ out.add(m[1]);
193
+ return out;
194
+ }
195
+ /**
196
+ * Drop the inactive arms of `#ifdef`/`#ifndef`/`#if defined(X)`/`#else`/`#elif`/
197
+ * `#endif` given a set of defined macro names, keeping line offsets (inactive
198
+ * lines are blanked, not removed). A conditional whose expression we can't
199
+ * evaluate (`#if SOME_EXPR`) keeps its body — better to over-keep than to drop
200
+ * live code. This is what makes a header included with a switch macro defined
201
+ * (vim's `ex_cmds.h` under `DO_DECLARE_EXCMD`) expose only its active table.
202
+ */
203
+ function evalConditionals(text, defined) {
204
+ if (!/#\s*if/.test(text))
205
+ return text;
206
+ const lines = text.split('\n');
207
+ // stack frame: parentActive = enclosing kept?; active = this arm kept?; taken = any arm taken yet
208
+ const stack = [];
209
+ const activeNow = () => (stack.length === 0 ? true : stack[stack.length - 1].active);
210
+ const condDefined = (expr) => {
211
+ let mm = expr.match(/^defined\s*\(?\s*(\w+)\s*\)?$/);
212
+ if (mm)
213
+ return defined.has(mm[1]);
214
+ mm = expr.match(/^!\s*defined\s*\(?\s*(\w+)\s*\)?$/);
215
+ if (mm)
216
+ return !defined.has(mm[1]);
217
+ return null; // unevaluable
218
+ };
219
+ for (let i = 0; i < lines.length; i++) {
220
+ const t = lines[i].trim();
221
+ let mm;
222
+ if ((mm = t.match(/^#\s*ifdef\s+(\w+)/))) {
223
+ const pa = activeNow();
224
+ const cond = defined.has(mm[1]);
225
+ stack.push({ parentActive: pa, active: pa && cond, taken: cond });
226
+ lines[i] = '';
227
+ continue;
228
+ }
229
+ if ((mm = t.match(/^#\s*ifndef\s+(\w+)/))) {
230
+ const pa = activeNow();
231
+ const cond = !defined.has(mm[1]);
232
+ stack.push({ parentActive: pa, active: pa && cond, taken: cond });
233
+ lines[i] = '';
234
+ continue;
235
+ }
236
+ if ((mm = t.match(/^#\s*if\s+(.+)$/))) {
237
+ const pa = activeNow();
238
+ const c = condDefined(mm[1].trim());
239
+ const cond = c === null ? true : c; // unevaluable → keep
240
+ stack.push({ parentActive: pa, active: pa && cond, taken: cond });
241
+ lines[i] = '';
242
+ continue;
243
+ }
244
+ if (/^#\s*elif\b/.test(t)) {
245
+ const top = stack[stack.length - 1];
246
+ if (top) {
247
+ top.active = top.parentActive && !top.taken;
248
+ top.taken = true;
249
+ }
250
+ lines[i] = '';
251
+ continue;
252
+ }
253
+ if (/^#\s*else\b/.test(t)) {
254
+ const top = stack[stack.length - 1];
255
+ if (top) {
256
+ top.active = top.parentActive && !top.taken;
257
+ top.taken = true;
258
+ }
259
+ lines[i] = '';
260
+ continue;
261
+ }
262
+ if (/^#\s*endif\b/.test(t)) {
263
+ stack.pop();
264
+ lines[i] = '';
265
+ continue;
266
+ }
267
+ if (!activeNow())
268
+ lines[i] = ''; // blank an inactive line (keep the newline)
269
+ }
270
+ return lines.join('\n');
271
+ }
272
+ /** Resolve a type token through object-like macro aliases (transitive, capped). */
273
+ function resolveTypeName(name, objEnv) {
274
+ let n = name;
275
+ for (let i = 0; objEnv && i < 5; i++) {
276
+ const v = objEnv.get(n);
277
+ const t = v?.trim().match(/^(?:struct\s+)?(\w+)$/);
278
+ if (!t)
279
+ break;
280
+ n = t[1];
281
+ }
282
+ return n;
283
+ }
284
+ /** Substitute call args for the macro's params (whole-token) in its expansion. */
285
+ function substituteMacro(def, args) {
286
+ const map = new Map();
287
+ def.params.forEach((p, i) => map.set(p, args[i] ?? ''));
288
+ return def.expansion.replace(/\b\w+\b/g, (tok) => (map.has(tok) ? map.get(tok) : tok));
289
+ }
290
+ /**
291
+ * Expand known function-like macro calls in `text` to a fixpoint (depth-capped).
292
+ * `MAKE_CMD("get",…,getCommand,…)` → the positional value list whose slots line
293
+ * up with the struct's fields, so the existing positional registration can read
294
+ * `getCommand` straight out of the `proc` slot.
295
+ */
296
+ function expandMacroCalls(text, env) {
297
+ if (env.size === 0)
298
+ return text;
299
+ let out = text;
300
+ for (let pass = 0; pass < 6; pass++) {
301
+ let changed = false;
302
+ const RE = /\b(\w+)\s*\(/g;
303
+ let m;
304
+ while ((m = RE.exec(out))) {
305
+ const def = env.get(m[1]);
306
+ if (!def)
307
+ continue;
308
+ const open = m.index + m[0].length - 1; // index of the `(`
309
+ const close = matchParen(out, open);
310
+ if (close < 0)
311
+ continue;
312
+ const args = splitTopLevel(out.slice(open + 1, close), ',').map((a) => a.trim());
313
+ out = out.slice(0, m.index) + substituteMacro(def, args) + out.slice(close + 1);
314
+ changed = true;
315
+ break; // restart scan — offsets shifted
316
+ }
317
+ if (!changed)
318
+ break;
319
+ }
320
+ return out;
321
+ }
322
+ /** A fn-pointer field looks like `… (*name)(…)` — capture `name`. A
323
+ * calling-convention / attribute macro may precede the `*`
324
+ * (`(ZEND_FASTCALL *name)`), so allow leading word tokens. */
325
+ const FNPTR_DECL_RE = /\(\s*(?:\w+\s+)*\*\s*(\w+)\s*\)\s*\(/;
326
+ /** `typedef RET (*NAME)(…)` — a function-pointer typedef (CC/attr macro before
327
+ * the `*` allowed, as in php's `typedef void (ZEND_FASTCALL *fn_t)(…)`). */
328
+ const FNPTR_TYPEDEF_RE = /\btypedef\b[^;{}]*?\(\s*(?:\w+\s+)*\*\s*(\w+)\s*\)\s*\(/g;
329
+ /** A whole brace-free `typedef … ;` statement — capture the guts to spot the
330
+ * function-TYPE form `typedef RET NAME(params)` (no `(*name)` pointer form). */
331
+ const FNTYPE_TYPEDEF_STMT_RE = /\btypedef\b([^;{}]*);/g;
332
+ /** Return-type keywords that must never be mistaken for the typedef's name. */
333
+ const C_TYPE_KEYWORDS = new Set([
334
+ 'void', 'int', 'char', 'short', 'long', 'unsigned', 'signed', 'float', 'double',
335
+ 'const', 'struct', 'union', 'enum', 'static', 'volatile', 'register', 'inline',
336
+ ]);
337
+ /** `#include "local/header"` — captured from RAW source (string contents survive). */
338
+ const INCLUDE_RE = /#[ \t]*include[ \t]+"([^"\n]+)"/g;
339
+ /** Included files worth scanning for registration tables (e.g. a generated `.def`). */
340
+ const INCLUDABLE_EXT = /\.(def|inc|h|hh|hpp|hxx|c|cc|cpp|cxx|ipp|tcc|tbl)$/i;
341
+ async function cFnPointerDispatchEdges(_queries, ctx, onYield, onFraction) {
342
+ let scannedFiles = 0;
52
343
  const files = ctx.getAllFiles().filter((f) => C_CPP_EXT.test(f));
53
344
  if (files.length === 0)
54
345
  return [];
55
- // Cache stripped source per file (read once, reused across passes).
56
- const srcCache = new Map();
346
+ // Within-pass progress: this is the pass that parks the "Linking dynamic
347
+ // dispatch" bar on C-heavy repos, so it reports a real fraction of its
348
+ // dominant work. `files` is swept once per file loop below (passes A, C, D,
349
+ // E — pass B is node-bound and comparatively brief), reported at the same
350
+ // per-16-files cadence as the cooperative yield.
351
+ const FILE_SWEEPS = 4;
352
+ const tick = async () => {
353
+ if ((++scannedFiles & 15) === 0) {
354
+ onFraction?.(scannedFiles / (files.length * FILE_SWEEPS));
355
+ await onYield();
356
+ }
357
+ };
358
+ // Cache raw + stripped source per file, LRU-BOUNDED. The old unbounded Maps
359
+ // retained every C/C++ file's raw AND stripped text for the whole pass —
360
+ // multiple GB on the Linux kernel, one of the two OOM culprits in #1212.
361
+ // Every sweep below iterates in `files` order, and node-kind scans return
362
+ // rows in file-commit order, so access is near-sequential and a small LRU
363
+ // hits; a miss just re-reads + re-strips.
364
+ const rawCache = new lru_cache_1.LRUCache(128);
365
+ const raw = (file) => {
366
+ if (rawCache.has(file))
367
+ return rawCache.get(file);
368
+ const r = ctx.readFile(file);
369
+ rawCache.set(file, r);
370
+ return r;
371
+ };
372
+ const srcCache = new lru_cache_1.LRUCache(128);
57
373
  const src = (file) => {
58
- if (srcCache.has(file))
59
- return srcCache.get(file);
60
- const raw = ctx.readFile(file);
61
- const s = raw == null ? '' : (0, strip_comments_1.stripCommentsForRegex)(raw, 'c');
374
+ // A cached '' (empty or unreadable file) returns '' where the miss path
375
+ // returns null for unreadable — every caller falsy-checks, so the two are
376
+ // interchangeable.
377
+ const hit = srcCache.get(file);
378
+ if (hit !== undefined)
379
+ return hit;
380
+ const r = raw(file);
381
+ const s = r == null ? '' : (0, strip_comments_1.stripCommentsForRegex)(r, 'c');
62
382
  srcCache.set(file, s);
63
- return raw == null ? null : s;
383
+ return r == null ? null : s;
64
384
  };
65
- // ---- Pass A: function-pointer typedefs (cross-file) ----
385
+ // Resolve a quoted include relative to the includer's directory, then the
386
+ // project root. Returns a project-root-relative path that exists on disk
387
+ // (even if it was never indexed — e.g. redis' generated `commands.def`).
388
+ const resolveInclude = (includer, inc) => {
389
+ const dir = path.posix.dirname(includer.replace(/\\/g, '/'));
390
+ const cand = path.posix.normalize(path.posix.join(dir, inc));
391
+ if (ctx.fileExists(cand))
392
+ return cand;
393
+ if (ctx.fileExists(inc))
394
+ return inc;
395
+ return null;
396
+ };
397
+ // ---- Pass A: function-pointer AND function-type typedefs (cross-file) ----
398
+ // fn-pointer: typedef RET (*NAME)(…) → a field `NAME f` is a fn ptr
399
+ // fn-type: typedef RET NAME(params) → a field `NAME *f` is a fn ptr
400
+ // The fn-type form is redis' command idiom: `typedef void redisCommandProc(client*)`
401
+ // declared as `redisCommandProc *proc;`. Without this, `proc` reads as data.
66
402
  const fnPtrTypedefs = new Set();
403
+ const fnTypeTypedefs = new Set();
67
404
  for (const file of files) {
405
+ await tick();
68
406
  const s = src(file);
69
407
  if (!s || !s.includes('typedef'))
70
408
  continue;
@@ -72,67 +410,114 @@ function cFnPointerDispatchEdges(queries, ctx) {
72
410
  let m;
73
411
  while ((m = FNPTR_TYPEDEF_RE.exec(s)))
74
412
  fnPtrTypedefs.add(m[1]);
413
+ FNTYPE_TYPEDEF_STMT_RE.lastIndex = 0;
414
+ while ((m = FNTYPE_TYPEDEF_STMT_RE.exec(s))) {
415
+ const guts = m[1];
416
+ if (guts.includes('(*') || guts.includes('( *'))
417
+ continue; // pointer form — handled above
418
+ const fm = guts.match(/\b(\w+)\s*\(/); // last identifier before the param list
419
+ if (fm && !C_TYPE_KEYWORDS.has(fm[1]))
420
+ fnTypeTypedefs.add(fm[1]);
421
+ }
75
422
  }
76
423
  // ---- Pass B: struct field layouts ----
77
- // structLayout: struct name → ordered fields (with fn-pointer flag).
424
+ // structLayout: struct name → ordered fields, for structs with ≥1 fn-pointer
425
+ // field (drives positional registration + dispatch).
426
+ // allStructFields: EVERY struct name → ALL its field layouts (a name can be
427
+ // reused across files — e.g. redis has two unrelated `client` structs), used
428
+ // to walk a chained receiver's field types (`c->cmd->proc`: client.cmd →
429
+ // redisCommand). The walk searches every same-named layout for the field.
78
430
  // fieldToStructs: fn-pointer field name → set of struct names that declare it.
79
431
  const structLayout = new Map();
432
+ const allStructFields = new Map();
80
433
  const fieldToStructs = new Map();
81
- for (const st of ctx.getNodesByKind('struct')) {
82
- if (!C_CPP_EXT.test(st.filePath))
83
- continue;
84
- const s = srcCache.get(st.filePath) ?? src(st.filePath);
85
- if (!s)
86
- continue;
87
- const body = sliceLines(s, st.startLine, st.endLine);
88
- const open = body.indexOf('{');
89
- const close = open >= 0 ? matchBrace(body, open) : -1;
90
- if (open < 0 || close < 0)
91
- continue;
92
- const inner = body.slice(open + 1, close);
434
+ // Parse a struct body (the text between its `{` and `}`) into ordered fields.
435
+ const parseStructFields = (inner) => {
93
436
  const fields = [];
94
437
  let idx = 0;
95
438
  for (const rawDecl of splitTopLevel(inner, ';')) {
96
439
  const decl = rawDecl.trim();
97
440
  if (!decl)
98
441
  continue;
99
- let name = null;
100
- let isFnPtr = false;
101
- const ptr = decl.match(FNPTR_DECL_RE);
102
- if (ptr) {
103
- name = ptr[1];
104
- isFnPtr = true;
105
- }
106
- else {
107
- // `TYPE [*]name` — fn-pointer when TYPE is a fn-pointer typedef.
108
- const fm = decl.match(/(\w+)\s+\*?\s*(\w+)\s*$/);
109
- if (fm) {
110
- name = fm[2];
111
- isFnPtr = fnPtrTypedefs.has(fm[1]);
442
+ // A field decl can declare several names sharing a leading type:
443
+ // `struct redisCommand *cmd, *lastcmd;`. Each declarator is its own
444
+ // positional slot and carries that type (so `client.cmd → redisCommand`).
445
+ const parts = splitTopLevel(decl, ',');
446
+ const firstTyped = parts[0].match(/(\w+)\s+\**\s*(\w+)\s*$/);
447
+ const sharedType = firstTyped ? firstTyped[1] : '';
448
+ for (let pi = 0; pi < parts.length; pi++) {
449
+ const p = parts[pi].trim();
450
+ let name = null;
451
+ let type = '';
452
+ let isFnPtr = false;
453
+ const ptr = p.match(FNPTR_DECL_RE);
454
+ if (ptr) {
455
+ name = ptr[1]; // `… (*name)(…)` — a function pointer
456
+ isFnPtr = true;
457
+ }
458
+ else if (pi === 0) {
459
+ if (firstTyped) {
460
+ name = firstTyped[2];
461
+ type = sharedType;
462
+ }
463
+ }
464
+ else {
465
+ // a subsequent declarator: `*name` / `**name` / `name`
466
+ const dm = p.match(/^\**\s*(\w+)/);
467
+ if (dm) {
468
+ name = dm[1];
469
+ type = sharedType;
470
+ }
112
471
  }
472
+ if (!ptr && type)
473
+ isFnPtr = fnPtrTypedefs.has(type) || fnTypeTypedefs.has(type);
474
+ // Always advance the positional index. An unparsed field (anonymous
475
+ // union, exotic declarator) still occupies one slot, and macro-expanded
476
+ // positional tables (redis' MAKE_CMD) only align if every field counts.
477
+ fields.push({ name: name ?? '', index: idx, isFnPtr: !!name && isFnPtr, type });
478
+ idx++;
113
479
  }
114
- if (!name)
115
- continue;
116
- fields.push({ name, index: idx, isFnPtr });
117
- if (isFnPtr) {
118
- if (!fieldToStructs.has(name))
119
- fieldToStructs.set(name, new Set());
120
- fieldToStructs.get(name).add(st.name);
480
+ }
481
+ return fields;
482
+ };
483
+ // Register a parsed struct under `name` into the three indexes.
484
+ const registerStructLayout = (name, fields) => {
485
+ if (!allStructFields.has(name))
486
+ allStructFields.set(name, []);
487
+ allStructFields.get(name).push(fields);
488
+ for (const f of fields) {
489
+ if (f.name && f.isFnPtr) {
490
+ if (!fieldToStructs.has(f.name))
491
+ fieldToStructs.set(f.name, new Set());
492
+ fieldToStructs.get(f.name).add(name);
121
493
  }
122
- idx++;
123
494
  }
124
495
  if (fields.some((f) => f.isFnPtr))
125
- structLayout.set(st.name, fields);
496
+ structLayout.set(name, fields);
497
+ };
498
+ for (const st of (ctx.iterateNodesByKind?.('struct') ?? ctx.getNodesByKind('struct'))) {
499
+ if ((++scannedFiles & 255) === 0)
500
+ await onYield();
501
+ if (!C_CPP_EXT.test(st.filePath))
502
+ continue;
503
+ const s = src(st.filePath);
504
+ if (!s)
505
+ continue;
506
+ const body = sliceLines(s, st.startLine, st.endLine);
507
+ const open = body.indexOf('{');
508
+ const close = open >= 0 ? matchBrace(body, open) : -1;
509
+ if (open < 0 || close < 0)
510
+ continue;
511
+ registerStructLayout(st.name, parseStructFields(body.slice(open + 1, close)));
126
512
  }
127
- if (structLayout.size === 0)
128
- return [];
513
+ // NB: no early return on an empty structLayout here — an inline `struct TAG
514
+ // { … } var[]` table whose struct never became a node (vim's `cmdname`, broken
515
+ // up by `#ifdef`) is discovered later during the unit scan. The `reg.size === 0`
516
+ // guard after registration still short-circuits when nothing bridges.
129
517
  const fnPtrFieldOf = (struct, field) => !!structLayout.get(struct)?.some((f) => f.name === field && f.isFnPtr);
130
- // C/C++ function + method nodes, materialized once (bounded by C/C++ files).
131
- const cFns = [];
132
- for (const fn of iterateFns(queries)) {
133
- if (C_CPP_EXT.test(fn.filePath))
134
- cFns.push(fn);
135
- }
518
+ // C/C++ function + method nodes are STREAMED per sweep (see passes D/E) —
519
+ // the old materialized `cFns` array held every function node on the repo
520
+ // (O(nodes) memory, part of the #1212 kernel OOM).
136
521
  // ---- function-name → node resolution (prefer a function in the same file) ----
137
522
  const resolveFn = (name, preferFile) => {
138
523
  const cands = ctx.getNodesByName(name).filter((n) => FN_KINDS.has(n.kind));
@@ -148,21 +533,50 @@ function cFnPointerDispatchEdges(queries, ctx) {
148
533
  return cands[0];
149
534
  };
150
535
  // ---- Pass C: registrations — Map<"struct.field", Set<funcNodeId>> ----
536
+ // Ids only — retaining the full Node per registration (the old `idToNode`)
537
+ // was write-only dead weight at O(registrations) memory.
151
538
  const reg = new Map();
152
- const idToNode = new Map();
153
539
  const addReg = (struct, field, fn) => {
154
540
  const key = `${struct}.${field}`;
155
541
  if (!reg.has(key))
156
542
  reg.set(key, new Set());
157
543
  reg.get(key).add(fn.id);
158
- idToNode.set(fn.id, fn);
544
+ };
545
+ // Bare arrays-of-fn-pointers (no struct): array VARIABLE name → per-file sets
546
+ // of registered function ids. Multi-entry because a file-scope `static` table
547
+ // name can recur across files (SameBoy declares `static opcode_t *opcodes[256]`
548
+ // in BOTH sm83_cpu.c and sm83_disassembler.c), so dispatch resolves same-file.
549
+ const arrayReg = new Map();
550
+ const addArrayReg = (name, file, fn) => {
551
+ let entries = arrayReg.get(name);
552
+ if (!entries) {
553
+ entries = [];
554
+ arrayReg.set(name, entries);
555
+ }
556
+ let e = entries.find((x) => x.file === file);
557
+ if (!e) {
558
+ e = { file, ids: new Set() };
559
+ entries.push(e);
560
+ }
561
+ e.ids.add(fn.id);
159
562
  };
160
563
  // A struct value `{ … }` (one element) — register its function entries to the
161
564
  // struct's fields, by `.field = fn` designators or by positional slot.
162
- const registerStructValue = (struct, valueBody, file) => {
565
+ const registerStructValue = (struct, valueBody, file, env) => {
163
566
  const layout = structLayout.get(struct);
164
567
  if (!layout)
165
568
  return;
569
+ if (env && env.size)
570
+ valueBody = expandMacroCalls(valueBody, env);
571
+ // A macro can expand to a whole brace-wrapped element (sqlite's
572
+ // `FUNCTION(…)` → `{nArg, …, xFunc, …}`); peel one outer layer so the
573
+ // positional slots are visible.
574
+ valueBody = valueBody.trim();
575
+ if (valueBody.startsWith('{')) {
576
+ const e = matchBrace(valueBody, 0);
577
+ if (e > 0 && valueBody.slice(e + 1).trim() === '')
578
+ valueBody = valueBody.slice(1, e);
579
+ }
166
580
  const items = splitTopLevel(valueBody, ',');
167
581
  let pos = 0;
168
582
  for (const rawItem of items) {
@@ -192,18 +606,187 @@ function cFnPointerDispatchEdges(queries, ctx) {
192
606
  pos++;
193
607
  }
194
608
  };
609
+ // Collect the literal function entries of an array-of-fn-pointers initializer
610
+ // and register them under the array's variable name. Entries may be positional
611
+ // (`fn`, `&fn`), designated by index (`[OP] = fn`), or cast-wrapped
612
+ // (`(handler_t)fn`, as in php's Zend dtor table). Non-identifier entries
613
+ // (`NULL`, `0`, a nested expression) are skipped — a miss, never a wrong edge.
614
+ // No index tracking: a runtime subscript fans the dispatch out to the whole
615
+ // set, exactly like a command table reaches every command.
616
+ const registerArrayValue = (name, body, file, env) => {
617
+ if (env && env.size)
618
+ body = expandMacroCalls(body, env);
619
+ for (const rawItem of splitTopLevel(body, ',')) {
620
+ let item = rawItem.trim();
621
+ if (!item)
622
+ continue;
623
+ const des = item.match(/^\[[^\]]*\]\s*=\s*([\s\S]*)$/); // `[IDX] = …` designator
624
+ if (des)
625
+ item = des[1].trim();
626
+ item = item.replace(/^\((?:[\w\s*]+)\)\s*/, '').replace(/^&\s*/, '').trim(); // (cast) / &
627
+ const id = item.match(/^(\w+)$/);
628
+ if (!id)
629
+ continue;
630
+ const fn = resolveFn(id[1], file);
631
+ if (fn)
632
+ addArrayReg(name, file, fn);
633
+ }
634
+ };
635
+ // Per-file macro + include parsing (any file, indexed or not), cached.
636
+ // Derived per-file caches, LRU-bounded like the content caches (#1212).
637
+ const fnMacroCache = new lru_cache_1.LRUCache(256);
638
+ const fileFnMacros = (file) => {
639
+ let m = fnMacroCache.get(file);
640
+ if (!m) {
641
+ m = parseFunctionMacros(src(file) ?? '');
642
+ fnMacroCache.set(file, m);
643
+ }
644
+ return m;
645
+ };
646
+ const objMacroCache = new lru_cache_1.LRUCache(256);
647
+ const fileObjMacros = (file) => {
648
+ let m = objMacroCache.get(file);
649
+ if (!m) {
650
+ m = parseObjectMacros(src(file) ?? '');
651
+ objMacroCache.set(file, m);
652
+ }
653
+ return m;
654
+ };
655
+ const definedCache = new lru_cache_1.LRUCache(256);
656
+ const fileDefinedNames = (file) => {
657
+ let d = definedCache.get(file);
658
+ if (!d) {
659
+ d = parseDefinedNames(src(file) ?? '');
660
+ definedCache.set(file, d);
661
+ }
662
+ return d;
663
+ };
664
+ const includeCache = new lru_cache_1.LRUCache(1024);
665
+ const localIncludesOf = (file) => {
666
+ let out = includeCache.get(file);
667
+ if (out)
668
+ return out;
669
+ out = [];
670
+ const rawText = raw(file);
671
+ if (rawText && rawText.includes('include')) {
672
+ INCLUDE_RE.lastIndex = 0;
673
+ let im;
674
+ while ((im = INCLUDE_RE.exec(rawText))) {
675
+ if (!INCLUDABLE_EXT.test(im[1]))
676
+ continue;
677
+ const t = resolveInclude(file, im[1]);
678
+ if (t)
679
+ out.push(t);
680
+ }
681
+ }
682
+ includeCache.set(file, out);
683
+ return out;
684
+ };
685
+ // A file's effective macro environment = its own #defines PLUS those of the
686
+ // headers it #includes (redis' `MAKE_CMD` sits beside the table; sqlite's
687
+ // `FUNCTION` lives in `sqliteInt.h`, included by the file with the table).
688
+ // First writer wins, so the file's own defs override included ones; depth-2
689
+ // covers a macro defined in a header-of-a-header.
690
+ const buildEnv = (file, depth, seen, fn, obj, def) => {
691
+ if (depth < 0 || seen.has(file))
692
+ return;
693
+ seen.add(file);
694
+ for (const [k, v] of fileFnMacros(file))
695
+ if (!fn.has(k))
696
+ fn.set(k, v);
697
+ for (const [k, v] of fileObjMacros(file))
698
+ if (!obj.has(k))
699
+ obj.set(k, v);
700
+ for (const n of fileDefinedNames(file))
701
+ def.add(n);
702
+ for (const inc of localIncludesOf(file))
703
+ buildEnv(inc, depth - 1, seen, fn, obj, def);
704
+ };
705
+ const indexedSet = new Set(files);
706
+ const seenInclude = new Set();
707
+ // Global variable → struct type, for resolving a dispatch through a file-scope
708
+ // table by subscript (`cmdnames[i].cmd_func(…)`).
709
+ const globalVarType = new Map();
710
+ // Process a `{ … }` initializer body (array of elements or a single struct).
711
+ const processInit = (struct, body, isArray, file, env) => {
712
+ if (isArray) {
713
+ for (const el of splitTopLevel(body, ',')) {
714
+ const t = el.trim();
715
+ if (t.startsWith('{')) {
716
+ const e = matchBrace(t, 0);
717
+ if (e > 0)
718
+ registerStructValue(struct, t.slice(1, e), file, env);
719
+ }
720
+ else if (t) {
721
+ // an element built by a macro (`MAKE_CMD(…)`/`FUNCTION(…)`) or a bare value
722
+ registerStructValue(struct, t, file, env);
723
+ }
724
+ }
725
+ }
726
+ else {
727
+ registerStructValue(struct, body, file, env);
728
+ }
729
+ };
195
730
  // `(?:struct )?TYPE name[opt] = {` initializers, where TYPE is a struct that
196
731
  // has ≥1 fn-pointer field. Handles both single (`= {…}`) and array
197
- // (`[] = { {…}, {…} }`) forms.
732
+ // (`[] = { {…}, {…} }`) forms. Macro calls inside an element are expanded first.
198
733
  const INIT_RE = /(?:^|[;{}])\s*(?:(?:static|const|extern|register|volatile)\s+)*(?:struct\s+)?(\w+)\s+(\w+)\s*(\[[^\]]*\])?\s*=\s*\{/g;
199
- for (const file of files) {
200
- const s = srcCache.get(file);
201
- if (!s || !s.includes('='))
202
- continue;
734
+ // `struct TAG { … } var[opt] [= {…}]` — the struct is defined INLINE with the
735
+ // table (vim's `cmdname`/`nv_cmd`); its layout never became a node, so parse it
736
+ // here and register it before reading the entries. No leading anchor: a
737
+ // `struct TAG {` with a brace body is always a definition (it may be preceded
738
+ // by a `#define …` line ending in a digit, as in vim), and the trailing
739
+ // `var … = {` check below is what distinguishes a TABLE from a plain type.
740
+ const INLINE_STRUCT_RE = /\bstruct\s+(\w+)\s*\{/g;
741
+ // `(?:static …)* ELEMTYPE [*] name[…] = { … }` — a bare array of function
742
+ // pointers (no struct wrapper). The optional `*` covers a function-TYPE
743
+ // typedef element (`opcode_t *opcodes[]`); a function-pointer typedef element
744
+ // (`zend_rc_dtor_func_t t[]`) needs none. The typedef-set membership gate
745
+ // (below) is what separates this from a plain data/struct array.
746
+ const ARRAY_TABLE_RE = /(?:^|[;{}])\s*(?:(?:static|const|extern|register|volatile)\s+)*(\w+)\s+(\*\s*)?(\w+)\s*\[[^\]]*\]\s*=\s*\{/g;
747
+ // Process ONE unit's text and discard it. The old shape built every unit up
748
+ // front (`const units: Unit[]`) — the full text of every C file plus its
749
+ // expanded includes held simultaneously, gigabytes on the kernel (#1212).
750
+ const processUnit = (unit) => {
751
+ const s = unit.text;
752
+ if (!s || !s.includes('{'))
753
+ return;
754
+ INLINE_STRUCT_RE.lastIndex = 0;
755
+ let im;
756
+ while ((im = INLINE_STRUCT_RE.exec(s))) {
757
+ const tag = im[1];
758
+ const sOpen = im.index + im[0].length - 1; // the struct body's `{`
759
+ const sClose = matchBrace(s, sOpen);
760
+ if (sClose < 0)
761
+ continue;
762
+ // After `}`, expect `var [opt] [= {…}]` to be a table; else it's a plain type.
763
+ const after = s.slice(sClose + 1);
764
+ const vm = after.match(/^\s*(\w+)\s*(\[[^\]]*\])?\s*(=\s*\{)?/);
765
+ if (!vm || !vm[1])
766
+ continue;
767
+ const fields = parseStructFields(s.slice(sOpen + 1, sClose));
768
+ if (!fields.some((f) => f.isFnPtr))
769
+ continue; // only tables of fn pointers matter
770
+ if (!structLayout.has(tag))
771
+ registerStructLayout(tag, fields);
772
+ globalVarType.set(vm[1], tag);
773
+ if (vm[3]) {
774
+ const aOpen = sClose + 1 + after.indexOf('{', vm[0].length - 1);
775
+ const aClose = matchBrace(s, aOpen);
776
+ if (aClose > 0) {
777
+ processInit(tag, s.slice(aOpen + 1, aClose), !!vm[2], unit.file, unit.env);
778
+ INLINE_STRUCT_RE.lastIndex = aClose;
779
+ }
780
+ }
781
+ }
782
+ if (!s.includes('='))
783
+ return;
203
784
  INIT_RE.lastIndex = 0;
204
785
  let m;
205
786
  while ((m = INIT_RE.exec(s))) {
206
- const struct = m[1];
787
+ let struct = m[1];
788
+ if (!structLayout.has(struct))
789
+ struct = resolveTypeName(struct, unit.objEnv);
207
790
  if (!structLayout.has(struct))
208
791
  continue;
209
792
  const isArray = !!m[3];
@@ -211,30 +794,72 @@ function cFnPointerDispatchEdges(queries, ctx) {
211
794
  const close = matchBrace(s, open);
212
795
  if (close < 0)
213
796
  continue;
214
- const body = s.slice(open + 1, close);
215
- if (isArray) {
216
- // top-level `{ … }` element groups
217
- for (const el of splitTopLevel(body, ',')) {
218
- const t = el.trim();
219
- if (t.startsWith('{')) {
220
- const e = matchBrace(t, 0);
221
- if (e > 0)
222
- registerStructValue(struct, t.slice(1, e), file);
223
- }
224
- else if (t) {
225
- // array of bare values (rare for structs) — treat as one positional slot
226
- registerStructValue(struct, t, file);
227
- }
228
- }
229
- }
230
- else {
231
- registerStructValue(struct, body, file);
232
- }
797
+ globalVarType.set(m[2], struct);
798
+ processInit(struct, s.slice(open + 1, close), isArray, unit.file, unit.env);
233
799
  INIT_RE.lastIndex = close;
234
800
  }
801
+ // Bare arrays-of-function-pointers (no struct, no field). Gated on the
802
+ // element type being a function typedef — a fn-TYPE typedef needs the `*`
803
+ // (array of pointers to it), a fn-pointer typedef does not. A data or
804
+ // struct array's element type is never in these sets, so it never fires.
805
+ ARRAY_TABLE_RE.lastIndex = 0;
806
+ let am;
807
+ while ((am = ARRAY_TABLE_RE.exec(s))) {
808
+ const elemType = am[1];
809
+ const hasStar = !!am[2];
810
+ if (!((fnTypeTypedefs.has(elemType) && hasStar) || fnPtrTypedefs.has(elemType)))
811
+ continue;
812
+ const open = am.index + am[0].length - 1; // the `{`
813
+ const close = matchBrace(s, open);
814
+ if (close < 0)
815
+ continue;
816
+ registerArrayValue(am[3], s.slice(open + 1, close), unit.file, unit.env);
817
+ ARRAY_TABLE_RE.lastIndex = close;
818
+ }
819
+ };
820
+ // ---- Pass C: registrations — stream each file (and its qualifying local
821
+ // includes) through processUnit, one at a time.
822
+ for (const file of files) {
823
+ await tick();
824
+ const env = new Map();
825
+ const objEnv = new Map();
826
+ const defined = new Set();
827
+ buildEnv(file, 2, new Set(), env, objEnv, defined);
828
+ const s = src(file);
829
+ if (s)
830
+ processUnit({ text: s, file, env, objEnv });
831
+ for (const target of localIncludesOf(file)) {
832
+ if (seenInclude.has(`${file}>${target}`))
833
+ continue;
834
+ const incSrc = src(target);
835
+ if (!incSrc)
836
+ continue;
837
+ if (indexedSet.has(target)) {
838
+ // Re-scan an indexed header only when this includer unlocks guarded code.
839
+ const ownDef = fileDefinedNames(target);
840
+ const adds = [...defined].some((n) => !ownDef.has(n));
841
+ if (!adds || !/#\s*if/.test(incSrc))
842
+ continue;
843
+ }
844
+ seenInclude.add(`${file}>${target}`);
845
+ // The include is pasted into the includer — evaluate its conditionals in
846
+ // the includer's defined set (a no-op when it has none). Re-parse the
847
+ // included file's OWN macros from that resolved text so a macro it defines
848
+ // conditionally (vim's `EXCMD`, whose plain last-wins parse picks the enum
849
+ // arm) overrides with the ARM THAT IS ACTUALLY ACTIVE here.
850
+ const text = evalConditionals(incSrc, defined);
851
+ const incEnv = new Map(env);
852
+ for (const [k, v] of parseFunctionMacros(text))
853
+ incEnv.set(k, v);
854
+ const incObjEnv = new Map(objEnv);
855
+ for (const [k, v] of parseObjectMacros(text))
856
+ incObjEnv.set(k, v);
857
+ processUnit({ text, file: target, env: incEnv, objEnv: incObjEnv });
858
+ }
235
859
  }
236
860
  // ---- receiver-type resolution within a function's source ----
237
- // `(?:struct )?TYPE [*]recv` declared in the params or body → TYPE (if a known struct).
861
+ // `(?:struct )?TYPE [*]recv` declared in the params or body → TYPE (if a known
862
+ // fn-pointer-bearing struct).
238
863
  const recvTypeIn = (fnSrc, recv) => {
239
864
  const re = new RegExp(`(?:struct\\s+)?(\\w+)\\s*\\*?\\s*\\b${recv}\\b\\s*(?:[,)=;]|\\[)`, 'g');
240
865
  let m;
@@ -244,26 +869,66 @@ function cFnPointerDispatchEdges(queries, ctx) {
244
869
  }
245
870
  return null;
246
871
  };
872
+ // Declared type of a local/param `v` — ANY type token, not just fn-pointer
873
+ // structs (the base of a chained receiver needn't carry a fn pointer itself).
874
+ // Falls back to a file-scope table variable (`cmdnames` in `cmdnames[i].fn()`).
875
+ const escapeRe = (x) => x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
876
+ const varTypeIn = (fnSrc, v) => {
877
+ const re = new RegExp(`(?:struct\\s+)?(\\w+)\\s*\\*?\\s*\\b${escapeRe(v)}\\b\\s*(?:[,)=;]|\\[)`, 'g');
878
+ let m;
879
+ while ((m = re.exec(fnSrc))) {
880
+ if (!C_TYPE_KEYWORDS.has(m[1]))
881
+ return m[1];
882
+ }
883
+ return globalVarType.get(v) ?? null;
884
+ };
885
+ // Resolve a member-access chain (`c->cmd`, or just `p`) to a struct type,
886
+ // walking each segment's declared field type. `c->cmd->proc` dispatch:
887
+ // base chain `c->cmd` → client.cmd's type `redisCommand`, the proc owner.
888
+ // Array subscripts (`cmdnames[i]`) are stripped — an index yields one element.
889
+ const resolveChainType = (fnSrc, chain) => {
890
+ const segs = chain.replace(/\s*\[[^\]]*\]/g, '').split(/\s*(?:->|\.)\s*/).filter(Boolean);
891
+ if (segs.length === 0)
892
+ return null;
893
+ let t = varTypeIn(fnSrc, segs[0]);
894
+ for (let i = 1; t && i < segs.length; i++) {
895
+ let next = null;
896
+ for (const fields of allStructFields.get(t) ?? []) {
897
+ const f = fields.find((fl) => fl.name === segs[i] && fl.type);
898
+ if (f) {
899
+ next = f.type;
900
+ break;
901
+ }
902
+ }
903
+ t = next;
904
+ }
905
+ return t;
906
+ };
247
907
  // ---- Pass D: field←field propagation (`a->f = b->g`) ----
248
908
  // Collected as (targetStruct.field ← sourceStruct.field) pairs, then merged to
249
909
  // a fixpoint so a hook slot inherits a registry field's handlers.
250
910
  const FIELD_ASSIGN_RE = /(\w+)\s*(?:->|\.)\s*(\w+)\s*=\s*(\w+)\s*(?:->|\.)\s*(\w+)/g;
251
911
  const propagations = [];
252
- for (const fn of cFns) {
253
- const s = srcCache.get(fn.filePath);
254
- if (!s)
255
- continue;
256
- const body = sliceLines(s, fn.startLine, fn.endLine);
257
- if (!body.includes('='))
912
+ for (const file of files) {
913
+ await tick();
914
+ const s = src(file);
915
+ if (!s || !s.includes('='))
258
916
  continue;
259
- FIELD_ASSIGN_RE.lastIndex = 0;
260
- let m;
261
- while ((m = FIELD_ASSIGN_RE.exec(body))) {
262
- const [, lrecv, lfield, rrecv, rfield] = m;
263
- const lt = recvTypeIn(body, lrecv);
264
- const rt = recvTypeIn(body, rrecv);
265
- if (lt && rt && fnPtrFieldOf(lt, lfield) && fnPtrFieldOf(rt, rfield)) {
266
- propagations.push({ to: `${lt}.${lfield}`, from: `${rt}.${rfield}` });
917
+ for (const fn of ctx.getNodesInFile(file)) {
918
+ if (!FN_KINDS.has(fn.kind))
919
+ continue;
920
+ const body = sliceLines(s, fn.startLine, fn.endLine);
921
+ if (!body.includes('='))
922
+ continue;
923
+ FIELD_ASSIGN_RE.lastIndex = 0;
924
+ let m;
925
+ while ((m = FIELD_ASSIGN_RE.exec(body))) {
926
+ const [, lrecv, lfield, rrecv, rfield] = m;
927
+ const lt = recvTypeIn(body, lrecv);
928
+ const rt = recvTypeIn(body, rrecv);
929
+ if (lt && rt && fnPtrFieldOf(lt, lfield) && fnPtrFieldOf(rt, rfield)) {
930
+ propagations.push({ to: `${lt}.${lfield}`, from: `${rt}.${rfield}` });
931
+ }
267
932
  }
268
933
  }
269
934
  }
@@ -286,67 +951,120 @@ function cFnPointerDispatchEdges(queries, ctx) {
286
951
  if (!changed)
287
952
  break;
288
953
  }
289
- if (reg.size === 0)
954
+ if (reg.size === 0 && arrayReg.size === 0)
290
955
  return [];
291
956
  // ---- Pass E: dispatch sites → edges ----
292
- // recv->field( or recv.field( where field is a known fn-pointer field.
293
- const DISPATCH_RE = /(\w+)\s*(?:->|\.)\s*(\w+)\s*\(/g;
957
+ // `base->…->field(` or `base.…field(` where `field` is a known fn-pointer field.
958
+ // The base may be a chain (`c->cmd->proc`) or carry array subscripts
959
+ // (`cmdnames[i].cmd_func`). An optional `)` before the call covers the
960
+ // parenthesized form `(cmdnames[i].cmd_func)(&ea)` vim uses.
961
+ const DISPATCH_RE = /((?:\w+(?:\s*\[[^\][]*\])?\s*(?:->|\.)\s*)+)(\w+)\s*\)?\s*\(/g;
962
+ // Bare-array dispatch: `tbl[i](…)` or the explicit-deref `(*tbl[i])(…)`. The
963
+ // subscript may itself contain a call (`tbl[GC_TYPE(p)](…)`), so the index
964
+ // class excludes only brackets. Precision comes from the `arrayReg` gate below
965
+ // — this fires only when `tbl` is a known fn-pointer array.
966
+ const ARRAY_DISPATCH_RE = /(?:\(\s*\*\s*)?\b(\w+)\s*\[[^\][]*\]\s*\)?\s*\(/g;
294
967
  const edges = [];
295
968
  const seen = new Set();
296
- for (const fn of cFns) {
297
- const s = srcCache.get(fn.filePath);
969
+ for (const file of files) {
970
+ await tick();
971
+ const s = src(file);
298
972
  if (!s)
299
973
  continue;
300
- const body = sliceLines(s, fn.startLine, fn.endLine);
301
- DISPATCH_RE.lastIndex = 0;
302
- let m;
303
- let added = 0;
304
- while ((m = DISPATCH_RE.exec(body)) && added < FANOUT_CAP) {
305
- const recv = m[1];
306
- const field = m[2];
307
- const owners = fieldToStructs.get(field);
308
- if (!owners || owners.size === 0)
309
- continue;
310
- // Resolve the receiver's struct type; else fall back to a field name that
311
- // belongs to exactly one struct.
312
- let struct = recvTypeIn(body, recv);
313
- if (!struct || !owners.has(struct))
314
- struct = owners.size === 1 ? [...owners][0] : null;
315
- if (!struct)
316
- continue;
317
- const targets = reg.get(`${struct}.${field}`);
318
- if (!targets)
974
+ for (const fn of ctx.getNodesInFile(file)) {
975
+ if (!FN_KINDS.has(fn.kind))
319
976
  continue;
320
- const line = fn.startLine + body.slice(0, m.index).split('\n').length - 1;
321
- for (const tid of targets) {
322
- if (tid === fn.id)
977
+ const body = sliceLines(s, fn.startLine, fn.endLine);
978
+ DISPATCH_RE.lastIndex = 0;
979
+ let m;
980
+ let added = 0;
981
+ while ((m = DISPATCH_RE.exec(body)) && added < FANOUT_CAP) {
982
+ const baseChain = m[1].replace(/\s*(?:->|\.)\s*$/, '').trim(); // receiver, minus the trailing arrow
983
+ const field = m[2];
984
+ const owners = fieldToStructs.get(field);
985
+ if (!owners || owners.size === 0)
323
986
  continue;
324
- const key = `${fn.id}>${tid}`;
325
- if (seen.has(key))
987
+ // 1) resolve the receiver chain's struct type precisely (handles c->cmd->proc);
988
+ // 2) else the last segment as a simple local/param of a fn-pointer-bearing struct;
989
+ // 3) else fall back to a field name that belongs to exactly one struct.
990
+ let struct = resolveChainType(body, baseChain);
991
+ if (!struct || !owners.has(struct)) {
992
+ const lastSeg = baseChain.replace(/\s*\[[^\]]*\]/g, '').split(/\s*(?:->|\.)\s*/).pop();
993
+ const t = recvTypeIn(body, lastSeg);
994
+ struct = t && owners.has(t) ? t : null;
995
+ }
996
+ if (!struct || !owners.has(struct))
997
+ struct = owners.size === 1 ? [...owners][0] : null;
998
+ if (!struct)
326
999
  continue;
327
- seen.add(key);
328
- edges.push({
329
- source: fn.id,
330
- target: tid,
331
- kind: 'calls',
332
- line,
333
- provenance: 'heuristic',
334
- metadata: {
335
- synthesizedBy: 'fn-pointer-dispatch',
336
- via: `${struct}.${field}`,
337
- registeredAt: `${fn.filePath}:${line}`,
338
- },
339
- });
340
- if (++added >= FANOUT_CAP)
341
- break;
1000
+ const targets = reg.get(`${struct}.${field}`);
1001
+ if (!targets)
1002
+ continue;
1003
+ const line = fn.startLine + body.slice(0, m.index).split('\n').length - 1;
1004
+ for (const tid of targets) {
1005
+ if (tid === fn.id)
1006
+ continue;
1007
+ const key = `${fn.id}>${tid}`;
1008
+ if (seen.has(key))
1009
+ continue;
1010
+ seen.add(key);
1011
+ edges.push({
1012
+ source: fn.id,
1013
+ target: tid,
1014
+ kind: 'calls',
1015
+ line,
1016
+ provenance: 'heuristic',
1017
+ metadata: {
1018
+ synthesizedBy: 'fn-pointer-dispatch',
1019
+ via: `${struct}.${field}`,
1020
+ registeredAt: `${fn.filePath}:${line}`,
1021
+ },
1022
+ });
1023
+ if (++added >= FANOUT_CAP)
1024
+ break;
1025
+ }
1026
+ }
1027
+ // ---- bare array-of-fn-pointers dispatch (`tbl[i](…)`) ----
1028
+ if (arrayReg.size && added < FANOUT_CAP) {
1029
+ ARRAY_DISPATCH_RE.lastIndex = 0;
1030
+ while ((m = ARRAY_DISPATCH_RE.exec(body)) && added < FANOUT_CAP) {
1031
+ const entries = arrayReg.get(m[1]);
1032
+ if (!entries)
1033
+ continue;
1034
+ // Same-file table wins on a name collision (two file-local `opcodes`);
1035
+ // a unique name resolves cross-file; otherwise ambiguous — bail.
1036
+ const ids = entries.length === 1
1037
+ ? entries[0].ids
1038
+ : (entries.find((e) => e.file === fn.filePath)?.ids ?? null);
1039
+ if (!ids)
1040
+ continue;
1041
+ const line = fn.startLine + body.slice(0, m.index).split('\n').length - 1;
1042
+ for (const tid of ids) {
1043
+ if (tid === fn.id)
1044
+ continue;
1045
+ const key = `${fn.id}>${tid}`;
1046
+ if (seen.has(key))
1047
+ continue;
1048
+ seen.add(key);
1049
+ edges.push({
1050
+ source: fn.id,
1051
+ target: tid,
1052
+ kind: 'calls',
1053
+ line,
1054
+ provenance: 'heuristic',
1055
+ metadata: {
1056
+ synthesizedBy: 'fn-pointer-dispatch',
1057
+ via: `${m[1]}[]`,
1058
+ registeredAt: `${fn.filePath}:${line}`,
1059
+ },
1060
+ });
1061
+ if (++added >= FANOUT_CAP)
1062
+ break;
1063
+ }
1064
+ }
342
1065
  }
343
1066
  }
344
1067
  }
345
1068
  return edges;
346
1069
  }
347
- /** C/C++ function + method nodes, streamed (memory-safe on symbol-dense repos). */
348
- function* iterateFns(queries) {
349
- yield* queries.iterateNodesByKind('function');
350
- yield* queries.iterateNodesByKind('method');
351
- }
352
1070
  //# sourceMappingURL=c-fnptr-synthesizer.js.map