{"id":583312,"date":"2025-07-28T13:24:03","date_gmt":"2025-07-28T12:24:03","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=go&#038;p=583312"},"modified":"2025-09-04T22:51:18","modified_gmt":"2025-09-04T21:51:18","slug":"interprocedural-analysis-catch-nil-dereferences-before-they-crash-your-code","status":"publish","type":"go","link":"https:\/\/blog.jetbrains.com\/zh-hans\/go\/2025\/07\/28\/interprocedural-analysis-catch-nil-dereferences-before-they-crash-your-code","title":{"rendered":"Interprocedural Analysis: Catch nil Dereferences Before They Crash Your Code"},"content":{"rendered":"\n<p><em>Note: <a href=\"https:\/\/www.jetbrains.com\/go\/download\/?section=mac\" target=\"_blank\" rel=\"noopener\">GoLand 2025.2<\/a> has been released, and interprocedural code analysis for detecting nil pointer dereferences is now available in the IDE.<\/em><\/p>\n\n\n\n<p>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&#8217;d like a full breakdown of all the updates, be sure to check out <a href=\"https:\/\/www.jetbrains.com\/help\/go\/2025.2\/release-notes-goland.html\" target=\"_blank\" rel=\"noopener\">the release notes<\/a>.&nbsp;<\/p>\n\n\n\n<p>In this post, we\u2019ll focus on one of the most significant new features: interprocedural code analysis for detecting <code>nil<\/code> 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.<\/p>\n\n\n\n<p>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 <a href=\"https:\/\/github.com\/JetBrains\/goland-documentation-samples\/tree\/main\/nilDereferenceAnalysis\" target=\"_blank\" rel=\"noopener\">following project<\/a> from GitHub.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><code>nil<\/code><\/strong> pointer dereference in Go<\/h2>\n\n\n\n<p>One of the most common pain points in the Go programming language is <code>nil<\/code> pointer dereference, and nearly every Go developer has encountered it at some point. Despite Go\u2019s simplicity and strong static typing, <code>nil<\/code> remains a source of subtle and often critical bugs.<\/p>\n\n\n\n<p>The impact of a <code>nil<\/code> 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.&nbsp;<\/p>\n\n\n\n<p>In Go, even more subtle issues can arise. Writing to a <code>nil<\/code> 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 <code>nil<\/code> pointer will result in an immediate panic. These kinds of errors are easy to overlook and hard to trace back once deployed.<\/p>\n\n\n\n<p>While some <code>nil<\/code> dereference issues can be caught through careful code review or testing, that\u2019s not always enough. In fast-paced development cycles or large codebases, it\u2019s easy for subtle <code>nil<\/code>-related bugs to slip through. Ideally, such issues should be detected automatically and as early as possible when writing the code.<\/p>\n\n\n\n<p>This is where static code analysis comes in. GoLand already includes a built-in <code>nil<\/code> dereference inspection that performs local intraprocedural analysis. It works well for many common scenarios, detecting when a pointer might be <code>nil<\/code> within the scope of a single function.<\/p>\n\n\n\n<p>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\u2019ve implemented something more powerful: interprocedural code analysis.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Interprocedural code analysis<\/h2>\n\n\n\n<p>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 <code>nil<\/code> value, might be far from where it causes an error. That is why interprocedural analysis is especially useful for detecting <code>nil<\/code> dereference issues.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Following the flow: Understanding <strong><code>nil<\/code><\/strong> dereferences<\/h3>\n\n\n\n<p>Now let\u2019s 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: <code>user.Age<\/code> might cause a <code>nil<\/code> dereference.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"3200\" height=\"728\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-1.1-main.png\" alt=\"\" class=\"wp-image-583380\"\/><\/figure>\n\n\n\n<p>Let\u2019s try to investigate this manually. To understand what\u2019s going on, we need to look at how the <code>NewUser<\/code> function is implemented. It is defined in a different file called <code><a href=\"http:\/\/model.go\" target=\"_blank\" rel=\"noopener\">model.go<\/a><\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"3200\" height=\"769\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-1.2-NewUser.png\" alt=\"\" class=\"wp-image-583435\"\/><\/figure>\n\n\n\n<p>This constructor looks a bit strange: <code>NewUser<\/code> returns <code>nil<\/code> if an error occurs, but in <code>main<\/code>, we use the result without checking. This creates a potential <code>nil<\/code> dereference.<\/p>\n\n\n\n<p>To fix this, we can rewrite <code>NewUser<\/code> to return both a result and an error \u2013 the more idiomatic Go style.<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"3200\" height=\"1666\" data-id=\"583457\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-1.3-main-NewUser-fix-1.png\" alt=\"\" class=\"wp-image-583457\"\/><\/figure>\n<\/figure>\n\n\n\n<p>Now the code is safer. We check for an error before accessing <code>user<\/code>, so there is no risk of dereferencing <code>nil<\/code>. Although this code looks correct, we still see the same warning.<\/p>\n\n\n\n<p>To figure out what&#8217;s going on, let\u2019s dig in deeper and take a closer look at the implementation of <code>CreateUser<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"3200\" height=\"988\" data-id=\"583468\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-1.4-CreateUser.png\" alt=\"\" class=\"wp-image-583468\"\/><\/figure>\n<\/figure>\n\n\n\n<p>Here we find the second cause of the problem.<\/p>\n\n\n\n<p>In the <code>CreateUser<\/code> function, there is a case where the code returns <code>nil<\/code> for both the <code>user<\/code> and the <code>error<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"3200\" height=\"340\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-1.5-CreateUser-return-nil-nil.png\" alt=\"\" class=\"wp-image-583479\"\/><\/figure>\n\n\n\n<p>This is a fairly common mistake in error handling. Returning <code>nil<\/code> 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 <code>nil<\/code>, and then tries to use the result. In our example, this leads to a crash when the code accesses <code>user.Age<\/code>.<\/p>\n\n\n\n<p>We can fix this by returning an actual error when the input is not valid:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"3200\" height=\"340\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-1.6-CreateUser-return-nil-nil-fix.png\" alt=\"\" class=\"wp-image-583490\"\/><\/figure>\n\n\n\n<p>With this change, the code becomes correct, and the inspection no longer reports a nil dereference.<\/p>\n\n\n\n<p>Finding issues like this by hand can be slow and frustrating, especially in large projects. The place where a <code>nil<\/code> value is created might be far from where it causes a problem.<\/p>\n\n\n\n<p>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: <em>Explain potential nil dereference<\/em>. This action opens the <em>Data Flow Analysis<\/em> tool window, where you get a step-by-step explanation of how the <code>nil<\/code> 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.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-1.7-DFA-toolwindow-movie.mov\"><\/video><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">When <code>nil<\/code> slips through: Catching unsafe arguments and receivers<\/h3>\n\n\n\n<p>Our analysis does more than track <code>return<\/code> values. It can also reason about parameter nilability by understanding whether a function expects a non-nil argument or can safely accept <code>nil<\/code>. This is particularly useful for catching cases where a <code>nil<\/code> value is unintentionally passed to a function that doesn\u2019t handle it properly.<\/p>\n\n\n\n<p>Let\u2019s take a look at another example:<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-5 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"3206\" height=\"756\" data-id=\"583512\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-2.1-prcoess.png\" alt=\"\" class=\"wp-image-583512\"\/><\/figure>\n<\/figure>\n\n\n\n<p>Here we call the <code>Copy<\/code> method on a <code>user<\/code>. At the same time, we pass <code>nil<\/code> as the context, assuming it is safe to do so.&nbsp;<\/p>\n\n\n\n<p>But the inspection shows a warning: The context argument might cause a <code>nil<\/code> dereference as we pass a <code>nil<\/code> value as the context. Let\u2019s check the implementation of the <code>Copy<\/code> method:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"3206\" height=\"960\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-2.2-Copy.png\" alt=\"\" class=\"wp-image-583523\" style=\"aspect-ratio:3.339583333333333;width:840px;height:auto\"\/><\/figure>\n\n\n\n<p>In this code, the method accesses <code>ctx.isDebugEnabled<\/code> without checking whether <code>ctx<\/code> is <code>nil<\/code>. If <code>ctx<\/code> is <code>nil<\/code>, the program will panic at runtime.<\/p>\n\n\n\n<p>To fix this, we can make the <code>ctx<\/code> parameter nil-safe by adding an explicit <code>nil<\/code> check before accessing its fields.<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-7 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"3206\" height=\"324\" data-id=\"583534\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-2.3-Copy-ctx-fix.png\" alt=\"\" class=\"wp-image-583534\"\/><\/figure>\n<\/figure>\n\n\n\n<p>With this change, the code becomes safe, and the warning at the call site disappears.<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-9 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"3206\" height=\"848\" data-id=\"583545\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-2.4-process.png\" alt=\"\" class=\"wp-image-583545\"\/><\/figure>\n<\/figure>\n\n\n\n<p>However, that is not the only problem. The analysis also reports a potential <code>nil<\/code> dereference related to the <code>user<\/code> variable.<\/p>\n\n\n\n<p>To understand why, we can use the <em>Explain potential nil dereference<\/em> action.<\/p>\n\n\n\n<figure class=\"wp-block-video\"><video controls src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-2.5-DFA-toolwindow-movie.mov\"><\/video><\/figure>\n\n\n\n<p>The <code>process<\/code> function allows <code>user<\/code> to be <code>nil<\/code>, and we pass it to <code>Copy<\/code> without checking.&nbsp;<\/p>\n\n\n\n<p>Inside the <code>Copy<\/code> method, the receiver <code>u<\/code> is used before being checked. Specifically, <code>u<\/code> is passed to the <code>logUserEvent<\/code> function, where a dereference occurs when accessing the <code>u.Name<\/code> field. Therefore, if the <code>user<\/code> variable in the <code>process<\/code> function is <code>nil<\/code>, a <code>nil<\/code> dereference will occur.<\/p>\n\n\n\n<p>These examples demonstrate that <code>nil<\/code> 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 <code>nil<\/code> value is created far from the place where it is used, separated from the dereference by multiple function calls, files, or packages.<\/p>\n\n\n\n<p>This is where interprocedural analysis helps. It tracks how <code>nil<\/code> 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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Quick documentation now shows nilability information<\/h2>\n\n\n\n<p>Nilability analysis in GoLand is not just for highlighting issues in the editor. As you&#8217;ve already seen, our analysis can determine whether a function might return <code>nil<\/code>, and whether it&#8217;s safe to pass <code>nil<\/code> 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\u2019s why we\u2019ve integrated nilability information directly into the quick documentation<strong> <\/strong>popup.<\/p>\n\n\n\n<p>Let\u2019s go back to the first example from earlier, before we applied any fixes. If we place the caret on the <code>NewUser<\/code> function and trigger the<em> <\/em>quick documentation, we will see a section called <em>Nilability info<\/em>. It shows the nilability of the function parameters and the <code>return<\/code> value. In this example, the function may return a <code>nil<\/code> result, and the quick documentation popup tells us that clearly.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"3206\" height=\"962\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-3.1-quick-documentation-NewUser.png\" alt=\"\" class=\"wp-image-583567\"\/><\/figure>\n\n\n\n<p>The same feature works for parameters and receivers. In the second example, also before applying any fixes, the <em>Nilability info<\/em> section shows us that both the receiver <code>u<\/code> and the parameter <code>ctx<\/code> of the function are expected to be non-nil.<\/p>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-11 is-layout-flex wp-block-gallery-is-layout-flex\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"3206\" height=\"1346\" data-id=\"583578\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/Example-3.2-quick-documentation-Copy.png\" alt=\"\" class=\"wp-image-583578\"\/><\/figure>\n<\/figure>\n\n\n\n<p>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 <code>nil<\/code> dereferences. However, keep in mind that not all cases are covered by the analysis, so always review the code carefully.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Limitations and trade-offs<\/h2>\n\n\n\n<p>The first version of this analysis is simple and careful on purpose. It does not try to catch every possible <code>nil<\/code> dereference, and that is intentional. We\u2019ve focused on the most common and important cases, aiming to keep false positives to a minimum. We\u2019ll keep improving the analysis over time, adding new cases carefully. Our goal is to catch more problems without adding unnecessary noise.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Avoid panics; embrace safety<\/h2>\n\n\n\n<p>Interprocedural code analysis makes it much easier to catch and fix <code>nil<\/code> pointer dereference issues early. By tracking <code>nil<\/code> 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.&nbsp;<\/p>\n\n\n\n<p>We\u2019re excited to continue refining and expanding these capabilities in future updates. Stay tuned \u2013 and as always, we\u2019d love to hear your feedback!<\/p>\n\n\n\n<p><em>The GoLand Team<\/em><\/p>\n","protected":false},"author":1573,"featured_media":585281,"comment_status":"closed","ping_status":"closed","template":"","categories":[808],"tags":[8850],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/go\/583312"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/go"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/types\/go"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/users\/1573"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/comments?post=583312"}],"version-history":[{"count":10,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/go\/583312\/revisions"}],"predecessor-version":[{"id":596804,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/go\/583312\/revisions\/596804"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/media\/585281"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/media?parent=583312"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/categories?post=583312"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/tags?post=583312"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/cross-post-tag?post=583312"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}