internal/symbols: compute proper db names for generic functions

These should not include the type parameter/argument.

Fixes golang/go#63535

Change-Id: I08a5929825d569fb3104f084ea55766ba6f5542e
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/548195
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
diff --git a/internal/symbols/exported_functions.go b/internal/symbols/exported_functions.go
index 9177500..e210876 100644
--- a/internal/symbols/exported_functions.go
+++ b/internal/symbols/exported_functions.go
@@ -16,15 +16,14 @@
 
 	"golang.org/x/exp/slices"
 	"golang.org/x/tools/go/packages"
-	"golang.org/x/tools/go/ssa"
 	"golang.org/x/vulndb/internal/derrors"
 	"golang.org/x/vulndb/internal/osvutils"
 	"golang.org/x/vulndb/internal/report"
 	"golang.org/x/vulndb/internal/version"
 )
 
-// Exported returns a set of vulnerable symbols exported
-// by a package p from the module m.
+// Exported returns a set of vulnerable symbols, in the vuln
+// db format, exported by a package p from the module m.
 func Exported(m *report.Module, p *report.Package, errlog *log.Logger) (_ []string, err error) {
 	defer derrors.Wrap(&err, "Exported(%q, %q)", m.Module, p.Package)
 
@@ -175,22 +174,8 @@
 	names := map[string]bool{}
 	for _, e := range entries {
 		if pkgPath(e) == pkg.PkgPath {
-			names[ssaSymbolName(e)] = true
+			names[dbFuncName(e)] = true
 		}
 	}
 	return names, nil
 }
-
-func ssaSymbolName(fn *ssa.Function) string {
-	recv := fn.Signature.Recv()
-	if recv == nil {
-		return fn.Name()
-	}
-	recvType := recv.Type().String()
-	// Remove package path from type.
-	i := strings.LastIndexByte(recvType, '.')
-	if i < 0 {
-		return recvType + "." + fn.Name()
-	}
-	return recvType[i+1:] + "." + fn.Name()
-}
diff --git a/internal/symbols/patched_functions.go b/internal/symbols/patched_functions.go
index d1658dc..fa3c85c 100644
--- a/internal/symbols/patched_functions.go
+++ b/internal/symbols/patched_functions.go
@@ -364,17 +364,27 @@
 		return "" // sanity
 	}
 
+	// unpackIdent assumes e is of the form id or id[...]
+	// and then returns id. Otherwise, returns "".
+	unpackIdent := func(e ast.Expr) string {
+		switch xv := e.(type) {
+		case *ast.Ident:
+			return xv.Name
+		case *ast.IndexExpr:
+			if si, ok := xv.X.(*ast.Ident); ok {
+				return si.Name
+			}
+		}
+		return ""
+	}
+
+	// supported receiver type snames are id, *id, id[...], and *id[...].
 	t := ""
 	switch xv := field.Type.(type) {
 	case *ast.StarExpr:
-		if si, ok := xv.X.(*ast.Ident); ok {
-			t = si.Name
-		}
-	case *ast.Ident:
-		t = xv.Name
-	case *ast.IndexExpr:
-		// TODO(#63535): cover index instructions stemming from generics
-		return ""
+		t = unpackIdent(xv.X)
+	case *ast.Ident, *ast.IndexExpr:
+		t = unpackIdent(xv)
 	default:
 		panic(fmt.Sprintf("astSymbolName: unexpected receiver type: %v\n", reflect.TypeOf(field.Type)))
 	}
diff --git a/internal/symbols/patched_functions_test.go b/internal/symbols/patched_functions_test.go
index 31af754..194b3b4 100644
--- a/internal/symbols/patched_functions_test.go
+++ b/internal/symbols/patched_functions_test.go
@@ -198,6 +198,14 @@
 
 type B struct {}
 func (b *B) Do() {}
+
+type C[T any] struct {
+	t T
+}
+func (c C[T]) Do() {}
+func (c *C[T]) Bar() {}
+
+func Go[X any]() {}
 `
 	fset := token.NewFileSet() // positions are relative to fset
 	f, err := parser.ParseFile(fset, "src.go", src, 0)
@@ -212,7 +220,7 @@
 		}
 	}
 	sort.Strings(got)
-	want := []string{"A.Do", "B.Do", "Foo"}
+	want := []string{"A.Do", "B.Do", "C.Bar", "C.Do", "Foo", "Go"}
 	if diff := cmp.Diff(want, got); diff != "" {
 		t.Errorf("(-got, want+):\n%s", diff)
 	}
diff --git a/internal/symbols/utils.go b/internal/symbols/utils.go
index d27a022..be41b19 100644
--- a/internal/symbols/utils.go
+++ b/internal/symbols/utils.go
@@ -179,15 +179,17 @@
 
 // dbFuncName computes a function name consistent with the namings used in vulnerability
 // databases. Effectively, a qualified name of a function local to its enclosing package.
-// If a receiver is a pointer, this information is not encoded in the resulting name. The
-// name of anonymous functions is simply "". The function names are unique subject to the
-// enclosing package, but not globally.
+// If a receiver is a pointer, this information is not encoded in the resulting name. If
+// a function has type argument/parameter, this information is omitted. The name of
+// anonymous functions is simply "". The function names are unique subject to the enclosing
+// package, but not globally.
 //
 // Examples:
 //
 //	func (a A) foo (...) {...}  -> A.foo
 //	func foo(...) {...}         -> foo
 //	func (b *B) bar (...) {...} -> B.bar
+//	func (c C[T]) do(...) {...} -> C.do
 func dbFuncName(f *ssa.Function) string {
 	selectBound := func(f *ssa.Function) types.Type {
 		// If f is a "bound" function introduced by ssa for a given type, return the type.
@@ -220,9 +222,17 @@
 	}
 
 	if qprefix == "" {
-		return f.Name()
+		return funcName(f)
 	}
-	return qprefix + "." + f.Name()
+	return qprefix + "." + funcName(f)
+}
+
+// funcName returns the name of the ssa function f.
+// It is f.Name() without additional type argument
+// information in case of generics.
+func funcName(f *ssa.Function) string {
+	n, _, _ := strings.Cut(f.Name(), "[")
+	return n
 }
 
 // memberFuncs returns functions associated with the `member`: