cmd/vulnreport: check for presence of symbols in fix

The "vulnreport fix" command now verifies that all symbols are present
in the vulnerable package.

The "vulnreport fix" command now only adds symbols to the derived_symbols
field if they aren't already present in the symbols field.

Change-Id: I1a1f1e44e92e66a4c3b141dbff9b8e8fea265870
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/412536
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/cmd/vulnreport/main.go b/cmd/vulnreport/main.go
index e0eec04..9649506 100644
--- a/cmd/vulnreport/main.go
+++ b/cmd/vulnreport/main.go
@@ -13,6 +13,7 @@
 	"flag"
 	"fmt"
 	"go/build"
+	"go/types"
 	"log"
 	"os"
 	"os/exec"
@@ -24,6 +25,7 @@
 	"time"
 
 	"github.com/go-git/go-git/v5"
+	"golang.org/x/exp/slices"
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/vulndb/internal/cvelistrepo"
 	"golang.org/x/vulndb/internal/derrors"
@@ -278,9 +280,8 @@
 		if err != nil {
 			return false, err
 		}
-		if len(syms) > 0 {
+		if !slices.Equal(syms, r.Packages[i].DerivedSymbols) {
 			added = true
-			// Need to start from r because r.Packages is a slice of values.
 			r.Packages[i].DerivedSymbols = syms
 		}
 	}
@@ -348,13 +349,38 @@
 			return nil, fmt.Errorf("got module %v, expected %s", pm, module)
 		}
 	}
+
+	// Check to see that all symbols actually exist in the package.
+	// This should perhaps be a lint check, but lint doesn't
+	// load/typecheck packages at the moment, so do it here for now.
+	for _, sym := range p.Symbols {
+		if typ, method, ok := strings.Cut(sym, "."); ok {
+			n, ok := pkgs[0].Types.Scope().Lookup(typ).(*types.TypeName)
+			if !ok {
+				fmt.Fprintf(os.Stderr, "%v: type not found\n", typ)
+				continue
+			}
+			m, _, _ := types.LookupFieldOrMethod(n.Type(), true, pkgs[0].Types, method)
+			if m == nil {
+				fmt.Fprintf(os.Stderr, "%v: method not found\n", sym)
+			}
+		} else {
+			_, ok := pkgs[0].Types.Scope().Lookup(typ).(*types.Func)
+			if !ok {
+				fmt.Fprintf(os.Stderr, "%v: func not found\n", typ)
+			}
+		}
+	}
+
 	newsyms, err := exportedFunctions(pkgs, c)
 	if err != nil {
 		return nil, err
 	}
 	var newslice []string
 	for s := range newsyms {
-		newslice = append(newslice, s)
+		if !slices.Contains(p.Symbols, s) {
+			newslice = append(newslice, s)
+		}
 	}
 	sort.Strings(newslice)
 	return newslice, nil
diff --git a/go.mod b/go.mod
index 1450015..125eb78 100644
--- a/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@
 	github.com/client9/misspell v0.3.4
 	github.com/go-git/go-billy/v5 v5.3.1
 	github.com/go-git/go-git/v5 v5.4.2
-	github.com/google/go-cmp v0.5.7
+	github.com/google/go-cmp v0.5.8
 	github.com/google/go-github/v41 v41.0.0
 	github.com/google/safehtml v0.0.2
 	github.com/jba/templatecheck v0.6.0
@@ -25,6 +25,7 @@
 	go.opentelemetry.io/otel v1.4.0
 	go.opentelemetry.io/otel/metric v0.27.0
 	go.opentelemetry.io/otel/sdk v1.4.0
+	golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
 	golang.org/x/exp/event v0.0.0-20220218215828-6cf2b201936e
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
 	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
diff --git a/go.sum b/go.sum
index 2526759..4f32947 100644
--- a/go.sum
+++ b/go.sum
@@ -193,6 +193,8 @@
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
 github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -355,6 +357,8 @@
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0=
+golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/exp/event v0.0.0-20220218215828-6cf2b201936e h1:K2AuHMC+jaRTzAcivRwKOzjTZ1925Yx4xHMg07YoBQc=
 golang.org/x/exp/event v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AVlZHjhWbW/3yOcmKMtJiObwBPJajBlUpQXRijFNrNc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=