While building a tool that consumes cscope's cross-reference database, we
differential-tested cscope's own query engine (-L / line-oriented mode)
against a direct parse of cscope.out. The crossref database itself is
consistently correct; the query side shows three reproducible bug
classes on larger real-world files. Verified on cscope 15.9 built from
vanilla upstream source (the Homebrew build on macOS/arm64 — the formula
applies no patches, source is the SourceForge cscope-15.9.tar.gz);
databases built with -bkR and -bckR (both the compressed and the
uncompressed -c format are affected, interestingly dropping different
subsets — see class 2).
All examples below use the wpa_supplicant source tree (any recent
checkout reproduces).
cscope -bckR -f cs.out
cscope -d -f cs.out -L3 radius_msg_get_attr_ptr
For call sites inside:
static RadiusRxResult
radius_das_disconnect(struct radius_das_data *das, ...)
the same call site is reported twice, once with the caller
radius_das_disconnect (correct) and once with the caller
RadiusRxResult (the return type). The crossref contains a single,
correct $radius_das_disconnect function mark — the duplication happens
in the query engine's caller derivation.
src/radius/radius_das.c contains 8 call sites of
radius_msg_get_attr_ptr (crossref records at lines 76, 88, 100, 106,
121, 127, 133, 139 — verifiable by inspecting cscope.out directly).
-L3 radius_msg_get_attr_ptr returns only 5 of them against the
compressed database, and a different subset against the uncompressed
(-c) database. Lines 100 and 106 are dropped in both modes.
cscope -bckR -f cs.out
cscope -d -f cs.out -L3 fst_group_get_id | grep fst_internal
reports a hit at src/fst/fst_internal.h:1255 — that header is only
49 lines long; the real site is fst_session.c:1255 (the adjacent
compilation unit which #includes the header). Reproduces on a fresh
database every time in our testing.
A second variant: against the default (compressed) database,
-L3 os_free reports a hit at crypto_internal.c:223 — line 223 is
blank in the source, the crossref has no record for line 223, and the
actual call is at line 217 (also reported). Incidentally the same query
illustrates class 2 at larger magnitude: the uncompressed database
returns 10 rows for this file, the compressed one 32.
cscope.out directly yields the complete, correctly-numbered result
Hmmm... checked out git current source straight from their original upstream.
Class 1:
No two-line function definition for radius_das_disconnect matching your description seems to exist in there. The definition has looked like this, for the last 14 years and counting:
What gives?
The issue itself does exist, and is most likely an instance of the well-known and documented limitation about function pointers (like those in the definicition of struct radius_das_data) throwing cscope off-track.
Class2:
The number of actual call sites for radius_msg_get_attr_ptr in radius_das.c is not 8, but 17. cscope finds all of them (some twice), with no difference between default and -c modes, when run from the top-level or 'src' directory. Only when run from the 'src/radius' directory itself does it drop to 8 call sites located.
Class 3:
Completely fails to reproduce here.
Thanks — your function-pointer instinct was exactly right, so we chased
it into the scanner and have a root cause, a 6-line repro, and a
candidate patch.
6-line self-contained repro (no wpa_supplicant needed)
Root cause (fscanner.l)
cscope -d -f cs.out -L1 RxResultshows it: the lexer records thereturn type of a function-pointer parameter as a function
definition. In the
<WAS_IDENTIFIER>rule, a declarator of shapeT (*f)(args)matches the "a function definition" pattern — theidentifier
Tis followed by(, and theif (braces == 0 ...)testpasses. That phantom function has no body, so
fcndef/bracesleaveits scope open; it then swallows the FCNCALL attribution of every
subsequent file, producing the cross-file duplicate callers.
This is precisely the case the
FIXME HBB 20001003comment right abovethat rule anticipated ("the parsing bug concerning function pointer
usage").
Candidate patch (attached: cscope-306-fscanner.patch)
Adds a shape guard in that rule: if the text after the identifier is
( * ... ) (— i.e. a function-pointer declarator — treat it as afunction call/use, not a definition (
goto fcncal). Verified:RxResultdefinition, no duplicate caller,
-L1 RxResultempty);int f(int a){...}and its calls arestill detected; a genuine fn-ptr-returning definition
int (*g(int x))(void){...}is unaffected.Verified on the real wpa tree (stock vs patched): all three
originally-reported classes trace to this one phantom scope and are
resolved by the single patch —
RadiusRxResultcaller on radius_das.c sitesfst_group_get_id→fst_internal.h:1255(49-line file)$RadiusRxResultphantom marks in the crossrefThe multi-line form in radius_client.h (
RadiusRxResult (*handler)with the arg parens on the next line) is covered too — regress-306.sh
includes it as case 1b.
Your three points
source changed, only the query output, and the repro above removes
the tree dependency entirely.
for src/radius/radius_das.c. Same numbers, different scope.
15.9 from the vanilla SourceForge tarball (Homebrew applies no
patches). The 6-line repro above should be deterministic
cross-platform; if it isn't for you, that itself is useful data.
Classes 2 and 3 (dropped/drifted rows in radius_das.c) turned out to be
downstream of exactly this same phantom scope — the table above shows
them clearing together with class 1 under the one patch, which we take
as confirmation of the shared root cause.
Regression harness
Since 15.9 ships no test suite (
make checkis a no-op), the patchcomes with a small standalone regression script (attached:
regress-306.sh) covering three cases: the bug (fn-ptr param must not
create a phantom def), an ordinary function def+call must still be
detected, and a genuine fn-ptr-returning definition must still be a
def. Against stock 15.9 it reports case 1 FAIL; against the patched
build, ALL PASS. Usage:
./regress-306.sh /path/to/cscope.