Skip to content

A multi-line #[cfg(...)] can lose one of its twins, and a docs-only cfg_attr fences a function that has none #2390

Description

@BobbieBarker

This issue and its PR were written by an AI agent working on my behalf. I certify the DCO sign-off on every commit and answer for the change.

rust_cfg_qualified_name folds a function's #[cfg(...)] predicate into its qualified name so two mutually-exclusive twins get distinct QNs instead of one overwriting the other (#495). It reads the attribute three ways that the QN it mints shows are wrong, and the third one loses a twin again.

A multi-line predicate collapses both twins into one node

#[cfg(all(
    unix,
    feature = "alpha"
))]
pub fn twin() -> u32 { 1 }

#[cfg(all(
    unix,
    feature = "beta"
))]
pub fn twin() -> u32 { 2 }

#[cfg(all(unix, feature = "alpha"))]
pub fn oneline_twin() -> u32 { 3 }

#[cfg(all(unix, feature = "beta"))]
pub fn oneline_twin() -> u32 { 4 }

Indexed, that file yields three Function nodes where four belong:

tail='twin#cfg(all('
tail='oneline_twin#cfg(all(unix,feature=alpha))]'
tail='oneline_twin#cfg(all(unix,feature=beta))]'

Both twin clauses compute the same QN, so the second upsert overwrites the first and the alpha branch is gone. The one-line pair is kept apart correctly.

The suffix is copied from the cfg( it finds to the END of the decorator text, and for a multi-line attribute that text contains the newline, so the minted QN contains one too. cbm_defs_push then cuts a qualified name at a line break, which is deliberate and pinned by defs_push_cuts_multiline_names_and_rejects_js_literal_names (tests/test_extraction.c:3043). What survives is everything before the newline, and for these two functions that is the same string.

On tokio 1.40.0, 10 fenced QNs are truncated this way, thread_rng_n#cfg(any( among them. None of those 10 actually collides, because the part before the newline still differs between the twins there. The collision needs two predicates that agree up to the first line break, which is what the file above constructs.

An attribute that gates documentation fences a function with no twin

strstr(decorators[i], "cfg(") is unanchored, and cfg_attr contains its own nested cfg(. The common docs pattern therefore fences a function that is compiled unconditionally:

#[cfg_attr(docsrs, doc(cfg(feature = "rt")))]
pub async fn yield_now() {}

tokio/src/task/yield_now.rs:38 is exactly this, and the QN becomes yield_now#cfg(feature=rt)))], with unbalanced parentheses. 7 tokio nodes are fenced solely by a cfg_attr or doc(cfg(...)) and have no twin to be distinguished from.

rust_def_is_test, twenty lines above in the same file, already handles this by anchoring on the bracketed path, and says why: "match the bracketed path exactly so we do NOT match the unrelated #[test_case::case]". rust_cfg_qualified_name is the one Rust decorator inspector that does not follow that convention.

Every fence carries the attribute's own closing bracket

The same copy-to-end-of-text puts the ] into the QN. #[cfg(test)] mints ...add#cfg(test)], not ...add#cfg(test). Across ripgrep 14.1.1, tokio 1.40.0 and rust-analyzer 2024-09-30 that is 332 of 335 fenced QNs.

This is cosmetic on its own, and it is the same line of code as the truncation above, so it goes with it.

Why no test caught this

The only test that exercises the function is tests/repro/repro_issue495.c, which Makefile.cbm deliberately keeps out of the gating make test. It asserts that two twins' QNs differ and that one contains not(; it never asserts the string that gets minted. Nothing in the gating suite reads a fenced QN.

Measured

ripgrep 14.1.1 4649aa9700619f94cf9c66876e9549d83420e16c, tokio 1.40.0 ea6d652a102dee3f22b490db70545b7f66a23fb7, rust-analyzer 2024-09-30 822644d97d7f64e1bdff25b1d636e366a29facc4, indexed with a binary built from 5df8b044:

corpus fenced QNs carry a trailing ] truncated mid-predicate fenced by a cfg_attr only
ripgrep 131 131 0 0
tokio 176 173 10 7
rust-analyzer 28 28 0 0

Fixed by #2391.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions