Features

Interprocedural Analysis: Catch nil Dereferences Before They Crash Your Code

The upcoming GoLand 2025.2 release introduces a powerful set of new features and improvements designed to help you write safer, more reliable Go code. If you’d like a full breakdown of all the updates, be sure to check out the release notes

In this post, we’ll focus on one of the most significant new features: interprocedural code analysis for detecting nil pointer dereferences. By helping you catch subtle bugs that often slip through code reviews and tests, this improvement makes your production code more stable and easier to maintain.

The GoLand team has put a lot of effort into delivering deeper, smarter static analysis to improve your development experience and help prevent those frustrating runtime panics. If you want to try this feature in your IDE, you can clone the following project from GitHub.

nil pointer dereference in Go

One of the most common pain points in the Go programming language is nil pointer dereference, and nearly every Go developer has encountered it at some point. Despite Go’s simplicity and strong static typing, nil remains a source of subtle and often critical bugs.

The impact of a nil dereference can be severe, especially in production environments. A single unexpected dereference can crash an entire service, bringing down an API or worker process with little to no warning. 

In Go, even more subtle issues can arise. Writing to a nil channel, for instance, can cause a goroutine to be blocked forever, potentially leading to deadlocks and cascading system failures. Attempting to access fields on an uninitialized nil pointer will result in an immediate panic. These kinds of errors are easy to overlook and hard to trace back once deployed.

While some nil dereference issues can be caught through careful code review or testing, that’s not always enough. In fast-paced development cycles or large codebases, it’s easy for subtle nil-related bugs to slip through. Ideally, such issues should be detected automatically and as early as possible when writing the code.

This is where static code analysis comes in. GoLand already includes a built-in nil dereference inspection that performs local intraprocedural analysis. It works well for many common scenarios, detecting when a pointer might be nil within the scope of a single function.

However, the current analysis only works within individual functions. It does not follow how values move between functions, so it can miss problems that involve multiple calls. These more complex cases are common in real-world Go code and are often the most dangerous. To catch them, we’ve implemented something more powerful: interprocedural code analysis.

Interprocedural code analysis

Interprocedural analysis, also called global analysis, helps you understand how values move through function calls. It looks beyond a single function to follow data across files and packages. In contrast, intraprocedural or local analysis only checks what happens inside one function. Local problems are often easy to catch by reviewing a single function. But global problems are harder to find because the source of an issue, such as a nil value, might be far from where it causes an error. That is why interprocedural analysis is especially useful for detecting nil dereference issues.

Following the flow: Understanding nil dereferences

Now let’s take a look at an example. This code looks pretty straightforward. We create a user using a constructor and print its fields. But the analysis gives us a warning: user.Age might cause a nil dereference.

Let’s try to investigate this manually. To understand what’s going on, we need to look at how the NewUser function is implemented. It is defined in a different file called model.go.

This constructor looks a bit strange: NewUser returns nil if an error occurs, but in main, we use the result without checking. This creates a potential nil dereference.

To fix this, we can rewrite NewUser to return both a result and an error – the more idiomatic Go style.

Now the code is safer. We check for an error before accessing user, so there is no risk of dereferencing nil. Although this code looks correct, we still see the same warning.

To figure out what’s going on, let’s dig in deeper and take a closer look at the implementation of CreateUser.

Here we find the second cause of the problem.

In the CreateUser function, there is a case where the code returns nil for both the user and the error.

This is a fairly common mistake in error handling. Returning nil without an error makes it look like everything went fine, while in reality the result is not valid. The caller checks only the error, sees that it is nil, and then tries to use the result. In our example, this leads to a crash when the code accesses user.Age.

We can fix this by returning an actual error when the input is not valid:

With this change, the code becomes correct, and the inspection no longer reports a nil dereference.

Finding issues like this by hand can be slow and frustrating, especially in large projects. The place where a nil value is created might be far from where it causes a problem.

That is why GoLand highlights such issues right in the editor as soon as they are detected. For these warnings, we offer a dedicated context action: Explain potential nil dereference. This action opens the Data Flow Analysis tool window, where you get a step-by-step explanation of how the nil value flows through the code and where it is eventually used. This makes it much easier to understand and fix the issue without searching through the entire codebase.

When nil slips through: Catching unsafe arguments and receivers

Our analysis does more than track return values. It can also reason about parameter nilability by understanding whether a function expects a non-nil argument or can safely accept nil. This is particularly useful for catching cases where a nil value is unintentionally passed to a function that doesn’t handle it properly.

Let’s take a look at another example:

Here we call the Copy method on a user. At the same time, we pass nil as the context, assuming it is safe to do so. 

But the inspection shows a warning: The context argument might cause a nil dereference as we pass a nil value as the context. Let’s check the implementation of the Copy method:

In this code, the method accesses ctx.isDebugEnabled without checking whether ctx is nil. If ctx is nil, the program will panic at runtime.

To fix this, we can make the ctx parameter nil-safe by adding an explicit nil check before accessing its fields.

With this change, the code becomes safe, and the warning at the call site disappears.

However, that is not the only problem. The analysis also reports a potential nil dereference related to the user variable.

To understand why, we can use the Explain potential nil dereference action.

The process function allows user to be nil, and we pass it to Copy without checking. 

Inside the Copy method, the receiver u is used before being checked. Specifically, u is passed to the logUserEvent function, where a dereference occurs when accessing the u.Name field. Therefore, if the user variable in the process function is nil, a nil dereference will occur.

These examples demonstrate that nil dereference issues are often subtle and easy to overlook. Even if the code looks clean and idiomatic, small assumptions can lead to runtime crashes. Tracing the root cause manually can be surprisingly tricky, especially when the origin of the nil value is created far from the place where it is used, separated from the dereference by multiple function calls, files, or packages.

This is where interprocedural analysis helps. It tracks how nil values move through function calls. Instead of guessing where the problem started, you can clearly see the full path from the origin to the point of dereference.

Quick documentation now shows nilability information

Nilability analysis in GoLand is not just for highlighting issues in the editor. As you’ve already seen, our analysis can determine whether a function might return nil, and whether it’s safe to pass nil as an argument to a particular parameter. As the analysis understands how functions are expected to behave, we decided to make this information easy to access. That’s why we’ve integrated nilability information directly into the quick documentation popup.

Let’s go back to the first example from earlier, before we applied any fixes. If we place the caret on the NewUser function and trigger the quick documentation, we will see a section called Nilability info. It shows the nilability of the function parameters and the return value. In this example, the function may return a nil result, and the quick documentation popup tells us that clearly.

The same feature works for parameters and receivers. In the second example, also before applying any fixes, the Nilability info section shows us that both the receiver u and the parameter ctx of the function are expected to be non-nil.

This small addition makes a big difference. With a quick lookup, you get an overview of important details, which can help you write safer code and reduce the risk of unexpected nil dereferences. However, keep in mind that not all cases are covered by the analysis, so always review the code carefully.

Limitations and trade-offs

The first version of this analysis is simple and careful on purpose. It does not try to catch every possible nil dereference, and that is intentional. We’ve focused on the most common and important cases, aiming to keep false positives to a minimum. We’ll keep improving the analysis over time, adding new cases carefully. Our goal is to catch more problems without adding unnecessary noise.

Avoid panics; embrace safety

Interprocedural code analysis makes it much easier to catch and fix nil pointer dereference issues early. By tracking nil values across functions, files, and packages, this analysis makes it easier to understand the root causes of potential bugs before they hit production, reducing downtime and preventing costly incidents. 

We’re excited to continue refining and expanding these capabilities in future updates. Stay tuned – and as always, we’d love to hear your feedback!

The GoLand Team

image description

Discover more