internal/result: move structs to separate package

Move structs for the govulncheck JSON output to a separate package.

Change-Id: Ifb886850a6615c8a20373b00e2065f7d4770bdb2
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/474479
Run-TryBot: Julie Qiu <julieqiu@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/govulncheck/integration/k8s/k8s.go b/cmd/govulncheck/integration/k8s/k8s.go
index c19db34..e1674e9 100644
--- a/cmd/govulncheck/integration/k8s/k8s.go
+++ b/cmd/govulncheck/integration/k8s/k8s.go
@@ -10,7 +10,7 @@
 	"os"
 
 	"github.com/google/go-cmp/cmp"
-	"golang.org/x/vuln/internal/govulncheck"
+	"golang.org/x/vuln/internal/result"
 )
 
 const usage = `test helper for examining the output of running govulncheck on k8s@v1.15.11.
@@ -29,7 +29,7 @@
 		log.Fatal("Failed to read:", out)
 	}
 
-	var r govulncheck.Result
+	var r result.Result
 	if err := json.Unmarshal(outJson, &r); err != nil {
 		log.Fatal("Failed to load json into internal/govulncheck.Result:", err)
 	}
diff --git a/cmd/govulncheck/integration/stackrox-scanner/scanner.go b/cmd/govulncheck/integration/stackrox-scanner/scanner.go
index 4d46955..bfd5058 100644
--- a/cmd/govulncheck/integration/stackrox-scanner/scanner.go
+++ b/cmd/govulncheck/integration/stackrox-scanner/scanner.go
@@ -10,7 +10,7 @@
 	"os"
 
 	"github.com/google/go-cmp/cmp"
-	"golang.org/x/vuln/internal/govulncheck"
+	"golang.org/x/vuln/internal/result"
 )
 
 const usage = `test helper for examining the output of running govulncheck on
@@ -30,7 +30,7 @@
 		log.Fatal("Failed to read:", out)
 	}
 
-	var r govulncheck.Result
+	var r result.Result
 	if err := json.Unmarshal(outJson, &r); err != nil {
 		log.Fatal("Failed to load json into internal/govulncheck.Result:", err)
 	}
diff --git a/exp/govulncheck/govulncheck.go b/exp/govulncheck/govulncheck.go
index d09b0d0..2b4fb8c 100644
--- a/exp/govulncheck/govulncheck.go
+++ b/exp/govulncheck/govulncheck.go
@@ -5,7 +5,10 @@
 // Package govulncheck provides an experimental govulncheck API.
 package govulncheck
 
-import "golang.org/x/vuln/internal/govulncheck"
+import (
+	"golang.org/x/vuln/internal/govulncheck"
+	"golang.org/x/vuln/internal/result"
+)
 
 var (
 	// Source reports vulnerabilities that affect the analyzed packages.
@@ -20,22 +23,22 @@
 	Config = govulncheck.Config
 
 	// Result is the result of executing Source.
-	Result = govulncheck.Result
+	Result = result.Result
 
 	// Vuln represents a single OSV entry.
-	Vuln = govulncheck.Vuln
+	Vuln = result.Vuln
 
 	// Module represents a specific vulnerability relevant to a
 	// single module or package.
-	Module = govulncheck.Module
+	Module = result.Module
 
 	// Package is a Go package with known vulnerable symbols.
-	Package = govulncheck.Package
+	Package = result.Package
 
 	// CallStacks contains a representative call stack for each
 	// vulnerable symbol that is called.
-	CallStack = govulncheck.CallStack
+	CallStack = result.CallStack
 
 	// StackFrame represents a call stack entry.
-	StackFrame = govulncheck.StackFrame
+	StackFrame = result.StackFrame
 )
diff --git a/internal/govulncheck/callstacks.go b/internal/govulncheck/callstacks.go
index 85ec630..df861d9 100644
--- a/internal/govulncheck/callstacks.go
+++ b/internal/govulncheck/callstacks.go
@@ -12,6 +12,7 @@
 	"strings"
 
 	"golang.org/x/vuln/internal"
+	"golang.org/x/vuln/internal/result"
 	"golang.org/x/vuln/vulncheck"
 )
 
@@ -129,7 +130,7 @@
 //     "F calls W, which eventually calls V"
 //
 // If it can't find any of these functions, summarizeCallStack returns the empty string.
-func summarizeCallStack(cs CallStack, topPkgs map[string]bool, vulnPkg string) string {
+func summarizeCallStack(cs result.CallStack, topPkgs map[string]bool, vulnPkg string) string {
 	iTop, iTopEnd, topFunc, topEndFunc := summarizeTop(cs.Frames, topPkgs)
 	if iTop < 0 {
 		return ""
@@ -176,8 +177,8 @@
 //
 //	[p.V p.W q.Q ...]        -> (1, 1, p.W, p.W)
 //	[p.V p.W p.Z$1 q.Q ...]  -> (1, 2, p.W, p.Z)
-func summarizeTop(frames []*StackFrame, topPkgs map[string]bool) (iTop, iTopEnd int, topFunc, topEndFunc string) {
-	iTopEnd = lowest(frames, func(e *StackFrame) bool {
+func summarizeTop(frames []*result.StackFrame, topPkgs map[string]bool) (iTop, iTopEnd int, topFunc, topEndFunc string) {
+	iTopEnd = lowest(frames, func(e *result.StackFrame) bool {
 		return topPkgs[e.PkgPath]
 	})
 	if iTopEnd < 0 {
@@ -193,7 +194,7 @@
 
 	topEndFunc = creatorName(topEndFunc)
 
-	iTop = lowest(frames, func(e *StackFrame) bool {
+	iTop = lowest(frames, func(e *result.StackFrame) bool {
 		return topPkgs[e.PkgPath] && !isAnonymousFunction(e.FuncName)
 	})
 	if iTop < 0 {
@@ -215,8 +216,8 @@
 //
 //	[x x q.Q v.V v.W]   -> (3, v.V, v.V)
 //	[x x q.Q v.V$1 v.W] -> (3, v.V, v.W)
-func summarizeVuln(frames []*StackFrame, iTop int, vulnPkg string) (iVulnStart int, vulnStartFunc, vulnFunc string) {
-	iVulnStart = highest(frames[iTop+1:], func(e *StackFrame) bool {
+func summarizeVuln(frames []*result.StackFrame, iTop int, vulnPkg string) (iVulnStart int, vulnStartFunc, vulnFunc string) {
+	iVulnStart = highest(frames[iTop+1:], func(e *result.StackFrame) bool {
 		return e.PkgPath == vulnPkg
 	})
 	if iVulnStart < 0 {
@@ -232,7 +233,7 @@
 
 	vulnStartFunc = creatorName(vulnStartFunc)
 
-	iVuln := highest(frames[iVulnStart:], func(e *StackFrame) bool {
+	iVuln := highest(frames[iVulnStart:], func(e *result.StackFrame) bool {
 		return e.PkgPath == vulnPkg && !isAnonymousFunction(e.FuncName)
 	})
 	if iVuln < 0 {
diff --git a/internal/govulncheck/callstacks_test.go b/internal/govulncheck/callstacks_test.go
index 2ff2e6e..dc3c869 100644
--- a/internal/govulncheck/callstacks_test.go
+++ b/internal/govulncheck/callstacks_test.go
@@ -14,6 +14,7 @@
 
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/tools/go/packages/packagestest"
+	"golang.org/x/vuln/internal/result"
 	"golang.org/x/vuln/internal/test"
 	"golang.org/x/vuln/osv"
 	"golang.org/x/vuln/vulncheck"
@@ -115,11 +116,11 @@
 	}
 }
 
-func stringToCallStack(s string) CallStack {
-	var cs CallStack
+func stringToCallStack(s string) result.CallStack {
+	var cs result.CallStack
 	for _, e := range strings.Fields(s) {
 		parts := strings.Split(e, ".")
-		cs.Frames = append(cs.Frames, &StackFrame{
+		cs.Frames = append(cs.Frames, &result.StackFrame{
 			PkgPath:  parts[0],
 			FuncName: parts[1],
 		})
diff --git a/internal/govulncheck/json.go b/internal/govulncheck/json.go
index 3e76548..7eec6a7 100644
--- a/internal/govulncheck/json.go
+++ b/internal/govulncheck/json.go
@@ -12,6 +12,7 @@
 	"io"
 
 	"golang.org/x/vuln/client"
+	"golang.org/x/vuln/internal/result"
 )
 
 type jsonOutput struct {
@@ -20,7 +21,7 @@
 
 func (o *jsonOutput) intro(ctx context.Context, dbClient client.Client, dbs []string, source bool) {}
 
-func (o *jsonOutput) result(r *Result, verbose, source bool) error {
+func (o *jsonOutput) result(r *result.Result, verbose, source bool) error {
 	b, err := json.MarshalIndent(r, "", "\t")
 	if err != nil {
 		return err
diff --git a/internal/govulncheck/print_test.go b/internal/govulncheck/print_test.go
index 97ad1b5..7c7d9b5 100644
--- a/internal/govulncheck/print_test.go
+++ b/internal/govulncheck/print_test.go
@@ -10,6 +10,7 @@
 
 	"github.com/google/go-cmp/cmp"
 	"golang.org/x/vuln/internal"
+	"golang.org/x/vuln/internal/result"
 	"golang.org/x/vuln/osv"
 )
 
@@ -126,10 +127,10 @@
 	}}}
 
 func TestPrintTextNoVulns(t *testing.T) {
-	r := &Result{Vulns: []*Vuln{
+	r := &result.Result{Vulns: []*result.Vuln{
 		{
 			OSV: testVuln1,
-			Modules: []*Module{
+			Modules: []*result.Module{
 				{
 					Path:         "golang.org/vmod",
 					FoundVersion: "v0.0.1",
@@ -166,17 +167,17 @@
 }
 
 func TestPrintTextSource(t *testing.T) {
-	r := &Result{Vulns: []*Vuln{
+	r := &result.Result{Vulns: []*result.Vuln{
 		{
 			OSV: testVuln1,
-			Modules: []*Module{
+			Modules: []*result.Module{
 				{
 					Path:         "golang.org/vmod",
 					FoundVersion: "v0.0.1",
 					FixedVersion: "v0.1.3",
-					Packages: []*Package{
+					Packages: []*result.Package{
 						{
-							CallStacks: []CallStack{{Summary: "main calls vmod.Vuln"}},
+							CallStacks: []result.CallStack{{Summary: "main calls vmod.Vuln"}},
 						},
 					},
 				},
@@ -184,11 +185,11 @@
 		},
 		{
 			OSV: testVuln2,
-			Modules: []*Module{
+			Modules: []*result.Module{
 				{
 					Path:         internal.GoStdModulePath,
 					FoundVersion: "v0.0.1",
-					Packages: []*Package{
+					Packages: []*result.Package{
 						{
 							Path: "net/http",
 						},
@@ -236,10 +237,10 @@
 }
 
 func TestPrintTextBinary(t *testing.T) {
-	r := &Result{Vulns: []*Vuln{
+	r := &result.Result{Vulns: []*result.Vuln{
 		{
 			OSV: testVuln1,
-			Modules: []*Module{
+			Modules: []*result.Module{
 				{
 					Path:         "golang.org/vmod",
 					FoundVersion: "v0.0.1",
@@ -251,11 +252,11 @@
 		},
 		{
 			OSV: testVuln2,
-			Modules: []*Module{
+			Modules: []*result.Module{
 				{
 					Path:         internal.GoStdModulePath,
 					FoundVersion: "v0.0.1",
-					Packages: []*Package{
+					Packages: []*result.Package{
 						{
 							Path: "net/http",
 						},
@@ -296,17 +297,17 @@
 }
 
 func TestPrintTextMultiModuleAndStacks(t *testing.T) {
-	r := &Result{Vulns: []*Vuln{
+	r := &result.Result{Vulns: []*result.Vuln{
 		{
 			OSV: testVuln1,
-			Modules: []*Module{
+			Modules: []*result.Module{
 				{
 					Path:         "golang.org/vmod",
 					FoundVersion: "v0.0.1",
 					FixedVersion: "v0.1.3",
-					Packages: []*Package{
+					Packages: []*result.Package{
 						{
-							CallStacks: []CallStack{{Summary: "main calls vmod.Vuln"}, {Summary: "main calls vmod.VulnFoo"}},
+							CallStacks: []result.CallStack{{Summary: "main calls vmod.Vuln"}, {Summary: "main calls vmod.VulnFoo"}},
 						},
 					},
 				},
@@ -314,12 +315,12 @@
 					Path:         "golang.org/vmod1",
 					FoundVersion: "v0.0.3",
 					FixedVersion: "v0.0.4",
-					Packages: []*Package{
+					Packages: []*result.Package{
 						{
-							CallStacks: []CallStack{{Summary: "Foo calls vmod1.Vuln"}},
+							CallStacks: []result.CallStack{{Summary: "Foo calls vmod1.Vuln"}},
 						},
 						{
-							CallStacks: []CallStack{{Summary: "Bar calls vmod1.VulnFoo"}},
+							CallStacks: []result.CallStack{{Summary: "Bar calls vmod1.VulnFoo"}},
 						},
 					},
 				},
diff --git a/internal/govulncheck/result.go b/internal/govulncheck/result.go
index 9110105..5a33410 100644
--- a/internal/govulncheck/result.go
+++ b/internal/govulncheck/result.go
@@ -6,13 +6,8 @@
 package govulncheck
 
 import (
-	"fmt"
-	"go/token"
-	"strings"
-
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/vuln/client"
-	"golang.org/x/vuln/osv"
 )
 
 // LoadMode is the level of information needed for each package
@@ -33,144 +28,3 @@
 	// By default, GoVersion is the go command version found from the PATH.
 	GoVersion string
 }
-
-// Result is the result of executing Source or Binary.
-type Result struct {
-	// Vulns contains all vulnerabilities that are called or imported by
-	// the analyzed module.
-	Vulns []*Vuln
-}
-
-// Vuln represents a single OSV entry.
-type Vuln struct {
-	// OSV contains all data from the OSV entry for this vulnerability.
-	OSV *osv.Entry
-
-	// Modules contains all of the modules in the OSV entry where a
-	// vulnerable package is imported by the target source code or binary.
-	//
-	// For example, a module M with two packages M/p1 and M/p2, where only p1
-	// is vulnerable, will appear in this list if and only if p1 is imported by
-	// the target source code or binary.
-	Modules []*Module
-}
-
-// IsCalled reports whether the vulnerability is called, therefore
-// affecting the target source code or binary.
-func (v *Vuln) IsCalled() bool {
-	for _, m := range v.Modules {
-		for _, p := range m.Packages {
-			if len(p.CallStacks) > 0 {
-				return true
-			}
-		}
-	}
-	return false
-}
-
-// Module represents a specific vulnerability relevant to a single module.
-type Module struct {
-	// Path is the module path of the module containing the vulnerability.
-	//
-	// Importable packages in the standard library will have the path "stdlib".
-	Path string
-
-	// FoundVersion is the module version where the vulnerability was found.
-	FoundVersion string
-
-	// FixedVersion is the module version where the vulnerability was
-	// fixed. If there are multiple fixed versions in the OSV report, this will
-	// be the latest fixed version.
-	//
-	// This is empty if a fix is not available.
-	FixedVersion string
-
-	// Packages contains all the vulnerable packages in OSV entry that are
-	// imported by the target source code or binary.
-	//
-	// For example, given a module M with two packages M/p1 and M/p2, where
-	// both p1 and p2 are vulnerable, p1 and p2 will each only appear in this
-	// list they are individually imported by the target source code or binary.
-	Packages []*Package
-}
-
-// Package is a Go package with known vulnerable symbols.
-type Package struct {
-	// Path is the import path of the package containing the vulnerability.
-	Path string
-
-	// CallStacks contains a representative call stack for each
-	// vulnerable symbol that is called.
-	//
-	// For vulnerabilities found from binary analysis, only CallStack.Symbol
-	// will be provided.
-	//
-	// For non-affecting vulnerabilities reported from the source mode
-	// analysis, this will be empty.
-	CallStacks []CallStack
-}
-
-// CallStacks contains a representative call stack for a vulnerable
-// symbol.
-type CallStack struct {
-	// Symbol is the name of the detected vulnerable function
-	// or method.
-	//
-	// This follows the naming convention in the OSV report.
-	Symbol string
-
-	// Summary is a one-line description of the callstack, used by the
-	// default govulncheck mode.
-	//
-	// Example: module3.main calls github.com/shiyanhui/dht.DHT.Run
-	Summary string
-
-	// Frames contains an entry for each stack in the call stack.
-	//
-	// Frames are sorted starting from the entry point to the
-	// imported vulnerable symbol. The last frame in Frames should match
-	// Symbol.
-	Frames []*StackFrame
-}
-
-// StackFrame represents a call stack entry.
-type StackFrame struct {
-	// PackagePath is the import path.
-	PkgPath string
-
-	// FuncName is the function name.
-	FuncName string
-
-	// RecvType is the fully qualified receiver type,
-	// if the called symbol is a method.
-	//
-	// The client can create the final symbol name by
-	// prepending RecvType to FuncName.
-	RecvType string
-
-	// Position describes an arbitrary source position
-	// including the file, line, and column location.
-	// A Position is valid if the line number is > 0.
-	Position token.Position
-}
-
-// Name returns the full qualified function name from sf,
-// adjusted to remove pointer annotations.
-func (sf *StackFrame) Name() string {
-	var n string
-	if sf.RecvType == "" {
-		n = fmt.Sprintf("%s.%s", sf.PkgPath, sf.FuncName)
-	} else {
-		n = fmt.Sprintf("%s.%s", sf.RecvType, sf.FuncName)
-	}
-	return strings.TrimPrefix(n, "*")
-}
-
-// Pos returns the position of the call in sf as string.
-// If position is not available, return "".
-func (sf *StackFrame) Pos() string {
-	if sf.Position.IsValid() {
-		return sf.Position.String()
-	}
-	return ""
-}
diff --git a/internal/govulncheck/result_test.go b/internal/govulncheck/result_test.go
index 081da3d..d0d52c4 100644
--- a/internal/govulncheck/result_test.go
+++ b/internal/govulncheck/result_test.go
@@ -7,16 +7,18 @@
 import (
 	"go/token"
 	"testing"
+
+	"golang.org/x/vuln/internal/result"
 )
 
 func TestStackFrame(t *testing.T) {
 	for _, test := range []struct {
-		sf       *StackFrame
+		sf       *result.StackFrame
 		wantFunc string
 		wantPos  string
 	}{
 		{
-			&StackFrame{
+			&result.StackFrame{
 				PkgPath:  "golang.org/x/vuln/vulncheck",
 				FuncName: "Foo",
 				Position: token.Position{Filename: "some/path/file.go", Line: 12},
@@ -25,7 +27,7 @@
 			"some/path/file.go:12",
 		},
 		{
-			&StackFrame{
+			&result.StackFrame{
 				PkgPath:  "golang.org/x/vuln/vulncheck",
 				RecvType: "golang.org/x/vuln/vulncheck.Bar",
 				FuncName: "Foo",
@@ -49,17 +51,17 @@
 	// p is both the module and package path, and
 	// s is the called symbol. If s is "", then
 	// there is no called symbol.
-	vuln := func(syms ...[2]string) *Vuln {
-		v := &Vuln{}
+	vuln := func(syms ...[2]string) *result.Vuln {
+		v := &result.Vuln{}
 		for _, sym := range syms {
-			p := &Package{Path: sym[0]}
-			v.Modules = append(v.Modules, &Module{
+			p := &result.Package{Path: sym[0]}
+			v.Modules = append(v.Modules, &result.Module{
 				Path:     sym[0],
-				Packages: []*Package{p},
+				Packages: []*result.Package{p},
 			})
 			if symbol := sym[1]; symbol != "" {
-				cs := CallStack{Symbol: symbol}
-				p.CallStacks = []CallStack{cs}
+				cs := result.CallStack{Symbol: symbol}
+				p.CallStacks = []result.CallStack{cs}
 			}
 		}
 		return v
@@ -67,7 +69,7 @@
 
 	for _, test := range []struct {
 		desc string
-		v    *Vuln
+		v    *result.Vuln
 		want bool
 	}{
 		{"called - single module", vuln([2]string{"golang.org/p1", "Foo"}), true},
diff --git a/internal/govulncheck/run.go b/internal/govulncheck/run.go
index bfdfaf1..a02052f 100644
--- a/internal/govulncheck/run.go
+++ b/internal/govulncheck/run.go
@@ -11,6 +11,7 @@
 	"io"
 	"sort"
 
+	"golang.org/x/vuln/internal/result"
 	"golang.org/x/vuln/osv"
 	"golang.org/x/vuln/vulncheck"
 )
@@ -23,7 +24,7 @@
 //
 // This function is used for source code analysis by cmd/govulncheck and
 // exp/govulncheck.
-func Source(ctx context.Context, cfg *Config, pkgs []*vulncheck.Package) (*Result, error) {
+func Source(ctx context.Context, cfg *Config, pkgs []*vulncheck.Package) (*result.Result, error) {
 	vcfg := &vulncheck.Config{
 		Client:          cfg.Client,
 		SourceGoVersion: cfg.GoVersion,
@@ -38,7 +39,7 @@
 // Binary detects presence of vulnerable symbols in exe.
 //
 // This function is used for binary analysis by cmd/govulncheck.
-func Binary(ctx context.Context, cfg *Config, exe io.ReaderAt) (*Result, error) {
+func Binary(ctx context.Context, cfg *Config, exe io.ReaderAt) (*result.Result, error) {
 	vcfg := &vulncheck.Config{
 		Client: cfg.Client,
 	}
@@ -49,7 +50,7 @@
 	return createBinaryResult(vr), nil
 }
 
-func createSourceResult(vr *vulncheck.Result, pkgs []*vulncheck.Package) *Result {
+func createSourceResult(vr *vulncheck.Result, pkgs []*vulncheck.Package) *result.Result {
 	topPkgs := map[string]bool{}
 	for _, p := range pkgs {
 		topPkgs[p.PkgPath] = true
@@ -75,27 +76,27 @@
 	// Create Result where each vulncheck.Vuln{OSV, ModPath, PkgPath} becomes
 	// a separate Vuln{OSV, Modules{Packages{PkgPath}}} entry. We merge the
 	// results later.
-	r := &Result{}
+	r := &result.Result{}
 	for _, vv := range vr.Vulns {
-		p := &Package{Path: vv.PkgPath}
-		m := &Module{
+		p := &result.Package{Path: vv.PkgPath}
+		m := &result.Module{
 			Path:         vv.ModPath,
 			FoundVersion: foundVersion(vv.ModPath, modVersions),
 			FixedVersion: fixedVersion(vv.ModPath, vv.OSV.Affected),
-			Packages:     []*Package{p},
+			Packages:     []*result.Package{p},
 		}
-		v := &Vuln{OSV: vv.OSV, Modules: []*Module{m}}
+		v := &result.Vuln{OSV: vv.OSV, Modules: []*result.Module{m}}
 
 		if vv.CallSink != 0 {
 			k := key{id: vv.OSV.ID, pkg: vv.PkgPath, mod: vv.ModPath}
 			vcs := uniqueCallStack(vv, callStacks[vv], vulnsPerPkg[k], vr)
 			if vcs != nil {
-				cs := CallStack{
+				cs := result.CallStack{
 					Frames: stackFramesfromEntries(vcs),
 					Symbol: vv.Symbol,
 				}
 				cs.Summary = summarizeCallStack(cs, topPkgs, p.Path)
-				p.CallStacks = []CallStack{cs}
+				p.CallStacks = []result.CallStack{cs}
 			}
 		}
 		r.Vulns = append(r.Vulns, v)
@@ -106,23 +107,23 @@
 	return r
 }
 
-func createBinaryResult(vr *vulncheck.Result) *Result {
+func createBinaryResult(vr *vulncheck.Result) *result.Result {
 	modVersions := moduleVersionMap(vr.Modules)
 	// Create Result where each vulncheck.Vuln{OSV, ModPath, PkgPath} becomes
 	// a separate Vuln{OSV, Modules{Packages{PkgPath}}} entry. We merge the
 	// results later.
-	r := &Result{}
+	r := &result.Result{}
 	for _, vv := range vr.Vulns {
-		p := &Package{Path: vv.PkgPath}
+		p := &result.Package{Path: vv.PkgPath}
 		// in binary mode, call stacks contain just the symbol data
-		p.CallStacks = []CallStack{{Symbol: vv.Symbol}}
-		m := &Module{
+		p.CallStacks = []result.CallStack{{Symbol: vv.Symbol}}
+		m := &result.Module{
 			Path:         vv.ModPath,
 			FoundVersion: foundVersion(vv.ModPath, modVersions),
 			FixedVersion: fixedVersion(vv.ModPath, vv.OSV.Affected),
-			Packages:     []*Package{p},
+			Packages:     []*result.Package{p},
 		}
-		v := &Vuln{OSV: vv.OSV, Modules: []*Module{m}}
+		v := &result.Vuln{OSV: vv.OSV, Modules: []*result.Module{m}}
 		r.Vulns = append(r.Vulns, v)
 	}
 
@@ -136,36 +137,36 @@
 // For instance, Vulns with the same OSV field are
 // merged into a single one. The same applies for
 // Modules of a Vuln, and Packages of a Module.
-func merge(r *Result) *Result {
-	nr := &Result{}
+func merge(r *result.Result) *result.Result {
+	nr := &result.Result{}
 	// merge vulns by their ID. Note that there can
 	// be several OSVs with the same ID but different
 	// pointer values
 	osvs := make(map[string]*osv.Entry)
-	vs := make(map[string][]*Module)
+	vs := make(map[string][]*result.Module)
 	for _, v := range r.Vulns {
 		osvs[v.OSV.ID] = v.OSV
 		vs[v.OSV.ID] = append(vs[v.OSV.ID], v.Modules...)
 	}
 
 	for id, mods := range vs {
-		v := &Vuln{OSV: osvs[id], Modules: mods}
+		v := &result.Vuln{OSV: osvs[id], Modules: mods}
 		nr.Vulns = append(nr.Vulns, v)
 	}
 
 	// merge modules
 	for _, v := range nr.Vulns {
-		ms := make(map[string][]*Module)
+		ms := make(map[string][]*result.Module)
 		for _, m := range v.Modules {
 			ms[m.Path] = append(ms[m.Path], m)
 		}
 
-		var nms []*Module
+		var nms []*result.Module
 		for mpath, mods := range ms {
 			// modules with the same path must have
 			// same found and fixed versions
 			validateModuleVersions(mods)
-			nm := &Module{
+			nm := &result.Module{
 				Path:         mpath,
 				FixedVersion: mods[0].FixedVersion,
 				FoundVersion: mods[0].FoundVersion,
@@ -181,14 +182,14 @@
 	// merge packages
 	for _, v := range nr.Vulns {
 		for _, m := range v.Modules {
-			ps := make(map[string][]*Package)
+			ps := make(map[string][]*result.Package)
 			for _, p := range m.Packages {
 				ps[p.Path] = append(ps[p.Path], p)
 			}
 
-			var nps []*Package
+			var nps []*result.Package
 			for ppath, pkgs := range ps {
-				np := &Package{Path: ppath}
+				np := &result.Package{Path: ppath}
 				for _, p := range pkgs {
 					np.CallStacks = append(np.CallStacks, p.CallStacks...)
 				}
@@ -202,7 +203,7 @@
 
 // validateModuleVersions checks that all modules have
 // the same found and fixed version. If not, panics.
-func validateModuleVersions(modules []*Module) {
+func validateModuleVersions(modules []*result.Module) {
 	var found, fixed string
 	for i, m := range modules {
 		if i == 0 {
@@ -217,7 +218,7 @@
 }
 
 // sortResults sorts Vulns, Modules, and Packages of r.
-func sortResult(r *Result) {
+func sortResult(r *result.Result) {
 	sort.Slice(r.Vulns, func(i, j int) bool {
 		return r.Vulns[i].OSV.ID > r.Vulns[j].OSV.ID
 	})
@@ -236,10 +237,10 @@
 // stackFramesFromEntries creates a sequence of stack
 // frames from vcs. Position of a StackFrame is the
 // call position of the corresponding stack entry.
-func stackFramesfromEntries(vcs vulncheck.CallStack) []*StackFrame {
-	var frames []*StackFrame
+func stackFramesfromEntries(vcs vulncheck.CallStack) []*result.StackFrame {
+	var frames []*result.StackFrame
 	for _, e := range vcs {
-		fr := &StackFrame{
+		fr := &result.StackFrame{
 			FuncName: e.Function.Name,
 			PkgPath:  e.Function.PkgPath,
 			RecvType: e.Function.RecvType,
diff --git a/internal/govulncheck/scan.go b/internal/govulncheck/scan.go
index 3e531d1..b8206f0 100644
--- a/internal/govulncheck/scan.go
+++ b/internal/govulncheck/scan.go
@@ -17,6 +17,7 @@
 	"golang.org/x/tools/go/buildutil"
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/vuln/client"
+	"golang.org/x/vuln/internal/result"
 	"golang.org/x/vuln/vulncheck"
 )
 
@@ -126,7 +127,7 @@
 	// config GoVersion is "", which means use current
 	// Go version at path.
 	cfg := &Config{Client: dbClient}
-	var res *Result
+	var res *result.Result
 	if c.sourceAnalysis {
 		var pkgs []*vulncheck.Package
 		pkgs, err = loadPackages(c, dir)
diff --git a/internal/govulncheck/text.go b/internal/govulncheck/text.go
index 2f3e5cc..b454987 100644
--- a/internal/govulncheck/text.go
+++ b/internal/govulncheck/text.go
@@ -16,6 +16,7 @@
 
 	"golang.org/x/vuln/client"
 	"golang.org/x/vuln/internal"
+	"golang.org/x/vuln/internal/result"
 	"golang.org/x/vuln/osv"
 	"golang.org/x/vuln/vulncheck"
 )
@@ -35,7 +36,7 @@
 	intro(ctx context.Context, dbClient client.Client, dbs []string, source bool)
 
 	// result communicates the result of running govulncheck to the user.
-	result(r *Result, verbose, source bool) error
+	result(r *result.Result, verbose, source bool) error
 
 	// progress communicates a progress update to the user.
 	progress(msg string)
@@ -81,7 +82,7 @@
 	tmpl.Execute(o.to, i)
 }
 
-func (o *readableOutput) result(r *Result, verbose, source bool) error {
+func (o *readableOutput) result(r *result.Result, verbose, source bool) error {
 	lineWidth := 80 - labelWidth
 	funcMap := template.FuncMap{
 		// used in template for counting vulnerabilities
@@ -133,7 +134,7 @@
 
 // createTmplResult transforms Result r into a
 // template structure for printing.
-func createTmplResult(r *Result, verbose, source bool) tmplResult {
+func createTmplResult(r *result.Result, verbose, source bool) tmplResult {
 	// unaffected are (imported) OSVs none of
 	// which vulnerabilities are called.
 	var unaffected []tmplVulnInfo
@@ -175,7 +176,7 @@
 // createTmplVulnInfo creates a template vuln info for
 // a vulnerability that is called by source code or
 // present in the binary.
-func createTmplVulnInfo(v *Vuln, verbose, source bool) tmplVulnInfo {
+func createTmplVulnInfo(v *result.Vuln, verbose, source bool) tmplVulnInfo {
 	vInfo := tmplVulnInfo{
 		ID:      v.OSV.ID,
 		Details: v.OSV.Details,
@@ -183,7 +184,7 @@
 
 	// stacks returns call stack info of p as a
 	// string depending on verbose and source mode.
-	stacks := func(p *Package) string {
+	stacks := func(p *result.Package) string {
 		if !source {
 			return ""
 		}
@@ -248,7 +249,7 @@
 	return vInfo
 }
 
-func defaultCallStacks(css []CallStack) string {
+func defaultCallStacks(css []result.CallStack) string {
 	var summaries []string
 	for _, cs := range css {
 		summaries = append(summaries, cs.Summary)
@@ -268,7 +269,7 @@
 	return b.String()
 }
 
-func verboseCallStacks(css []CallStack) string {
+func verboseCallStacks(css []result.CallStack) string {
 	// Display one full call stack for each vuln.
 	i := 1
 	var b strings.Builder
diff --git a/internal/govulncheck/util.go b/internal/govulncheck/util.go
index 25a10c6..916004e 100644
--- a/internal/govulncheck/util.go
+++ b/internal/govulncheck/util.go
@@ -9,6 +9,7 @@
 
 	"golang.org/x/mod/semver"
 	"golang.org/x/vuln/internal"
+	"golang.org/x/vuln/internal/result"
 	isem "golang.org/x/vuln/internal/semver"
 	"golang.org/x/vuln/osv"
 	"golang.org/x/vuln/vulncheck"
@@ -64,7 +65,7 @@
 
 // highest returns the highest (one with the smallest index) entry in the call
 // stack for which f returns true.
-func highest(cs []*StackFrame, f func(e *StackFrame) bool) int {
+func highest(cs []*result.StackFrame, f func(e *result.StackFrame) bool) int {
 	for i := 0; i < len(cs); i++ {
 		if f(cs[i]) {
 			return i
@@ -75,7 +76,7 @@
 
 // lowest returns the lowest (one with the largest index) entry in the call
 // stack for which f returns true.
-func lowest(cs []*StackFrame, f func(e *StackFrame) bool) int {
+func lowest(cs []*result.StackFrame, f func(e *result.StackFrame) bool) int {
 	for i := len(cs) - 1; i >= 0; i-- {
 		if f(cs[i]) {
 			return i
diff --git a/internal/result/result.go b/internal/result/result.go
new file mode 100644
index 0000000..16eceea
--- /dev/null
+++ b/internal/result/result.go
@@ -0,0 +1,155 @@
+// 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 result contains the JSON output structs for govulncheck.
+package result
+
+import (
+	"fmt"
+	"go/token"
+	"strings"
+
+	"golang.org/x/vuln/osv"
+)
+
+// Result is the result of executing Source or Binary.
+type Result struct {
+	// Vulns contains all vulnerabilities that are called or imported by
+	// the analyzed module.
+	Vulns []*Vuln
+}
+
+// Vuln represents a single OSV entry.
+type Vuln struct {
+	// OSV contains all data from the OSV entry for this vulnerability.
+	OSV *osv.Entry
+
+	// Modules contains all of the modules in the OSV entry where a
+	// vulnerable package is imported by the target source code or binary.
+	//
+	// For example, a module M with two packages M/p1 and M/p2, where only p1
+	// is vulnerable, will appear in this list if and only if p1 is imported by
+	// the target source code or binary.
+	Modules []*Module
+}
+
+// IsCalled reports whether the vulnerability is called, therefore
+// affecting the target source code or binary.
+func (v *Vuln) IsCalled() bool {
+	for _, m := range v.Modules {
+		for _, p := range m.Packages {
+			if len(p.CallStacks) > 0 {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// Module represents a specific vulnerability relevant to a single module.
+type Module struct {
+	// Path is the module path of the module containing the vulnerability.
+	//
+	// Importable packages in the standard library will have the path "stdlib".
+	Path string
+
+	// FoundVersion is the module version where the vulnerability was found.
+	FoundVersion string
+
+	// FixedVersion is the module version where the vulnerability was
+	// fixed. If there are multiple fixed versions in the OSV report, this will
+	// be the latest fixed version.
+	//
+	// This is empty if a fix is not available.
+	FixedVersion string
+
+	// Packages contains all the vulnerable packages in OSV entry that are
+	// imported by the target source code or binary.
+	//
+	// For example, given a module M with two packages M/p1 and M/p2, where
+	// both p1 and p2 are vulnerable, p1 and p2 will each only appear in this
+	// list they are individually imported by the target source code or binary.
+	Packages []*Package
+}
+
+// Package is a Go package with known vulnerable symbols.
+type Package struct {
+	// Path is the import path of the package containing the vulnerability.
+	Path string
+
+	// CallStacks contains a representative call stack for each
+	// vulnerable symbol that is called.
+	//
+	// For vulnerabilities found from binary analysis, only CallStack.Symbol
+	// will be provided.
+	//
+	// For non-affecting vulnerabilities reported from the source mode
+	// analysis, this will be empty.
+	CallStacks []CallStack
+}
+
+// CallStacks contains a representative call stack for a vulnerable
+// symbol.
+type CallStack struct {
+	// Symbol is the name of the detected vulnerable function
+	// or method.
+	//
+	// This follows the naming convention in the OSV report.
+	Symbol string
+
+	// Summary is a one-line description of the callstack, used by the
+	// default govulncheck mode.
+	//
+	// Example: module3.main calls github.com/shiyanhui/dht.DHT.Run
+	Summary string
+
+	// Frames contains an entry for each stack in the call stack.
+	//
+	// Frames are sorted starting from the entry point to the
+	// imported vulnerable symbol. The last frame in Frames should match
+	// Symbol.
+	Frames []*StackFrame
+}
+
+// StackFrame represents a call stack entry.
+type StackFrame struct {
+	// PackagePath is the import path.
+	PkgPath string
+
+	// FuncName is the function name.
+	FuncName string
+
+	// RecvType is the fully qualified receiver type,
+	// if the called symbol is a method.
+	//
+	// The client can create the final symbol name by
+	// prepending RecvType to FuncName.
+	RecvType string
+
+	// Position describes an arbitrary source position
+	// including the file, line, and column location.
+	// A Position is valid if the line number is > 0.
+	Position token.Position
+}
+
+// Name returns the full qualified function name from sf,
+// adjusted to remove pointer annotations.
+func (sf *StackFrame) Name() string {
+	var n string
+	if sf.RecvType == "" {
+		n = fmt.Sprintf("%s.%s", sf.PkgPath, sf.FuncName)
+	} else {
+		n = fmt.Sprintf("%s.%s", sf.RecvType, sf.FuncName)
+	}
+	return strings.TrimPrefix(n, "*")
+}
+
+// Pos returns the position of the call in sf as string.
+// If position is not available, return "".
+func (sf *StackFrame) Pos() string {
+	if sf.Position.IsValid() {
+		return sf.Position.String()
+	}
+	return ""
+}