Repository navigation
fix(registry): recognize C# lower-case type keywords in receiver_chain_admits - #2170
Conversation
…n_admits receiver_chain_admits() fast-admits any lower-case-rooted receiver chain as a value, not a type, which is true in Go/Python/Swift but not for C#'s built-in type aliases (int, string, bool, ...): those are lower-case KEYWORDS that name a type, so a static call like int.TryParse(...) skips the chain-consistency check and can fabricate a CALLS edge to any unrelated same-named method (DeusData#2121). Add a closed keyword set consulted only for a lower-case root, so every other language's lower-case value roots (vm.load, http.Get, os.path.join) are unaffected. Signed-off-by: Amir Fathi <[email protected]>
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
|
Both red legs here ( |
|
Thank you for the C# keyword fix and negative controls. The current registry's lower-case receiver handling supports the reported gap. Review should preserve the C# keyword cases without admitting unrelated receiver names in other languages. We still need to attribute the Windows failure from its evidence; an unchanged Windows-specific file list alone would not prove it unrelated. The contribution is queued for that focused review. |
|
Read the job log rather than the file list this time. The windows-latest CLANG64 leg fails inside tests/test_parallel_harness_contract.sh's own scheduler self-test, step 0i: "FAIL: Windows timeout race refused without a surviving descendant to refuse over, the fixture no longer exercises the race" (job 103034332390). That's a timing fixture for the test harness's own process cleanup, nothing in registry.c or test_pipeline.c. shard-completeness and ci-ok both fail downstream of that missing shard manifest (job 103051948933 shows no manifest uploaded for windows-latest-CLANG64-0). |
DeusData
left a comment
There was a problem hiding this comment.
Thank you for this. The C# bug is real and your fix for it is right: in int.TryParse, string.Join or object.ReferenceEquals the lower-case root is a type, and treating a reserved type keyword the way an upper-case root is treated fixes all of them cleanly. I verified it on the merge result with today's main (e783f73d): clean merge, even though both sides edited tests/test_pipeline.c; pipeline + registry 357 passed, including both issue2121 tests; memory-core linter unchanged.
One change before it merges, and it is a rule of the codebase rather than a nit. Your comment says the keyword list is deliberately language-agnostic ("closed list, no caller-language read"). Our standing rule for call resolution is the opposite: suppressors stay per-language and are gated on the file's language, so a guard that fixes one language can never trade away recall in another. Every existing guard in registry.c follows it for exactly that reason. Here the words are reserved type keywords only in C#. In Python, JavaScript or Go, object, string, char, float and dynamic are ordinary identifiers, so a lower-case variable named object calling a method would now fall through to the chain-consistency check instead of passing as a value root, and that can drop a real CALLS edge in a language this PR never meant to touch.
What I would ask for:
- Apply the type-keyword branch only when the calling file is C#. The registry already has the shape for this: the per-language gate is decided at the call sites and passed into the helper as
enabled, never read inside it — see the comment above the Python / JS-TS guard. Those call sites are inpass_calls.candpass_parallel.c, and they must enumerate the identical language set, or the sequential and parallel resolvers diverge. Please keep that lockstep. - Keep your two C# tests, and add the negative control that pins the other side: a Python (or Go) file in which a lower-case variable named
object(orstring) calls a method, asserting that the CALLS edge still resolves. That test is what will stop the next widening of the list from quietly costing another language.
If another language turns out to need the same treatment later, it can join the gate on its own, with its own test. Thank you for the careful true-positive test you already wrote; it is the reason this is a small ask rather than a redesign.
The DeusData#2121 fix treated C#'s reserved type keywords (int, string, object, ...) as value roots in receiver_chain_admits without checking the calling file's language. In Python, JS or Go those same spellings are ordinary identifiers, so a lower-case variable named object or string calling a method would fall through to the chain-consistency check and could drop a real CALLS edge outside C#. Add cbm_registry_resolve_lang(..., is_csharp) alongside the existing language-blind cbm_registry_resolve, and thread is_csharp from each file's own language at every call site that resolves a real receiver chain: pass_calls.c and pass_parallel.c (the sequential/parallel CALLS resolvers named in review), plus pass_semantic.c and pass_usages.c so INHERITS/DECORATES/THROWS/RW edges stay gated the same way rather than silently reverting to the pre-DeusData#2121 language-blind behavior. The two remaining language-blind callers (pass_calls.c's Python-only Depends scan, pass_lsp_cross.c's base-QN resolve which already excludes C#) are documented in place as never reaching a C# receiver chain. Adds a negative control test asserting a lower-case object/string receiver root still resolves as a value in a non-C# file, pinning the side of the gate a widened keyword list could quietly cost. Signed-off-by: Amir Fathi <[email protected]>
AmirF194
left a comment
There was a problem hiding this comment.
Both applied. cbm_registry_resolve_lang(..., is_csharp) sits alongside the language-blind cbm_registry_resolve, and is_csharp is threaded from each file's own language at every real receiver-chain call site: pass_calls.c and pass_parallel.c as you named, plus pass_semantic.c and pass_usages.c so INHERITS/DECORATES/THROWS/RW stay gated the same way instead of quietly reverting to language-blind. Two remaining language-blind callers (pass_calls.c's Python-only Depends scan, pass_lsp_cross.c's base-QN resolve which already excludes C#) are documented in place as never reaching a C# receiver chain.
Added the negative control: a lower-case object/string receiver root in a non-C# file still resolves as a value, pinning the side a widened keyword list could quietly cost.
Verified in a clean container: 289/290 pipeline tests pass (the one failure, git_context_linked_worktree, needs a git binary the container doesn't have and is unrelated to this change). Pushed 5a3e825.
Column-limit re-wrap only, no token changes. Reproduced the CI lint failure with clang-format 18/20 (BasedOnStyle: LLVM, ColumnLimit: 100) against the .clang-format the repo's own lint.sh uses. Signed-off-by: Amir Fathi <[email protected]>
|
Fixed and pushed (1ebd1e4). The lint job's clang-format check was failing on the three new cbm_registry_resolve_lang call sites, all over the 100-column limit. Reproduced with clang-format 18 and 20 against the repo's own .clang-format, re-wrapped, confirmed clean with a dry-run diff. Whitespace only, no token changes. |
DeusData
left a comment
There was a problem hiding this comment.
Thank you, @AmirF194, for the fix and for turning around the last review so quickly. The keyword gate is the right idea: in int.TryParse, string.Join or object.ReferenceEquals the lower-case root is a type, and your three tests pin both sides of that. I'm sorry this sat long enough for main to move underneath it. That is on us, not you.
Since your last push, main gained a language-aware registry API of its own (132ded8, 3 October): cbm_registry_resolve_lang(..., CBMLanguage lang) (declared in src/pipeline/pipeline_internal.h, defined in src/pipeline/registry.c) and cbm_registry_resolve_call_lang(..., CBMLanguage lang). The branch now conflicts with main, and the two cbm_registry_resolve_lang declarations clash. So here is what we would ask for:
- Rebase onto current
main. - Derive the C# gate from the
CBMLanguagethat main's resolve functions already receive, instead of adding abool is_csharpparameter. That should make most of the call-site threading unnecessary, and keeps one registry API. The reason this matters beyond tidiness: if a rebase kept main'sCBMLanguageparameter and the call sites still passedlang == CBM_LANG_CSHARP, C would accept it silently.CBM_LANG_GOis 0 andCBM_LANG_PYTHONis 1, so C# files would resolve with Python scope and every other file with Go scope. - Keep your three tests: the
int.TryParserefusal, the chain-matches positive control, and the non-C#object./string.value-root control.
On scope: please keep this PR to the CALLS resolvers (pass_calls.c and pass_parallel.c), as the 23 September review asked. The extension to C# USAGE, THROWS/RAISES, READS/WRITES, INHERITS, DECORATES and route-handler lookups in 5a3e825 is very welcome, and is probably a quality gain too. As a separate follow-up PR, we can measure it on its own, so a reviewer sees exactly what each change moves. Our house rule is one claim per PR, so the two get judged separately rather than one holding up the other.
Once the rebased version is up we will measure it on our C# corpora, together with a related change on our side that uses the C# LSP's own answer for BCL receivers (int.TryParse already resolves to System.Int32.TryParse there). Your keyword gate still matters for receivers the LSP says nothing about. Thanks again for sticking with this!
DeusData
left a comment
There was a problem hiding this comment.
Thank you very much for this, @AmirF194! The analysis in the description is spot on: receiver_chain_admits fast-admits every lower-case receiver root as a value, and C# spells its built-in types as lower-case keywords. So int.TryParse(...) skipped the chain check entirely and could bind to any project method named TryParse. That is exactly #2121. The closed, C#-only keyword list is the right shape: these words are grammar, not a name heuristic, and gating it on the file's language keeps object, string and friends ordinary identifiers in Python, JS and Go. The second test, showing that a real project type whose chain matches still resolves, is a great touch.
Main has moved quite a bit under this branch since September, so a few requests before it can land:
-
Name clash with main. Main now has
cbm_registry_resolve_lang(..., CBMLanguage lang)insrc/pipeline/pipeline_internal.h, the same name as this PR's newcbm_registry_resolve_lang(..., bool is_csharp), so the branch will no longer build once main is merged in. It also means the language is already available where you need it:- Both resolvers now reach the registry through one language-aware entry point,
cbm_registry_resolve_call_scoped(..., CBMLanguage lang, ...)→cbm_registry_resolve_call_lang(...)→registry_resolve_scoped(...), used bypass_calls.candpass_parallel.calike. - Could you pass that
CBMLanguagedown toresolve_name_lookup→receiver_chain_admits(callee, candidate, lang)and testlang == CBM_LANG_CSHARPthere, instead of threading a newbool is_csharpthrough the call sites? - That would shrink the diff to
registry.cplus the tests. It also removes the seq/parallel lockstep risk the comments warn about, because there is only one entry point to keep in sync.
- Both resolvers now reach the registry through one language-aware entry point,
-
Keyword list: consider adding
nintandnuint(C# 9 native-sized integers). They are keywords in receiver position too (nint.TryParse). -
An end-to-end test on both resolvers would make the fix stick.
tests/test_parallel.chas a harness that indexes a small fixture sequentially and in parallel (run_sequential_with_lsp_cross/run_parallel). The issue's own two files fit it well:int.TryParse(input, out _)must not create CALLS to the project'sTsidNode.TryParse;- control:
TsidNode.TryParse(input, out _)still binds.
-
Lint: CI's lint is
scripts/lint.sh --ci. Besides cppcheck 2.20 and clang-format 20 (the Homebrew LLVM build; a standalone clang-format-20 can report false whole-file drift), it runs:- the NOLINT whitelist check;
- the memory-core ratchet, under which a file's raw
malloc/calloc/realloc/free/strdupcount must not grow. Allocate throughsrc/foundation/mem_core.h. A new table or list is a common way to trip it.
A run of that script after the main merge would catch all three.
One heads-up so our work fits together. We are about to open a PR for #1692: log calls inside Kafka consumers became fake Kafka routes. There, the C# LSP already proves that the receiver is a library type, for example m_Logger.LogError → Microsoft.Extensions.Logging.ILogger.LogError, but that target has no graph node, so the pipeline fell back to a name guess. The fix stops the name guess when the LSP resolved the call to a library symbol, as main already does for Rust (#2053).
- Because the C# LSP also knows
int.TryParseisSystem.Int32.TryParse, that change will stop the #2121 fabrication too, whenever the LSP resolves the receiver. - Your registry-side check stays valuable: it covers the calls the LSP does not resolve, and it keeps the name-guess path honest on its own. So the two complement each other, and we will reference this PR in ours.
Thanks again for the careful work and the clear write-up!
Brings the branch up to date with main a222715. Since September, main added its own language-aware registry API (132ded8): cbm_registry_resolve_lang(..., CBMLanguage lang), under the same name as this branch's cbm_registry_resolve_lang(..., bool is_csharp), and both CALLS resolvers now reach the registry through one entry point, cbm_registry_resolve_call_scoped -> cbm_registry_resolve_call_lang. Seven files conflict: pass_calls.c, pass_lsp_cross.c, pass_parallel.c, pass_semantic.c, pass_usages.c, pipeline.h and registry.c. All seven are resolved to main's side, since main's CBMLanguage parameter supersedes the is_csharp threading. The next commit re-applies the C# keyword gate on main's API. tests/test_pipeline.c merged cleanly. Signed-off-by: Martin Vogel <[email protected]>
Follow-up to the review of DeusData#2170, on top of the contributor's change. receiver_chain_admits() fast-admits a lower-case receiver root as a value. C# spells its built-in types as lower-case keywords, so int.TryParse skipped the chain check and could bind to any project method named TryParse (DeusData#2121). This keeps the contributor's closed keyword list and moves the gate onto the language main already passes: - cbm_registry_resolve_call_lang, the CALLS entry point both resolvers share through cbm_registry_resolve_call_scoped, hands the calling file's CBMLanguage down to receiver_chain_admits, which applies the keyword branch only for CBM_LANG_CSHARP. Every other resolve passes CBM_LANG_COUNT, so USAGE, THROWS/RAISES, READS/WRITES, INHERITS, DECORATES and route-handler lookups are unchanged; extending the gate to them is a separate change. - nint and nuint (C# 9 native-sized integers) join the list. - The per-file resolve cache records the C# bit, so a C# call and a C# reference to the same name never share one cached answer. - The three unit tests call the CALLS entry point with a real CBMLanguage (C#, Python, Go) instead of a bool. - parallel_csharp_keyword_receiver_no_name_guess_issue2121 indexes the issue's two files with the sequential and the parallel pipeline: no CALLS edge from int.TryParse or nint.TryParse to the project's TsidNode.TryParse, while TsidNode.TryParse(...) still binds. Co-authored-by: Amir Fathi <[email protected]> Signed-off-by: Martin Vogel <[email protected]>
|
Thank you, @AmirF194, for this fix and for your patience while What we changed, on top of your commits (which are untouched):
How we checked it: the new end-to-end test and your refusal test fail on One limit to be aware of: when several project methods share the name and the file has We will merge once CI is green on the new head. Thank you again for the careful work and the clear write-up! |
|
Thank you again for the C# keyword guard and controls. The October 20 update records the language-parameter adaptation and end-to-end coverage as completed on our side. Sorry for the delay in the remaining maintainer follow-up—I’ll follow up soon. |
`dynamic` is a contextual keyword, not a reserved type keyword like `int`. The dynamic type has no static members, so a `dynamic.X` receiver is a variable named `dynamic` (or a user type spelled `@dynamic`), never the built-in type. On the keyword list it sent such a call through the receiver chain check and refused it. Drop `dynamic` from cs_type_keyword_root. `nint` and `nuint` stay: they name the built-in type in receiver position. On dotnet/runtime all 586 `nint.`/`nuint.` member accesses are static members of the built-in types, while none of the 10 `dynamic.` receivers is the built-in type. registry_receiver_chain_csharp_dynamic_variable_still_binds_issue2121 pins it: a C# call through a variable named `dynamic` still binds. It fails with `dynamic` on the list and passes without it. Signed-off-by: Martin Vogel <[email protected]>
Brings the branch up to date with main 6d34ca1. Two resolutions: - tests/test_parallel.c (textual conflict): main added parallel_csharp_nested_type_and_global_namespace_binding and parallel_csharp_parse_damaged_namespace_is_not_global at the same spot as this branch's parallel_csharp_keyword_receiver_no_name_guess_issue2121. Resolved as main's file plus this branch's own hunk (all three tests and their RUN_TEST lines kept); the zero-context patch-id of the branch's change to the file is unchanged. - src/pipeline/registry.c (semantic conflict; merged cleanly as text but would not compile): main's cbm_registry_swift_candidates (DeusData#2436) calls receiver_chain_admits with two arguments, and this branch gives it a third, the calling file's language. The Swift caller now passes CBM_LANG_SWIFT. Behaviour is unchanged: the type-keyword branch applies to CBM_LANG_CSHARP only. Signed-off-by: Martin Vogel <[email protected]>
|
Thank you again, @AmirF194, and sorry this took longer than the "soon" we promised on October 25. Your fix has held up well: on current We pushed two more commits on top of your branch (your commits are untouched):
How we checked it: the new One correction to our October 19 note: the C# LSP change for #1692 that we said would complement this PR is not going ahead for now, so your registry-side check is what fixes this part of #2121. If you push to this branch again, please pull first, since it now has our commits on top. We will merge once CI is green. Thank you for the careful work and the clear write-up! |
Brings the branch up to main ff812bf. Conflict in src/pipeline/registry.c: main (DeusData#2170) gave registry_resolve_scoped a `call_lang` argument (the calling file's language on the CALLS path, CBM_LANG_COUNT on every other lookup), while this branch wrapped both lookups with the VB.NET case-insensitive retry. Resolved by keeping the retry and passing main's argument through: vb_resolve_folded takes `call_lang` and hands it to its own registry_resolve_scoped call, CBM_LANG_COUNT from cbm_registry_resolve_lang and the caller's language from cbm_registry_resolve_call_lang, as main does for the first lookup. Signed-off-by: Martin Vogel <[email protected]>
What does this PR do?
receiver_chain_admits()(src/pipeline/registry.c) fast-admits a lower-case-rooted receiver chain as a value rather than a type. That's correct for Go, Python and Swift (vm.load,http.Get,os.path.join), but C# spells its built-in types as lower-case keywords (int,string,bool, ...), so a static call likeint.TryParse(...)skips the chain-consistency check entirely and can fabricate aCALLSedge to any unrelated method sharing the same name (#2121).Fix: a closed set of C# type keywords is checked only when the receiver root is lower-case; anything not in that set still fast-admits exactly as before, so other languages' lower-case value roots are unaffected.
Refs #2121
What does this PR do NOT fix?
The issue's second example,
Options.Create(new object()), no longer reproduces at HEAD: PR #1897 (merged 2026-09-04) already added the chain-consistency guard this PR extends, and it happens to cover that upper-case-rooted shape already.How was this verified?
registry_receiver_chain_refuses_lowercase_type_keyword_issue2121. Without the fix,ASSERT_NULLin that test trips because the fabricated edge still resolves; with the fix it's clean. Ran both ways in the same Docker image (ubuntu:24.04, ASan+UBSan test build).index_repository+trace_path):int.TryParsefabricates an edge to an unrelatedTryParseon main; zero edges on this branch.registry_receiver_chain_keeps_type_keyword_when_chain_matches_issue2121, confirming the guard still resolves a real project type whose chain matches, so it isn't just refusing everything lower-case.pipelinesuite (279 tests) green on this branch;cppcheckandclang-format --dry-runclean on the changed file. Not checked against the exact CI toolchain: local cppcheck was 2.13.0 and clang-format 18.1.3, CI pins 2.20.0 and clang-format-20.