go/callgraph/vta: avoids cycles for pathological recursive types

Helper type logic did not handle properly pathological recursive types
such as type B *B. This CL addresses that.

For golang/go#51560

Change-Id: Ibb69ac68d77fc3956b98c701f17d28b9f30ac22c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/399674
Reviewed-by: Tim King <taking@google.com>
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
Auto-Submit: Zvonimir Pavlinovic <zpavlinovic@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/go/callgraph/vta/testdata/src/callgraph_recursive_types.go b/go/callgraph/vta/testdata/src/callgraph_recursive_types.go
new file mode 100644
index 0000000..6c3fef6
--- /dev/null
+++ b/go/callgraph/vta/testdata/src/callgraph_recursive_types.go
@@ -0,0 +1,56 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// go:build ignore
+
+package testdata
+
+type I interface {
+	Foo() I
+}
+
+type A struct {
+	i int
+	a *A
+}
+
+func (a *A) Foo() I {
+	return a
+}
+
+type B **B
+
+type C *D
+type D *C
+
+func Bar(a *A, b *B, c *C, d *D) {
+	Baz(a)
+	Baz(a.a)
+
+	sink(*b)
+	sink(*c)
+	sink(*d)
+}
+
+func Baz(i I) {
+	i.Foo()
+}
+
+func sink(i interface{}) {
+	print(i)
+}
+
+// Relevant SSA:
+// func Baz(i I):
+//   t0 = invoke i.Foo()
+//   return
+//
+// func Bar(a *A, b *B):
+//   t0 = make I <- *A (a)
+//   t1 = Baz(t0)
+//   ...
+
+// WANT:
+// Bar: Baz(t0) -> Baz; Baz(t4) -> Baz; sink(t10) -> sink; sink(t13) -> sink; sink(t7) -> sink
+// Baz: invoke i.Foo() -> A.Foo
diff --git a/go/callgraph/vta/utils.go b/go/callgraph/vta/utils.go
index e7a97e2..0049955 100644
--- a/go/callgraph/vta/utils.go
+++ b/go/callgraph/vta/utils.go
@@ -85,32 +85,52 @@
 // pointer to interface and if yes, returns the interface type.
 // Otherwise, returns nil.
 func interfaceUnderPtr(t types.Type) types.Type {
-	p, ok := t.Underlying().(*types.Pointer)
-	if !ok {
-		return nil
-	}
+	seen := make(map[types.Type]bool)
+	var visit func(types.Type) types.Type
+	visit = func(t types.Type) types.Type {
+		if seen[t] {
+			return nil
+		}
+		seen[t] = true
 
-	if isInterface(p.Elem()) {
-		return p.Elem()
-	}
+		p, ok := t.Underlying().(*types.Pointer)
+		if !ok {
+			return nil
+		}
 
-	return interfaceUnderPtr(p.Elem())
+		if isInterface(p.Elem()) {
+			return p.Elem()
+		}
+
+		return visit(p.Elem())
+	}
+	return visit(t)
 }
 
 // functionUnderPtr checks if type `t` is a potentially nested
 // pointer to function type and if yes, returns the function type.
 // Otherwise, returns nil.
 func functionUnderPtr(t types.Type) types.Type {
-	p, ok := t.Underlying().(*types.Pointer)
-	if !ok {
-		return nil
-	}
+	seen := make(map[types.Type]bool)
+	var visit func(types.Type) types.Type
+	visit = func(t types.Type) types.Type {
+		if seen[t] {
+			return nil
+		}
+		seen[t] = true
 
-	if isFunction(p.Elem()) {
-		return p.Elem()
-	}
+		p, ok := t.Underlying().(*types.Pointer)
+		if !ok {
+			return nil
+		}
 
-	return functionUnderPtr(p.Elem())
+		if isFunction(p.Elem()) {
+			return p.Elem()
+		}
+
+		return visit(p.Elem())
+	}
+	return visit(t)
 }
 
 // sliceArrayElem returns the element type of type `t` that is
diff --git a/go/callgraph/vta/vta_test.go b/go/callgraph/vta/vta_test.go
index 33ceaf9..8303908 100644
--- a/go/callgraph/vta/vta_test.go
+++ b/go/callgraph/vta/vta_test.go
@@ -24,6 +24,7 @@
 		"testdata/src/callgraph_collections.go",
 		"testdata/src/callgraph_fields.go",
 		"testdata/src/callgraph_field_funcs.go",
+		"testdata/src/callgraph_recursive_types.go",
 	} {
 		t.Run(file, func(t *testing.T) {
 			prog, want, err := testProg(file)