Skip to content

Instantly share code, notes, and snippets.

@korniltsev
Last active December 8, 2023 09:00
Show Gist options
  • Select an option

  • Save korniltsev/f6c639c68c7c40e1ead5db6012abed92 to your computer and use it in GitHub Desktop.

Select an option

Save korniltsev/f6c639c68c7c40e1ead5db6012abed92 to your computer and use it in GitHub Desktop.
generic inline location bug reproducer
package main
import (
"bytes"
"fmt"
"runtime"
"runtime/pprof"
"strings"
"testing"
"github.com/google/pprof/profile"
)
type opAlloc struct {
buf [128]byte
}
type opCall struct {
}
func storeAlloc() {
l = new(string)
}
func nonRecursiveGenericAllocFunction[CurrentOp any, OtherOp any](alloc bool) {
if alloc {
storeAlloc()
} else {
nonRecursiveGenericAllocFunction[OtherOp, CurrentOp](true)
}
}
//go:noinline
func test() {
nonRecursiveGenericAllocFunction[opAlloc, opCall](true)
nonRecursiveGenericAllocFunction[opCall, opAlloc](false)
}
var l *string
func profileToString(p *profile.Profile) []string {
var res []string
for _, s := range p.Sample {
res = append(res, sampleToString(s))
}
return res
}
func sampleToString(s *profile.Sample) string {
var funcs []string
for i := len(s.Location) - 1; i >= 0; i-- {
loc := s.Location[i]
funcs = locationToStrings(loc, funcs)
}
return fmt.Sprintf("%s %v", strings.Join(funcs, ";"), s.Value)
}
func locationToStrings(loc *profile.Location, funcs []string) []string {
for j := range loc.Line {
line := loc.Line[len(loc.Line)-1-j]
funcs = append(funcs, line.Function.Name)
}
return funcs
}
func TestGenericsBug(t *testing.T) {
previousRate := runtime.MemProfileRate
runtime.MemProfileRate = 1
defer func() {
runtime.MemProfileRate = previousRate
l = nil
}()
test()
runtime.GC()
buf := bytes.NewBuffer(nil)
if err := pprof.WriteHeapProfile(buf); err != nil {
t.Fatalf("writing profile: %v", err)
}
p, err := profile.Parse(buf)
if err != nil {
t.Fatalf("profile.Parse: %v", err)
}
const expectedSample = "testing.tRunner;p1.TestGenericsBug;p1.test;p1.nonRecursiveGenericAllocFunction[go.shape.struct {},go.shape.struct { p1.buf [128]uint8 }];p1.nonRecursiveGenericAllocFunction[go.shape.struct { p1.buf [128]uint8 },go.shape.struct {}];p1.storeAlloc [1 16 1 16]"
const expectedLocation = "p1.nonRecursiveGenericAllocFunction[go.shape.struct {},go.shape.struct { p1.buf [128]uint8 }];p1.nonRecursiveGenericAllocFunction[go.shape.struct { p1.buf [128]uint8 },go.shape.struct {}];p1.storeAlloc"
var s *profile.Sample
for _, sample := range p.Sample {
if sampleToString(sample) == expectedSample {
s = sample
break
}
}
if s == nil {
t.Fatalf("expected \n%s\ngot\n%s", expectedSample, strings.Join(profileToString(p), "\n"))
}
loc := s.Location[0]
actual := strings.Join(locationToStrings(loc, nil), ";")
if expectedLocation != actual {
t.Errorf("expected a location with 3 functions ( 2 inlined )\n%s\ngot\n%s\n", expectedLocation, actual)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment