Skip to content

Commit 4dc11ae

Browse files
huanduianlancetaylor
authored andcommitted
reflect: fix panic in DeepEqual when checking a cycle
Before this change, when DeepEqual checks values with cycle, it may panic due to stack overflow. Here is a sample to reproduce the issue. makeCycleMap := func() interface{} { cycleMap := map[string]interface{}{} cycleMap["foo"] = cycleMap return cycleMap } m1 := makeCycleMap() m2 := makeCycleMap() reflect.DeepEqual(m1, m2) // stack overflow The root cause is that DeepEqual fails to cache interface values in visited map, which is used to detect cycle. DeepEqual calls CanAddr to check whether a value should be cached or not. However, all values referenced by interface don't have flagAddr thus all these values are not cached. THe fix is to remove CanAddr calls and use underlying ptr in value directly. As ptr is only read-only in DeepEqual for caching, it's safe to do so. We don't use UnsafeAddr this time, because this method panics when CanAddr returns false. Fixes #33907 Change-Id: I2aa88cc060a2c2192b1d34c129c0aad4bd5597e7 Reviewed-on: https://go-review.googlesource.com/c/go/+/191940 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent a5026af commit 4dc11ae

File tree

2 files changed

+20
-8
lines changed

2 files changed

+20
-8
lines changed

src/reflect/all_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,13 +787,21 @@ type Loopy interface{}
787787

788788
var loop1, loop2 Loop
789789
var loopy1, loopy2 Loopy
790+
var cycleMap1, cycleMap2, cycleMap3 map[string]interface{}
790791

791792
func init() {
792793
loop1 = &loop2
793794
loop2 = &loop1
794795

795796
loopy1 = &loopy2
796797
loopy2 = &loopy1
798+
799+
cycleMap1 = map[string]interface{}{}
800+
cycleMap1["cycle"] = cycleMap1
801+
cycleMap2 = map[string]interface{}{}
802+
cycleMap2["cycle"] = cycleMap2
803+
cycleMap3 = map[string]interface{}{}
804+
cycleMap3["different"] = cycleMap3
797805
}
798806

799807
var deepEqualTests = []DeepEqualTest{
@@ -860,6 +868,8 @@ var deepEqualTests = []DeepEqualTest{
860868
{&loop1, &loop2, true},
861869
{&loopy1, &loopy1, true},
862870
{&loopy1, &loopy2, true},
871+
{&cycleMap1, &cycleMap2, true},
872+
{&cycleMap1, &cycleMap3, false},
863873
}
864874

865875
func TestDeepEqual(t *testing.T) {
@@ -868,7 +878,7 @@ func TestDeepEqual(t *testing.T) {
868878
test.b = test.a
869879
}
870880
if r := DeepEqual(test.a, test.b); r != test.eq {
871-
t.Errorf("DeepEqual(%v, %v) = %v, want %v", test.a, test.b, r, test.eq)
881+
t.Errorf("DeepEqual(%#v, %#v) = %v, want %v", test.a, test.b, r, test.eq)
872882
}
873883
}
874884
}

src/reflect/deepequal.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,20 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool, depth int) bool {
3333

3434
// We want to avoid putting more in the visited map than we need to.
3535
// For any possible reference cycle that might be encountered,
36-
// hard(t) needs to return true for at least one of the types in the cycle.
37-
hard := func(k Kind) bool {
38-
switch k {
36+
// hard(v1, v2) needs to return true for at least one of the types in the cycle,
37+
// and it's safe and valid to get Value's internal pointer.
38+
hard := func(v1, v2 Value) bool {
39+
switch v1.Kind() {
3940
case Map, Slice, Ptr, Interface:
40-
return true
41+
// Nil pointers cannot be cyclic. Avoid putting them in the visited map.
42+
return !v1.IsNil() && !v2.IsNil()
4143
}
4244
return false
4345
}
4446

45-
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
46-
addr1 := unsafe.Pointer(v1.UnsafeAddr())
47-
addr2 := unsafe.Pointer(v2.UnsafeAddr())
47+
if hard(v1, v2) {
48+
addr1 := v1.ptr
49+
addr2 := v2.ptr
4850
if uintptr(addr1) > uintptr(addr2) {
4951
// Canonicalize order to reduce number of entries in visited.
5052
// Assumes non-moving garbage collector.

0 commit comments

Comments
 (0)