go/ssa/interp: Adds reflect.DeepEqual model for interp testing

Implements reflect.DeepEqual() for testing interp packages. Adds test that exercises this.

Fixes a nil pointer dereference in ext۰reflect۰Value۰Elem in interp.

Updates golang/go#48525

Change-Id: Ib32e96d37aa5c85cefe59e9514d4e162d7f7011e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/391314
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Tim King <taking@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Trust: Tim King <taking@google.com>
This commit is contained in:
Tim King 2022-03-09 17:38:21 -08:00
parent e342718dfc
commit 761e51fea3
5 changed files with 220 additions and 2 deletions

View File

@ -111,6 +111,7 @@ var testdataTests = []string{
"complit.go",
"convert.go",
"coverage.go",
"deepequal.go",
"defer.go",
"fieldprom.go",
"ifaceconv.go",

View File

@ -407,7 +407,11 @@ func ext۰reflect۰Value۰Elem(fr *frame, args []value) value {
case iface:
return makeReflectValue(x.t, x.v)
case *value:
return makeReflectValue(rV2T(args[0]).t.Underlying().(*types.Pointer).Elem(), *x)
var v value
if x != nil {
v = *x
}
return makeReflectValue(rV2T(args[0]).t.Underlying().(*types.Pointer).Elem(), v)
default:
panic(fmt.Sprintf("reflect.(Value).Elem(%T)", x))
}

93
go/ssa/interp/testdata/deepequal.go vendored Normal file
View File

@ -0,0 +1,93 @@
// This interpreter test is designed to test the test copy of DeepEqual.
//
// Validate this file with 'go run' after editing.
package main
import "reflect"
func assert(cond bool) {
if !cond {
panic("failed")
}
}
type X int
type Y struct {
y *Y
z [3]int
}
var (
a = []int{0, 1, 2, 3}
b = []X{0, 1, 2, 3}
c = map[int]string{0: "zero", 1: "one"}
d = map[X]string{0: "zero", 1: "one"}
e = &Y{}
f = (*Y)(nil)
g = &Y{y: e}
h *Y
)
func init() {
h = &Y{} // h->h
h.y = h
}
func main() {
assert(reflect.DeepEqual(nil, nil))
assert(reflect.DeepEqual((*int)(nil), (*int)(nil)))
assert(!reflect.DeepEqual(nil, (*int)(nil)))
assert(reflect.DeepEqual(0, 0))
assert(!reflect.DeepEqual(0, int64(0)))
assert(!reflect.DeepEqual("", 0))
assert(reflect.DeepEqual(a, []int{0, 1, 2, 3}))
assert(!reflect.DeepEqual(a, []int{0, 1, 2}))
assert(!reflect.DeepEqual(a, []int{0, 1, 0, 3}))
assert(reflect.DeepEqual(b, []X{0, 1, 2, 3}))
assert(!reflect.DeepEqual(b, []X{0, 1, 0, 3}))
assert(reflect.DeepEqual(c, map[int]string{0: "zero", 1: "one"}))
assert(!reflect.DeepEqual(c, map[int]string{0: "zero", 1: "one", 2: "two"}))
assert(!reflect.DeepEqual(c, map[int]string{1: "one", 2: "two"}))
assert(!reflect.DeepEqual(c, map[int]string{1: "one"}))
assert(reflect.DeepEqual(d, map[X]string{0: "zero", 1: "one"}))
assert(!reflect.DeepEqual(d, map[int]string{0: "zero", 1: "one"}))
assert(reflect.DeepEqual(e, &Y{}))
assert(reflect.DeepEqual(e, &Y{z: [3]int{0, 0, 0}}))
assert(!reflect.DeepEqual(e, &Y{z: [3]int{0, 1, 0}}))
assert(reflect.DeepEqual(f, (*Y)(nil)))
assert(!reflect.DeepEqual(f, nil))
// eq_h -> eq_h. Pointer structure and elements are equal so DeepEqual.
eq_h := &Y{}
eq_h.y = eq_h
assert(reflect.DeepEqual(h, eq_h))
// deepeq_h->h->h. Pointed to elem of (deepeq_h, h) are (h,h). (h,h) are deep equal so h and deepeq_h are DeepEqual.
deepeq_h := &Y{}
deepeq_h.y = h
assert(reflect.DeepEqual(h, deepeq_h))
distinct := []interface{}{a, b, c, d, e, f, g, h}
for x := range distinct {
for y := range distinct {
assert((x == y) == reflect.DeepEqual(distinct[x], distinct[y]))
}
}
// anonymous struct types.
assert(reflect.DeepEqual(struct{}{}, struct{}{}))
assert(reflect.DeepEqual(struct{ x int }{1}, struct{ x int }{1}))
assert(!reflect.DeepEqual(struct{ x int }{}, struct{ x int }{5}))
assert(!reflect.DeepEqual(struct{ x, y int }{0, 1}, struct{ x int }{0}))
assert(reflect.DeepEqual(struct{ x, y int }{2, 3}, struct{ x, y int }{2, 3}))
assert(!reflect.DeepEqual(struct{ x, y int }{4, 5}, struct{ x, y int }{4, 6}))
}

View File

@ -0,0 +1,109 @@
package reflect
// Not an actual implementation of DeepEqual. This is a model that supports
// the bare minimum needed to get through testing interp.
//
// Does not handle cycles.
//
// Note: unclear if reflect.go can support this.
func DeepEqual(x, y interface{}) bool {
if x == nil || y == nil {
return x == y
}
v1 := ValueOf(x)
v2 := ValueOf(y)
return deepValueEqual(v1, v2, make(map[visit]bool))
}
// Key for the visitedMap in deepValueEqual.
type visit struct {
a1, a2 uintptr
typ Type
}
func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
if v1.Type() != v2.Type() {
return false
}
// Short circuit on reference types that can lead to cycles in comparison.
switch v1.Kind() {
case Pointer, Map, Slice, Interface:
k := visit{v1.Pointer(), v2.Pointer(), v1.Type()} // Not safe for moving GC.
if visited[k] {
// The comparison algorithm assumes that all checks in progress are true when it reencounters them.
return true
}
visited[k] = true
}
switch v1.Kind() {
case Array:
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
return false
}
}
return true
case Slice:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.Pointer() == v2.Pointer() {
return true
}
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
return false
}
}
return true
case Interface:
if v1.IsNil() || v2.IsNil() {
return v1.IsNil() == v2.IsNil()
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
case Ptr:
if v1.Pointer() == v2.Pointer() {
return true
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
case Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {
return false
}
}
return true
case Map:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.Pointer() == v2.Pointer() {
return true
}
for _, k := range v1.MapKeys() {
val1 := v1.MapIndex(k)
val2 := v2.MapIndex(k)
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {
return false
}
}
return true
case Func:
return v1.IsNil() && v2.IsNil()
default:
// Normal equality suffices
return v1.Interface() == v2.Interface() // try interface comparison as a fallback.
}
}

View File

@ -11,9 +11,20 @@ type Value struct {
func (Value) String() string
func (Value) Elem() string
func (Value) Elem() Value
func (Value) Kind() Kind
func (Value) Int() int64
func (Value) IsValid() bool
func (Value) IsNil() bool
func (Value) Len() int
func (Value) Pointer() uintptr
func (Value) Index(i int) Value
func (Value) Type() Type
func (Value) Field(int) Value
func (Value) MapIndex(Value) Value
func (Value) MapKeys() []Value
func (Value) NumField() int
func (Value) Interface() interface{}
func SliceOf(Type) Type