vulncheck: add imports only mode for vuln detection in binaries

Cherry-picked: https://go-review.googlesource.com/c/exp/+/382554

Change-Id: I309a431804cd788ba8b69279c8792d162facf7ea
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/395059
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/vulncheck/binary.go b/vulncheck/binary.go
index 17b5d16..2fcf0ca 100644
--- a/vulncheck/binary.go
+++ b/vulncheck/binary.go
@@ -28,28 +28,70 @@
 
 	result := &Result{}
 	for pkg, symbols := range packageSymbols {
-		for _, symbol := range symbols {
-			for _, osv := range modVulns.VulnsForSymbol(pkg, symbol) {
-				for _, affected := range osv.Affected {
-					if affected.Package.Name != pkg {
-						continue
-					}
-					for _, symbol := range affected.EcosystemSpecific.Symbols {
-						vuln := &Vuln{
-							OSV:     osv,
-							Symbol:  symbol,
-							PkgPath: pkg,
-							// TODO(zpavlinovic): infer mod path from PkgPath and modules?
-						}
-						result.Vulns = append(result.Vulns, vuln)
-					}
-				}
-			}
+		if cfg.ImportsOnly {
+			addImportsOnlyVulns(pkg, symbols, result, modVulns)
+		} else {
+			addSymbolVulns(pkg, symbols, result, modVulns)
 		}
 	}
 	return result, nil
 }
 
+// addImportsOnlyVulns adds Vuln entries to result in imports only mode, i.e., for each vulnerable symbol
+// of pkg.
+func addImportsOnlyVulns(pkg string, symbols []string, result *Result, modVulns moduleVulnerabilities) {
+	for _, osv := range modVulns.VulnsForPackage(pkg) {
+		for _, affected := range osv.Affected {
+			if affected.Package.Name != pkg {
+				continue
+			}
+
+			var syms []string
+			if len(affected.EcosystemSpecific.Symbols) == 0 {
+				// If every symbol of pkg is vulnerable, we would ideally compute
+				// every symbol mentioned in the pkg and then add Vuln entry for it,
+				// just as we do in Source. However, we don't have code of pkg here
+				// so we have to do best we can, which is the symbols of pkg actually
+				// appearing in the binary.
+				syms = symbols
+			} else {
+				syms = affected.EcosystemSpecific.Symbols
+			}
+
+			for _, symbol := range syms {
+				vuln := &Vuln{
+					OSV:     osv,
+					Symbol:  symbol,
+					PkgPath: pkg,
+					// TODO(zpavlinovic): infer mod path from PkgPath and modules?
+				}
+				result.Vulns = append(result.Vulns, vuln)
+			}
+		}
+	}
+}
+
+// addSymbolVulns adds Vuln entries to result for every symbol of pkg in the binary that is vulnerable.
+func addSymbolVulns(pkg string, symbols []string, result *Result, modVulns moduleVulnerabilities) {
+	for _, symbol := range symbols {
+		for _, osv := range modVulns.VulnsForSymbol(pkg, symbol) {
+			for _, affected := range osv.Affected {
+				if affected.Package.Name != pkg {
+					continue
+				}
+				vuln := &Vuln{
+					OSV:     osv,
+					Symbol:  symbol,
+					PkgPath: pkg,
+					// TODO(zpavlinovic): infer mod path from PkgPath and modules?
+				}
+				result.Vulns = append(result.Vulns, vuln)
+				break
+			}
+		}
+	}
+}
+
 func convertModules(mods []*packages.Module) []*Module {
 	vmods := make([]*Module, len(mods))
 	// TODO(github.com/golang/go/issues/50030): should we share unique