Hello everyone.
I have a question about a common programming error I have made several times. It regards the use of a pointer to a loop variable outside of the loop. Basically, we want to convert a slice, map, or channel of items (the source sequence) into a map, slice, or channel of pointers to respective items in the source sequence. But, rather than point to the actual values, we point to the loop variable.
I have written
some code in a GitHub gist to demonstrate this mistake. I think this mistake can be caught by code analysis tools, but I don't know of any that catches these. If there is any, please let me know so that I can save my future self some unnecessary trouble. If there isn't, how can I make one (probably add it to vet)?
Here's the code sample, if you prefer to read it all here.
// escape_test.go
package main_test
import (
"testing"
"testing/quick"
"unsafe"
)
func all(xs []*int, f func(x *int) bool) (result bool) {
result = true
for _, v := range xs {
result = result && f(v)
}
return result
}
func Test_references_to_loop_variable_outside_the_loop_have_same_value(t *testing.T) {
f := func(xs []int) bool {
var loopVariableAddress uintptr
var zeroOfUintptr uintptr
var copies = make([]*int, 0, len(xs))
for _, x := range xs {
if loopVariableAddress == zeroOfUintptr {
// Store the loop variable's address for later comparison.
loopVariableAddress = uintptr(unsafe.Pointer(&x))
}
// Copy the address of x into a slice.
copies = append(copies, &x)
// The append statement above is most likely a mistake.
// We probably mean
// copies = append(copies, &xs[index]);
// assuming `index` is the ignored loop index.
}
return all(copies, func(x *int) bool {
// All values in `copies` are the same pointer address.
return uintptr(unsafe.Pointer(x)) == loopVariableAddress && *x == *copies[0]
})
}
if err := quick.Check(f, nil); err != nil {
t.Fatal(err)
}
}