Safeguard
Developer Tools•

Reflection and reachability: what static analysis cannot see

Reflection, dynamic loading and callbacks hide calls from static analysis. Learn what that means for a not reachable verdict and how to cover the gap.

Safeguard Security Team
3 min read

Static reachability misses calls that are decided at runtime, such as reflection, dynamic loading and some callbacks. A not reachable verdict is therefore weakest in code that uses those features heavily. Check frameworks, plugins and dependency injection by hand for critical findings before relying on the label.

What reflection does

The Java tutorial describes reflection as letting a program examine classes, load classes by name, read and write fields, and invoke methods by name at runtime. The method to call can be a string that arrives from a config file.

For a call graph, that is a problem. The arrow is not written in the code. It is created when the program runs.

What the Go checker says

The Go vulnerability checker documents this limit plainly. Calls made through the reflect package are not visible to static analysis, and vulnerable code reached only that way will not be reported in source mode. It also says use of the unsafe package may cause false negatives, and that interface and function pointer calls are analyzed conservatively.

That is useful honesty. It tells you what a clean result does not cover.

Where this bites in practice

  • Frameworks that wire code by name. Web frameworks, serializers and ORMs often call your classes through reflection.
  • Plugin systems. Code is chosen from configuration.
  • Dependency injection. The concrete class is decided at startup.
  • Deserialization. Class names come from data.

Deserialization deserves a second look in particular. Some serious library flaws sit in code that only runs when attacker supplied data names a class.

A review habit for critical findings

  1. Read the advisory. Does it describe a trigger that involves untrusted input, a config option or a specific feature?
  2. Search your code for that feature or option.
  3. Check whether a framework might invoke the function for you.
  4. If you cannot rule it out, treat the finding as reachable until you can.

This costs minutes for a few findings. It does not scale to thousands, which is why you reserve it for severity and exposure.

Pair static with other evidence

Dynamic traces, test coverage, or a runtime inventory can fill part of the gap. See static vs dynamic reachability.

An example worth checking

A web framework maps request paths to handler classes by name from a config file. Your static tool sees no call from the router to those handlers, so any dependency used only inside them can look unreachable. The tool is not broken. The wiring is simply outside what it reads. If your tool supports it, declare those handlers as entry points. If it does not, note the framework in your review checklist for critical findings.

Quick check

Does reflection make reachability useless?

No. Most calls in most programs are direct. Reflection limits confidence in a not reachable result for code that depends on it.

Should a reflective path count as reachable by default?

For critical issues where you cannot rule it out, yes. Treat it as reachable until evidence says otherwise.

Safeguard includes reachability analysis in its dependency scanning, and its free scan is a way to see what reported paths look like.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.