Hyang-Ah Hana Kim has uploaded this change for review.
gopls/internal/vulncheck: make vulnFact gob encoding stable
analysis Fact type must be deterministically serializable.
The default Gob encoder output of map type is not deterministric, so we
need to provide custom GobEncode/Decode implementation.
Change-Id: Ide31c9c5f88cf354048e145fca8cdd0cc581ab39
---
M gopls/internal/vulncheck/analyzer.go
A gopls/internal/vulncheck/analyzer_test.go
2 files changed, 120 insertions(+), 1 deletion(-)
diff --git a/gopls/internal/vulncheck/analyzer.go b/gopls/internal/vulncheck/analyzer.go
index c05394f..e0ccc54 100644
--- a/gopls/internal/vulncheck/analyzer.go
+++ b/gopls/internal/vulncheck/analyzer.go
@@ -7,6 +7,8 @@
package vulncheck
import (
+ "bytes"
+ "encoding/gob"
"encoding/json"
"errors"
"fmt"
@@ -15,6 +17,7 @@
"go/types"
"log"
"os"
+ "sort"
"strings"
"sync"
@@ -335,7 +338,45 @@
// Existence of an entry with an empty path indicates
// the whole package is affected by the vulnerability.
// (e.g. init)
- Path map[string][]string
+ Path vulnID2Path
+}
+
+// Vuln ID -> Reference path to a known vulnerable symbol.
+type vulnID2Path map[string][]string
+
+// wirePkgToPath is an intermidiate representation of an elment in vulnID2Path
+// used by GobEncode.
+type wirePkgToPath struct {
+ VulnID string
+ Path []string
+}
+
+// GobDecode implements gob.GobDecoder.
+func (p *vulnID2Path) GobDecode(src []byte) error {
+ var entries []wirePkgToPath
+ if err := gob.NewDecoder(bytes.NewReader(src)).Decode(&entries); err != nil {
+ return err
+ }
+ *p = make(map[string][]string)
+ for _, e := range entries {
+ (*p)[e.VulnID] = e.Path
+ }
+ return nil
+}
+
+// GobEncode implements gob.GobEncoder that provides deterministic encoding.
+func (p vulnID2Path) GobEncode() ([]byte, error) {
+ var entries = make([]wirePkgToPath, 0, len(p))
+ for pkg, path := range p {
+ entries = append(entries, wirePkgToPath{VulnID: pkg, Path: path})
+ }
+ sort.Slice(entries, func(i, j int) bool { return entries[i].VulnID < entries[j].VulnID })
+ var b bytes.Buffer
+ enc := gob.NewEncoder(&b)
+ if err := enc.Encode(entries); err != nil {
+ return nil, err
+ }
+ return b.Bytes(), nil
}
func (f *vulnFact) AFact() {}
diff --git a/gopls/internal/vulncheck/analyzer_test.go b/gopls/internal/vulncheck/analyzer_test.go
new file mode 100644
index 0000000..872e27a
--- /dev/null
+++ b/gopls/internal/vulncheck/analyzer_test.go
@@ -0,0 +1,65 @@
+// Copyright 2022 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.
+
+// Package vulncheck provides vulnerability analyzer
+// using golang.org/x/vuln APIs.
+package vulncheck
+
+import (
+ "bytes"
+ "encoding/gob"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+)
+
+func Test_vulnFact_GobEncodeDecode(t *testing.T) {
+ tests := []struct {
+ name string
+ path map[string][]string
+ }{
+ {
+ name: "nil",
+ path: nil,
+ },
+ {
+ name: "empty",
+ path: map[string][]string{},
+ },
+ {
+ name: "all",
+ path: map[string][]string{
+ "foo": nil,
+ "bar": {}, // encoding/gob treats nil and empty slice to be equal. We use cmpopts.EquateEmpty.
+ "baz": {"baz"},
+ "bax": {"bar", "baz"},
+ },
+ }}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ in := &vulnFact{Path: tt.path}
+ // encode twice and check results are same.
+ var encoded [2][]byte
+ for i := 0; i < 2; i++ {
+ b := &bytes.Buffer{}
+ if err := gob.NewEncoder(b).Encode(in); err != nil {
+ t.Fatalf("failed to encode vulnFact: %v", err)
+ }
+ encoded[i] = b.Bytes()
+ }
+ if !bytes.Equal(encoded[0], encoded[1]) {
+ t.Error("failed to encode deterministically")
+ }
+ // decode and check the output is identical to the input.
+ var out *vulnFact
+ if err := gob.NewDecoder(bytes.NewBuffer(encoded[0])).Decode(&out); err != nil {
+ t.Fatalf("failed to decode encoded vulnFact: %v", err)
+ }
+ if diff := cmp.Diff(in, out, cmpopts.EquateEmpty()); diff != "" {
+ t.Errorf("failed to decode to original value: (-want, +got): %v", diff)
+ }
+ })
+ }
+}
To view, visit change 410369. To unsubscribe, or for help writing mail filters, visit settings.
Patch set 1:Run-TryBot +1
Attention is currently required from: Hyang-Ah Hana Kim.
Kokoro presubmit build finished with status: FAILURE
Logs at: https://source.cloud.google.com/results/invocations/e531cedd-f177-4df2-a35d-f876076f143c
Patch set 1:gopls-CI -1
Attention is currently required from: Hyang-Ah Hana Kim.
Kokoro presubmit build finished with status: FAILURE
Logs at: https://source.cloud.google.com/results/invocations/6fcd1b9d-34e1-4844-b892-8134bcce28e9
Patch set 3:gopls-CI -1
Patch set 4:Run-TryBot +1
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/06ddcaf4-acfd-447a-b8b7-88ffbbef8bf9
Patch set 4:gopls-CI +1
Attention is currently required from: Alan Donovan.
Patch set 5:Run-TryBot +1
Attention is currently required from: Alan Donovan.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/99794e53-fae4-4b35-ad9e-525d65726531
Patch set 5:gopls-CI +1
Attention is currently required from: Hyang-Ah Hana Kim.
Patch set 5:Code-Review +2
1 comment:
File gopls/internal/vulncheck/analyzer.go:
Patch Set #5, Line 364: var entries = make([]wirePkgToPath, 0, len(p))
There's a nice generic function waiting to get out of here.
Given:
package maps
type Entry[K comparable, V any] struct { Key K; Value V }
func SortedEntries[K comparable, V any](m map[K]V) []Entry[K,V]
entries := maps.SortedEntries(p)
One can define:
type GobMap[K comparable, V any] map[K]V
func (m GobMap[K, V]) GobEncode() ([]byte, error) {
entries := maps.SortedEntries(p)
var b bytes.Buffer
if err := gob.NewEncoder(&b).Encode(entries); err != nil {
return nil, err
}
return b.Bytes(), nil
} func (m *GobMap[K, V]) GobDecode(src []byte) error {
var entries []maps.Entry[K,V]
if err := gob.NewDecoder(bytes.NewReader(src)).Decode(&entries); err != nil
{
return err
}
*m = make(GobMap[K,V], len(entries))
for _, e := range entries {
(*m)[e.Key] = e.Value
}
return nil
}
To view, visit change 410369. To unsubscribe, or for help writing mail filters, visit settings.