Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit memory aliasing in for loop

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.

like image 726
Dean Avatar asked Jun 18 '20 08:06

Dean


2 Answers

Indexing will solve the problem:

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

}
like image 156
Xesina Avatar answered Nov 19 '22 17:11

Xesina


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.

like image 55
blackgreen Avatar answered Nov 19 '22 17:11

blackgreen