vulndb/internal/audit: improve support for init functions

For top-level main packages, init functions of those packages as well
as init functions of transitive dependencies should be considered entry
points. This CL ensures this is done correctly.

Change-Id: Ic282ee53630044854eb8d12307436bc462c9f7ba
Reviewed-on: https://go-review.googlesource.com/c/exp/+/348229
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Tim King <taking@google.com>
Trust: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/vulndb/internal/audit/detect_callgraph_test.go b/vulndb/internal/audit/detect_callgraph_test.go
index 4813770..4753d1f 100644
--- a/vulndb/internal/audit/detect_callgraph_test.go
+++ b/vulndb/internal/audit/detect_callgraph_test.go
@@ -8,6 +8,10 @@
 	"go/token"
 	"reflect"
 	"testing"
+
+	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/go/packages/packagestest"
+	"golang.org/x/vulndb/osv"
 )
 
 func TestSymbolVulnDetectionVTA(t *testing.T) {
@@ -80,3 +84,76 @@
 		}
 	}
 }
+
+func TestInitReachability(t *testing.T) {
+	e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
+		{
+			Name: "golang.org/inittest",
+			Files: map[string]interface{}{"main.go": `
+			package main
+
+			import "example.com/vuln"
+
+			func main() {
+				vuln.Foo() // benign
+			}
+			`},
+		},
+		{
+			Name: "example.com@v1.1.1",
+			Files: map[string]interface{}{"vuln/vuln.go": `
+			package vuln
+
+			func init() {
+				Bad() // bad
+			}
+
+			func Foo() {}
+			func Bad() {}
+			`},
+		},
+	})
+	defer e.Cleanup()
+
+	_, pkgs, _, err := loadAndBuildPackages(e, "/inittest/main.go")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	modVulns := ModuleVulnerabilities{
+		{
+			mod: &packages.Module{Path: "example.com", Version: "v1.1.1"},
+			vulns: []*osv.Entry{
+				{
+					ID: "V3",
+					Affected: []osv.Affected{{
+						Package:           osv.Package{Name: "example.com/vuln"},
+						Ranges:            osv.Affects{{Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "1.0.0"}, {Fixed: "1.1.2"}}}},
+						EcosystemSpecific: osv.EcosystemSpecific{Symbols: []string{"Bad"}},
+					}},
+				},
+			},
+		},
+	}
+	results := VulnerableSymbols(pkgs, modVulns)
+
+	if results.SearchMode != CallGraphSearch {
+		t.Errorf("want call graph search mode; got %v", results.SearchMode)
+	}
+
+	want := []Finding{
+		{
+			Symbol: "example.com/vuln.Bad",
+			Trace: []TraceElem{
+				{Description: "command-line-arguments.init(...)", Position: &token.Position{}},
+				{Description: "example.com/vuln.init(...)", Position: &token.Position{}},
+				{Description: "example.com/vuln.init#1(...)", Position: &token.Position{}}},
+			Type:     FunctionType,
+			Position: &token.Position{Line: 5, Filename: "vuln.go"},
+			weight:   0,
+		},
+	}
+	if got := projectFindings(results.VulnFindings["V3"]); !reflect.DeepEqual(want, got) {
+		t.Errorf("want %v findings (projected); got %v", want, got)
+	}
+}
diff --git a/vulndb/internal/audit/utils.go b/vulndb/internal/audit/utils.go
index c7395a7..8fe5609 100644
--- a/vulndb/internal/audit/utils.go
+++ b/vulndb/internal/audit/utils.go
@@ -51,8 +51,9 @@
 
 	for _, edge := range node.Out {
 		callee := edge.Callee.Func
-		// Skip synthetic functions wrapped around source functions.
-		if edge.Site == call && callee.Synthetic == "" {
+		// Some callgraph analyses, such as CHA, might return synthetic (interface)
+		// methods as well as the concrete methods. Skip such synthetic functions.
+		if edge.Site == call {
 			matches = append(matches, callee)
 		}
 	}