diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index fb1a29d060..0438cc4017 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1056,6 +1056,30 @@ func init() { cycleMap2["cycle"] = cycleMap2 cycleMap3 = map[string]any{} cycleMap3["different"] = cycleMap3 + + p1.b = append(p1.b, p2, p1, p1) + p2.b = append(p2.b, p1, p2, p1) +} + +type slicePtr struct { + b []*slicePtr +} + +var ( + p1 = new(slicePtr) + p2 = new(slicePtr) +) + +type structFloat64 struct { + D float64 +} + +type structFloat32 struct { + D float32 +} + +type structChanInt32 struct { + D chan int32 } var deepEqualTests = []DeepEqualTest{ @@ -1075,6 +1099,10 @@ var deepEqualTests = []DeepEqualTest{ {[]byte{1, 2, 3}, []byte{1, 2, 3}, true}, {[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true}, {MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true}, + {[]int32{1, 2, 3}, []int32{1, 2, 3}, true}, + {p1, p2, true}, + {[]*int{ptr(1), ptr(2)}, []*int{ptr(1), ptr(2)}, true}, + {[]int32{1, 2, 3, 4}[:3], []int32{1, 2, 3, 5}[:3], true}, // Inequalities {1, 2, false}, @@ -1096,6 +1124,9 @@ var deepEqualTests = []DeepEqualTest{ {fn3, fn3, false}, {[][]int{{1}}, [][]int{{2}}, false}, {&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false}, + {[]int32{1, 2, 3}, []int32{2, 1, 3}, false}, + {[]*int{ptr(1), ptr(2)}, []*int{ptr(2), ptr(1)}, false}, + {[]int32{1, 2, 3, 4}, []int32{1, 2, 3, 5}, false}, // Fun with floating point. {math.NaN(), math.NaN(), false}, @@ -1133,6 +1164,60 @@ var deepEqualTests = []DeepEqualTest{ {&loopy1, &loopy2, true}, {&cycleMap1, &cycleMap2, true}, {&cycleMap1, &cycleMap3, false}, + + // struct slice and StructOf slice + {[]structFloat64{structFloat64{D: negativeZero[float64]()}}, []structFloat64{structFloat64{D: 0}}, true}, + { + []any{newStructOf(VisibleFields(TypeFor[structFloat64]()), setStructOfField("D", negativeZero[float64]()))}, + []any{newStructOf(VisibleFields(TypeFor[structFloat64]()), setStructOfField("D", float64(0)))}, + true, + }, + + {[]structFloat32{structFloat32{D: negativeZero[float32]()}}, []structFloat32{structFloat32{D: 0}}, true}, + { + []any{newStructOf(VisibleFields(TypeFor[structFloat32]()), setStructOfField("D", negativeZero[float32]()))}, + []any{newStructOf(VisibleFields(TypeFor[structFloat32]()), setStructOfField("D", float32(0)))}, + true, + }, + + {[]structChanInt32{structChanInt32{D: make(chan int32)}}, []structChanInt32{structChanInt32{D: make(chan int32)}}, false}, + { + []any{newStructOf(VisibleFields(TypeFor[structChanInt32]()), setStructOfField("D", make(chan int32)))}, + []any{newStructOf(VisibleFields(TypeFor[structChanInt32]()), setStructOfField("D", make(chan int32)))}, + false, + }, +} + +func ptr[T any](a T) *T { + return &a +} + +type fieldSet func(Value) + +// newStructOf call StructOf get typ +// use typ new any value +// use set change new any value +func newStructOf(field []StructField, set ...fieldSet) (ret any) { + typ := StructOf(field) + rret := New(typ).Elem() + for _, f := range set { + f(rret) + } + return rret.Interface() +} + +// setStructOfField set a field of the struct represented by reflect.Value +func setStructOfField(fieldName string, fieldValue any) func(Value) { + return func(v Value) { + field := v.FieldByName(fieldName) + field.Set(ValueOf(fieldValue)) + } +} + +func negativeZero[T float32 | float64]() T { + var f T + f = -f + return f } func TestDeepEqual(t *testing.T) { @@ -1274,6 +1359,8 @@ var deepEqualPerfTests = []struct { {x: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}, y: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}, {x: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}, y: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}}, + + {x: make([]int16, 10240), y: make([]int16, 10240)}, } func TestDeepEqualAllocs(t *testing.T) { diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index 041c3e1f7e..ebc2c62498 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -7,6 +7,7 @@ package reflect import ( + "internal/abi" "internal/bytealg" "unsafe" ) @@ -99,15 +100,23 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { if v1.IsNil() != v2.IsNil() { return false } - if v1.Len() != v2.Len() { + len := v1.Len() + if len != v2.Len() { return false } - if v1.UnsafePointer() == v2.UnsafePointer() { + v1ptr := v1.UnsafePointer() + v2ptr := v2.UnsafePointer() + if v1ptr == v2ptr { return true } - // Special case for []byte, which is common. - if v1.Type().Elem().Kind() == Uint8 { - return bytealg.Equal(v1.Bytes(), v2.Bytes()) + // Special case raw memory. Particularly, []byte is very common and is handled here. + elem := v1.typ().Elem() + if isDeepEqualRawMemory(elem) { + size := elem.Size_ * uintptr(len) + return bytealg.Equal( + unsafe.Slice((*byte)(v1ptr), size), + unsafe.Slice((*byte)(v2ptr), size), + ) } for i := 0; i < v1.Len(); i++ { if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { @@ -175,6 +184,22 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { } } +// isDeepEqualRawMemory reports whether DeepEqual can compare +// two instances of a type by just comparing their raw memory contents. +func isDeepEqualRawMemory(typ *abi.Type) (ok bool) { + // Note: Here is an incorrect implementation : + // + // return typ.TFlag&abi.TFlagRegularMemory != 0 + // + // The reason is DeepEqual can't determine whether two pointer values are equal by comparing them byte by byte. + // Doing so would report unequal when it shouldn't, + // in the case where two pointers are different but point to the same contents + // (e.g. two *int32s that point to different int32s, + // both of which contain the same value). + + return typ.PtrBytes == 0 && typ.TFlag&abi.TFlagRegularMemory != 0 +} + // DeepEqual reports whether x and y are “deeply equal,” defined as follows. // Two values of identical type are deeply equal if one of the following cases applies. // Values of distinct types are never deeply equal.