aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/vulncheck/util.go
blob: a85b55bb0434d5a7a9305b4f48c538b4cf5364f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 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.

//go:build go1.18
// +build go1.18

package vulncheck

import (
	"fmt"
	"go/token"
	"strings"

	"golang.org/x/tools/internal/lsp/protocol"
	"golang.org/x/vuln/osv"
	"golang.org/x/vuln/vulncheck"
)

// fixedVersion returns the semantic version of the module
// version with a fix. The semantic version is
// as defined by SemVer 2.0.0, with no leading “v” prefix.
// Returns an empty string if there is no reported fix.
func fixedVersion(info *osv.Entry) string {
	var fixed string
	for _, a := range info.Affected {
		for _, r := range a.Ranges {
			if r.Type != "SEMVER" {
				continue
			}
			for _, e := range r.Events {
				if e.Fixed != "" {
					// assuming the later entry has higher semver.
					// TODO: check assumption.
					fixed = "v" + e.Fixed
				}
			}
		}
	}
	return fixed
}

const maxNumCallStacks = 64

func toCallStacks(src []vulncheck.CallStack) []CallStack {
	if len(src) > maxNumCallStacks {
		src = src[:maxNumCallStacks]
	}
	var dest []CallStack
	for _, s := range src {
		dest = append(dest, toCallStack(s))
	}
	return dest
}

func toCallStack(src vulncheck.CallStack) CallStack {
	var dest []StackEntry
	for _, e := range src {
		dest = append(dest, toStackEntry(e))
	}
	return dest
}

func toStackEntry(src vulncheck.StackEntry) StackEntry {
	f, call := src.Function, src.Call
	pos := f.Pos
	desc := funcName(f)
	if src.Call != nil {
		pos = src.Call.Pos
		desc = funcNameInCallSite(call) + " called from " + desc
		if !call.Resolved {
			// In case of a statically unresolved call site, communicate to the client
			// that this was approximately resolved to f

			desc += " [approx.]"
		}
	}
	return StackEntry{
		Name: desc,
		URI:  filenameToURI(pos),
		Pos:  posToPosition(pos),
	}
}

func funcName(fn *vulncheck.FuncNode) string {
	return strings.TrimPrefix(fn.String(), "*")
}

func funcNameInCallSite(call *vulncheck.CallSite) string {
	if call.RecvType == "" {
		return call.Name
	}
	return fmt.Sprintf("%s.%s", call.RecvType, call.Name)
}

// href returns a URL embedded in the entry if any.
// If no suitable URL is found, it returns a default entry in
// pkg.go.dev/vuln.
func href(vuln *osv.Entry) string {
	for _, affected := range vuln.Affected {
		if url := affected.DatabaseSpecific.URL; url != "" {
			return url
		}
	}
	for _, r := range vuln.References {
		if r.Type == "WEB" {
			return r.URL
		}
	}
	return fmt.Sprintf("https://pkg.go.dev/vuln/%s", vuln.ID)
}

func filenameToURI(pos *token.Position) protocol.DocumentURI {
	if pos == nil || pos.Filename == "" {
		return ""
	}
	return protocol.URIFromPath(pos.Filename)
}

func posToPosition(pos *token.Position) (p protocol.Position) {
	// token.Position.Line starts from 1, and
	// LSP protocol's position line is 0-based.
	if pos != nil {
		p.Line = uint32(pos.Line - 1)
		// TODO(hyangah): LSP uses UTF16 column.
		// We need utility like span.ToUTF16Column,
		// but somthing that does not require file contents.
	}
	return p
}