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
Closed

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

pwaller opened this issue Mar 5, 2019 · 15 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@pwaller
Copy link
Contributor

pwaller 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
Copy link
Contributor Author

pwaller 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
Copy link
Contributor Author

pwaller 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
Copy link
Contributor Author

pwaller 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
Copy link
Contributor Author

pwaller 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
Copy link
Member

ALTree 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
@ALTree ALTree added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Mar 6, 2019
@pwaller
Copy link
Contributor Author

pwaller 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
Copy link
Contributor Author

pwaller 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
Copy link
Contributor Author

pwaller 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
Copy link
Contributor

@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
Copy link

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

@randall77
Copy link
Contributor

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.

@pwaller
Copy link
Contributor Author

pwaller 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
Copy link

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

@randall77
Copy link
Contributor

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

@randall77 randall77 reopened this Mar 6, 2019
@gopherbot
Copy link

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

@golang golang locked and limited conversation to collaborators Mar 6, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants