proc: for optimized functions allow .closureptr to not exist (#3808)
* proc: flag variables correctly when range-over-func stmts are used Argument variables of a range-over-func body closure should be returned flagged as normal local variables, not as function arguments. Updates #3806 * proc: for optimized functions allow .closureptr to not exist For optimized functions .closureptr is sometimes omitted from DWARF, allow it to be 0 and try to recover the range-over-func stack by best effort. Fixes #3806
This commit is contained in:
parent
b9fadbae9b
commit
582305a813
51
_fixtures/setiterator.go
Normal file
51
_fixtures/setiterator.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -463,7 +463,7 @@ type compileUnit struct {
|
|||||||
entry *dwarf.Entry // debug_info entry describing this compile unit
|
entry *dwarf.Entry // debug_info entry describing this compile unit
|
||||||
isgo bool // true if this is the go compile unit
|
isgo bool // true if this is the go compile unit
|
||||||
lineInfo *line.DebugLineInfo // debug_line segment associated with this 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
|
producer string // producer attribute
|
||||||
|
|
||||||
offset dwarf.Offset // offset of the entry describing the compile unit
|
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.
|
image *Image // parent image of this compilation unit.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type optimizedFlags uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
optimizedInlined optimizedFlags = 1 << iota
|
||||||
|
optimizedOptimized
|
||||||
|
)
|
||||||
|
|
||||||
type fileLine struct {
|
type fileLine struct {
|
||||||
file string
|
file string
|
||||||
line int
|
line int
|
||||||
@ -605,7 +612,7 @@ func (fn *Function) NameWithoutTypeParams() string {
|
|||||||
|
|
||||||
// Optimized returns true if the function was optimized by the compiler.
|
// Optimized returns true if the function was optimized by the compiler.
|
||||||
func (fn *Function) Optimized() bool {
|
func (fn *Function) Optimized() bool {
|
||||||
return fn.cu.optimized
|
return fn.cu.optimized != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrologueEndPC returns the PC just after the function prologue
|
// 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 != "" {
|
if cu.isgo && cu.producer != "" {
|
||||||
semicolon := strings.Index(cu.producer, ";")
|
semicolon := strings.Index(cu.producer, ";")
|
||||||
if semicolon < 0 {
|
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 {
|
} 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"
|
const regabi = " regabi"
|
||||||
if i := strings.Index(cu.producer[semicolon:], regabi); i > 0 {
|
if i := strings.Index(cu.producer[semicolon:], regabi); i > 0 {
|
||||||
i += semicolon
|
i += semicolon
|
||||||
|
@ -71,6 +71,10 @@ const (
|
|||||||
// If localsOnlyRangeBodyClosures is set simpleLocals only returns
|
// If localsOnlyRangeBodyClosures is set simpleLocals only returns
|
||||||
// variables containing the range body closure.
|
// variables containing the range body closure.
|
||||||
localsOnlyRangeBodyClosures
|
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
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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:]...)
|
scope2 = FrameToScope(scope.target, scope.target.Memory(), scope.g, scope.threadID, scope.rangeFrames[2*i:]...)
|
||||||
scope.enclosingRangeScopes[i] = scope2
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -377,7 +390,9 @@ func (scope *EvalScope) setupRangeFrames() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
scope.enclosingRangeScopes = make([]*EvalScope, len(scope.rangeFrames)/2)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -455,7 +470,7 @@ func (scope *EvalScope) simpleLocals(flags localsFlags, wantedName string) ([]*V
|
|||||||
}
|
}
|
||||||
vars = append(vars, val)
|
vars = append(vars, val)
|
||||||
depth := entry.Depth
|
depth := entry.Depth
|
||||||
if entry.Tag == dwarf.TagFormalParameter {
|
if (flags&localsIsRangeBody == 0) && (entry.Tag == dwarf.TagFormalParameter) {
|
||||||
if depth <= 1 {
|
if depth <= 1 {
|
||||||
depth = 0
|
depth = 0
|
||||||
}
|
}
|
||||||
|
@ -573,7 +573,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
|
|||||||
return 0, nil, fmt.Errorf("DWARF read error: %v", err)
|
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] {
|
if runtimeWhitelist[fn.Name] {
|
||||||
runtimeOptimizedWorkaround(bi, fn.cu.image, dwarfTree)
|
runtimeOptimizedWorkaround(bi, fn.cu.image, dwarfTree)
|
||||||
} else {
|
} else {
|
||||||
|
@ -948,6 +948,10 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
|
|||||||
nonMonotonicSP := false
|
nonMonotonicSP := false
|
||||||
var closurePtr int64
|
var closurePtr int64
|
||||||
|
|
||||||
|
optimized := func(fn *Function) bool {
|
||||||
|
return fn.cu.optimized&optimizedOptimized != 0
|
||||||
|
}
|
||||||
|
|
||||||
appendFrame := func(fr Stackframe) {
|
appendFrame := func(fr Stackframe) {
|
||||||
frames = append(frames, fr)
|
frames = append(frames, fr)
|
||||||
if fr.closurePtr != 0 {
|
if fr.closurePtr != 0 {
|
||||||
@ -960,6 +964,9 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
|
|||||||
if fr.SystemStack {
|
if fr.SystemStack {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if closurePtr == 0 && optimized(fr.Call.Fn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if closurePtr < 0 {
|
if closurePtr < 0 {
|
||||||
// closure is stack allocated, check that it is on this frame
|
// closure is stack allocated, check that it is on this frame
|
||||||
return fr.contains(closurePtr)
|
return fr.contains(closurePtr)
|
||||||
@ -994,12 +1001,30 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
|
|||||||
frames = append(frames, fr)
|
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 {
|
switch stage {
|
||||||
case startStage:
|
case startStage:
|
||||||
appendFrame(fr)
|
appendFrame(fr)
|
||||||
rangeParent = fr.Call.Fn.extra(tgt.BinInfo()).rangeParent
|
rangeParent = fr.Call.Fn.extra(tgt.BinInfo()).rangeParent
|
||||||
stage = normalStage
|
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
|
frames = nil
|
||||||
addRetFrame = false
|
addRetFrame = false
|
||||||
stage = doneStage
|
stage = doneStage
|
||||||
@ -1011,7 +1036,7 @@ func rangeFuncStackTrace(tgt *Target, g *G) ([]Stackframe, error) {
|
|||||||
stage = lastFrameStage
|
stage = lastFrameStage
|
||||||
} else if fr.Call.Fn.extra(tgt.BinInfo()).rangeParent == rangeParent && closurePtrOk(&fr) {
|
} else if fr.Call.Fn.extra(tgt.BinInfo()).rangeParent == rangeParent && closurePtrOk(&fr) {
|
||||||
appendFrame(fr)
|
appendFrame(fr)
|
||||||
if closurePtr == 0 {
|
if !optimized(fr.Call.Fn) && closurePtr == 0 {
|
||||||
frames = nil
|
frames = nil
|
||||||
addRetFrame = false
|
addRetFrame = false
|
||||||
stage = doneStage
|
stage = doneStage
|
||||||
|
@ -1836,8 +1836,31 @@ func TestCapturedVariable(t *testing.T) {
|
|||||||
assertVariable(t, v, varTest{
|
assertVariable(t, v, varTest{
|
||||||
name: "c",
|
name: "c",
|
||||||
preserveName: true,
|
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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user