Skip to content

fix(cfnptr): a C function-pointer table links only to C and C++ functions - #2935

Merged
colbymchenry merged 1 commit into
mainfrom
claude/pr-1989-cfnptr-c-only
Oct 27, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/pr-1989-cfnptr-c-only

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

The C function-pointer pass (fn-pointer-dispatch) now links a table's registered handlers only to functions in C and C++ files. Before, a C table could link to a Python or Rust function that shares the handler's name, and that function could also take the place of the real C handler.

Root cause

resolveFn in src/resolution/c-fnptr-synthesizer.ts looks a registered name up with ctx.getNodesByName(name), filtered only to function and method kinds. In a repository that also has Rust or Python, the candidates included those languages' functions. A registered name with no C function node behind it, like gen_probe in the test, landed on the foreign function. With several candidates, the first wins unless one is in the registering file, so a Python impl_read that sorted first took the place of the C impl_read the table names. Both reproduce on main with the PR's test.

What came from the contributor, and what we changed

From @bompus, unchanged: the filter (C_CPP_EXT.test(n.filePath), the pass's own scan set, so a header the language detector labels Objective-C still counts) and the test.

Ours:

  • CHANGELOG. The bullet moved out of the released [1.6.1] section into [Unreleased] and was reworded.
  • Objective-C .m/.mm files stay out, as the PR has it. The pass never reads a table or dispatch in a .m file. Allowing those files as targets would let an Objective-C method named init or read stand in for a C handler of that name, the same cross-language mistake this fixes. On GLFW, which mixes C and Objective-C, main links 354 table entries and none into a .m file. Its one C table that names a .m function, _glfwConnectCocoa in platform.c, is an anonymous-struct table that main doesn't bridge either.

Validation

  • Linux kernel subtree (d135ab45: rust/, drivers/android, drivers/base, drivers/net/dsa, fs, tools/net, include/linux; 6,580 files, under the big-index rule): the graph is byte-identical on both arms, with 12,244 fn-pointer-dispatch edges, all into C or C++ files. On current main this tree has no edge into Rust or Python, so the PR's 193, from a whole-kernel index on its fork, don't show here.

    • A smaller tree holding the PR's three example sites has none either: pmcraid's ->reset_cmd = cmd, afs's ->update_time = new and map.c's p->lock = lock, with rust/ and tools/net/ynl. On main, a plain assignment of a variable doesn't register; only struct-value initializers, arrays and propagation between function-pointer fields do.
    • The whole kernel is beyond the big-index rule here, so the fix's effect is shown by the test: a C table and a same-named Python function, which main links wrongly.
  • Red→green. The new case in c-fnptr-synthesizer.test.ts fails on main, where impl_read links to aaa/gen.py, and passes here; the suite has 19/19.

  • Controls. C and C++ graphs are byte-identical (dump-graph, every table): redis 7f440372 (863 fn-pointer edges), GLFW 92dcf4ce (354), jq 49fbca12, leveldb 7ee830d0.

  • Kernel parity. The change is TS-side: the kernel ports only the pass's per-file sweep, not its linking, so both engines take the same edges. kernel-parity.mjs on the kernel's drivers/base, drivers/net/dsa and drivers/android (279 C files) gives 240 at byte parity, 0 with diffs and 39 deferred to wasm by policy.

  • Full suite: 762 files passed, 6 skipped; 9,075 tests passed, 85 skipped, 0 failed, on ece90943 (the same patch, one rebase earlier; the rebase brought only perf(resolution): read a bare JS/TS call's local binding once per match — graph byte-identical #2934, which touches name-matcher.ts).

Lands #1989 by @bompus.

🤖 Generated with Claude Code

…ions

resolveFn in the C function-pointer pass looked a registered handler's
name up across every language, filtered only to function and method
kinds. In a repository that also holds Rust or Python, a C table could
link to a same-named Rust or Python function, and when that function
sorted first it also took the place of the real C handler.

The fix keeps candidates in C and C++ files only, using the pass's own
C_CPP_EXT, the file set it scans for tables and dispatch. Headers the
language detector labels Objective-C still count. Objective-C `.m` and
`.mm` files stay out, like they are out of the scan: a table in a `.m`
file is never read, and allowing them would let an Objective-C method
named `init` or `read` stand in for a C handler of that name.
On GLFW, which mixes C and Objective-C, main links 354 table entries,
none into a .m file; its one C table naming a .m function
(_glfwConnectCocoa) is an anonymous-struct table the pass doesn't
bridge on main either.

Validation, main b6379e6 vs this:
- c-fnptr-synthesizer.test.ts: the new case fails on main (impl_read
  links to aaa/gen.py) and passes here; 19/19.
- Linux d135ab45 subtree (rust/, drivers/android, drivers/base,
  drivers/net/dsa, fs, tools/net, include/linux; 6,580 files):
  byte-identical, 12,244 fn-pointer-dispatch edges, all into C or C++
  files on both arms. Main links none of this tree into Rust or Python,
  so the PR's 193 (a whole-kernel index on its fork) don't show here; a subtree holding its three example sites (pmcraid's
  `->reset_cmd = cmd`, afs's `->update_time = new`, map.c's
  `p->lock = lock`) has none either, since a plain assignment of a
  variable doesn't register on main.
- C and C++ controls byte-identical (dump-graph, every table): redis
  7f440372 (863 fn-pointer-dispatch edges), GLFW 92dcf4ce (354), jq
  49fbca12 and leveldb 7ee830d0.
- The resolver change is TS-side; the kernel ports only the per-file
  sweep, so both engines take the same edges. kernel-parity on the
  kernel's drivers/base, drivers/net/dsa and drivers/android (279 C
  files): 240 at parity, 0 diffs, 39 deferred to wasm by policy.

Ours on top: the CHANGELOG bullet moved from the released 1.6.1
section to Unreleased and reworded.

Original PR #1989 by @bompus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants