Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: heapBitsSetTypeGCProg: total bits 0 but progSize 1193712 #30606

Closed
pwaller opened this issue Mar 5, 2019 · 15 comments

Comments

Projects
None yet
4 participants
@pwaller
Copy link
Contributor

commented Mar 5, 2019

What version of Go are you using (go version)?

$ go version
go version go1.12 linux/amd64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/pwaller/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/pwaller/.local"
GOPROXY=""
GORACE=""
GOROOT="/home/pwaller/sdk/go1.12"
GOTMPDIR=""
GOTOOLDIR="/home/pwaller/sdk/go1.12/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/pwaller/.local/src/github.com/pwaller/randstruct/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build163535967=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Generate random struct types and random values for fuzz testing some serialization/de-serialization software.

I include the program in the <details> below.

The go.mod file only has this one dependency, for generating random values:

require github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf
main.go example program
package main

import (
	"fmt"
	"reflect"

	fuzz "github.com/google/gofuzz"
)

func main() {
	f := fuzz.
		NewWithSeed(1).
		MaxDepth(2).
		Funcs(
			func(t *T, c fuzz.Continue) {
				typ := randomStruct(c)
				fmt.Printf("%v\n", typ)
				dataPtr := reflect.New(typ)
				c.Fuzz(dataPtr.Interface())
				t.v = dataPtr.Elem().Interface()
			},
		)
	for i := 0; i < 102400; i++ {
		var t T
		f.Fuzz(&t)
	}
}

type T struct{ v interface{} }

func randomKind(c fuzz.Continue) reflect.Kind {
	return kinds[c.Intn(len(kinds))]
}

func randomKindScalar(c fuzz.Continue) reflect.Kind {
	return kindsScalar[c.Intn(len(kindsScalar))]
}

func randomStruct(c fuzz.Continue) reflect.Type {
	return randomType(reflect.Struct, c)
}

func randomType(k reflect.Kind, c fuzz.Continue) reflect.Type {
	if k == reflect.Invalid {
		if c.RandUint64()&0x3 == 0 {
			k = randomKindScalar(c)
		} else {
			k = randomKind(c)
		}
	}
	t, ok := types[k]
	if ok {
		return t
	}

	switch k {
	case reflect.Struct:
		nElem := c.Intn(16)
		fields := make([]reflect.StructField, nElem)
		for i := range fields {
			fields[i] = reflect.StructField{
				Name: names[i],
				Type: randomType(reflect.Invalid, c),
			}
		}

		return reflect.StructOf(fields)

	case reflect.Ptr:
		return reflect.PtrTo(randomType(reflect.Invalid, c))

	case reflect.Array:
		return reflect.ArrayOf(c.Intn(16), randomType(reflect.Invalid, c))

	case reflect.Slice:
		return reflect.SliceOf(randomType(reflect.Invalid, c))

	case reflect.Map:
		var mapType reflect.Type
		valType := randomType(reflect.Invalid, c)
		for mapType == nil {
			func() {
				defer func() { recover() }()
				keyType := randomType(reflect.Invalid, c)
				mapType = reflect.MapOf(keyType, valType)
			}()
		}
		return mapType

	default:
		panic(fmt.Errorf("unimplemented: %v", k))
	}
}

var kindsScalar = [...]reflect.Kind{
	// reflect.Bool,
	// reflect.Int,
	// reflect.Int8,
	// reflect.Int16,
	// reflect.Int32,
	reflect.Int64,
	// reflect.Uint,
	// reflect.Uint8,
	// reflect.Uint16,
	// reflect.Uint32,
	reflect.Uint64,
	// reflect.Uintptr,
	reflect.Float32,
	reflect.Float64,
	// reflect.Complex64,
	// reflect.Complex128,
}

var kinds = [...]reflect.Kind{
	// reflect.Bool,
	// reflect.Int,
	// reflect.Int8,
	// reflect.Int16,
	// reflect.Int32,
	reflect.Int64,
	// reflect.Uint,
	// reflect.Uint8,
	// reflect.Uint16,
	// reflect.Uint32,
	reflect.Uint64,
	// reflect.Uintptr,
	reflect.Float32,
	reflect.Float64,
	// reflect.Complex64,
	// reflect.Complex128,

	reflect.Array,
	// reflect.Chan,
	// reflect.Func,
	// reflect.Interface,
	reflect.Map,
	reflect.Ptr,
	reflect.Slice,
	reflect.String,
	reflect.Struct,
	// // reflect.UnsafePointer,
}

var types = map[reflect.Kind]reflect.Type{
	reflect.Bool:       reflect.ValueOf(bool(false)).Type(),
	reflect.Int:        reflect.ValueOf(int(0)).Type(),
	reflect.Int8:       reflect.ValueOf(int8(0)).Type(),
	reflect.Int16:      reflect.ValueOf(int16(0)).Type(),
	reflect.Int32:      reflect.ValueOf(int32(0)).Type(),
	reflect.Int64:      reflect.ValueOf(int64(0)).Type(),
	reflect.Uint:       reflect.ValueOf(uint(0)).Type(),
	reflect.Uint8:      reflect.ValueOf(uint8(0)).Type(),
	reflect.Uint16:     reflect.ValueOf(uint16(0)).Type(),
	reflect.Uint32:     reflect.ValueOf(uint32(0)).Type(),
	reflect.Uint64:     reflect.ValueOf(uint64(0)).Type(),
	reflect.Uintptr:    reflect.ValueOf(uintptr(0)).Type(),
	reflect.Float32:    reflect.ValueOf(float32(0)).Type(),
	reflect.Float64:    reflect.ValueOf(float64(0)).Type(),
	reflect.Complex64:  reflect.ValueOf(complex64(0)).Type(),
	reflect.Complex128: reflect.ValueOf(complex128(0)).Type(),
	reflect.String:     reflect.ValueOf("").Type(),
}

var names = [...]string{
	"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10",
	"F11", "F12", "F13", "F14", "F15",
}

What did you expect to see?

I expected that it should successfully generate random structure types filled with fields of random types (including nested structures, maps, etc).

What did you see instead?

The code does successfully generate random structures. It can run for quite a long time without issues. However, it also causes runtime crashes. I have chosen my variables to crash things quite quickly in the above example. The crashes manifest after generating 80-1000 structs for my given examples.

I found two different crashes by varying NewWithSeed(1) to NewWithSeed(2):

NewWithSeed(1) : panic: runtime error: invalid memory address or nil pointer dereference

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4746a5]

goroutine 1 [running]:
reflect.StructOf(0xc000fab800, 0xe, 0xe, 0x0, 0x0)
	/home/pwaller/sdk/go1.12/src/reflect/type.go:2684 +0x9b5
main.randomType(0x19, 0xc0010ea0f0, 0xc0000941e0, 0x4f9860, 0x4f9860)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:68 +0x4b7
main.randomStruct(...)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:40
main.main.func1(0xc0010ea0e0, 0xc0010ea0f0, 0xc0000941e0)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:16 +0x57
reflect.Value.call(0x4ddfe0, 0x5093b8, 0x13, 0x500f57, 0x4, 0xc0010dfdc8, 0x2, 0x2, 0xc0010dfda8, 0xc0010ea100, ...)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:447 +0x461
reflect.Value.Call(0x4ddfe0, 0x5093b8, 0x13, 0xc0010dfdc8, 0x2, 0x2, 0x0, 0x0, 0x0)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:308 +0xa4
github.com/google/gofuzz.(*fuzzerContext).tryCustom(0xc0010ea0f0, 0x4d19a0, 0xc0010ea0e0, 0x16, 0xc0010ea0e0)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:330 +0x256
github.com/google/gofuzz.(*fuzzerContext).doFuzz(0xc0010ea0f0, 0x4e5d00, 0xc0010ea0e0, 0x199, 0x0)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:222 +0xbec
github.com/google/gofuzz.(*Fuzzer).fuzzWithContext(...)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:199
github.com/google/gofuzz.(*Fuzzer).Fuzz(0xc000072040, 0x4d19a0, 0xc0010ea0e0)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:173 +0x12c
main.main()
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:25 +0xc8
exit status 2

NewWithSeed(2) : runtime: heapBitsSetTypeGCProg: total bits 0 but progSize 1193712

runtime: heapBitsSetTypeGCProg: total bits 0 but progSize 1193712
fatal error: heapBitsSetTypeGCProg: unexpected bit count

goroutine 1 [running]:
runtime.throw(0x507bf9, 0x2b)
	/home/pwaller/sdk/go1.12/src/runtime/panic.go:617 +0x72 fp=0xc0001e17a0 sp=0xc0001e1770 pc=0x429f22
runtime.heapBitsSetTypeGCProg(0x7f417e961700, 0x20300000000000, 0x7f417eb35fff, 0x1236f0, 0x123700, 0x123700, 0x124000, 0xc0000f6c04)
	/home/pwaller/sdk/go1.12/src/runtime/mbitmap.go:1530 +0x343 fp=0xc0001e1808 sp=0xc0001e17a0 pc=0x4142a3
runtime.heapBitsSetType(0xc00056e000, 0x124000, 0x123700, 0xc00055ea20)
	/home/pwaller/sdk/go1.12/src/runtime/mbitmap.go:1057 +0x8d4 fp=0xc0001e18e0 sp=0xc0001e1808 pc=0x413dd4
runtime.mallocgc(0x124000, 0xc00055ea20, 0x1001, 0x1ac)
	/home/pwaller/sdk/go1.12/src/runtime/malloc.go:969 +0x51c fp=0xc0001e1980 sp=0xc0001e18e0 pc=0x40ad5c
reflect.unsafe_New(0xc00055ea20, 0x1ac)
	/home/pwaller/sdk/go1.12/src/runtime/malloc.go:1073 +0x38 fp=0xc0001e19b0 sp=0xc0001e1980 pc=0x40b418
reflect.New(0x524360, 0xc00055ea20, 0x500e5f, 0x3, 0xc0001e1a58)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:2290 +0x45 fp=0xc0001e19e0 sp=0xc0001e19b0 pc=0x4819b5
main.main.func1(0xc00010fc30, 0xc00010fc40, 0xc0000801e0)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:18 +0xf3 fp=0xc0001e1a78 sp=0xc0001e19e0 pc=0x4c22b3
runtime.call32(0xc000080270, 0x5093b8, 0xc00000c0e0, 0x1800000018)
	/home/pwaller/sdk/go1.12/src/runtime/asm_amd64.s:519 +0x3b fp=0xc0001e1aa8 sp=0xc0001e1a78 pc=0x451adb
reflect.Value.call(0x4ddfe0, 0x5093b8, 0x13, 0x500f57, 0x4, 0xc0001e1dc8, 0x2, 0x2, 0xc0001e1da8, 0xc00010fc50, ...)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:447 +0x461 fp=0xc0001e1cc8 sp=0xc0001e1aa8 pc=0x47ab51
reflect.Value.Call(0x4ddfe0, 0x5093b8, 0x13, 0xc0001e1dc8, 0x2, 0x2, 0x0, 0x0, 0x0)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:308 +0xa4 fp=0xc0001e1d30 sp=0xc0001e1cc8 pc=0x47a5d4
github.com/google/gofuzz.(*fuzzerContext).tryCustom(0xc00010fc40, 0x4d19a0, 0xc00010fc30, 0x16, 0xc00010fc30)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:330 +0x256 fp=0xc0001e1e08 sp=0xc0001e1d30 pc=0x4bf796
github.com/google/gofuzz.(*fuzzerContext).doFuzz(0xc00010fc40, 0x4e5d00, 0xc00010fc30, 0x199, 0x0)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:222 +0xbec fp=0xc0001e1ed8 sp=0xc0001e1e08 pc=0x4bf3fc
github.com/google/gofuzz.(*Fuzzer).fuzzWithContext(...)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:199
github.com/google/gofuzz.(*Fuzzer).Fuzz(0xc000072040, 0x4d19a0, 0xc00010fc30)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:173 +0x12c fp=0xc0001e1f40 sp=0xc0001e1ed8 pc=0x4be5fc
main.main()
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:25 +0xc8 fp=0xc0001e1f98 sp=0xc0001e1f40 pc=0x4c1a58
runtime.main()
	/home/pwaller/sdk/go1.12/src/runtime/proc.go:200 +0x20c fp=0xc0001e1fe0 sp=0xc0001e1f98 pc=0x42b86c
runtime.goexit()
	/home/pwaller/sdk/go1.12/src/runtime/asm_amd64.s:1337 +0x1 fp=0xc0001e1fe8 sp=0xc0001e1fe0 pc=0x453671
exit status 2

Additional notes

  • This FIXME looks suspicious, since it is the site of the first crash in reflect.StructOf:

    go/src/reflect/type.go

    Lines 2683 to 2684 in a563f2f

    // FIXME(sbinet) handle padding, fields smaller than a word
    elemGC := (*[1 << 30]byte)(unsafe.Pointer(ft.typ.gcdata))[:]

  • I have tried reproducing the crash by choosing specific examples of random structs taken from just before the crash and calling reflect.New() on them repeatedly. This hasn't been fruitful for me so far, so I haven't been able to get an example crasher which runs without generating random structs.

  • Different types can be commented out in the var kinds to change what types get produced.

    • Various things work: {Ptr}, {Map}, {Slice} or {Struct} by themselves (with all scalar types except for complex) make it to >1M iterations without crashing. As do {Ptr, Struct}, {Ptr, String}, {Array, String}, {Ptr, Map, String}.
    • Once you've enabled ~four aggregate types it seems to reliably crash at a lower number of iterations (<100k).

The following is the shortest crash length I have found, crashing after only 4 structs are made:

Click to expand configuration for shortest crash length
// With NewWithSeed(1)

var kindsScalar = [...]reflect.Kind{
	// reflect.Bool,
	// reflect.Int,
	// reflect.Int8,
	// reflect.Int16,
	// reflect.Int32,
	reflect.Int64,
	// reflect.Uint,
	// reflect.Uint8,
	// reflect.Uint16,
	// reflect.Uint32,
	reflect.Uint64,
	// reflect.Uintptr,
	// reflect.Float32,
	reflect.Float64,
	// reflect.Complex64,
	// reflect.Complex128,
}

var kinds = [...]reflect.Kind{
	// reflect.Bool,
	// reflect.Int,
	// reflect.Int8,
	// reflect.Int16,
	// reflect.Int32,
	reflect.Int64,
	// reflect.Uint,
	// reflect.Uint8,
	// reflect.Uint16,
	// reflect.Uint32,
	reflect.Uint64,
	// reflect.Uintptr,
	// reflect.Float32,
	reflect.Float64,
	// reflect.Complex64,
	// reflect.Complex128,

	reflect.Array,
	// reflect.Chan,
	// reflect.Func,
	// reflect.Interface,
	// reflect.Map,
	// reflect.Ptr,
	reflect.Slice,
	reflect.String,
	reflect.Struct,
	// // reflect.UnsafePointer,
}
Output and stack trace
0
struct { F0 int64 }
1
struct { F0 string }
2
struct { F0 struct { F0 float64; F1 int64 }; F1 float64; F2 int64; F3 struct { F0 [11]struct { F0 []struct { F0 uint64; F1 []string; F2 []string }; F1 int64; F2 string; F3 uint64; F4 uint64; F5 float64; F6 uint64; F7 float64; F8 int64 }; F1 [9]string; F2 int64; F3 []struct { F0 string; F1 [2]uint64; F2 struct { F0 struct { F0 float64; F1 struct { F0 [][7]string; F1 float64 }; F2 [3]int64; F3 float64; F4 []uint64; F5 [10]uint64; F6 int64; F7 int64; F8 [14]int64 }; F1 uint64; F2 string; F3 struct { F0 float64; F1 int64; F2 float64; F3 uint64; F4 struct {}; F5 float64; F6 float64; F7 uint64; F8 [15]string; F9 int64; F10 string; F11 [1]struct { F0 uint64; F1 uint64; F2 [8]string; F3 float64; F4 [9][]string; F5 [0]float64; F6 [][0]uint64; F7 int64 }; F12 float64; F13 uint64; F14 string }; F4 uint64; F5 uint64 }; F3 struct { F0 [][]struct { F0 int64; F1 string; F2 [3]struct { F0 int64; F1 [][5]struct { F0 int64 }; F2 uint64 }; F3 int64; F4 int64; F5 uint64 }; F1 uint64; F2 [][]string; F3 string; F4 string; F5 float64; F6 uint64; F7 uint64; F8 uint64; F9 [14]struct { F0 uint64; F1 [11]uint64; F2 int64; F3 int64; F4 uint64; F5 []string; F6 [11]struct { F0 [][11]uint64 }; F7 int64; F8 string; F9 float64; F10 int64; F11 []uint64 }; F10 int64; F11 float64; F12 [8]int64; F13 []int64; F14 []int64 }; F4 int64; F5 int64; F6 uint64; F7 [][7]string; F8 string; F9 []struct { F0 uint64; F1 string; F2 float64 }; F10 int64; F11 string; F12 uint64; F13 int64 }; F4 int64; F5 [11]float64 }; F4 string; F5 [0]struct { F0 int64 }; F6 uint64; F7 int64 }
3
struct { F0 float64; F1 float64; F2 int64; F3 uint64 }
4
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4746a5]

goroutine 1 [running]:
reflect.StructOf(0xc0000b9180, 0x6, 0x6, 0x0, 0x0)
	/home/pwaller/sdk/go1.12/src/reflect/type.go:2684 +0x9b5
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d7840)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:69 +0x4b7
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d6a80)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d8080)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d7840)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d7f40)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0xc0000b09a0, 0x4f5100)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0xc0000b0930, 0xc0000beb00)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0xc0000bebe0)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d7840)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x5faa80, 0x4d7800)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x11, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d5480)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:75 +0x26e
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d7f40)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d6a80)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x5faa80, 0x4f5100)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x11, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d7840)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:75 +0x26e
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d6a80)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x524360, 0x4d7f40)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomType(0x19, 0xc000010e90, 0xc0000941e0, 0x4f9860, 0x4f9860)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:65 +0x363
main.randomStruct(...)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:41
main.main.func1(0xc000010e80, 0xc000010e90, 0xc0000941e0)
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:16 +0x57
reflect.Value.call(0x4ddfe0, 0x5093b8, 0x13, 0x500f57, 0x4, 0xc000065dc8, 0x2, 0x2, 0xc000065da8, 0xc000010ea0, ...)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:447 +0x461
reflect.Value.Call(0x4ddfe0, 0x5093b8, 0x13, 0xc000065dc8, 0x2, 0x2, 0x0, 0x0, 0x0)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:308 +0xa4
github.com/google/gofuzz.(*fuzzerContext).tryCustom(0xc000010e90, 0x4d19a0, 0xc000010e80, 0x16, 0xc000010e80)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:330 +0x256
github.com/google/gofuzz.(*fuzzerContext).doFuzz(0xc000010e90, 0x4e5d00, 0xc000010e80, 0x199, 0x0)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:222 +0xbec
github.com/google/gofuzz.(*Fuzzer).fuzzWithContext(...)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:199
github.com/google/gofuzz.(*Fuzzer).Fuzz(0xc000072040, 0x4d19a0, 0xc000010e80)
	/home/pwaller/.local/pkg/mod/github.com/google/gofuzz@v0.0.0-20170612174753-24818f796faf/fuzz.go:173 +0x12c
main.main()
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/main.go:26 +0xe5
exit status 2

/cc @rsc @aclements @mwhudson @sbinet - cc derived from stacktrace since it points at the runtime and reflect package appearing at top of stack traces.

@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

I've built a simpler testcase and published it here: https://gist.github.com/pwaller/a99e3836293e23677ad08052263b293a - it's >200kB.

It eliminates the randomness. Trying to reduce it to a smaller program with goreduce, but noting my progress in case someone else is looking and/or I get sidetracked and fail at making something smaller.

$ wget https://gist.github.com/pwaller/a99e3836293e23677ad08052263b293a/raw/0df788e6e99959501c87bfefe1795f732ad03e4e/testcase.go
$ go run testcase.go
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x46a085]

goroutine 1 [running]:
reflect.StructOf(0xc0002fcf00, 0x6, 0x6, 0x0, 0x0)
	/home/pwaller/sdk/go1.12/src/reflect/type.go:2684 +0x9b5
main.init.ializers()
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/crasher/tmp/testcase.go:28 +0x5e426
exit status 2

@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

Here is a 50 32-line reproducer with no dependencies.

Decreasing any of the array lengths by 1 or removing any field causes it to not crash.

package main

import "reflect"

func main() {}

var typeUint64 = reflect.ValueOf(uint64(0)).Type()

var x = reflect.StructOf([]reflect.StructField{
	{Name: "F1", Type: reflect.ArrayOf(4, typeUint64)},
	{Name: "F5", Type: reflect.ArrayOf(7,
		reflect.StructOf([]reflect.StructField{
			{Name: "F1", Type: reflect.SliceOf(typeUint64)},
			{Name: "F5", Type: reflect.StructOf([]reflect.StructField{
				{Name: "F8", Type: reflect.ArrayOf(16,
					reflect.ArrayOf(1,
						reflect.StructOf([]reflect.StructField{
							{Name: "F1", Type: reflect.ArrayOf(32,
								reflect.StructOf([]reflect.StructField{
									{Name: "F5", Type: reflect.StructOf([]reflect.StructField{
										{Name: "F0", Type: typeUint64},
									})},
								}))},
							{Name: "F6", Type: reflect.ArrayOf(115,
								reflect.StructOf([]reflect.StructField{
									{Name: "F8", Type: typeUint64},
								}))},
						})))},
			})},
		})),
	},
})

$ go run .
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x469d05]

goroutine 1 [running]:
reflect.StructOf(0xc00009aa90, 0x2, 0x2, 0x0, 0x0)
	/home/pwaller/sdk/go1.12/src/reflect/type.go:2684 +0x9b5
main.init.ializers()
	/home/pwaller/.local/tmp/crasher/testcase.go:9 +0xa81
exit status 2
@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

go build -gcflags=-msan: Edit: this was that I was not building with CC=clang go build -msan, but go build -gcflags=-msan, which was wrong. msan actually wasn't doing anything here.

fatal error: msan

goroutine 1 [running, locked to thread]:
runtime.throw(0x4b2ca7, 0x4)
	/home/pwaller/sdk/go1.12/src/runtime/panic.go:617 +0x72 fp=0xc000052758 sp=0xc000052728 pc=0x426ed2
runtime.msanread(0x56b642, 0x1)
	/home/pwaller/sdk/go1.12/src/runtime/msan0.go:19 +0x36 fp=0xc000052778 sp=0xc000052758 pc=0x424116
main.init()
	<autogenerated>:1 +0x3a fp=0xc000052798 sp=0xc000052778 pc=0x483b4a
runtime.main()
	/home/pwaller/sdk/go1.12/src/runtime/proc.go:188 +0x1c8 fp=0xc0000527e0 sp=0xc000052798 pc=0x4287d8
runtime.goexit()
	/home/pwaller/sdk/go1.12/src/runtime/asm_amd64.s:1337 +0x1 fp=0xc0000527e8 sp=0xc0000527e0 pc=0x44f541
@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

Simplest crasher so far:

package main

import "reflect"

func main() {}

func typ(x interface{}) reflect.Type { return reflect.ValueOf(x).Type() }

var z = reflect.StructOf([]reflect.StructField{
	{Name: "F1", Type: reflect.ArrayOf(4, typ(uint64(0)))},
	{Name: "F5", Type: reflect.ArrayOf(2,
		typ(
			struct {
				F1 []uint64
				F8 [8192 - 3 + 1]uint64
			}{},
		)),
	},
})

If I remove the +1 from the array size, it works. The -3 represents the size of the []uint64. Haven't been able to remove anything else or reduce any outer constants any further so far.

@ALTree

This comment has been minimized.

Copy link
Member

commented Mar 6, 2019

I can reproduce this on 1.11 so not a 1.12 regression. Milestoning as 1.13

@ALTree ALTree added this to the Go1.13 milestone Mar 6, 2019

@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

The effect of changing the array size is to go from this branch:

go/src/reflect/type.go

Lines 2883 to 2886 in 178a2c4

case typ.kind&kindGCProg == 0 && array.size <= maxPtrmaskBytes*8*ptrSize:
// Element is small with pointer mask; array is still small.
// Create direct pointer mask by turning each 1 bit in elem
// into count 1 bits in larger mask.

to this branch:

go/src/reflect/type.go

Lines 2900 to 2906 in 178a2c4

default:
// Create program that emits one element
// and then repeats to make the array.
prog := []byte{0, 0, 0, 0} // will be length of prog
elemGC := (*[1 << 30]byte)(unsafe.Pointer(typ.gcdata))[:]
elemPtrs := typ.ptrdata / ptrSize
if typ.kind&kindGCProg == 0 {

I am guessing that there may be a bug in the latter program generation, and this bug may get lucky and not crash, except in circumstances that happen to be exercised in the minimal reproducer.

@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

The following patch appears to cure the segfault, which is because ft.typ.gcdata is nil. This is nil because hasGCProg is set because F8 has a gcprog. However, F1 does not and so when it is considered, ft.typ.gcdata is nil, which results in a nil dereference.

What I haven't yet understood is why the crash ceases in the minimal reproducer if you change the array constants, e.g. {Name: "F1", Type: reflect.ArrayOf(3, typ(uint64(0)))},. In that case I would still expect it be structurally the same and crash in the same way, but apparently it does not.

diff --git a/src/reflect/type.go b/src/reflect/type.go
index 5ce80c61dc..d452946553 100644
--- a/src/reflect/type.go
+++ b/src/reflect/type.go
@@ -2681,10 +2681,10 @@ func StructOf(fields []StructField) Type {
                                break
                        }
                        // FIXME(sbinet) handle padding, fields smaller than a word
-                       elemGC := (*[1 << 30]byte)(unsafe.Pointer(ft.typ.gcdata))[:]
                        elemPtrs := ft.typ.ptrdata / ptrSize
                        switch {
                        case ft.typ.kind&kindGCProg == 0 && ft.typ.ptrdata != 0:
+                               elemGC := (*[1 << 30]byte)(unsafe.Pointer(ft.typ.gcdata))[:]
                                // Element is small with pointer mask; use as literal bits.
                                mask := elemGC
                                // Emit 120-bit chunks of full bytes (max is 127 but we avoid using partial bytes).
@@ -2697,6 +2697,7 @@ func StructOf(fields []StructField) Type {
                                prog = append(prog, byte(n))
                                prog = append(prog, mask[:(n+7)/8]...)
                        case ft.typ.kind&kindGCProg != 0:
+                               elemGC := (*[1 << 30]byte)(unsafe.Pointer(ft.typ.gcdata))[:]
                                // Element has GC program; emit one element.
                                elemProg := elemGC[4 : 4+*(*uint32)(unsafe.Pointer(&elemGC[0]))-1]
                                prog = append(prog, elemProg...)
@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

The heapBitsSetTypeGCProg runtime panic (NewWithSeed(2)) is still present even with the above patch. Here's a minimal reproducer:

(Edit: made reproducer smaller. Note that 5472*sizeof(slice) =16386.)

package main

import "reflect"

func main() {}

func typ(x interface{}) reflect.Type { return reflect.ValueOf(x).Type() }

var x = reflect.New(reflect.StructOf([]reflect.StructField{
	{Name: "F5", Type: reflect.StructOf([]reflect.StructField{
		{Name: "F4", Type: reflect.ArrayOf(5462,
			reflect.SliceOf(typ(uint64(0))))},
	})},
}))

Stack trace:

$ go run .
runtime: heapBitsSetTypeGCProg: total bits 2475 but progSize 138600
fatal error: heapBitsSetTypeGCProg: unexpected bit count

goroutine 1 [running, locked to thread]:
runtime.throw(0x4b6adc, 0x2b)
	/home/pwaller/sdk/go1.12/src/runtime/panic.go:617 +0x72 fp=0xc00008cd00 sp=0xc00008ccd0 pc=0x426e52
runtime.heapBitsSetTypeGCProg(0x7f19f607f200, 0x20300000000000, 0x7f19f6279fff, 0x21d68, 0x21d68, 0x21d68, 0x22000, 0xc0000a0164)
	/home/pwaller/sdk/go1.12/src/runtime/mbitmap.go:1530 +0x343 fp=0xc00008cd68 sp=0xc00008cd00 pc=0x413683
runtime.heapBitsSetType(0xc0000a4000, 0x22000, 0x21d68, 0xc0000740c0)
	/home/pwaller/sdk/go1.12/src/runtime/mbitmap.go:1057 +0x8d4 fp=0xc00008ce40 sp=0xc00008cd68 pc=0x4131b4
runtime.mallocgc(0x22000, 0xc0000740c0, 0x1, 0xc0000900f0)
	/home/pwaller/sdk/go1.12/src/runtime/malloc.go:969 +0x51c fp=0xc00008cee0 sp=0xc00008ce40 pc=0x40aa8c
reflect.unsafe_New(0xc0000740c0, 0x4944e0)
	/home/pwaller/sdk/go1.12/src/runtime/malloc.go:1073 +0x38 fp=0xc00008cf10 sp=0xc00008cee0 pc=0x40b148
reflect.New(0x4ca8a0, 0xc0000740c0, 0x1, 0x4ca8a0, 0xc0000740c0)
	/home/pwaller/sdk/go1.12/src/reflect/value.go:2290 +0x45 fp=0xc00008cf40 sp=0xc00008cf10 pc=0x476e45
main.init.ializers()
	/home/pwaller/.local/src/github.com/pwaller/randstruct/cmd/randstruct/crasher1/main.go:9 +0x228 fp=0xc00008cf88 sp=0xc00008cf40 pc=0x482638
@randall77

This comment has been minimized.

Copy link
Contributor

commented Mar 6, 2019

@sbinet This smells like a bug in the code that generates GC programs in reflect.StructOf.

The size is just big enough to force a GC program in the reflect.ArrayOf call. That forces the reflect.StructOf call to also use a GC program.

Looks like a missing stop mark at the end. That is ok by itself, but when incorporating a program in another program the code drops the last byte, and if the last byte isn't a stop mark then we throw away part of the program we actually need.

@gopherbot

This comment has been minimized.

Copy link

commented Mar 6, 2019

Change https://golang.org/cl/165857 mentions this issue: reflect: fix StructOf GC programs

@randall77

This comment has been minimized.

Copy link
Contributor

commented Mar 6, 2019

Looks like this bug has existed since StructOf was introduced in 1.7. I don't think it is worth backporting.

Counter argument though, by omitting the last byte of the program we're potentially using a random byte in the heap for the last byte, which could lead to arbitrarily weird things happening in the GC. It would be unlikely - the program (without the last byte) would have to be exactly the size of a sizeclass to have the missing byte be nonzero.

@gopherbot gopherbot closed this in 05b3db2 Mar 6, 2019

@pwaller

This comment has been minimized.

Copy link
Contributor Author

commented Mar 6, 2019

@randall77 the master branch still crashes for me using my testcase in the original issue body ("main.go example program", which is click-to-expand).

@gopherbot

This comment has been minimized.

Copy link

commented Mar 6, 2019

Change https://golang.org/cl/165859 mentions this issue: cmd/compile: use full package path to print unexported interface methods

@randall77

This comment has been minimized.

Copy link
Contributor

commented Mar 6, 2019

Indeed, I only fixed your last example. Both the original crashes are still happening.

@randall77 randall77 reopened this Mar 6, 2019

@gopherbot

This comment has been minimized.

Copy link

commented Mar 6, 2019

Change https://golang.org/cl/165860 mentions this issue: reflect: fix more issues with StructOf GC programs

@gopherbot gopherbot closed this in 9dc3b8b Mar 7, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.