But C does allow dynamic selection of the callee by use of function
pointers.
Yes, but I don't think this is a problem. It's dynamic selection
from a fixed set of targets - the union of the functions declared by
the program (and in scope for a given pointer assignment, though this
can be ignored for the sake of simplicity), and the functions defined
by the implementation.
If static analysis can determine:
1. the set of all possible targets of function pointers, ie the set
described above, and
2. whether a function pointer is dereferenced in a given function
then it can add a node to the graph for each possible target (step 1)
and an edge to each such node, from every node where a function
pointer is dereferenced (2). This may produce some redundant nodes
(since some functions may be called both normally and through
pointers), and will almost certainly produce nodes that are never
actually reached in any execution of the program. It will also
almost certainly produce many edges that are never actually taken
(and cannot be taken, because no path through the program would
assign a function pointer a value such that the edge *would* be
taken).
However, the point of this mooted static analysis is to determine the
maximum possible depth of the call graph - it's pessimistic. So
having extra nodes and edges doesn't violate the specification.
In short (I know, too late), it's not necessary for this analysis to
determine whether function pointer A will point to function X in any
actual execution of the program; it's only necessary to assume that
it might, and add the corresponding edge to the graph.
In general dataflow analysis to determine which function(s)
could be the value of a pointer at a given call site is the Halting
Problem; and even in practical cases it is often nontrivial.
Agreed, and if this was meant to be a useful analysis of the call
graph that would be a problem. But for determining maximum possible
depth in this pessimistic, imprecise way, it isn't necessarily one.
Of course, the presence of function pointers, combined with this
pessimistic assumption that a function pointer can point to any
function, is likely to produce a cycle in the graph, in which case
this procedure determines that the maximum call depth is infinite -
not a useful answer anyway.
But I'd agree that *useful* analysis of calls through function
pointers is equivalent to the Halting Problem in the general case.