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:
parent
0b3c7d80cd
commit
2cadddd787
37
_fixtures/issue1531.go
Normal file
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user