A static call graph is an estimate, not an exact map. Computing the exact one is undecidable, so tools approximate, and the usual approximation includes every real call plus some that never happen. For reachability, that means a "reachable" label can be wrong more often than a "not reachable" one.
What a call graph is
A call graph is a set of nodes and edges. Each node is a function, and each edge says one function can call another. Reachability analysis asks whether a path of edges runs from something your application executes to the vulnerable function inside a dependency.
Call graphs come in two broad kinds. A dynamic call graph records one real run, for example from a profiler. It can be exact, but only for that run. A static call graph tries to cover every possible run, which is why it is approximate. Both points come from the Wikipedia call graph entry.
Why the estimate leans toward extra edges
Take a method call on an interface in an object-oriented language. The tool may not know which concrete class will be used, so it may add an edge to every class that could fit. Some of those edges will never run. This is called over-approximation, and it is a deliberate choice: it is safer to include a call that never happens than to miss one that does.
For a security question, that tilt matters. Over-approximation protects you from false "not reachable" claims but produces more "reachable" findings than reality would. The graph can also miss edges when code uses reflection or dynamic loading, which are patterns where behaviour is decided at runtime.
Precision costs time
The same source describes a spectrum. A context-insensitive graph has one node per function and is cheap. A fully context-sensitive graph has a node for every call stack that can reach a function, which is much more precise and usually too slow for large programs. Tools choose a point in between.
| Approach | Precision | Cost | Typical risk |
|---|---|---|---|
| Context-insensitive | Lower | Low | Extra reachable findings |
| Partly context-sensitive | Medium | Medium | Some extra findings |
| Fully context-sensitive | High | Very high | Rarely practical at scale |
| Dynamic (one run) | Exact for that run | Needs execution | Misses paths not exercised |
What to do with this
Read "reachable" as "a path was found that the tool believes is possible", and read "not reachable" as "no path under the tool's assumptions". If a decision carries cost, such as a risk acceptance, ask for the call path and read it. A path you can follow by eye is stronger than a bare label.
Safeguard includes reachability analysis alongside SBOM and dependency scanning, and you can run a free scan at gold.safeguard.sh/scan to see the output on your own repository.
Quick check
Can a static call graph ever be exact?
Not in general. The exact version is an undecidable problem, so tools approximate. Small, simple programs may happen to come out exact, but you cannot count on it.
Is a dynamic call graph better?
It is exact for the run it recorded, but it only shows paths that run exercised. Many teams use static analysis for coverage and runtime data as a cross-check.
Sources
Last reviewed: 19 October 2026.