google/go-cmp

fatal error: checkptr: pointer arithmetic computed bad pointer value (test -race)

smyrman opened this issue · 3 comments

I found a possible issue similar to #167. This time however, the issue occurs on the following line. The issue occur when running test -race of my package on Linux (Github Actions) with Go 1.19.

Failing line:

step.vx, step.xkey = vx.Index(ix), ix

Extract of test-run stack-trace:

fatal error: checkptr: pointer arithmetic computed bad pointer value

goroutine 185 [running]:
runtime.throw({0xdc4d45?, 0x43e72d?})
	/opt/hostedtoolcache/go/1.19.5/x64/src/runtime/panic.go:10[47](https://github.com/clarify/api/actions/runs/4020253092/jobs/6908017645#step:9:48) +0x5d fp=0xc0001cb1[48](https://github.com/clarify/api/actions/runs/4020253092/jobs/6908017645#step:9:49) sp=0xc0001cb118 pc=0x46fb5d
runtime.checkptrArithmetic(0x203000?, {0xc0001cb1f8?, 0x53626f?, 0x4a8ae5?})
	/opt/hostedtoolcache/go/1.19.5/x64/src/runtime/checkptr.go:52 +0xbb fp=0xc0001cb178 sp=0xc0001cb148 pc=0x43e8fb
reflect.add(...)
	/opt/hostedtoolcache/go/1.19.5/x64/src/reflect/type.go:1094
reflect.arrayAt(...)
	/opt/hostedtoolcache/go/1.19.5/x64/src/reflect/value.go:2701
reflect.Value.Index({0xcddde0?, 0xc0000d9708?, 0xc0001cb250?}, 0x0)
	/opt/hostedtoolcache/go/1.19.5/x64/src/reflect/value.go:1416 +0x254 fp=0xc0001cb218 sp=0xc0001cb178 pc=0x548134
github.com/google/go-cmp/cmp.(*state).compareSlice.func1(0x0, 0xffffffffffffffff)
	/home/runner/go/pkg/mod/github.com/google/go-cmp@v0.5.9/cmp/compare.go:440 +0x8d fp=0xc0001cb280 sp=0xc0001cb218 pc=0xc58e0d
github.com/google/go-cmp/cmp.(*state).compareSlice(0xc000246780, {0x114b340, 0xcddde0}, {0xcddde0?, 0xc0000d9708?, 0xc000250af0?}, {0xcddde0?, 0xc0000d9728?, 0x3c2ff40?})
	/home/runner/go/pkg/mod/github.com/google/go-cmp@v0.5.9/cmp/compare.go:462 +0x917 fp=0xc0001cb478 sp=0xc0001cb280 pc=0xc58237
github.com/google/go-cmp/cmp.(*state).compareAny(0xc000246780, {0x11459e0, 0xc0001bfc00?})
	/home/runner/go/pkg/mod/github.com/google/go-cmp@v0.5.9/cmp/compare.go:289 +0x12a5 fp=0xc0001cb718 sp=0xc0001cb478 pc=0xc5[49](https://github.com/clarify/api/actions/runs/4020253092/jobs/6908017645#step:9:50)65
github.com/google/go-cmp/cmp.(*state).compareStruct(0xc000246780, {0x114b340, 0xd44[52](https://github.com/clarify/api/actions/runs/4020253092/jobs/6908017645#step:9:53)0}, {0xd44520?, 0xc0000d9700?, 0x0?}, {0xd44520?, 0xc0000d9720?, 0xc000219c00?})
	/home/runner/go/pkg/mod/github.com/google/go-cmp@v0.5.9/cmp/compare.go:412 +0xa52 fp=0xc0001cb8d8 sp=0xc0001cb718 pc=0xc577b2
github.com/google/go-cmp/cmp.(*state).compareAny(0xc000246780, {0x1144c90, 0xc000219c00?})
	/home/runner/go/pkg/mod/github.com/google/go-cmp@v0.5.9/cmp/compare.go:287 +0x1412 fp=0xc0001cbb78 sp=0xc0001cb8d8 pc=0xc[54](https://github.com/clarify/api/actions/runs/4020253092/jobs/6908017645#step:9:55)ad2
github.com/google/go-cmp/cmp.Diff({0xd44520, 0xc0000d9700}, {0xd44520, 0xc0000d9720}, {0x0, 0x0, 0x0})
	/home/runner/go/pkg/mod/github.com/google/go-cmp@v0.5.9/cmp/compare.go:120 +0xd5 fp=0xc0001cbbf8 sp=0xc0001cbb78 pc=0xc52175
dsnet commented

The only use of unsafe in cmp is to forcibly unexport an unexported field in a struct:

// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
// a struct such that the value has read-write permissions.
//
// The parent struct, v, must be addressable, while f must be a StructField
// describing the field to retrieve. If addr is false,
// then the returned value will be shallowed copied to be non-addressable.
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
if !addr {
// A field is addressable if and only if the struct is addressable.
// If the original parent value was not addressable, shallow copy the
// value to make it non-addressable to avoid leaking an implementation
// detail of how forcibly exporting a field works.
if ve.Kind() == reflect.Interface && ve.IsNil() {
return reflect.Zero(f.Type)
}
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
}
return ve
}

The code is simple, has been reviewed by many eyes, and is probably correct.

The cause of this panic is more likely due to a race condition in your program. Have you run your tests with the "-race" flag?

I am running the the test with -race. I will do some more investigation before coming back.

I have done some more investigation.

TL;DR: There is a data-race in the application that gets detected by Go.19.x in particular. When issuing code that calls reflect.Value.Index (such as cmp.Diff, t.Log("%#v", v) or fmt.Printf("%#v", v)), I get the checkptr error instead.

In detail:

It appear to me that the error happens inside reflect.Value.Index, or more specifically in the the reflect/type.go add function when running tests with test -race. However, this might not actually be the root cause of the issue.

So far, I have found that the issue is not reported for Go 1.18.x; it only appears on Go 1.19.x. I have tested 1.19.5, 1.19.4, and 1.19.1, and all can reproduce the issue. The code involved is using generics, and the failing test doesn't cause a panic when run seperatly. Only when all pacakge tests are run, do I get the issue. Even adding a new (unrleated) test function, will make the issue go away.

This can not only be triggered by go-cmp, but also by a simple t.Logf("DEBUG %#v", myRef). which again call fmt.printf and the same reflect.Value.Index path. Which probably means that go-cmp is off the hook here. Removing both the cmp.Diff and t.Logf statements, will reveal that there is actually a data race going. on.

WARNING: DATA RACE

So it seams that the call to reflect.Value.Index is causing the data race message to be surpressed?

I will close this issue for now.