Null pointer dereferences and race conditions share a common trait: they are bugs about paths, not just lines. A variable might be safely checked for null on one branch and dereferenced unchecked on another; a shared resource might be locked in one function and read without synchronization in a callback three files away. Pattern-matching scanners that look at code line-by-line routinely miss both. Snyk Code, the static application security testing (SAST) engine Snyk built on technology it acquired from DeepCode AI in October 2020, takes a different approach: it models how execution can actually flow through a program before it decides whether a given line is dangerous. This post walks through, at a mechanical level, how control flow and data flow modeling let Snyk Code reason about concurrency bugs and missing null checks — based on Snyk's own public documentation and engineering write-ups, not speculation about proprietary internals.
What is control flow analysis, and why does it matter for these bug classes?
Control flow analysis is the process of building a graph of every possible execution path through a function or program, rather than reading source code as a flat sequence of statements. In this graph — a control flow graph (CFG) — each node is a basic block of code and each edge represents a possible jump: an if branch, a loop back-edge, a function call, an exception handler, or a thread spawn. A scanner that only checks "was this variable compared to null anywhere in the file?" cannot tell whether that check actually protects the specific dereference it's looking at. A scanner that walks the CFG can determine, for a specific line 47, exactly which branches at lines 10, 22, and 35 had to be true to reach it — and whether a null-check on one of those branches actually dominates (i.e., necessarily precedes) the dereference. Race conditions have the same shape: you need to know which code paths can execute concurrently and whether they touch the same memory or resource without a synchronization edge between them, which is impossible to determine from text pattern matching alone.
How does Snyk Code build its control flow model of a codebase?
Snyk Code parses source into an abstract syntax tree (AST) for each supported language, then derives control flow and data flow graphs from that AST as part of what Snyk calls its semantic code analysis. According to Snyk's public documentation, this representation is language-agnostic at the semantic layer — the same underlying graph structures represent a null-check-then-dereference pattern in Java, JavaScript/TypeScript, Python, C/C++, C#, Go, Ruby, PHP, Kotlin, and other supported languages, even though the surface syntax differs. Snyk Code performs this analysis natively without requiring the code to be built or compiled first, which is what lets scans run directly against a pull request or a local IDE session in seconds rather than requiring a full CI build. The graphs are then queried by a combination of rule-based logic and machine learning models that Snyk states were trained on a large corpus of open-source code and security-relevant commits, so the same structural pattern — say, a socket callback writing to a field also written on another thread — gets ranked by both a symbolic rule and a learned confidence score rather than a single hardcoded regex.
How does control flow analysis specifically catch a null pointer dereference?
It catches a null dereference by proving there exists at least one path from a value's origin to its use where no intervening branch guarantees the value is non-null. Concretely, Snyk Code's data flow tracking follows a variable from its source — a function return, a map lookup, a deserialized field, an optional/nullable-typed parameter — through every reassignment and every conditional along the CFG. If it finds a path where the variable reaches a dereference (a . method call, an array index, a field access) without passing through a node that narrows the type to "definitely not null" (an if (x != null) guard, a try/catch around the exact call, an Objects.requireNonNull), it flags that specific path. This is why Snyk Code can distinguish if (user != null) { return user.getName(); } (safe) from if (user != null) { log(user); } return user.getName(); (unsafe) even though both contain an identical null check nearby — the check simply doesn't dominate the dereference in the second case. This path-sensitivity is also what Snyk cites as the reason its false-positive rate on this bug class is lower than tools that only check for the presence of a null check somewhere in the enclosing function.
How does the same graph model let Snyk Code find race conditions?
It extends the same graph with concurrency-relevant edges: thread creation, lock acquisition/release, async callback registration, and shared-state access, then looks for shared writable state reachable from two or more paths that can execute concurrently without an intervening synchronization edge. For example, if a field is written inside a synchronized block in one method but read from an unsynchronized method that can be invoked from a separate thread pool or event loop, the interprocedural version of the CFG — which links call graphs across functions and, where determinable, across files — surfaces that the two accesses are not ordered by any happens-before relationship. Snyk Code applies this to patterns common in real codebases: unsynchronized singleton initialization (double-checked locking done incorrectly), shared mutable collections accessed from request-handler threads, and time-of-check-to-time-of-use (TOCTOU) gaps where a resource is checked in one statement and acted on in a later statement with no atomic guarantee between them. Because concurrency bugs are inherently about the relationship between two or more locations rather than a single bad line, this class of finding is only reachable at all once you have a graph connecting those locations — which is the core argument Snyk makes for semantic analysis over line-based scanning for this bug category specifically.
How does Snyk Code balance this deeper analysis against scan speed and noise?
Snyk publishes scan-speed as a headline metric precisely because deeper, path-sensitive analysis is normally the tradeoff that makes SAST tools slow: Snyk states that most Snyk Code scans complete in under a few minutes and are designed to run inside a pull request check or an IDE plugin (Snyk supports VS Code, IntelliJ-family IDEs, Eclipse, and Visual Studio) without blocking a developer's workflow. To keep noise manageable at that speed, Snyk Code pairs its symbolic CFG/data-flow rules with a learned scoring layer — the same DeepCode AI heritage models mentioned above — so that a structurally plausible finding (a path exists) is additionally scored against patterns Snyk's models associate with real historical vulnerabilities versus benign code shapes, and Snyk uses that score to prioritize or suppress low-confidence results. In 2023 Snyk layered generative AI on top of this pipeline with DeepCode AI Fix, which uses the same underlying graph context (the specific path that triggered the finding) to propose a source-level patch rather than a generic suggestion, because the fix generator has visibility into exactly which branch was missing a guard or which access was unsynchronized.
How Safeguard Helps
Static analysis tools like Snyk Code are built to answer a specific question well: given this source code, does an unsafe execution path exist? That's necessary but not sufficient for software supply chain security, because a clean SAST scan on your own repository says nothing about the provenance of the dependencies, containers, and build artifacts that ship alongside that code. Safeguard is built to close that gap. We give engineering and security teams a continuous, verifiable record of what actually went into a build — SBOM generation and diffing, dependency and container provenance tracking, and build-pipeline integrity checks — so that a codebase can pass its SAST gate and still be checked for the risks control-flow analysis was never designed to see: a compromised upstream package, an unsigned artifact injected mid-pipeline, or a dependency that silently changed behavior between releases. Teams running Snyk Code or any other SAST tool as part of their AppSec program can layer Safeguard underneath as the supply chain integrity control, so that source-level correctness and artifact-level trust are both verified before code reaches production. If you want to see how Safeguard maps your existing dependency graph and build pipeline against known supply chain attack patterns, our team is happy to walk through it.