gocore: break up test to make it more understandable Also, remove unused test code and add common elements between the test programs to testenv. Change-Id: Ib5c8e2d1768016df54f3212d79ec9542492367dd Reviewed-on: https://go-review.googlesource.com/c/debug/+/659795 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Nicolas Hillegeer <aktau@google.com> Auto-Submit: Michael Knyszek <mknyszek@google.com>
diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go index 4ab0704..ffb9bd4 100644 --- a/internal/gocore/gocore_test.go +++ b/internal/gocore/gocore_test.go
@@ -1,5 +1,5 @@ // Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style +// Use of this srcFile code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris @@ -36,9 +36,8 @@ return p } -// loadExampleGenerated generates a core from a binary built with -// runtime.GOROOT(). -func loadExampleGenerated(t *testing.T, buildFlags, env []string) *Process { +// createAndLoadCore generates a core from a binary built with runtime.GOROOT(). +func createAndLoadCore(t *testing.T, srcFile string, buildFlags, env []string) *Process { t.Helper() testenv.MustHaveGoBuild(t) switch runtime.GOOS { @@ -57,7 +56,7 @@ } dir := t.TempDir() - file, output, err := generateCore(dir, buildFlags, env) + file, output, err := generateCore(srcFile, dir, buildFlags, env) t.Logf("crasher output: %s", output) if err != nil { t.Fatalf("generateCore() got err %v want nil", err) @@ -158,24 +157,23 @@ return cmd.Process.Pid, b.Bytes(), nil } -func generateCore(dir string, buildFlags, env []string) (string, []byte, error) { +func generateCore(srcFile, dir string, buildFlags, env []string) (string, []byte, error) { goTool, err := testenv.GoTool() if err != nil { return "", nil, fmt.Errorf("cannot find go tool: %w", err) } - const source = "./testdata/coretest/test.go" cwd, err := os.Getwd() if err != nil { return "", nil, fmt.Errorf("erroring getting cwd: %w", err) } - srcPath := filepath.Join(cwd, source) + srcPath := filepath.Join(cwd, srcFile) argv := []string{"build"} argv = append(argv, buildFlags...) - argv = append(argv, "-o", "test.exe", srcPath) + argv = append(argv, "-o", filepath.Join(dir, "test.exe"), "./"+filepath.Base(srcFile)) cmd := exec.Command(goTool, argv...) - cmd.Dir = dir + cmd.Dir = filepath.Dir(srcPath) b, err := cmd.CombinedOutput() if err != nil { @@ -241,7 +239,7 @@ if len(p.env) != 0 { parts = append(parts, "env="+strings.Join(p.env, ",")) } - return cmp.Or(strings.Join(parts, "%"), "DEFAULT") + return cmp.Or(strings.Join(parts, "%"), "default") } // Variations in build and execution environments common to different tests. @@ -251,77 +249,122 @@ {buildFlags: []string{"-buildmode=pie"}, env: []string{"GO_DEBUG_TEST_COREDUMP_FILTER=0x3f"}}, } +func testSrcFiles(t *testing.T) []string { + srcs, err := filepath.Glob("testdata/testprogs/*.go") + if err != nil { + t.Skipf("failed to find sources: %v", err) + } + return srcs +} + func TestVersions(t *testing.T) { t.Run("goroot", func(t *testing.T) { for _, test := range variations { - t.Run(test.String(), func(t *testing.T) { - p := loadExampleGenerated(t, test.buildFlags, test.env) - checkProcess(t, p) - }) + for _, src := range testSrcFiles(t) { + t.Run(test.String()+"/"+filepath.Base(src), func(t *testing.T) { + p := createAndLoadCore(t, src, test.buildFlags, test.env) + checkProcess(t, p) + }) + } } }) } func TestObjects(t *testing.T) { + const largeObjectThreshold = 32768 + t.Run("goroot", func(t *testing.T) { for _, test := range variations { t.Run(test.String(), func(t *testing.T) { - const largeObjectThreshold = 32768 + t.Run("bigslice.go", func(t *testing.T) { + p := createAndLoadCore(t, "testdata/testprogs/bigslice.go", test.buildFlags, test.env) - p := loadExampleGenerated(t, test.buildFlags, test.env) + // Statistics to check. + largeObjects := 0 // Number of objects larger than (or equal to largeObjectThreshold) + bigSliceElemObjects := 0 - // Statistics to check. - n := 0 - largeObjects := 0 // Number of objects larger than (or equal to largeObjectThreshold) - myPairObjects := 0 - anyNodeObjects := 0 - typeSafeNodeObjects := 0 - bigSliceElemObjects := 0 - - p.ForEachObject(func(x Object) bool { - siz := p.Size(x) - typ := typeName(p, x) - t.Logf("%s size=%d", typ, p.Size(x)) - if siz >= largeObjectThreshold { - largeObjects++ + p.ForEachObject(func(x Object) bool { + siz := p.Size(x) + typ := typeName(p, x) + //t.Logf("%s size=%d", typ, p.Size(x)) + if siz >= largeObjectThreshold { + largeObjects++ + } + switch typ { + case "main.bigSliceElem": + bigSliceElemObjects++ + } + return true + }) + if largeObjects != 1 { + t.Errorf("expected exactly one object larger than %d, found %d", largeObjectThreshold, largeObjects) } - switch typ { - case "main.myPair": - myPairObjects++ - case "main.anyNode": - anyNodeObjects++ - case "main.typeSafeNode[main.myPair]": - typeSafeNodeObjects++ - case "main.bigSliceElem": - bigSliceElemObjects++ + + // Check object counts. + if want := 32 << 10; bigSliceElemObjects != want { + t.Errorf("expected exactly %d main.globalBigSliceInt objects, found %d", want, bigSliceElemObjects) } - n++ - return true }) - if n < 10 { - t.Errorf("#objects = %d, want >10", n) - } - if largeObjects != 2 { - t.Errorf("expected exactly one object larger than %d, found %d", largeObjectThreshold, largeObjects) - } + t.Run("large.go", func(t *testing.T) { + p := createAndLoadCore(t, "testdata/testprogs/large.go", test.buildFlags, test.env) - // Check object counts. - const depth = 5 - const tsTrees = 3 - const anTrees = 2 - const nodes = 1<<depth - 1 - if want := tsTrees*nodes + anTrees*nodes*2; myPairObjects != want { - t.Errorf("expected exactly %d main.myPair objects, found %d", want, myPairObjects) - } - if want := anTrees * nodes; anyNodeObjects != want { - t.Errorf("expected exactly %d main.anyNode objects, found %d", want, anyNodeObjects) - } - if want := tsTrees * nodes; typeSafeNodeObjects != want { - t.Errorf("expected exactly %d main.typeSafeNode[main.myPair] objects, found %d", want, typeSafeNodeObjects) - } - if want := 32 << 10; bigSliceElemObjects != want { - t.Errorf("expected exactly %d main.globalBigSliceInt objects, found %d", want, bigSliceElemObjects) - } + // Statistics to check. + largeObjects := 0 // Number of objects larger than (or equal to largeObjectThreshold) + p.ForEachObject(func(x Object) bool { + siz := p.Size(x) + //typ := typeName(p, x) + //t.Logf("%s size=%d", typ, p.Size(x)) + if siz >= largeObjectThreshold { + largeObjects++ + } + return true + }) + if largeObjects != 1 { + t.Errorf("expected exactly one object larger than %d, found %d", largeObjectThreshold, largeObjects) + } + }) + t.Run("trees.go", func(t *testing.T) { + p := createAndLoadCore(t, "testdata/testprogs/trees.go", test.buildFlags, test.env) + + // Statistics to check. + n := 0 + myPairObjects := 0 + anyNodeObjects := 0 + typeSafeNodeObjects := 0 + + p.ForEachObject(func(x Object) bool { + typ := typeName(p, x) + //t.Logf("%s size=%d", typ, p.Size(x)) + switch typ { + case "main.myPair": + myPairObjects++ + case "main.anyNode": + anyNodeObjects++ + case "main.typeSafeNode[main.myPair]": + typeSafeNodeObjects++ + } + n++ + return true + }) + if n < 10 { + t.Errorf("#objects = %d, want >10", n) + } + + // Check object counts. + const depth = 5 + const tsTrees = 3 + const anTrees = 2 + const nodes = 1<<depth - 1 + if want := tsTrees*nodes + anTrees*nodes*2; myPairObjects != want { + t.Errorf("expected exactly %d main.myPair objects, found %d", want, myPairObjects) + } + if want := anTrees * nodes; anyNodeObjects != want { + t.Errorf("expected exactly %d main.anyNode objects, found %d", want, anyNodeObjects) + } + if want := tsTrees * nodes; typeSafeNodeObjects != want { + t.Errorf("expected exactly %d main.typeSafeNode[main.myPair] objects, found %d", want, typeSafeNodeObjects) + } + }) }) } })
diff --git a/internal/gocore/testdata/README b/internal/gocore/testdata/README deleted file mode 100644 index 7fa4d21..0000000 --- a/internal/gocore/testdata/README +++ /dev/null
@@ -1,44 +0,0 @@ -This directory contains a simple core file for use in testing. - -It was generated by running the following program with go1.9.0. - -package main - -func main() { - _ = *(*int)(nil) -} - -The core file includes the executable by reference using an absolute -path. The executable was at /tmp/test, so that path is reproduced -here so the core dump reader can find the executable using this -testdata directory as the base directory. - -core1.10 is produced in the same way, except using go1.10.0. -core1.11 is produced in the same way, except using go1.11.0. - - -steps for subsequent versions: - -mkdir /tmp/coretest -rm -fr /tmp/coretest/* # in case there's old junk there -cp coretest/test.go /tmp/coretest -cd /tmp/coretest -go build test.go -GOTRACEBACK=crash ./test -zip 1.12.zip /tmp/coretest/core /tmp/coretest/test // use your version number - -Then move the 1.12.zip to this directory. -Add a new test to TestVersions in ../gocore_test.go. - -## runtimetype - -This directory also contains the source code to generate the runtimetype core, -which is used to test getting runtime type for a interface. - -steps for generate runtimetype core: - -cd testdata/runtimetype -go build -o runtimetype . -ulimit -c unlimited -GOTRACEBACK=crash ./runtimetype -zip runtimetype.zip runtimetype core
diff --git a/internal/gocore/testdata/coretest/test.go b/internal/gocore/testdata/coretest/test.go deleted file mode 100644 index d217112..0000000 --- a/internal/gocore/testdata/coretest/test.go +++ /dev/null
@@ -1,197 +0,0 @@ -package main - -import ( - "os" - "runtime" -) - -// Large is an object that (since Go 1.22) is allocated in a span that has a -// non-nil largeType field. Meaning it must be (>maxSmallSize-mallocHeaderSize). -// At the time of writing this is (32768 - 8). -type Large struct { - ptr *uint8 // Object must contain a pointer to trigger code path. - arr [32768 - 8]uint8 -} - -func useLarge(o *Large, ready chan<- struct{}) { - o.ptr = &o.arr[5] - o.arr[5] = 0xCA - ready <- struct{}{} - <-block -} - -type AnyTree struct { - root any -} - -type anyNode struct { - left, right any // *anyNode, to test direct eface type walking - entry1 any // myPair, to test indirect eface type walking - entry2 Xer // myPair, to test indirect iface type walking -} - -func makeAnyTree(depth int64) any { - if depth == 0 { - return nil - } - e1 := myPair{depth, depth} - e2 := myPair{depth, depth} - n := &anyNode{ - left: makeAnyTree(depth - 1), - right: makeAnyTree(depth - 1), - entry1: e1, - entry2: e2, - } - if depth%2 == 0 { - // Test dealing with direct interfaces of wrapped structures. - return anyNodeWrap2{{n}} - } - return n -} - -//go:noinline -func (a *AnyTree) count() int { - return countAnyNode(a.root) -} - -func countAnyNode(a any) int { - switch v := a.(type) { - case *anyNode: - return v.count() - case anyNodeWrap2: - return v.unwrap().count() - } - return 0 -} - -// This is load-bearing to make sure anyNodeWrap2 ends up in the binary. -// -//go:noinline -func (w anyNodeWrap2) unwrap() *anyNode { - return w[0].anyNode -} - -func (a *anyNode) count() int { - return 1 + countAnyNode(a.left) + countAnyNode(a.right) -} - -type anyNodeWrap struct{ *anyNode } -type anyNodeWrap2 [1]anyNodeWrap - -type TypeSafeTree[K any] struct { - root *typeSafeNode[K] -} - -type typeSafeNode[K any] struct { - left, right *typeSafeNode[K] - entry *K -} - -func makeTypeSafeTree(depth int64) *typeSafeNode[myPair] { - if depth == 0 { - return nil - } - return &typeSafeNode[myPair]{ - left: makeTypeSafeTree(depth - 1), - right: makeTypeSafeTree(depth - 1), - entry: &myPair{depth, depth}, - } -} - -//go:noinline -func (t *TypeSafeTree[K]) count() int { - return t.root.count() -} - -func (t *typeSafeNode[K]) count() int { - if t == nil { - return 0 - } - return 1 + t.left.count() + t.right.count() -} - -type myPair struct { - x, y int64 -} - -func (p myPair) X() int64 { - return p.x -} - -type Xer interface { - X() int64 -} - -type bigSliceElem struct { - x, y, z float64 -} - -var globalAnyTree AnyTree -var globalAnyTreeFM func() int -var globalTypeSafeTree TypeSafeTree[myPair] -var globalTypeSafeTreeFM func() int -var globalBigSlice []*bigSliceElem - -var block = make(chan struct{}) - -var a anyNode - -func init() { - if coredumpFilter := os.Getenv("GO_DEBUG_TEST_COREDUMP_FILTER"); coredumpFilter != "" { - if err := os.WriteFile("/proc/self/coredump_filter", []byte(coredumpFilter), 0600); err != nil { - os.Stderr.WriteString("crash: unable to set coredump_filter: ") - os.Stderr.WriteString(err.Error()) - os.Stderr.WriteString("\n") - os.Exit(0) // Don't crash (which is an error for the called). - } - } -} - -func main() { - globalBigSlice = make([]*bigSliceElem, 32<<10) - for i := range globalBigSlice { - globalBigSlice[i] = &bigSliceElem{float64(i), float64(i) - 0.5, float64(i * 124)} - } - globalAnyTree.root = makeAnyTree(5) - globalTypeSafeTree.root = makeTypeSafeTree(5) - - ready := make(chan struct{}) - go func() { - var anyTree AnyTree - var anyTreeFM AnyTree // Captured in a method value. - var typeSafeTree TypeSafeTree[myPair] - var typeSafeTreeFM TypeSafeTree[myPair] // Captured in a method value. - - // TODO(mknyszek): AnyTree is described in DWARF in pieces, and we can't handle - // that yet. - // - // anyTree.root = makeAnyTree(5) - anyTreeFM.root = makeAnyTree(5) - globalAnyTreeFM = anyTreeFM.count - typeSafeTree.root = makeTypeSafeTree(5) - typeSafeTreeFM.root = makeTypeSafeTree(5) - globalTypeSafeTreeFM = typeSafeTreeFM.count - - ready <- struct{}{} - <-block - - runtime.KeepAlive(anyTree) - runtime.KeepAlive(typeSafeTree) - }() - - // Create a large value and reference - var o Large - go useLarge(&o, ready) // Force an escape of o. - o.arr[14] = 0xDE // Prevent a future smart compiler from allocating o directly on useLarge's stack. - - // This is load-bearing to make sure anyNodeWrap2 and the count methods end up in the DWARF. - println("tree counts:", globalAnyTree.count(), globalTypeSafeTree.count()) - - // Make sure both goroutines are ready. - <-ready - <-ready - - _ = *(*int)(nil) - - runtime.KeepAlive(&o) -}
diff --git a/internal/gocore/testdata/runtimetype/go.mod b/internal/gocore/testdata/runtimetype/go.mod deleted file mode 100644 index 594b4df..0000000 --- a/internal/gocore/testdata/runtimetype/go.mod +++ /dev/null
@@ -1,3 +0,0 @@ -module example.com/m - -go 1.17
diff --git a/internal/gocore/testdata/runtimetype/main.go b/internal/gocore/testdata/runtimetype/main.go deleted file mode 100644 index df647f9..0000000 --- a/internal/gocore/testdata/runtimetype/main.go +++ /dev/null
@@ -1,14 +0,0 @@ -package main - -import ( - pkga "example.com/m/path-a/pkg" - pkgb "example.com/m/path-b/pkg" -) - -var global []interface{} - -func main() { - global = append(global, pkga.NewIfaceDirect(), pkga.NewIfaceInDirect(), pkgb.NewIfaceDirect(), pkgb.NewIfaceInDirect()) - - _ = *(*int)(nil) -}
diff --git a/internal/gocore/testdata/runtimetype/path-a/pkg/pkg.go b/internal/gocore/testdata/runtimetype/path-a/pkg/pkg.go deleted file mode 100644 index e0dfa4f..0000000 --- a/internal/gocore/testdata/runtimetype/path-a/pkg/pkg.go +++ /dev/null
@@ -1,32 +0,0 @@ -package pkg - -type T1 struct { - f1 uint64 - f2 uint64 -} - -type T2 struct { - f1 uint64 -} - -type IfaceDirect interface { - M1() -} - -type IfaceInDirect interface { - M2() -} - -func (t *T1) M1() { -} - -func (t T2) M2() { -} - -func NewIfaceDirect() IfaceDirect { - return &T1{100, 10} -} - -func NewIfaceInDirect() IfaceInDirect { - return T2{200} -}
diff --git a/internal/gocore/testdata/runtimetype/path-b/pkg/pkg.go b/internal/gocore/testdata/runtimetype/path-b/pkg/pkg.go deleted file mode 100644 index f31a5ff..0000000 --- a/internal/gocore/testdata/runtimetype/path-b/pkg/pkg.go +++ /dev/null
@@ -1,36 +0,0 @@ -package pkg - -type T1 struct { - f1 uint64 - f2 uint64 - f3 uint64 - f4 uint64 -} - -type T2 struct { - f1 uint64 - f2 uint64 - f3 uint64 -} - -type IfaceDirect interface { - M1() -} - -type IfaceInDirect interface { - M2() -} - -func (t *T1) M1() { -} - -func (t T2) M2() { -} - -func NewIfaceDirect() IfaceDirect { - return &T1{1000, 0, 0, 0} -} - -func NewIfaceInDirect() IfaceInDirect { - return T2{2000, 0, 0} -}
diff --git a/internal/gocore/testdata/testprogs/bigslice.go b/internal/gocore/testdata/testprogs/bigslice.go new file mode 100644 index 0000000..d383fea --- /dev/null +++ b/internal/gocore/testdata/testprogs/bigslice.go
@@ -0,0 +1,31 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +// Tests to make sure pointers in big slices are handled correctly. + +package main + +import ( + "os" + + "golang.org/x/debug/internal/testenv" +) + +type bigSliceElem struct { + x, y, z float64 +} + +var globalBigSlice []*bigSliceElem + +func main() { + testenv.RunThenCrash(os.Getenv("GO_DEBUG_TEST_COREDUMP_FILTER"), func() any { + globalBigSlice = make([]*bigSliceElem, 32<<10) + for i := range globalBigSlice { + globalBigSlice[i] = &bigSliceElem{float64(i), float64(i) - 0.5, float64(i * 124)} + } + return nil + }) +}
diff --git a/internal/gocore/testdata/testprogs/large.go b/internal/gocore/testdata/testprogs/large.go new file mode 100644 index 0000000..a37078d --- /dev/null +++ b/internal/gocore/testdata/testprogs/large.go
@@ -0,0 +1,47 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +// Tests to make sure a large object referenced only from a stack +// can be found. + +package main + +import ( + "os" + + "golang.org/x/debug/internal/testenv" +) + +// Large is an object that (since Go 1.22) is allocated in a span that has a +// non-nil largeType field. Meaning it must be (>maxSmallSize-mallocHeaderSize). +// At the time of writing this is (32768 - 8). +type Large struct { + ptr *uint8 // Object must contain a pointer to trigger code path. + arr [32768 - 8]uint8 +} + +func useLarge(o *Large, ready chan<- struct{}) { + o.ptr = &o.arr[5] + o.arr[5] = 0xCA + ready <- struct{}{} + <-block +} + +var block = make(chan struct{}) + +func main() { + testenv.RunThenCrash(os.Getenv("GO_DEBUG_TEST_COREDUMP_FILTER"), func() any { + ready := make(chan struct{}) + + // Create a large value and reference + var o Large + go useLarge(&o, ready) // Force an escape of o. + o.arr[14] = 0xDE // Prevent a future smart compiler from allocating o directly on useLarge's stack. + + <-ready + return &o + }) +}
diff --git a/internal/gocore/testdata/testprogs/trees.go b/internal/gocore/testdata/testprogs/trees.go new file mode 100644 index 0000000..8cfc986 --- /dev/null +++ b/internal/gocore/testdata/testprogs/trees.go
@@ -0,0 +1,161 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +package main + +import ( + "os" + "runtime" + + "golang.org/x/debug/internal/testenv" +) + +type AnyTree struct { + root any +} + +type anyNode struct { + left, right any // *anyNode, to test direct eface type walking + entry1 any // myPair, to test indirect eface type walking + entry2 Xer // myPair, to test indirect iface type walking +} + +func makeAnyTree(depth int64) any { + if depth == 0 { + return nil + } + e1 := myPair{depth, depth} + e2 := myPair{depth, depth} + n := &anyNode{ + left: makeAnyTree(depth - 1), + right: makeAnyTree(depth - 1), + entry1: e1, + entry2: e2, + } + if depth%2 == 0 { + // Test dealing with direct interfaces of wrapped structures. + return anyNodeWrap2{{n}} + } + return n +} + +//go:noinline +func (a *AnyTree) count() int { + return countAnyNode(a.root) +} + +func countAnyNode(a any) int { + switch v := a.(type) { + case *anyNode: + return v.count() + case anyNodeWrap2: + return v.unwrap().count() + } + return 0 +} + +// This is load-bearing to make sure anyNodeWrap2 ends up in the binary. +// +//go:noinline +func (w anyNodeWrap2) unwrap() *anyNode { + return w[0].anyNode +} + +func (a *anyNode) count() int { + return 1 + countAnyNode(a.left) + countAnyNode(a.right) +} + +type anyNodeWrap struct{ *anyNode } +type anyNodeWrap2 [1]anyNodeWrap + +type TypeSafeTree[K any] struct { + root *typeSafeNode[K] +} + +type typeSafeNode[K any] struct { + left, right *typeSafeNode[K] + entry *K +} + +func makeTypeSafeTree(depth int64) *typeSafeNode[myPair] { + if depth == 0 { + return nil + } + return &typeSafeNode[myPair]{ + left: makeTypeSafeTree(depth - 1), + right: makeTypeSafeTree(depth - 1), + entry: &myPair{depth, depth}, + } +} + +//go:noinline +func (t *TypeSafeTree[K]) count() int { + return t.root.count() +} + +func (t *typeSafeNode[K]) count() int { + if t == nil { + return 0 + } + return 1 + t.left.count() + t.right.count() +} + +type myPair struct { + x, y int64 +} + +func (p myPair) X() int64 { + return p.x +} + +type Xer interface { + X() int64 +} + +var globalAnyTree AnyTree +var globalAnyTreeFM func() int +var globalTypeSafeTree TypeSafeTree[myPair] +var globalTypeSafeTreeFM func() int + +var block = make(chan struct{}) +var a anyNode + +func main() { + testenv.RunThenCrash(os.Getenv("GO_DEBUG_TEST_COREDUMP_FILTER"), func() any { + globalAnyTree.root = makeAnyTree(5) + globalTypeSafeTree.root = makeTypeSafeTree(5) + + ready := make(chan struct{}) + go func() { + var anyTree AnyTree + var anyTreeFM AnyTree // Captured in a method value. + var typeSafeTree TypeSafeTree[myPair] + var typeSafeTreeFM TypeSafeTree[myPair] // Captured in a method value. + + // TODO(mknyszek): AnyTree is described in DWARF in pieces, and we can't handle + // that yet. + // + // anyTree.root = makeAnyTree(5) + anyTreeFM.root = makeAnyTree(5) + globalAnyTreeFM = anyTreeFM.count + typeSafeTree.root = makeTypeSafeTree(5) + typeSafeTreeFM.root = makeTypeSafeTree(5) + globalTypeSafeTreeFM = typeSafeTreeFM.count + + ready <- struct{}{} + <-block + + runtime.KeepAlive(anyTree) + runtime.KeepAlive(typeSafeTree) + }() + + // This is load-bearing to make sure anyNodeWrap2 and the count methods end up in the DWARF. + println("tree counts:", globalAnyTree.count(), globalTypeSafeTree.count()) + + <-ready + return nil + }) +}
diff --git a/internal/testenv/crash.go b/internal/testenv/crash.go new file mode 100644 index 0000000..4a775be --- /dev/null +++ b/internal/testenv/crash.go
@@ -0,0 +1,41 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testenv + +import ( + "os" + "runtime" +) + +// RunThenCrash sets the provided core dump filter (optional) for +// the process, runs f, then crashes. +// +// The slice returned by f is kept alive across the crash. +func RunThenCrash(coredumpFilter string, f func() any) { + // Set coredump filter (Linux only). + if runtime.GOOS == "linux" && coredumpFilter != "" { + if err := os.WriteFile("/proc/self/coredump_filter", []byte(coredumpFilter), 0600); err != nil { + os.Stderr.WriteString("crash: unable to set coredump_filter: ") + os.Stderr.WriteString(err.Error()) + os.Stderr.WriteString("\n") + os.Exit(0) // Don't crash (which is an error for the called). + } + } + + // Run f. + result := f() + crash() + runtime.KeepAlive(result) +} + +// Crash crashes the program. +// +// Make it noinline so registers are spilled before entering, otherwise imprecise DWARF will be our doom. +// Delve has trouble with this too; 'result' in RunThenCrash won't be visible otherwise. +// +//go:noinline +func crash() { + _ = *(*int)(nil) +}