internal/lsp: handle invalid positions in semantic token debug logic

Check that a position is in range before using it.

Fixes golang/vscode-go#1656

Change-Id: I1598ebab76a1775afd8f63b9849049b31fb74a8b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/339169
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go
index d455a25..e2c7244 100644
--- a/internal/lsp/cache/parse.go
+++ b/internal/lsp/cache/parse.go
@@ -1072,10 +1072,10 @@
 	// Avoid doing tok.Offset(to) since that panics if badExpr ends at EOF.
 	// It also panics if the position is not in the range of the file, and
 	// badExprs may not necessarily have good positions, so check first.
-	if !inRange(tok, from) {
+	if !source.InRange(tok, from) {
 		return false
 	}
-	if !inRange(tok, to-1) {
+	if !source.InRange(tok, to-1) {
 		return false
 	}
 	fromOffset := tok.Offset(from)
@@ -1112,12 +1112,6 @@
 	return replaceNode(parent, bad, at)
 }
 
-// inRange reports whether the given position is in the given token.File.
-func inRange(tok *token.File, pos token.Pos) bool {
-	size := tok.Pos(tok.Size())
-	return int(pos) >= tok.Base() && pos <= size
-}
-
 // precedingToken scans src to find the token preceding pos.
 func precedingToken(pos token.Pos, tok *token.File, src []byte) token.Token {
 	s := &scanner.Scanner{}
diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go
index 073336b..cfc8719 100644
--- a/internal/lsp/semantic.go
+++ b/internal/lsp/semantic.go
@@ -236,9 +236,13 @@
 	}
 	if len(e.stack) > 0 {
 		loc := e.stack[len(e.stack)-1].Pos()
-		add := e.pgf.Tok.PositionFor(loc, false)
-		nm := filepath.Base(add.Filename)
-		msg = append(msg, fmt.Sprintf("(%s:%d,col:%d)", nm, add.Line, add.Column))
+		if !source.InRange(e.pgf.Tok, loc) {
+			msg = append(msg, fmt.Sprintf("invalid position %v for %s", loc, e.pgf.URI))
+		} else {
+			add := e.pgf.Tok.PositionFor(loc, false)
+			nm := filepath.Base(add.Filename)
+			msg = append(msg, fmt.Sprintf("(%s:%d,col:%d)", nm, add.Line, add.Column))
+		}
 	}
 	msg = append(msg, "]")
 	return strings.Join(msg, " ")
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index a30cc75..4ff5d57 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -544,3 +544,9 @@
 func IsCommandLineArguments(s string) bool {
 	return strings.Contains(s, "command-line-arguments")
 }
+
+// InRange reports whether the given position is in the given token.File.
+func InRange(tok *token.File, pos token.Pos) bool {
+	size := tok.Pos(tok.Size())
+	return int(pos) >= tok.Base() && pos <= size
+}