diff --git a/_fixtures/setiterator.go b/_fixtures/setiterator.go new file mode 100644 index 00000000..80252ee0 --- /dev/null +++ b/_fixtures/setiterator.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "iter" + "strconv" + "time" +) + +func main() { + set := New[string]() + for i := 10; i < 100; i++ { + set.Add(strconv.Itoa(i)) + } + PrintAllElements[string](set) +} + +// Set holds a set of elements. +type Set[E comparable] struct { + m map[E]struct{} +} + +// New returns a new [Set]. +func New[E comparable]() *Set[E] { + return &Set[E]{m: make(map[E]struct{})} +} + +// All is an iterator over the elements of s. +func (s *Set[E]) All() iter.Seq[E] { + return func(yield func(E) bool) { + for v := range s.m { + tmp := make([]byte, 1024) + str := string(tmp) + if !yield(v) { + return + } + go func() { println(str) }() + } + } +} + +func (s *Set[E]) Add(v E) { + s.m[v] = struct{}{} +} + +func PrintAllElements[E comparable](s *Set[E]) { + for v := range s.All() { + time.Sleep(100 * time.Second) + fmt.Println(v) + } +} diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index d861bd59..a49dd789 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -463,7 +463,7 @@ type compileUnit struct { entry *dwarf.Entry // debug_info entry describing this compile unit isgo bool // true if this is the go compile unit lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit - optimized bool // this compile unit is optimized + optimized optimizedFlags // this compile unit is optimized producer string // producer attribute offset dwarf.Offset // offset of the entry describing the compile unit @@ -471,6 +471,13 @@ type compileUnit struct { image *Image // parent image of this compilation unit. } +type optimizedFlags uint8 + +const ( + optimizedInlined optimizedFlags = 1 << iota + optimizedOptimized +) + type fileLine struct { file string line int @@ -605,7 +612,7 @@ func (fn *Function) NameWithoutTypeParams() string { // Optimized returns true if the function was optimized by the compiler. func (fn *Function) Optimized() bool { - return fn.cu.optimized + return fn.cu.optimized != 0 } // PrologueEndPC returns the PC just after the function prologue @@ -2472,9 +2479,18 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB if cu.isgo && cu.producer != "" { semicolon := strings.Index(cu.producer, ";") if semicolon < 0 { - cu.optimized = goversion.ProducerAfterOrEqual(cu.producer, 1, 10) + cu.optimized = 0 + if goversion.ProducerAfterOrEqual(cu.producer, 1, 10) { + cu.optimized = optimizedInlined | optimizedOptimized + } } else { - cu.optimized = !strings.Contains(cu.producer[semicolon:], "-N") || !strings.Contains(cu.producer[semicolon:], "-l") + cu.optimized = optimizedInlined | optimizedOptimized + if strings.Contains(cu.producer[semicolon:], "-N") { + cu.optimized &^= optimizedOptimized + } + if strings.Contains(cu.producer[semicolon:], "-l") { + cu.optimized &^= optimizedInlined + } const regabi = " regabi" if i := strings.Index(cu.producer[semicolon:], regabi); i > 0 { i += semicolon diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index d146123c..08133d65 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -71,6 +71,10 @@ const ( // If localsOnlyRangeBodyClosures is set simpleLocals only returns // variables containing the range body closure. localsOnlyRangeBodyClosures + + // If localsIsRangeBody is set DW_AT_formal_parameter variables will be + // considered local variables. + localsIsRangeBody ) // ConvertEvalScope returns a new EvalScope in the context of the @@ -319,7 +323,12 @@ func (scope *EvalScope) Locals(flags localsFlags, wantedName string) ([]*Variabl return vars2 } - vars0, err := scope.simpleLocals(flags, wantedName) + rangeBodyFlags := localsFlags(0) + if scope.Fn != nil && scope.Fn.rangeParentName() != "" { + rangeBodyFlags = localsFlags(localsIsRangeBody) + } + + vars0, err := scope.simpleLocals(flags|rangeBodyFlags, wantedName) if err != nil { return nil, err } @@ -344,7 +353,11 @@ func (scope *EvalScope) Locals(flags localsFlags, wantedName string) ([]*Variabl scope2 = FrameToScope(scope.target, scope.target.Memory(), scope.g, scope.threadID, scope.rangeFrames[2*i:]...) scope.enclosingRangeScopes[i] = scope2 } - vars, err := scope2.simpleLocals(flags, wantedName) + rangeBodyFlags := localsFlags(localsIsRangeBody) + if i == len(scope.enclosingRangeScopes)-1 { + rangeBodyFlags = 0 + } + vars, err := scope2.simpleLocals(flags|rangeBodyFlags, wantedName) if err != nil { return nil, err } @@ -377,7 +390,9 @@ func (scope *EvalScope) setupRangeFrames() error { if err != nil { return err } - scope.rangeFrames = scope.rangeFrames[2:] // skip the first frame and its return frame + if len(scope.rangeFrames) > 0 { + scope.rangeFrames = scope.rangeFrames[2:] // skip the first frame and its return frame + } scope.enclosingRangeScopes = make([]*EvalScope, len(scope.rangeFrames)/2) return nil } @@ -455,7 +470,7 @@ func (scope *EvalScope) simpleLocals(flags localsFlags, wantedName string) ([]*V } vars = append(vars, val) depth := entry.Depth - if entry.Tag == dwarf.TagFormalParameter { + if (flags&localsIsRangeBody == 0) && (entry.Tag == dwarf.TagFormalParameter) { if depth <= 1 { depth = 0 } diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index ef9a9d05..f045c3ab 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -573,7 +573,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i return 0, nil, fmt.Errorf("DWARF read error: %v", err) } - if bi.regabi && fn.cu.optimized { + if bi.regabi && fn.Optimized() { if runtimeWhitelist[fn.Name] { runtimeOptimizedWorkaround(bi, fn.cu.image, dwarfTree) } else { diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index c34f44fa..33d98148 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -948,6 +948,10 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) { nonMonotonicSP := false var closurePtr int64 + optimized := func(fn *Function) bool { + return fn.cu.optimized&optimizedOptimized != 0 + } + appendFrame := func(fr Stackframe) { frames = append(frames, fr) if fr.closurePtr != 0 { @@ -960,6 +964,9 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) { if fr.SystemStack { return false } + if closurePtr == 0 && optimized(fr.Call.Fn) { + return true + } if closurePtr < 0 { // closure is stack allocated, check that it is on this frame return fr.contains(closurePtr) @@ -994,12 +1001,30 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) { frames = append(frames, fr) } + if fr.Call.Fn == nil { + if stage == startStage { + frames = nil + addRetFrame = false + stage = doneStage + return false + } else { + return true + } + } + switch stage { case startStage: appendFrame(fr) rangeParent = fr.Call.Fn.extra(tgt.BinInfo()).rangeParent stage = normalStage - if rangeParent == nil || closurePtr == 0 { + stop := false + if rangeParent == nil { + stop = true + } + if !optimized(fr.Call.Fn) && closurePtr == 0 { + stop = true + } + if stop { frames = nil addRetFrame = false stage = doneStage @@ -1011,7 +1036,7 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) { stage = lastFrameStage } else if fr.Call.Fn.extra(tgt.BinInfo()).rangeParent == rangeParent && closurePtrOk(&fr) { appendFrame(fr) - if closurePtr == 0 { + if !optimized(fr.Call.Fn) && closurePtr == 0 { frames = nil addRetFrame = false stage = doneStage diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 02c3e628..0fe452da 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -1836,8 +1836,31 @@ func TestCapturedVariable(t *testing.T) { assertVariable(t, v, varTest{ name: "c", preserveName: true, - value: "struct { main.name string; main.thing main.Thing } {name: \"Success\", thing: main.Thing {str: \"hello\"}}", - varType: "struct { main.name string; main.thing main.Thing }", + + value: "struct { main.name string; main.thing main.Thing } {name: \"Success\", thing: main.Thing {str: \"hello\"}}", + varType: "struct { main.name string; main.thing main.Thing }", }) }) } + +func TestSetupRangeFramesCrash(t *testing.T) { + // See issue #3806 + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 23) { + t.Skip("N/A") + } + + for _, options := range []protest.BuildFlags{0, protest.EnableInlining | protest.EnableOptimization} { + withTestProcessArgs("setiterator", t, ".", []string{}, options, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + setFileBreakpoint(p, t, fixture.Source, 48) + assertNoError(grp.Continue(), t, "Continue") + scope, err := evalScope(p) + assertNoError(err, t, "EvalScope") + v, err := scope.LocalVariables(normalLoadConfig) + assertNoError(err, t, "LocalVariables") + t.Logf("%#v", v) + if len(v) != 1 { + t.Fatalf("wrong number of variables") + } + }) + } +}