Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,15 @@ func (tv *TypedValue) ComputeMapKey(m *Machine, store Store, omitType bool) (key
}
return nilStr, false
}
// Statically uncomparable key types are rejected at preprocess, so any
// uncomparable tv.T here arrived via interface boxing. Match Go by
// naming the outer dynamic type — isComparable recurses, so a struct

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment can be simplified

// or array with an uncomparable field reports the enclosing type, not
// the leaf.
if !isComparable(tv.T) {
panic(&Exception{Value: typedString(
"runtime error: hash of unhashable type " + tv.T.String())})
}
// General case.
bz := make([]byte, 0, 64)
// Charge per-byte for all bytes appended to bz in this call (TypeID
Expand Down Expand Up @@ -1658,8 +1667,6 @@ func (tv *TypedValue) ComputeMapKey(m *Machine, store Store, omitType bool) (key
bz = append(bz, av.Data...)
}
bz = append(bz, ']')
case *SliceType:
panic(&Exception{Value: typedString("runtime error: slice type cannot be used as map key")})
case *StructType:
sv := tv.V.(*StructValue)
sl := len(sv.Fields)
Expand All @@ -1681,9 +1688,11 @@ func (tv *TypedValue) ComputeMapKey(m *Machine, store Store, omitType bool) (key
case *ChanType:
panic("channel type is not yet supported")
default:
panic(fmt.Sprintf(
"unexpected map key type %s",
tv.T.String()))
// Defensive fallback. The isComparable check above already
// catches *SliceType, *MapType, *FuncType, and any composite that
// reaches here via interface boxing.
panic(&Exception{Value: typedString(
"runtime error: hash of unhashable type " + tv.T.String())})
}
return MapKey(bz), false
}
Expand Down
2 changes: 1 addition & 1 deletion gnovm/tests/files/maplit6.gno
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ func main() {
}

// Error:
// runtime error: slice type cannot be used as map key
// runtime error: hash of unhashable type []int
2 changes: 1 addition & 1 deletion gnovm/tests/files/maplit7.gno
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ func main() {
}

// Error:
// runtime error: slice type cannot be used as map key
// runtime error: hash of unhashable type []int
4 changes: 2 additions & 2 deletions gnovm/tests/files/range13.gno
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ func main() {
}

// Stacktrace:
// panic: runtime error: slice type cannot be used as map key
// panic: runtime error: hash of unhashable type []int
// main<VPBlock(1,0)>()
// main/range13.gno:5

// Error:
// runtime error: slice type cannot be used as map key
// runtime error: hash of unhashable type []int
15 changes: 15 additions & 0 deletions gnovm/tests/files/types/hash_uncomp_iface_array_map.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

// Array of maps via interface{}: panic must name the outer array
// type, not the inner map element type.
func main() {
defer func() {
r := recover()
println("recover:", r)
}()
m := map[interface{}]int{}
m[[1]map[int]int{}] = 1
}

// Output:
// recover: runtime error: hash of unhashable type [1]map[int]int
15 changes: 15 additions & 0 deletions gnovm/tests/files/types/hash_uncomp_iface_func.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

// Func dynamic key via interface{}: previously triggered an uncatchable
// host panic. Must be a Gno-catchable Exception now.
func main() {
defer func() {
r := recover()
println("recover:", r)
}()
m := map[interface{}]int{}
m[func() {}] = 1
}

// Output:
// recover: runtime error: hash of unhashable type func()
15 changes: 15 additions & 0 deletions gnovm/tests/files/types/hash_uncomp_iface_map.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

// Map dynamic key via interface{}: previously triggered an uncatchable
// host panic. Must be a Gno-catchable Exception now.
func main() {
defer func() {
r := recover()
println("recover:", r)
}()
m := map[interface{}]int{}
m[map[int]int{}] = 1
}

// Output:
// recover: runtime error: hash of unhashable type map[int]int
15 changes: 15 additions & 0 deletions gnovm/tests/files/types/hash_uncomp_iface_slice.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

// Slice as map[interface{}] key: must be a Gno-catchable panic naming
// the dynamic type, matching Go's runtime "hash of unhashable type".
func main() {
defer func() {
r := recover()
println("recover:", r)
}()
m := map[interface{}]int{}
m[[]int{}] = 1
}

// Output:
// recover: runtime error: hash of unhashable type []int
17 changes: 17 additions & 0 deletions gnovm/tests/files/types/hash_uncomp_iface_struct_slice.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

// Struct containing a slice, used as map[interface{}] key: the panic
// must name the outer struct dynamic type, not the inner slice leaf.
type S struct{ s []int }

func main() {
defer func() {
r := recover()
println("recover:", r)
}()
m := map[interface{}]int{}
m[S{}] = 1
}

// Output:
// recover: runtime error: hash of unhashable type main.S