74

I'm using golangci-lint and I'm getting an error on the following code:

versions []ObjectDescription
... (populate versions) ...

for i, v := range versions {
    res := createWorkerFor(&v)
    ...

}

the error is:

G601: Implicit memory aliasing in for loop. (gosec)
                     res := createWorkerFor(&v)
                                            ^

What does "implicit memory aliasing in for loop" mean, exactly? I could not find any error description in the golangci-lint documentation. I don't understand this error.

0

2 Answers 2

106

Go 1.22 (February 2024)

This issue disappears with Go 1.22 because the loop variable is not reused. From the draft release notes:

In Go 1.22, each iteration of the loop creates new variables, to avoid accidental sharing bugs.

Older versions of gosec — and tools that use gosec such as golangci-lint — might still report an issue here. If your go.mod file declares the Go version as 1.22 or above, this could becomes a false positive. Run unit tests to make sure. Anyway, to exclude a specific line of code from gosec's analysis, add a comment after it:

// #nosec G601

or exclude it using golangci-lint's configuration.

Previous versions

The warning means, in short, that you are taking the address of a loop variable.

This happens because in for statements the iteration variable(s) is reused. At each iteration, the value of the next element in the range expression is assigned to the iteration variable; v doesn't change, only its value changes. Hence, the expression &v is referring to the same location in memory.

The following code prints the same memory address four times:

for _, n := range []int{1, 2, 3, 4} {
    fmt.Printf("%p\n", &n)
}

When you store the address of the iteration variable, or when you use it in a closure inside the loop, by the time you dereference the pointer, its value might have changed. Static analysis tools will detect this and emit the warning you see.

Common ways to prevent the issue are:

  • index the ranged slice/array/map. This takes the address of the actual element at i-th position, instead of the iteration variable
for i := range versions {
    res := createWorkerFor(&versions[i])
}
  • reassign the iteration variable inside the loop
for _, v := range versions {
    v := v
    res := createWorkerFor(&v) // this is now the address of the inner v
}
  • with closures, pass the iteration variable as argument to the closure
for _, v := range versions { 
    go func(arg ObjectDescription) {
        x := &arg // safe
    }(v)
}

In case you dereference sequentially within the loop and you know for sure that nothing is leaking the pointer, you might get away with ignoring this check. However the job of the linter is precisely to report code patterns that could cause issues, so it's a good idea to fix it anyway.

Sign up to request clarification or add additional context in comments.

Comments

52

Indexing will solve the problem:

for i := range versions {
    res := createWorkerFor(&versions[i])
    ...

}

3 Comments

But why do I need to use indexing when I am just passing it to a function that wouldn't leak the pointer?
The linter probably does not know that yout function would not leak the pointer. that's why we can disable such linters: they can often return false positives. imagine that at some point you, or someone else, change the function and it starts to leak the pointer? the function is opaque.
@TiagoPeczenyj thanks for the detailed explanation, although it was meant to be a rhetorical question and 3 other people probably upvoted because they also got annoyed by the false positive

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.