diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go index 1b43742c8a..1fba99f1fd 100644 --- a/go/ssa/interp/interp_test.go +++ b/go/ssa/interp/interp_test.go @@ -111,6 +111,7 @@ var testdataTests = []string{ "complit.go", "convert.go", "coverage.go", + "deepequal.go", "defer.go", "fieldprom.go", "ifaceconv.go", diff --git a/go/ssa/interp/reflect.go b/go/ssa/interp/reflect.go index 0a4465b0b8..9f2f9e1e45 100644 --- a/go/ssa/interp/reflect.go +++ b/go/ssa/interp/reflect.go @@ -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)) } diff --git a/go/ssa/interp/testdata/deepequal.go b/go/ssa/interp/testdata/deepequal.go new file mode 100644 index 0000000000..4fad2d657c --- /dev/null +++ b/go/ssa/interp/testdata/deepequal.go @@ -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})) +} diff --git a/go/ssa/interp/testdata/src/reflect/deepequal.go b/go/ssa/interp/testdata/src/reflect/deepequal.go new file mode 100644 index 0000000000..a48e4dafab --- /dev/null +++ b/go/ssa/interp/testdata/src/reflect/deepequal.go @@ -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. + } +} diff --git a/go/ssa/interp/testdata/src/reflect/reflect.go b/go/ssa/interp/testdata/src/reflect/reflect.go index 8a23d272f9..207e7dcfd3 100644 --- a/go/ssa/interp/testdata/src/reflect/reflect.go +++ b/go/ssa/interp/testdata/src/reflect/reflect.go @@ -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