proc: Update map reading code for Go 1.12 (#1532)

Go 1.12 introduced a change to the internal map representation where
empty map cells can be marked with a tophash value of 1 instead of just
0.

Fixes #1531
This commit is contained in:
Alessandro Arzilli 2019-04-26 19:23:43 +02:00 committed by Derek Parker
parent 0b3c7d80cd
commit 2cadddd787
3 changed files with 100 additions and 6 deletions

37
_fixtures/issue1531.go Normal file

@ -0,0 +1,37 @@
package main
import (
"fmt"
"runtime"
)
type W struct {
x int
y int
}
func main() {
testMaps()
}
func testMaps() {
m := make(map[string]W)
m["t"] = W{}
m["s"] = W{}
m["r"] = W{}
m["v"] = W{}
mm := map[string]W{
"r": {},
"s": {},
"t": {},
"v": {},
}
delete(mm, "s")
delete(m, "t")
runtime.Breakpoint()
fmt.Println(m, mm)
}

@ -18,6 +18,7 @@ import (
"github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/reader" "github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/go-delve/delve/pkg/goversion"
) )
const ( const (
@ -28,8 +29,10 @@ const (
chanRecv = "chan receive" chanRecv = "chan receive"
chanSend = "chan send" chanSend = "chan send"
hashTophashEmpty = 0 // used by map reading code, indicates an empty bucket hashTophashEmptyZero = 0 // used by map reading code, indicates an empty cell
hashMinTopHash = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated hashTophashEmptyOne = 1 // used by map reading code, indicates an empty cell in Go 1.12 and later
hashMinTopHashGo111 = 4 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated, in Go1.11
hashMinTopHashGo112 = 5 // used by map reading code, indicates minimum value of tophash that isn't empty or evacuated, in Go1.12
maxFramePrefetchSize = 1 * 1024 * 1024 // Maximum prefetch size for a stack frame maxFramePrefetchSize = 1 * 1024 * 1024 // Maximum prefetch size for a stack frame
@ -1763,6 +1766,9 @@ type mapIterator struct {
maxNumBuckets uint64 // maximum number of buckets to scan maxNumBuckets uint64 // maximum number of buckets to scan
idx int64 idx int64
hashTophashEmptyOne uint64 // Go 1.12 and later has two sentinel tophash values for an empty cell, this is the second one (the first one hashTophashEmptyZero, the same as Go 1.11 and earlier)
hashMinTopHash uint64 // minimum value of tophash for a cell that isn't either evacuated or empty
} }
// Code derived from go/src/runtime/hashmap.go // Code derived from go/src/runtime/hashmap.go
@ -1814,6 +1820,13 @@ func (v *Variable) mapIterator() *mapIterator {
return nil return nil
} }
it.hashTophashEmptyOne = hashTophashEmptyZero
it.hashMinTopHash = hashMinTopHashGo111
if producer := v.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 12) {
it.hashTophashEmptyOne = hashTophashEmptyOne
it.hashMinTopHash = hashMinTopHashGo112
}
return it return it
} }
@ -1851,7 +1864,7 @@ func (it *mapIterator) nextBucket() bool {
oldb := it.oldbuckets.clone() oldb := it.oldbuckets.clone()
oldb.Addr += uintptr(uint64(it.oldbuckets.DwarfType.Size()) * oldbidx) oldb.Addr += uintptr(uint64(it.oldbuckets.DwarfType.Size()) * oldbidx)
if mapEvacuated(oldb) { if it.mapEvacuated(oldb) {
break break
} }
@ -1953,7 +1966,7 @@ func (it *mapIterator) next() bool {
return false return false
} }
it.idx++ it.idx++
if h != hashTophashEmpty { if h != hashTophashEmptyZero && h != it.hashTophashEmptyOne {
return true return true
} }
} }
@ -1969,7 +1982,7 @@ func (it *mapIterator) value() *Variable {
return v return v
} }
func mapEvacuated(b *Variable) bool { func (it *mapIterator) mapEvacuated(b *Variable) bool {
if b.Addr == 0 { if b.Addr == 0 {
return true return true
} }
@ -1983,7 +1996,8 @@ func mapEvacuated(b *Variable) bool {
if err != nil { if err != nil {
return true return true
} }
return tophash0 > hashTophashEmpty && tophash0 < hashMinTopHash //TODO: this needs to be > hashTophashEmptyOne for go >= 1.12
return tophash0 > it.hashTophashEmptyOne && tophash0 < it.hashMinTopHash
} }
return true return true
} }

@ -3,6 +3,7 @@ package service_test
import ( import (
"errors" "errors"
"fmt" "fmt"
"go/constant"
"runtime" "runtime"
"sort" "sort"
"strings" "strings"
@ -1197,3 +1198,45 @@ func TestCallFunction(t *testing.T) {
} }
}) })
} }
func TestIssue1531(t *testing.T) {
// Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0.
withTestProcess("issue1531", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
hasKeys := func(mv *proc.Variable, keys ...string) {
n := 0
for i := 0; i < len(mv.Children); i += 2 {
cv := &mv.Children[i]
s := constant.StringVal(cv.Value)
found := false
for j := range keys {
if keys[j] == s {
found = true
break
}
}
if !found {
t.Errorf("key %q not allowed", s)
return
}
n++
}
if n != len(keys) {
t.Fatalf("wrong number of keys found")
}
}
mv, err := evalVariable(p, "m", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable(m)")
cmv := api.ConvertVar(mv)
t.Logf("m = %s", cmv.SinglelineString())
hasKeys(mv, "s", "r", "v")
mmv, err := evalVariable(p, "mm", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable(mm)")
cmmv := api.ConvertVar(mmv)
t.Logf("mm = %s", cmmv.SinglelineString())
hasKeys(mmv, "r", "t", "v")
})
}