runtime: rename mapiterinit and mapiternext

mapiterinit allows external linkname. These users must allocate their
own iter struct for initialization by mapiterinit. Since the type is
unexported, they also must define the struct themselves. As a result,
they of course define the struct matching the old hiter definition (in
map_noswiss.go).

The old definition is smaller on 32-bit platforms. On those platforms,
mapiternext will clobber memory outside of the caller's allocation.

On all platforms, the pointer layout between the old hiter and new
maps.Iter does not match. Thus the GC may miss pointers and free
reachable objects early, or it may see non-pointers that look like heap
pointers and throw due to invalid references to free objects.

To avoid these issues, we must keep mapiterinit and mapiternext with the
old hiter definition. The most straightforward way to do this is to use
mapiterinit and mapiternext as a compatibility layer between the old and
new iter types.

The first step to that is to move normal map use off of these functions,
which is what this CL does.

Introduce new mapIterStart and mapIterNext functions that replace the
former functions everywhere in the toolchain. These have the same
behavior as the old functions.

This CL temporarily makes the old functions throw to ensure we don't
have hidden dependencies on them. We cannot remove them entirely because
GOEXPERIMENT=noswissmap still uses the old names, and internal/goobj
requires all builtins to exist regardless of GOEXPERIMENT. The next CL
will introduce the compatibility layer.

I want to avoid using linkname between runtime and reflect, as that
would also allow external linknames. So mapIterStart and mapIterNext are
duplicated in reflect, which can be done trivially, as it imports
internal/runtime/maps.

For #71408.

Change-Id: I6a6a636c6d4bd1392618c67ca648d3f061afe669
Reviewed-on: https://go-review.googlesource.com/c/go/+/643898
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Keith Randall <khr@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Michael Pratt 2025-01-24 13:34:26 -05:00 committed by Gopher Robot
parent 4ebd5bf855
commit 78e6f2a1c8
11 changed files with 115 additions and 51 deletions

View File

@ -152,12 +152,14 @@ func mapassign_fast32ptr(mapType *byte, hmap map[any]any, key unsafe.Pointer) (v
func mapassign_fast64(mapType *byte, hmap map[any]any, key uint64) (val *any) func mapassign_fast64(mapType *byte, hmap map[any]any, key uint64) (val *any)
func mapassign_fast64ptr(mapType *byte, hmap map[any]any, key unsafe.Pointer) (val *any) func mapassign_fast64ptr(mapType *byte, hmap map[any]any, key unsafe.Pointer) (val *any)
func mapassign_faststr(mapType *byte, hmap map[any]any, key string) (val *any) func mapassign_faststr(mapType *byte, hmap map[any]any, key string) (val *any)
func mapiterinit(mapType *byte, hmap map[any]any, hiter *any) func mapiterinit(mapType *byte, hmap map[any]any, hiter *any) // old maps
func mapIterStart(mapType *byte, hmap map[any]any, hiter *any) // swiss maps
func mapdelete(mapType *byte, hmap map[any]any, key *any) func mapdelete(mapType *byte, hmap map[any]any, key *any)
func mapdelete_fast32(mapType *byte, hmap map[any]any, key uint32) func mapdelete_fast32(mapType *byte, hmap map[any]any, key uint32)
func mapdelete_fast64(mapType *byte, hmap map[any]any, key uint64) func mapdelete_fast64(mapType *byte, hmap map[any]any, key uint64)
func mapdelete_faststr(mapType *byte, hmap map[any]any, key string) func mapdelete_faststr(mapType *byte, hmap map[any]any, key string)
func mapiternext(hiter *any) func mapiternext(hiter *any) // old maps
func mapIterNext(hiter *any) // swiss maps
func mapclear(mapType *byte, hmap map[any]any) func mapclear(mapType *byte, hmap map[any]any)
// *byte is really *runtime.Type // *byte is really *runtime.Type

View File

@ -131,11 +131,13 @@ var runtimeDecls = [...]struct {
{"mapassign_fast64ptr", funcTag, 96}, {"mapassign_fast64ptr", funcTag, 96},
{"mapassign_faststr", funcTag, 89}, {"mapassign_faststr", funcTag, 89},
{"mapiterinit", funcTag, 97}, {"mapiterinit", funcTag, 97},
{"mapIterStart", funcTag, 97},
{"mapdelete", funcTag, 97}, {"mapdelete", funcTag, 97},
{"mapdelete_fast32", funcTag, 98}, {"mapdelete_fast32", funcTag, 98},
{"mapdelete_fast64", funcTag, 99}, {"mapdelete_fast64", funcTag, 99},
{"mapdelete_faststr", funcTag, 100}, {"mapdelete_faststr", funcTag, 100},
{"mapiternext", funcTag, 101}, {"mapiternext", funcTag, 101},
{"mapIterNext", funcTag, 101},
{"mapclear", funcTag, 102}, {"mapclear", funcTag, 102},
{"makechan64", funcTag, 104}, {"makechan64", funcTag, 104},
{"makechan", funcTag, 105}, {"makechan", funcTag, 105},

View File

@ -244,19 +244,24 @@ func walkRange(nrange *ir.RangeStmt) ir.Node {
// depends on layout of iterator struct. // depends on layout of iterator struct.
// See cmd/compile/internal/reflectdata/reflect.go:MapIterType // See cmd/compile/internal/reflectdata/reflect.go:MapIterType
var keysym, elemsym *types.Sym var keysym, elemsym *types.Sym
var iterInit, iterNext string
if buildcfg.Experiment.SwissMap { if buildcfg.Experiment.SwissMap {
keysym = th.Field(0).Sym keysym = th.Field(0).Sym
elemsym = th.Field(1).Sym // ditto elemsym = th.Field(1).Sym // ditto
iterInit = "mapIterStart"
iterNext = "mapIterNext"
} else { } else {
keysym = th.Field(0).Sym keysym = th.Field(0).Sym
elemsym = th.Field(1).Sym // ditto elemsym = th.Field(1).Sym // ditto
iterInit = "mapiterinit"
iterNext = "mapiternext"
} }
fn := typecheck.LookupRuntime("mapiterinit", t.Key(), t.Elem(), th) fn := typecheck.LookupRuntime(iterInit, t.Key(), t.Elem(), th)
init = append(init, mkcallstmt1(fn, reflectdata.RangeMapRType(base.Pos, nrange), ha, typecheck.NodAddr(hit))) init = append(init, mkcallstmt1(fn, reflectdata.RangeMapRType(base.Pos, nrange), ha, typecheck.NodAddr(hit)))
nfor.Cond = ir.NewBinaryExpr(base.Pos, ir.ONE, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym), typecheck.NodNil()) nfor.Cond = ir.NewBinaryExpr(base.Pos, ir.ONE, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym), typecheck.NodNil())
fn = typecheck.LookupRuntime("mapiternext", th) fn = typecheck.LookupRuntime(iterNext, th)
nfor.Post = mkcallstmt1(fn, typecheck.NodAddr(hit)) nfor.Post = mkcallstmt1(fn, typecheck.NodAddr(hit))
key := ir.NewStarExpr(base.Pos, typecheck.ConvNop(ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym), types.NewPtr(t.Key()))) key := ir.NewStarExpr(base.Pos, typecheck.ConvNop(ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym), types.NewPtr(t.Key())))

View File

@ -110,11 +110,13 @@ var builtins = [...]struct {
{"runtime.mapassign_fast64ptr", 1}, {"runtime.mapassign_fast64ptr", 1},
{"runtime.mapassign_faststr", 1}, {"runtime.mapassign_faststr", 1},
{"runtime.mapiterinit", 1}, {"runtime.mapiterinit", 1},
{"runtime.mapIterStart", 1},
{"runtime.mapdelete", 1}, {"runtime.mapdelete", 1},
{"runtime.mapdelete_fast32", 1}, {"runtime.mapdelete_fast32", 1},
{"runtime.mapdelete_fast64", 1}, {"runtime.mapdelete_fast64", 1},
{"runtime.mapdelete_faststr", 1}, {"runtime.mapdelete_faststr", 1},
{"runtime.mapiternext", 1}, {"runtime.mapiternext", 1},
{"runtime.mapIterNext", 1},
{"runtime.mapclear", 1}, {"runtime.mapclear", 1},
{"runtime.makechan64", 1}, {"runtime.makechan64", 1},
{"runtime.makechan", 1}, {"runtime.makechan", 1},

View File

@ -17,6 +17,14 @@ type mapType struct {
abi.OldMapType abi.OldMapType
} }
// Pushed from runtime.
//go:noescape
func mapiterinit(t *abi.Type, m unsafe.Pointer, it *hiter)
//go:noescape
func mapiternext(it *hiter)
func (t *rtype) Key() Type { func (t *rtype) Key() Type {
if t.Kind() != Map { if t.Kind() != Map {
panic("reflect: Key of non-map type " + t.String()) panic("reflect: Key of non-map type " + t.String())

View File

@ -8,14 +8,16 @@ package reflect
import ( import (
"internal/abi" "internal/abi"
"internal/race"
"internal/runtime/maps" "internal/runtime/maps"
"internal/runtime/sys"
"unsafe" "unsafe"
) )
// mapType represents a map type. // mapType represents a map type.
type mapType struct { //
abi.SwissMapType // TODO(prattmic): Only used within this file, could be cleaned up.
} type mapType = abi.SwissMapType
func (t *rtype) Key() Type { func (t *rtype) Key() Type {
if t.Kind() != Map { if t.Kind() != Map {
@ -176,6 +178,31 @@ func (v Value) MapIndex(key Value) Value {
return copyVal(typ, fl, e) return copyVal(typ, fl, e)
} }
// Equivalent to runtime.mapIterStart.
//
//go:noinline
func mapIterStart(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
if race.Enabled && m != nil {
callerpc := sys.GetCallerPC()
race.ReadPC(unsafe.Pointer(m), callerpc, abi.FuncPCABIInternal(mapIterStart))
}
it.Init(t, m)
it.Next()
}
// Equivalent to runtime.mapIterNext.
//
//go:noinline
func mapIterNext(it *maps.Iter) {
if race.Enabled {
callerpc := sys.GetCallerPC()
race.ReadPC(unsafe.Pointer(it.Map()), callerpc, abi.FuncPCABIInternal(mapIterNext))
}
it.Next()
}
// MapKeys returns a slice containing all the keys present in the map, // MapKeys returns a slice containing all the keys present in the map,
// in unspecified order. // in unspecified order.
// It panics if v's Kind is not [Map]. // It panics if v's Kind is not [Map].
@ -187,13 +214,17 @@ func (v Value) MapKeys() []Value {
fl := v.flag.ro() | flag(keyType.Kind()) fl := v.flag.ro() | flag(keyType.Kind())
m := v.pointer() // Escape analysis can't see that the map doesn't escape. It sees an
// escape from maps.IterStart, via assignment into it, even though it
// doesn't escape this function.
mptr := abi.NoEscape(v.pointer())
m := (*maps.Map)(mptr)
mlen := int(0) mlen := int(0)
if m != nil { if m != nil {
mlen = maplen(m) mlen = maplen(mptr)
} }
var it maps.Iter var it maps.Iter
mapiterinit(v.typ(), m, &it) mapIterStart(tt, m, &it)
a := make([]Value, mlen) a := make([]Value, mlen)
var i int var i int
for i = 0; i < len(a); i++ { for i = 0; i < len(a); i++ {
@ -205,7 +236,7 @@ func (v Value) MapKeys() []Value {
break break
} }
a[i] = copyVal(keyType, fl, key) a[i] = copyVal(keyType, fl, key)
mapiternext(&it) mapIterNext(&it)
} }
return a[:i] return a[:i]
} }
@ -317,12 +348,14 @@ func (iter *MapIter) Next() bool {
panic("MapIter.Next called on an iterator that does not have an associated map Value") panic("MapIter.Next called on an iterator that does not have an associated map Value")
} }
if !iter.hiter.Initialized() { if !iter.hiter.Initialized() {
mapiterinit(iter.m.typ(), iter.m.pointer(), &iter.hiter) t := (*mapType)(unsafe.Pointer(iter.m.typ()))
m := (*maps.Map)(iter.m.pointer())
mapIterStart(t, m, &iter.hiter)
} else { } else {
if iter.hiter.Key() == nil { if iter.hiter.Key() == nil {
panic("MapIter.Next called on exhausted iterator") panic("MapIter.Next called on exhausted iterator")
} }
mapiternext(&iter.hiter) mapIterNext(&iter.hiter)
} }
return iter.hiter.Key() != nil return iter.hiter.Key() != nil
} }

View File

@ -3603,12 +3603,6 @@ func mapdelete(t *abi.Type, m unsafe.Pointer, key unsafe.Pointer)
//go:noescape //go:noescape
func mapdelete_faststr(t *abi.Type, m unsafe.Pointer, key string) func mapdelete_faststr(t *abi.Type, m unsafe.Pointer, key string)
//go:noescape
func mapiterinit(t *abi.Type, m unsafe.Pointer, it *hiter)
//go:noescape
func mapiternext(it *hiter)
//go:noescape //go:noescape
func maplen(m unsafe.Pointer) int func maplen(m unsafe.Pointer) int

View File

@ -176,11 +176,21 @@ func mapdelete(t *abi.SwissMapType, m *maps.Map, key unsafe.Pointer) {
// Do not remove or change the type signature. // Do not remove or change the type signature.
// See go.dev/issue/67401. // See go.dev/issue/67401.
// //
//go:linkname mapiterinit // TODO go:linkname mapiterinit
func mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) { func mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
// N.B. This is required by the builtin list in internal/goobj because
// it is a builtin for old maps.
throw("unreachable")
}
// mapIterStart initializes the Iter struct used for ranging over maps and
// performs the first step of iteration. The Iter struct pointed to by 'it' is
// allocated on the stack by the compilers order pass or on the heap by
// reflect. Both need to have zeroed it since the struct contains pointers.
func mapIterStart(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
if raceenabled && m != nil { if raceenabled && m != nil {
callerpc := sys.GetCallerPC() callerpc := sys.GetCallerPC()
racereadpc(unsafe.Pointer(m), callerpc, abi.FuncPCABIInternal(mapiterinit)) racereadpc(unsafe.Pointer(m), callerpc, abi.FuncPCABIInternal(mapIterStart))
} }
it.Init(t, m) it.Init(t, m)
@ -199,11 +209,19 @@ func mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
// Do not remove or change the type signature. // Do not remove or change the type signature.
// See go.dev/issue/67401. // See go.dev/issue/67401.
// //
//go:linkname mapiternext // TODO go:linkname mapiternext
func mapiternext(it *maps.Iter) { func mapiternext(it *maps.Iter) {
// N.B. This is required by the builtin list in internal/goobj because
// it is a builtin for old maps.
throw("unreachable")
}
// mapIterNext performs the next step of iteration. Afterwards, the next
// key/elem are in it.Key()/it.Elem().
func mapIterNext(it *maps.Iter) {
if raceenabled { if raceenabled {
callerpc := sys.GetCallerPC() callerpc := sys.GetCallerPC()
racereadpc(unsafe.Pointer(it.Map()), callerpc, abi.FuncPCABIInternal(mapiternext)) racereadpc(unsafe.Pointer(it.Map()), callerpc, abi.FuncPCABIInternal(mapIterNext))
} }
it.Next() it.Next()
@ -317,10 +335,10 @@ func reflect_mapdelete_faststr(t *abi.SwissMapType, m *maps.Map, key string) {
// Do not remove or change the type signature. // Do not remove or change the type signature.
// See go.dev/issue/67401. // See go.dev/issue/67401.
// //
//go:linkname reflect_mapiterinit reflect.mapiterinit // TODO go:linkname reflect_mapiterinit reflect.mapiterinit
func reflect_mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) { //func reflect_mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
mapiterinit(t, m, it) // mapiterinit(t, m, it)
} //}
// reflect_mapiternext is for package reflect, // reflect_mapiternext is for package reflect,
// but widely used packages access it using linkname. // but widely used packages access it using linkname.
@ -334,10 +352,10 @@ func reflect_mapiterinit(t *abi.SwissMapType, m *maps.Map, it *maps.Iter) {
// Do not remove or change the type signature. // Do not remove or change the type signature.
// See go.dev/issue/67401. // See go.dev/issue/67401.
// //
//go:linkname reflect_mapiternext reflect.mapiternext // TODO go:linkname reflect_mapiternext reflect.mapiternext
func reflect_mapiternext(it *maps.Iter) { //func reflect_mapiternext(it *maps.Iter) {
mapiternext(it) // mapiternext(it)
} //}
// reflect_mapiterkey was for package reflect, // reflect_mapiterkey was for package reflect,
// but widely used packages access it using linkname. // but widely used packages access it using linkname.
@ -348,10 +366,10 @@ func reflect_mapiternext(it *maps.Iter) {
// Do not remove or change the type signature. // Do not remove or change the type signature.
// See go.dev/issue/67401. // See go.dev/issue/67401.
// //
//go:linkname reflect_mapiterkey reflect.mapiterkey // TODO go:linkname reflect_mapiterkey reflect.mapiterkey
func reflect_mapiterkey(it *maps.Iter) unsafe.Pointer { //func reflect_mapiterkey(it *maps.Iter) unsafe.Pointer {
return it.Key() // return it.Key()
} //}
// reflect_mapiterelem was for package reflect, // reflect_mapiterelem was for package reflect,
// but widely used packages access it using linkname. // but widely used packages access it using linkname.
@ -362,10 +380,10 @@ func reflect_mapiterkey(it *maps.Iter) unsafe.Pointer {
// Do not remove or change the type signature. // Do not remove or change the type signature.
// See go.dev/issue/67401. // See go.dev/issue/67401.
// //
//go:linkname reflect_mapiterelem reflect.mapiterelem // TODO go:linkname reflect_mapiterelem reflect.mapiterelem
func reflect_mapiterelem(it *maps.Iter) unsafe.Pointer { //func reflect_mapiterelem(it *maps.Iter) unsafe.Pointer {
return it.Elem() // return it.Elem()
} //}
// reflect_maplen is for package reflect, // reflect_maplen is for package reflect,
// but widely used packages access it using linkname. // but widely used packages access it using linkname.

View File

@ -74,7 +74,7 @@ func LookupStringConversionKeyedArrayLit(m map[[2]string]int, bytes []byte) int
func MapClearReflexive(m map[int]int) { func MapClearReflexive(m map[int]int) {
// amd64:`.*runtime\.mapclear` // amd64:`.*runtime\.mapclear`
// amd64:-`.*runtime\.mapiterinit` // amd64:-`.*runtime\.(mapiterinit|mapIterStart)`
for k := range m { for k := range m {
delete(m, k) delete(m, k)
} }
@ -83,7 +83,7 @@ func MapClearReflexive(m map[int]int) {
func MapClearIndirect(m map[int]int) { func MapClearIndirect(m map[int]int) {
s := struct{ m map[int]int }{m: m} s := struct{ m map[int]int }{m: m}
// amd64:`.*runtime\.mapclear` // amd64:`.*runtime\.mapclear`
// amd64:-`.*runtime\.mapiterinit` // amd64:-`.*runtime\.(mapiterinit|mapIterStart)`
for k := range s.m { for k := range s.m {
delete(s.m, k) delete(s.m, k)
} }
@ -91,14 +91,14 @@ func MapClearIndirect(m map[int]int) {
func MapClearPointer(m map[*byte]int) { func MapClearPointer(m map[*byte]int) {
// amd64:`.*runtime\.mapclear` // amd64:`.*runtime\.mapclear`
// amd64:-`.*runtime\.mapiterinit` // amd64:-`.*runtime\.(mapiterinit|mapIterStart)`
for k := range m { for k := range m {
delete(m, k) delete(m, k)
} }
} }
func MapClearNotReflexive(m map[float64]int) { func MapClearNotReflexive(m map[float64]int) {
// amd64:`.*runtime\.mapiterinit` // amd64:`.*runtime\.(mapiterinit|mapIterStart)`
// amd64:-`.*runtime\.mapclear` // amd64:-`.*runtime\.mapclear`
for k := range m { for k := range m {
delete(m, k) delete(m, k)
@ -106,7 +106,7 @@ func MapClearNotReflexive(m map[float64]int) {
} }
func MapClearInterface(m map[interface{}]int) { func MapClearInterface(m map[interface{}]int) {
// amd64:`.*runtime\.mapiterinit` // amd64:`.*runtime\.(mapiterinit|mapIterStart)`
// amd64:-`.*runtime\.mapclear` // amd64:-`.*runtime\.mapclear`
for k := range m { for k := range m {
delete(m, k) delete(m, k)
@ -115,7 +115,7 @@ func MapClearInterface(m map[interface{}]int) {
func MapClearSideEffect(m map[int]int) int { func MapClearSideEffect(m map[int]int) int {
k := 0 k := 0
// amd64:`.*runtime\.mapiterinit` // amd64:`.*runtime\.(mapiterinit|mapIterStart)`
// amd64:-`.*runtime\.mapclear` // amd64:-`.*runtime\.mapclear`
for k = range m { for k = range m {
delete(m, k) delete(m, k)

View File

@ -458,14 +458,14 @@ func f28(b bool) {
func f29(b bool) { func f29(b bool) {
if b { if b {
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ (runtime.hiter|internal/runtime/maps.Iter)$" for k := range m { // ERROR "live at call to (mapiterinit|mapIterStart): .autotmp_[0-9]+$" "live at call to (mapiternext|mapIterNext): .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ (runtime.hiter|internal/runtime/maps.Iter)$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
} }
} }
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" for k := range m { // ERROR "live at call to (mapiterinit|mapIterStart): .autotmp_[0-9]+$" "live at call to (mapiternext|mapIterNext): .autotmp_[0-9]+$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
} }
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" for k := range m { // ERROR "live at call to (mapiterinit|mapIterStart): .autotmp_[0-9]+$" "live at call to (mapiternext|mapIterNext): .autotmp_[0-9]+$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
} }
} }

View File

@ -456,14 +456,14 @@ func f28(b bool) {
func f29(b bool) { func f29(b bool) {
if b { if b {
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ (runtime.hiter|internal/runtime/maps.Iter)$" for k := range m { // ERROR "live at call to (mapiterinit|mapIterStart): .autotmp_[0-9]+$" "live at call to (mapiternext|mapIterNext): .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ (runtime.hiter|internal/runtime/maps.Iter)$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
} }
} }
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" for k := range m { // ERROR "live at call to (mapiterinit|mapIterStart): .autotmp_[0-9]+$" "live at call to (mapiternext|mapIterNext): .autotmp_[0-9]+$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
} }
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" for k := range m { // ERROR "live at call to (mapiterinit|mapIterStart): .autotmp_[0-9]+$" "live at call to (mapiternext|mapIterNext): .autotmp_[0-9]+$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
} }
} }