Skip to content

Handle functions passed as an argument - #79

Merged
rikvanriel merged 7 commits into
facebookexperimental:mainfrom
rikvanriel:scratch/riel/series-a-function-arguments
Aug 28, 2026
Merged

Handle functions passed as an argument#79
rikvanriel merged 7 commits into
facebookexperimental:mainfrom
rikvanriel:scratch/riel/series-a-function-arguments

Conversation

@rikvanriel

Copy link
Copy Markdown
Contributor

Handle functions passed as an argument

extract_functions_with_calls returns three vectors as a bare tuple, so every
caller repeats their order and a reader has to count positions to tell which
is which. Two callers destructure it, one of them discarding two elements.

Return a struct instead. No behavioural change.

Assisted-by: claw:claude-opus-5
Signed-off-by: Rik van Riel <[email protected]>
A driver's interrupt handler is reachable only because its name was passed to
request_irq, and nothing records that. `callers nic_intr` answers that nothing
calls it, which is how a handler looks right before someone concludes it is
dead code. The same holds for INIT_WORK, timer_setup, kthread_run and every
other registrar that takes a function by name: 53,918 such arguments go to a
callee that is itself a function, 146,295 to a macro.

Record one row per call argument that names an identifier the file does not
bind to a value. Whether that identifier belongs to a function cannot be
decided while indexing, because the answer usually lives in another file and
files are indexed in parallel; the reader decides it against the functions
table.

Excluded at extraction, all decidable from the file alone: a name the file
declares as a local, a parameter or a file-scope variable, since `min_t(u32,
len, size)` names no function even where some tree defines one called `len`;
a call naming itself, which is recursion; an export or `container_of`, which
name a function without handing it anywhere; and `module_init`, whose
initcall is recorded already.

Indexing the kernel at 0595459f:

    $ semcode-index --database /tmp/idx-a1/.semcode.db
    $ du -sm /tmp/idx-a1/.semcode.db/argument_functions.lance
    41	/tmp/idx-a1/.semcode.db/argument_functions.lance

1,921,278 rows over 37,285 files, of which 242,279 name a function. The
remaining rows name enum constants and macros, and cost 41 MB against an
index of 903 MB.

Assisted-by: claw:claude-opus-5
Signed-off-by: Rik van Riel <[email protected]>
`registrations e1000_intr` said the handler is installed nowhere, because
nothing joined the argument it is passed as. A reader then has to grep for the
name to find out that the driver hands it to request_irq.

Report those calls, keeping them apart from struct-member installations: the
two are different claims, and only the second names a slot.

    (semcode) registrations e1000_intr
    Finding where e1000_intr is installed

    === Handed to ===
    1 calls are handed it as an argument:
      1. request_irq() argument 1 at drivers/net/ethernet/intel/e1000e/netdev.c:2190 in e1000_request_irq

which is where the driver installs it:

    err = request_irq(adapter->pdev->irq, e1000_intr, IRQF_SHARED,
                      netdev->name, netdev);

An argument only counts where its name belongs to a function at the revision
asked about, so an enum constant sitting in the same position reports nothing.
Over the kernel at 0595459f that leaves 242,279 arguments naming 57,541
distinct functions.

Being handed to a call is not being called by it: `callers e1000_intr` still
reports only e1000_netpoll, since whether request_irq invokes what it is given
is a fact about request_irq.

Assisted-by: claw:claude-opus-5
Signed-off-by: Rik van Riel <[email protected]>
A registrar is not a name on a list. request_threaded_irq stores the handler
it is given in irqaction::handler; request_irq is a registrar only because it
hands its own parameter to that one; every subsystem has its own, and any
enumeration of them would miss those.

Read a body and say what it does with one named parameter: stores it in a
member, hands it to another call, or invokes it. Intraprocedural, one body at
a time, no value tracking beyond the parameter's own name. The wrapper case
falls out of following the handover.

Nothing consumes this yet.

Assisted-by: claw:claude-opus-5
Signed-off-by: Rik van Riel <[email protected]>
Being handed to request_irq is only useful if it says where the handler ends
up, and request_irq stores nothing: it hands its parameter to
request_threaded_irq, which writes it into irqaction::handler. Reporting the
first hop alone leaves the reader to walk the wrappers by hand.

Follow the parameter until a body stores it in a member or calls it, and print
the route:

    (semcode) registrations e1000_intr
    === Handed to ===
    1 calls are handed it as an argument:
      1. request_irq() argument 1 at drivers/net/ethernet/intel/e1000e/netdev.c:2190 in e1000_request_irq
         installs it in irqaction::handler through request_irq(handler) -> request_threaded_irq(handler)

The search is breadth first because a body usually hands the same parameter to
more than one call, an error path among them, and depth first down the first
of those reaches nothing: devm_request_irq hands its handler to
devm_request_result before it hands it to devm_request_threaded_irq, and only
the second leads to irqaction::handler, four hops away.

A position only qualifies if it takes a function pointer, following one
typedef, since otherwise an integer written into an integer member reports
nla_put_u32 installing something in nlattr::nla_type, which it does not.

Over the 6,000 busiest positions in the kernel at 0595459f, 79 take a function
and 64 of those reach a member or a call, covering 10,924 call sites:

    $ handover_probe /tmp/idx-a1/.semcode.db ~/linux 0595459f 6000
    of the 6000 busiest positions (1445058 call sites):
      take a function at that position: 79
      reach a member: 64 positions, 10924 sites

Both hops stay in the output. A two-hop claim printed as a one-hop fact is
harder to check than no answer at all.

Assisted-by: claw:claude-opus-5
Signed-off-by: Rik van Riel <[email protected]>
INIT_WORK, timer_setup and call_rcu do not call what they are given; kref_put
does. Both read as one edge, and the difference is the whole question for
anyone reasoning about a race: two chains that differ only in when the callee
runs are the same graph to a reader.

Storing a function in a member defers it by construction, since whoever
dispatches through that member chooses the time, and invoking it happens
before the call returns. The distinction is already in what the body does, so
it is derived rather than stored, and the answer states it:

    installs it in irqaction::handler, called later through
        request_irq(handler) -> request_threaded_irq(handler)

Assisted-by: claw:claude-opus-5
Signed-off-by: Rik van Riel <[email protected]>
`callers neigh_hash_free_rcu` reported that nothing calls it. The callback runs
from RCU, and the only thing that says which objects it frees is the head it
was queued on:

    call_rcu(&old_nht->rcu, neigh_hash_free_rcu);

Following the callback argument reaches rcu_head::func, which is where every
RCU callback lands and therefore says nothing about this one. The type is in
the other argument.

Record it where the file declares the base's type, and report it beside the
slot:

    (semcode) registrations neigh_hash_free_rcu
      2. call_rcu() argument 1 at net/core/neighbour.c:618 in neigh_hash_grow
         attached to neigh_hash_table::rcu
         installs it in rcu_head::func, called later through call_rcu(func) -> __call_rcu_common(func)

Not specific to RCU: the same shape records which object a completion, a timer
or a work item belongs to. Over the kernel at 0595459f, 246,809 of 1,921,278
rows name a subject; the rest pass no object, or pass one the file does not
declare.

Assisted-by: claw:claude-opus-5
Signed-off-by: Rik van Riel <[email protected]>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Aug 28, 2026
@rikvanriel rikvanriel changed the title Scratch/riel/series a function arguments Handle functions passed as an argument Aug 28, 2026
@rikvanriel
rikvanriel merged commit a0ba440 into facebookexperimental:main Aug 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant