<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Go on Jake Bailey</title>
    <link>https://jakebailey.dev/tags/go/</link>
    <description>Recent content in Go on Jake Bailey</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 06 Apr 2025 09:49:14 -0700</lastBuildDate>
    <atom:link href="https://jakebailey.dev/tags/go/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Detecting dubious shadowing in Go</title>
      <link>https://jakebailey.dev/posts/go-shadowing/</link>
      <pubDate>Sun, 06 Apr 2025 09:49:14 -0700</pubDate>
      <guid>https://jakebailey.dev/posts/go-shadowing/</guid>
      <description>The most common porting bug in the TypeScript Go port</description>
      <content:encoded><![CDATA[<p>If you hadn&rsquo;t already heard, we&rsquo;re
<a href="https://devblogs.microsoft.com/typescript/typescript-native-port/">porting the TypeScript compiler to Go</a>.
This is a certified Big Deal, no small feat. Language choice is one of those
contentious things, and I&rsquo;m not going to go into great detail about it here, but
one factor in the language choice is that the ported code is very, very close to
the original TypeScript code.</p>
<p>But obviously Go isn&rsquo;t TypeScript. There are whole classes of bugs that can
happen in Go that won&rsquo;t happen in TypeScript (and vice versa). If you aren&rsquo;t
careful, a direct translation could behave differently, or you could add all new
bugs.</p>
<p>There was one specific kind of bug that came up the most as more and more code
was ported: unintentional shadowing.</p>
<h2 id="enter-the-shadow-realm">Enter the Shadow Realm</h2>
<p>Can you spot the bug?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Checker</span><span class="p">)</span><span class="w"> </span><span class="nf">getUnresolvedSymbolForEntityName</span><span class="p">(</span><span class="nx">name</span><span class="w"> </span><span class="o">*</span><span class="nx">ast</span><span class="p">.</span><span class="nx">Node</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="nx">ast</span><span class="p">.</span><span class="nx">Symbol</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">unresolvedSymbols</span><span class="p">[</span><span class="nx">path</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">result</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">newSymbol</span><span class="p">(</span><span class="nx">ast</span><span class="p">.</span><span class="nx">SymbolFlagsTypeAlias</span><span class="p">,</span><span class="w"> </span><span class="nx">text</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">c</span><span class="p">.</span><span class="nx">unresolvedSymbols</span><span class="p">[</span><span class="nx">path</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">result</span><span class="p">.</span><span class="nx">Parent</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">parentSymbol</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">c</span><span class="p">.</span><span class="nx">declaredTypeLinks</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="nx">result</span><span class="p">).</span><span class="nx">declaredType</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">unresolvedType</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>The intent here was to return the new symbol, but Go&rsquo;s <code>:=</code> operator creates a
new variable within the scope of the if statement&rsquo;s block, so this function
always returns <code>nil</code>. <code>:=</code> should have been <code>=</code>. One character, very hard to
spot.</p>
<p>Seasoned Go devs are probably screaming right now. &ldquo;<em>NoOoOoOo why aren&rsquo;t you
using an early return here??</em>&rdquo; And they&rsquo;re right! A more idiomatic translation
would be:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Checker</span><span class="p">)</span><span class="w"> </span><span class="nf">getUnresolvedSymbolForEntityName</span><span class="p">(</span><span class="nx">name</span><span class="w"> </span><span class="o">*</span><span class="nx">ast</span><span class="p">.</span><span class="nx">Node</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="nx">ast</span><span class="p">.</span><span class="nx">Symbol</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">unresolvedSymbols</span><span class="p">[</span><span class="nx">path</span><span class="p">];</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">newSymbol</span><span class="p">(</span><span class="nx">ast</span><span class="p">.</span><span class="nx">SymbolFlagsTypeAlias</span><span class="p">,</span><span class="w"> </span><span class="nx">text</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">c</span><span class="p">.</span><span class="nx">unresolvedSymbols</span><span class="p">[</span><span class="nx">path</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="p">.</span><span class="nx">Parent</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">parentSymbol</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">c</span><span class="p">.</span><span class="nx">declaredTypeLinks</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="nx">result</span><span class="p">).</span><span class="nx">declaredType</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">unresolvedType</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>But remember, this is a port. We&rsquo;re not really <em>trying</em> to change the style of
the code all of the time. In fact, the code may have been partially
autogenerated and then copy/pasted. Or just split-screened with the original
code and typed out (i.e., error-prone).</p>
<p>If you look at the original TypeScript code, you&rsquo;ll see what we&rsquo;re trying to
emulate:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">getUnresolvedSymbolForEntityName</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">name</span>: <span class="kt">EntityNameOrEntityNameExpression</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">unresolvedSymbols</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="nx">path</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">result</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">unresolvedSymbols</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nx">path</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">result</span> <span class="o">=</span> <span class="nx">createSymbol</span><span class="p">(</span><span class="nx">SymbolFlags</span><span class="p">.</span><span class="nx">TypeAlias</span><span class="p">,</span> <span class="nx">text</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">parent</span> <span class="o">=</span> <span class="nx">parentSymbol</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">links</span><span class="p">.</span><span class="nx">declaredType</span> <span class="o">=</span> <span class="nx">unresolvedType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Should this have been an early return? In my opinion? Yes, absolutely.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
Regardless of the language. But, I didn&rsquo;t write this code, it just is what it
is.</p>
<p>This bug kept happening over and over during the port; multiple people on the
team complained about it. It&rsquo;s not hard to see why; it becomes muscle memory to
type <code>:=</code>, and then it&rsquo;s just one character away from <code>=</code> so not too easy to
notice (yourself, or in review). The above example is one of the easier ones to
see visually, but we have other examples too. For example, this kind of code is
all over our type relations code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">switch</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">source</span><span class="p">.</span><span class="nx">flags</span><span class="o">&amp;</span><span class="nx">TypeFlagsIndexedAccess</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">isRelatedTo</span><span class="p">(</span><span class="nx">source</span><span class="p">.</span><span class="nx">objectType</span><span class="p">,</span><span class="w"> </span><span class="nx">target</span><span class="p">.</span><span class="nx">objectType</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">TernaryFalse</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">result</span><span class="w"> </span><span class="o">&amp;=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">isRelatedTo</span><span class="p">(</span><span class="nx">source</span><span class="p">.</span><span class="nx">indexType</span><span class="p">,</span><span class="w"> </span><span class="nx">target</span><span class="p">.</span><span class="nx">indexType</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">TernaryFalse</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">result</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>You can imagine maybe that assignment should have been a <code>:=</code>.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> Or
maybe something else is wrong? Hard to say.</p>
<h2 id="a-little-goanalysis-goes-a-long-way">A little <code>go/analysis</code> goes a long way</h2>
<p>How do we avoid these problems?</p>
<p>As a compiler dev, I only have one solution to every problem: static analysis.</p>
<p>Go has a great static analysis framework called
<a href="https://pkg.go.dev/golang.org/x/tools/go/analysis"><code>go/analysis</code></a>, which is
itself built upon Go&rsquo;s built-in AST and type checking packages (<code>go/ast</code>,
<code>go/types</code>). With it, we can create our own analyzers to run over our code and
find errors. For convenience, those analyzers can then be compiled into
<code>golangci-lint</code> to be run with the rest of the linters.</p>
<p>An <code>Analyzer</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">myAnalyzer</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">analysis</span><span class="p">.</span><span class="nx">Analyzer</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;myAnalyzer&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Run</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">pass</span><span class="w"> </span><span class="o">*</span><span class="nx">analysis</span><span class="p">.</span><span class="nx">Pass</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">any</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">pass</span><span class="p">.</span><span class="nf">Reportf</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nf">Pos</span><span class="p">(),</span><span class="w"> </span><span class="s">&#34;%s is doing a bad thing!&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">node</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Declare an <code>Analyzer</code>, then the <code>Run</code> function is given all of the information
about the code being analyzed, report errors (even fixes and related positions,
LSP-style), even return analysis results to be consumed by other passes.</p>
<p>The Go team has actually already created a
<a href="https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow"><code>shadow</code></a>
analyzer that looks for mistakenly shadowed variables. How does it work?
Consider the following code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">f</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">condition</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>This code probably has a shadowing bug. The <code>shadow</code> pass determines this by
checking every variable to see if there&rsquo;s another variable it could shadow (same
name in a parent scope, with the same type), then checks to see if that
potentially shadowed variable is used &ldquo;after&rdquo; the inner declaration, where
&ldquo;after&rdquo; is a position check. This avoids false positives like when we don&rsquo;t use
the <code>outer</code> value later:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">f</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">condition</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// no use of &#34;value&#34; here!</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">someOtherValue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>This method is pretty good and does catch many bugs, but it&rsquo;s not perfect. Take
for example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">f</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">condition</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1234</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Is this a shadowing bug? I&rsquo;d say it isn&rsquo;t. Nothing we do in the inner scope is
observable in the outer scope since we&rsquo;re returning early. In different words,
no <em>use</em> of the outer scope&rsquo;s <code>value</code> is <em>reachable</em> from the shadowing in the
inner scope.</p>
<h2 id="going-with-the-control-flow">Going with the (control) flow</h2>
<p>Yes, I said it, the magic word, <em>reachable</em>. The thing we <em>want</em> to be checking
is whether any assignment to the inner declaration could <em>reach</em> a use of the
outer declaration. This is all just a dataflow analysis question in disguise.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>The existing <code>shadow</code> pass approximates reachability using source positions.
This is fast and easy for sure; just look up the scope chain, find what you
might shadow, then check if any use is &ldquo;after&rdquo; the shadowing.</p>
<p>But this had enough false positives that I wasn&rsquo;t comfortable enabling it.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
I figured there must be a way to use a proper
<a href="https://en.wikipedia.org/wiki/Control-flow_graph">control-flow graph (CFG)</a> to
figure this out.</p>
<p>Fortunately, control-flow graphs are a well established concept and the Go
tooling has ways to get them. The predominant way to do this is to use the
<a href="https://pkg.go.dev/golang.org/x/tools/go/ssa"><code>go/ssa</code></a> package, which builds a
<a href="https://en.wikipedia.org/wiki/Static_single-assignment_form">static single assignment (SSA)</a>
representation of the code. I opted <em>not</em> to use it, however. The SSA
representation is pretty fine-grained with its own quirks and (in my opinion)
better suited for more complicated analyses.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
<p>Instead, I used <a href="https://pkg.go.dev/golang.org/x/tools/go/cfg"><code>go/cfg</code></a>, which
builds simple control flow graphs out of the AST.</p>
<p>Asking for this info is pretty straightforward; just declare that your analyzer
needs it, and then grab its result.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">shadowAnalyzer</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">analysis</span><span class="p">.</span><span class="nx">Analyzer</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Requires</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="o">*</span><span class="nx">analysis</span><span class="p">.</span><span class="nx">Analyzer</span><span class="p">{</span><span class="nx">ctrlflow</span><span class="p">.</span><span class="nx">Analyzer</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Run</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">pass</span><span class="w"> </span><span class="o">*</span><span class="nx">analysis</span><span class="p">.</span><span class="nx">Pass</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">any</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">cfgs</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">pass</span><span class="p">.</span><span class="nx">ResultOf</span><span class="p">[</span><span class="nx">ctrlflow</span><span class="p">.</span><span class="nx">Analyzer</span><span class="p">].(</span><span class="o">*</span><span class="nx">ctrlflow</span><span class="p">.</span><span class="nx">CFGs</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">cfg</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cfgs</span><span class="p">.</span><span class="nf">FuncDecl</span><span class="p">(</span><span class="nx">node</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>What does the CFG look like for our previous example? For reference, the code
was:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">f</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">condition</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1234</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>This produces a CFG that looks like:</p>


<style>
div:nth-child(1 of .goat) > svg > g {
    text[x="16"][y="20"],
    text[x="24"][y="20"],
    text[x="32"][y="20"],
    text[x="40"][y="20"],
    text[x="48"][y="20"] { color: var(--safe-magenta); }
    text[x="280"][y="148"],
    text[x="288"][y="148"],
    text[x="296"][y="148"],
    text[x="304"][y="148"],
    text[x="312"][y="148"] { color: var(--safe-magenta); }
    text[x="16"][y="148"],
    text[x="24"][y="148"],
    text[x="32"][y="148"],
    text[x="40"][y="148"],
    text[x="48"][y="148"] { color: var(--safe-blue); }
    text[x="80"][y="164"],
    text[x="88"][y="164"],
    text[x="96"][y="164"],
    text[x="104"][y="164"],
    text[x="112"][y="164"] { color: var(--safe-blue); }
    text[x="72"][y="180"],
    text[x="80"][y="180"],
    text[x="88"][y="180"],
    text[x="96"][y="180"],
    text[x="104"][y="180"] { color: var(--safe-blue); }
}
</style>






<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 344 217"
      >
      <g transform='translate(8,16)'>
<path d='M 0,0 L 104,0' fill='none' stroke='currentColor'></path>
<path d='M 0,48 L 32,48' fill='none' stroke='currentColor'></path>
<path d='M 32,48 L 72,48' fill='none' stroke='currentColor'></path>
<path d='M 72,48 L 104,48' fill='none' stroke='currentColor'></path>
<path d='M 88,80 L 216,80' fill='none' stroke='currentColor'></path>
<path d='M 0,128 L 176,128' fill='none' stroke='currentColor'></path>
<path d='M 208,128 L 328,128' fill='none' stroke='currentColor'></path>
<path d='M 208,160 L 328,160' fill='none' stroke='currentColor'></path>
<path d='M 0,192 L 176,192' fill='none' stroke='currentColor'></path>
<path d='M 0,0 L 0,48' fill='none' stroke='currentColor'></path>
<path d='M 0,128 L 0,192' fill='none' stroke='currentColor'></path>
<path d='M 32,48 L 32,112' fill='none' stroke='currentColor'></path>
<path d='M 72,48 L 72,64' fill='none' stroke='currentColor'></path>
<path d='M 104,0 L 104,48' fill='none' stroke='currentColor'></path>
<path d='M 176,128 L 176,192' fill='none' stroke='currentColor'></path>
<path d='M 208,128 L 208,160' fill='none' stroke='currentColor'></path>
<path d='M 232,96 L 232,112' fill='none' stroke='currentColor'></path>
<path d='M 328,128 L 328,160' fill='none' stroke='currentColor'></path>
<path d='M 32,112 L 32,120' fill='none' stroke='currentColor'></path>
<polygon points='48.000000,112.000000 36.000000,106.400002 36.000000,117.599998' fill='currentColor' transform='rotate(90.000000, 32.000000, 112.000000)'></polygon>
<path d='M 232,112 L 232,120' fill='none' stroke='currentColor'></path>
<polygon points='248.000000,112.000000 236.000000,106.400002 236.000000,117.599998' fill='currentColor' transform='rotate(90.000000, 232.000000, 112.000000)'></polygon>
<path d='M 72,64 A 16,16 0 0,0 88,80' fill='none' stroke='currentColor'></path>
<path d='M 216,80 A 16,16 0 0,1 232,96' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='16' y='148' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='16' y='164' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='16' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='24' y='148' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='24' y='164' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='24' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='32' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='40' y='180' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='164' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='88' y='180' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='120' y='180' fill='currentColor' style='font-size:1em'>+</text>
<text text-anchor='middle' x='136' y='180' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='144' y='180' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='152' y='180' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='160' y='180' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='224' y='148' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='232' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='240' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='248' y='148' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='256' y='148' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='264' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='280' y='148' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='288' y='148' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='296' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='304' y='148' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='312' y='148' fill='currentColor' style='font-size:1em'>e</text>
</g>

    </svg>
  
</div>

<details>
  <summary>Expand to see the above in textual form</summary>
  <pre tabindex="0"><code>.0: # Body@L5
        value := 1
        condition
        succs: 1 2

.1: # IfThen@L7
        value := 2
        println(value)
        return value + 1234

.2: # IfDone@L7
        return value
</code></pre></details>

<p>This is exactly what we need! If we start at the inner declaration of <code>value</code>,
we can see that it doesn&rsquo;t flow into any use of the outer declaration, so this
code is safe.</p>
<p>Let&rsquo;s try something a little more complicated:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">f</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nx">N</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">i</span><span class="o">%</span><span class="mi">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nb">println</span><span class="p">(</span><span class="s">&#34;continue!&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">continue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1234</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Is there a shadowing bug here? Let&rsquo;s consult the CFG:</p>


<style>
div:nth-child(2 of .goat) > svg > g {
    text[x="16"][y="20"],
    text[x="24"][y="20"],
    text[x="32"][y="20"],
    text[x="40"][y="20"],
    text[x="48"][y="20"] { color: var(--safe-magenta); }
    text[x="304"][y="20"],
    text[x="312"][y="20"],
    text[x="320"][y="20"],
    text[x="328"][y="20"],
    text[x="336"][y="20"] { color: var(--safe-magenta); }
    text[x="256"][y="84"],
    text[x="264"][y="84"],
    text[x="272"][y="84"],
    text[x="280"][y="84"],
    text[x="288"][y="84"] { color: var(--safe-blue); }
    text[x="248"][y="212"],
    text[x="256"][y="212"],
    text[x="264"][y="212"],
    text[x="272"][y="212"],
    text[x="280"][y="212"] { color: var(--safe-blue); }
    text[x="240"][y="228"],
    text[x="248"][y="228"],
    text[x="256"][y="228"],
    text[x="264"][y="228"],
    text[x="272"][y="228"] { color: var(--safe-blue); }
}
</style>






<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 368 265"
      >
      <g transform='translate(8,16)'>
<path d='M 0,0 L 104,0' fill='none' stroke='currentColor'></path>
<path d='M 136,0 L 200,0' fill='none' stroke='currentColor'></path>
<path d='M 232,0 L 352,0' fill='none' stroke='currentColor'></path>
<path d='M 104,16 L 128,16' fill='none' stroke='currentColor'></path>
<path d='M 200,16 L 224,16' fill='none' stroke='currentColor'></path>
<path d='M 136,32 L 176,32' fill='none' stroke='currentColor'></path>
<path d='M 176,32 L 200,32' fill='none' stroke='currentColor'></path>
<path d='M 232,32 L 352,32' fill='none' stroke='currentColor'></path>
<path d='M 0,48 L 104,48' fill='none' stroke='currentColor'></path>
<path d='M 240,64 L 344,64' fill='none' stroke='currentColor'></path>
<path d='M 32,80 L 144,80' fill='none' stroke='currentColor'></path>
<path d='M 192,80 L 232,80' fill='none' stroke='currentColor'></path>
<path d='M 240,112 L 272,112' fill='none' stroke='currentColor'></path>
<path d='M 272,112 L 312,112' fill='none' stroke='currentColor'></path>
<path d='M 312,112 L 344,112' fill='none' stroke='currentColor'></path>
<path d='M 40,128 L 224,128' fill='none' stroke='currentColor'></path>
<path d='M 232,144 L 256,144' fill='none' stroke='currentColor'></path>
<path d='M 40,160 L 96,160' fill='none' stroke='currentColor'></path>
<path d='M 96,160 L 224,160' fill='none' stroke='currentColor'></path>
<path d='M 168,192 L 344,192' fill='none' stroke='currentColor'></path>
<path d='M 72,208 L 120,208' fill='none' stroke='currentColor'></path>
<path d='M 32,224 L 72,224' fill='none' stroke='currentColor'></path>
<path d='M 72,240 L 96,240' fill='none' stroke='currentColor'></path>
<path d='M 96,240 L 120,240' fill='none' stroke='currentColor'></path>
<path d='M 168,240 L 344,240' fill='none' stroke='currentColor'></path>
<path d='M 0,0 L 0,48' fill='none' stroke='currentColor'></path>
<path d='M 16,96 L 16,208' fill='none' stroke='currentColor'></path>
<path d='M 40,128 L 40,160' fill='none' stroke='currentColor'></path>
<path d='M 72,208 L 72,224' fill='none' stroke='currentColor'></path>
<path d='M 72,224 L 72,240' fill='none' stroke='currentColor'></path>
<path d='M 96,160 L 96,192' fill='none' stroke='currentColor'></path>
<path d='M 104,0 L 104,16' fill='none' stroke='currentColor'></path>
<path d='M 104,16 L 104,48' fill='none' stroke='currentColor'></path>
<path d='M 120,208 L 120,240' fill='none' stroke='currentColor'></path>
<path d='M 136,0 L 136,32' fill='none' stroke='currentColor'></path>
<path d='M 160,48 L 160,64' fill='none' stroke='currentColor'></path>
<path d='M 168,192 L 168,240' fill='none' stroke='currentColor'></path>
<path d='M 176,32 L 176,64' fill='none' stroke='currentColor'></path>
<path d='M 200,0 L 200,16' fill='none' stroke='currentColor'></path>
<path d='M 200,16 L 200,32' fill='none' stroke='currentColor'></path>
<path d='M 224,128 L 224,160' fill='none' stroke='currentColor'></path>
<path d='M 232,0 L 232,32' fill='none' stroke='currentColor'></path>
<path d='M 240,64 L 240,112' fill='none' stroke='currentColor'></path>
<path d='M 272,112 L 272,128' fill='none' stroke='currentColor'></path>
<path d='M 312,112 L 312,176' fill='none' stroke='currentColor'></path>
<path d='M 344,64 L 344,112' fill='none' stroke='currentColor'></path>
<path d='M 344,192 L 344,240' fill='none' stroke='currentColor'></path>
<path d='M 352,0 L 352,32' fill='none' stroke='currentColor'></path>
<path d='M 96,192 L 96,200' fill='none' stroke='currentColor'></path>
<polygon points='112.000000,192.000000 100.000000,186.399994 100.000000,197.600006' fill='currentColor' transform='rotate(90.000000, 96.000000, 192.000000)'></polygon>
<polygon points='136.000000,16.000000 124.000000,10.400000 124.000000,21.600000' fill='currentColor' transform='rotate(0.000000, 128.000000, 16.000000)'></polygon>
<path d='M 160,40 L 160,48' fill='none' stroke='currentColor'></path>
<polygon points='176.000000,48.000000 164.000000,42.400002 164.000000,53.599998' fill='currentColor' transform='rotate(270.000000, 160.000000, 48.000000)'></polygon>
<polygon points='232.000000,16.000000 220.000000,10.400000 220.000000,21.600000' fill='currentColor' transform='rotate(0.000000, 224.000000, 16.000000)'></polygon>
<polygon points='240.000000,80.000000 228.000000,74.400002 228.000000,85.599998' fill='currentColor' transform='rotate(0.000000, 232.000000, 80.000000)'></polygon>
<polygon points='240.000000,144.000000 228.000000,138.399994 228.000000,149.600006' fill='currentColor' transform='rotate(180.000000, 232.000000, 144.000000)'></polygon>
<path d='M 312,176 L 312,184' fill='none' stroke='currentColor'></path>
<polygon points='328.000000,176.000000 316.000000,170.399994 316.000000,181.600006' fill='currentColor' transform='rotate(90.000000, 312.000000, 176.000000)'></polygon>
<path d='M 32,80 A 16,16 0 0,0 16,96' fill='none' stroke='currentColor'></path>
<path d='M 160,64 A 16,16 0 0,1 144,80' fill='none' stroke='currentColor'></path>
<path d='M 176,64 A 16,16 0 0,0 192,80' fill='none' stroke='currentColor'></path>
<path d='M 272,128 A 16,16 0 0,1 256,144' fill='none' stroke='currentColor'></path>
<path d='M 16,208 A 16,16 0 0,0 32,224' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>+</text>
<text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>+</text>
<text text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'>&lt;</text>
<text text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='184' y='20' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='212' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='184' y='228' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'>!</text>
<text text-anchor='middle' x='192' y='212' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='192' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='200' y='148' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='200' y='212' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='200' y='228' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='208' y='148' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='208' y='212' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='208' y='228' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='216' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='216' y='228' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='224' y='212' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='224' y='228' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='232' y='212' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='240' y='212' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='240' y='228' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='248' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='248' y='212' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='248' y='228' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='256' y='84' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='256' y='212' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='256' y='228' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='264' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='264' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'>%</text>
<text text-anchor='middle' x='264' y='212' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='264' y='228' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='272' y='84' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='272' y='212' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='272' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='280' y='84' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='280' y='212' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='288' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='288' y='212' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='288' y='228' fill='currentColor' style='font-size:1em'>+</text>
<text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='304' y='228' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='312' y='84' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='312' y='228' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='320' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='320' y='228' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='328' y='20' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='328' y='84' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='328' y='228' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='336' y='20' fill='currentColor' style='font-size:1em'>e</text>
</g>

    </svg>
  
</div>

<details>
  <summary>Expand to see the above in textual form</summary>
  <pre tabindex="0"><code>.0: # Body@L5
        value := 1
        i := 0
        succs: 3

.1: # ForBody@L7
        value := 2
        i%2 == 0
        succs: 5 6

.2: # ForDone@L7
        return value

.3: # ForLoop@L7
        i &lt; N
        succs: 1 2

.4: # ForPost@L7
        i++
        succs: 3

.5: # IfThen@L10
        println(&#34;continue!&#34;)
        succs: 4

.6: # IfDone@L10
        println(value)
        return value + 1234
</code></pre></details>

<p>If we follow the control flow from the node containing the inner declaration, we
can see that there <em>is</em> a path to <code>return value</code> in the outer scope. If <code>:=</code> was
meant to be <code>=</code>, the behavior would change; the function would return 2 instead
of 1. So, we say this is a potential shadowing bug, and so that inner variable
would be best renamed.</p>
<p>This CFG-based approach also works nicely for other constructs as well; we can
follow the inner declaration all the way through if statements, loops, switches,
everything; no special casing required.</p>
<p>The only interesting case is function literals. Is there a shadowing bug in this
code?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">f</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">callIt</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">println</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>I&rsquo;d say &ldquo;maybe&rdquo;, since swapping <code>:=</code> to <code>=</code> could change the behavior of the
code. The CFG wouldn&rsquo;t exactly show this, since the two functions have different
CFGs entirely. How we choose to relate them is pure choice! It&rsquo;s actually a
pretty similar problem to the infamous
<a href="https://github.com/microsoft/TypeScript/issues/9998">TypeScript issue 9998</a>; do
we consider this code to have executed immediately? Never? Potentially at any
time? I chose to ignore the problem and simply disallow shadowing across
function boundaries, which thankfully didn&rsquo;t have too high of a false-positive
rate to feel bad.</p>
<h2 id="enabling-the-analyzer">Enabling the analyzer</h2>
<p>With all of this in place, I created a lint rule that we could add to our
<code>customlint</code> plugin for <code>golangci-lint</code>, which you can see in
<a href="https://github.com/microsoft/typescript-go/pull/365">typescript-go PR #365</a>.
Surprisingly, this didn&rsquo;t negatively affect lint time. Most of the time, there
isn&rsquo;t shadowing,
<a href="https://pkg.go.dev/golang.org/x/tools/go/ast/inspector"><code>ast/inspector</code></a> avoids
the need to check a lot of the AST<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>, and there are some simple
checks that we can do on top of that.</p>
<p>The PR only fixed one bug, which is pretty good, but less than I was hoping for;
it turns out that people on the team had already been wasting time debugging to
find these issues, so they were all pretty much fixed already. Thankfully, the
rule seems to be very reliable, and no new shadowing bugs have appeared.</p>
<p>Anyway, hopefully that was an interesting look at one source of potential bugs
in Go (especially for ported code), and how static analysis can help. There are
of course <em>other</em> sources of bugs in Go, and I&rsquo;m planning to write about how I
was above to catch those too. Stay tuned for that!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I wouldn&rsquo;t have used an assignment expression either.
Expressions shouldn&rsquo;t assign things! You cannot convince me otherwise. My
brain just isn&rsquo;t wired for looking for side effects in the middle of
something else. Sorry (not sorry).&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Or maybe you can imagine this code should have been written
with early returns too. I digress.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>There&rsquo;s probably some proper way to describe this in terms of
&ldquo;dominators&rdquo;, &ldquo;dominance frontiers&rdquo;, a &ldquo;dominator tree&rdquo;, something like
that. But if I do the math, it&rsquo;s been almost 10 years since I took a
compiler optimization course, and I can&rsquo;t say I understood the terminology
back then either. So let&rsquo;s just stick with &ldquo;reachability&rdquo;.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>I <em>think</em> the Go team would agree;
<a href="https://pkg.go.dev/golang.org/x/tools@v0.31.0/go/analysis/passes/shadow">the docs say</a>:
&ldquo;[this analyzer] generates too many false positives and is not yet enabled
by default&rdquo;.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>I found that it modified the structure of the code enough to make it
difficult to work with for this particular use, doing things like constant
propagation and simplification. This is definitely great for other
analyzers, but it seemed a little challenging to use in my case. It&rsquo;s a
shame; <code>go/analysis</code> allows passes to share results, so using <code>go/ssa</code> would
have effectively been &ldquo;free&rdquo; via one of the other lint rules we run.
Thankfully, <code>go/cfg</code> is plenty fast and it doesn&rsquo;t seem to have much of an
impact on our lint time.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>This deserves its own post, honestly. In short, the
<a href="https://pkg.go.dev/golang.org/x/tools/go/ast/inspector"><code>ast/inspector</code></a>
package (along with the
<a href="https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inspect"><code>inspector</code></a>
pass) amortizes the cost of AST walks looking for <em>specific</em> node kinds by
walking all ASTs once, keeping track of which nodes are present in 32-bit
bitmasks, which works since Go has fewer than 32 node types. Then you can
repeatedly ask for certain nodes, and &ldquo;is this one of the nodes I need&rdquo; is
just a bitwise OR. I briefly thought &ldquo;oh man, can we do this in
TypeScript?&rdquo;, but then I realized that our AST has <em>349</em> node kinds, way
more than could fit in a single integer. Drat. It turns out that simple
languages are in fact faster to analyze (shocker).&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
  </channel>
</rss>
