Skip to content

Commit 32c4665

Browse files
committed
gopls/internal/golang/completion: avoid crash in comment completion
Avoid an observed crash in comment completion, as well as another nearby crash that was discovered in the course of debugging. Add debugging bug reports for the case of a missing function definition. Fixes golang/go#71273 Change-Id: Ibac993e0cf041fc074e5f00a7ce6d5718d77052f Reviewed-on: https://go-review.googlesource.com/c/tools/+/642877 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 85e8b42 commit 32c4665

File tree

5 files changed

+54
-24
lines changed

5 files changed

+54
-24
lines changed

gopls/internal/golang/completion/completion.go

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"golang.org/x/tools/go/ast/astutil"
3232
"golang.org/x/tools/gopls/internal/cache"
3333
"golang.org/x/tools/gopls/internal/cache/metadata"
34+
"golang.org/x/tools/gopls/internal/cache/parsego"
3435
"golang.org/x/tools/gopls/internal/file"
3536
"golang.org/x/tools/gopls/internal/fuzzy"
3637
"golang.org/x/tools/gopls/internal/golang"
@@ -218,17 +219,16 @@ type completer struct {
218219
// filename is the name of the file associated with this completion request.
219220
filename string
220221

221-
// file is the AST of the file associated with this completion request.
222-
file *ast.File
222+
// pgf is the AST of the file associated with this completion request.
223+
pgf *parsego.File // debugging
223224

224225
// goversion is the version of Go in force in the file, as
225226
// defined by x/tools/internal/versions. Empty if unknown.
226227
// Since go1.22 it should always be known.
227228
goversion string
228229

229-
// (tokFile, pos) is the position at which the request was triggered.
230-
tokFile *token.File
231-
pos token.Pos
230+
// pos is the position at which the request was triggered.
231+
pos token.Pos
232232

233233
// path is the path of AST nodes enclosing the position.
234234
path []ast.Node
@@ -410,7 +410,7 @@ func (c *completer) setSurrounding(ident *ast.Ident) {
410410
content: ident.Name,
411411
cursor: c.pos,
412412
// Overwrite the prefix only.
413-
tokFile: c.tokFile,
413+
tokFile: c.pgf.Tok,
414414
start: ident.Pos(),
415415
end: ident.End(),
416416
mapper: c.mapper,
@@ -435,7 +435,7 @@ func (c *completer) getSurrounding() *Selection {
435435
c.surrounding = &Selection{
436436
content: "",
437437
cursor: c.pos,
438-
tokFile: c.tokFile,
438+
tokFile: c.pgf.Tok,
439439
start: c.pos,
440440
end: c.pos,
441441
mapper: c.mapper,
@@ -609,8 +609,7 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
609609
},
610610
fh: fh,
611611
filename: fh.URI().Path(),
612-
tokFile: pgf.Tok,
613-
file: pgf.File,
612+
pgf: pgf,
614613
goversion: goversion,
615614
path: path,
616615
pos: pos,
@@ -711,15 +710,15 @@ func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
711710
// search queue or completion items directly for different completion contexts.
712711
func (c *completer) collectCompletions(ctx context.Context) error {
713712
// Inside import blocks, return completions for unimported packages.
714-
for _, importSpec := range c.file.Imports {
713+
for _, importSpec := range c.pgf.File.Imports {
715714
if !(importSpec.Path.Pos() <= c.pos && c.pos <= importSpec.Path.End()) {
716715
continue
717716
}
718717
return c.populateImportCompletions(importSpec)
719718
}
720719

721720
// Inside comments, offer completions for the name of the relevant symbol.
722-
for _, comment := range c.file.Comments {
721+
for _, comment := range c.pgf.File.Comments {
723722
if comment.Pos() < c.pos && c.pos <= comment.End() {
724723
c.populateCommentCompletions(comment)
725724
return nil
@@ -749,7 +748,7 @@ func (c *completer) collectCompletions(ctx context.Context) error {
749748

750749
switch n := c.path[0].(type) {
751750
case *ast.Ident:
752-
if c.file.Name == n {
751+
if c.pgf.File.Name == n {
753752
return c.packageNameCompletions(ctx, c.fh.URI(), n)
754753
} else if sel, ok := c.path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
755754
// Is this the Sel part of a selector?
@@ -921,14 +920,14 @@ func (c *completer) populateImportCompletions(searchImport *ast.ImportSpec) erro
921920
c.surrounding = &Selection{
922921
content: content,
923922
cursor: c.pos,
924-
tokFile: c.tokFile,
923+
tokFile: c.pgf.Tok,
925924
start: start,
926925
end: end,
927926
mapper: c.mapper,
928927
}
929928

930929
seenImports := make(map[string]struct{})
931-
for _, importSpec := range c.file.Imports {
930+
for _, importSpec := range c.pgf.File.Imports {
932931
if importSpec.Path.Value == importPath {
933932
continue
934933
}
@@ -1024,7 +1023,7 @@ func (c *completer) populateCommentCompletions(comment *ast.CommentGroup) {
10241023
c.setSurroundingForComment(comment)
10251024

10261025
// Using the next line pos, grab and parse the exported symbol on that line
1027-
for _, n := range c.file.Decls {
1026+
for _, n := range c.pgf.File.Decls {
10281027
declLine := safetoken.Line(file, n.Pos())
10291028
// if the comment is not in, directly above or on the same line as a declaration
10301029
if declLine != commentLine && declLine != commentLine+1 &&
@@ -1080,8 +1079,33 @@ func (c *completer) populateCommentCompletions(comment *ast.CommentGroup) {
10801079

10811080
// collect receiver struct fields
10821081
if node.Recv != nil {
1083-
sig := c.pkg.TypesInfo().Defs[node.Name].(*types.Func).Signature()
1084-
_, named := typesinternal.ReceiverNamed(sig.Recv()) // may be nil if ill-typed
1082+
obj := c.pkg.TypesInfo().Defs[node.Name]
1083+
switch obj.(type) {
1084+
case nil:
1085+
report := func() {
1086+
bug.Reportf("missing def for func %s", node.Name)
1087+
}
1088+
// Debugging golang/go#71273.
1089+
if !slices.Contains(c.pkg.CompiledGoFiles(), c.pgf) {
1090+
if c.snapshot.View().Type() == cache.GoPackagesDriverView {
1091+
report()
1092+
} else {
1093+
report()
1094+
}
1095+
} else {
1096+
report()
1097+
}
1098+
continue
1099+
case *types.Func:
1100+
default:
1101+
bug.Reportf("unexpected func obj type %T for %s", obj, node.Name)
1102+
}
1103+
sig := obj.(*types.Func).Signature()
1104+
recv := sig.Recv()
1105+
if recv == nil {
1106+
continue // may be nil if ill-typed
1107+
}
1108+
_, named := typesinternal.ReceiverNamed(recv)
10851109
if named != nil {
10861110
if recvStruct, ok := named.Underlying().(*types.Struct); ok {
10871111
for i := 0; i < recvStruct.NumFields(); i++ {
@@ -1133,7 +1157,7 @@ func (c *completer) setSurroundingForComment(comments *ast.CommentGroup) {
11331157
c.surrounding = &Selection{
11341158
content: cursorComment.Text[start:end],
11351159
cursor: c.pos,
1136-
tokFile: c.tokFile,
1160+
tokFile: c.pgf.Tok,
11371161
start: token.Pos(int(cursorComment.Slash) + start),
11381162
end: token.Pos(int(cursorComment.Slash) + end),
11391163
mapper: c.mapper,
@@ -1437,7 +1461,7 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
14371461
return nil
14381462
}
14391463

1440-
goversion := c.pkg.TypesInfo().FileVersions[c.file]
1464+
goversion := c.pkg.TypesInfo().FileVersions[c.pgf.File]
14411465

14421466
// Extract the package-level candidates using a quick parse.
14431467
var g errgroup.Group
@@ -1694,7 +1718,7 @@ func (c *completer) lexical(ctx context.Context) error {
16941718
// Make sure the package name isn't already in use by another
16951719
// object, and that this file doesn't import the package yet.
16961720
// TODO(adonovan): what if pkg.Path has vendor/ prefix?
1697-
if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.Types() && !alreadyImports(c.file, golang.ImportPath(pkg.Path())) {
1721+
if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.Types() && !alreadyImports(c.pgf.File, golang.ImportPath(pkg.Path())) {
16981722
seen[pkg.Name()] = struct{}{}
16991723
obj := types.NewPkgName(0, nil, pkg.Name(), pkg)
17001724
imp := &importInfo{

gopls/internal/golang/completion/postfix_snippets.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string,
653653
defaultName := imports.ImportPathToAssumedName(pkgPath)
654654

655655
// Check if file already imports pkgPath.
656-
for _, s := range c.file.Imports {
656+
for _, s := range c.pgf.File.Imports {
657657
// TODO(adonovan): what if pkgPath has a vendor/ suffix?
658658
// This may be the cause of go.dev/issue/56291.
659659
if string(metadata.UnquoteImportPath(s)) == pkgPath {

gopls/internal/golang/completion/util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func isBasicKind(t types.Type, k types.BasicInfo) bool {
284284
}
285285

286286
func (c *completer) editText(from, to token.Pos, newText string) ([]protocol.TextEdit, error) {
287-
start, end, err := safetoken.Offsets(c.tokFile, from, to)
287+
start, end, err := safetoken.Offsets(c.pgf.Tok, from, to)
288288
if err != nil {
289289
return nil, err // can't happen: from/to came from c
290290
}

gopls/internal/test/marker/testdata/completion/comment.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,9 @@ func Multiline() int { //@item(multiline, "Multiline", "func() int", "func")
7979
// //@complete(" ", multiline)
8080
return 0
8181
}
82+
83+
// This test checks that gopls does not panic if the receiver is syntactically
84+
// present but empty.
85+
//
86+
// //@complete(" ")
87+
func () _() {}

internal/packagesinternal/packages.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Package packagesinternal exposes internal-only fields from go/packages.
66
package packagesinternal
77

8-
var GetDepsErrors = func(p interface{}) []*PackageError { return nil }
8+
var GetDepsErrors = func(p any) []*PackageError { return nil }
99

1010
type PackageError struct {
1111
ImportStack []string // shortest path from package named on command line to this one
@@ -16,5 +16,5 @@ type PackageError struct {
1616
var TypecheckCgo int
1717
var DepsErrors int // must be set as a LoadMode to call GetDepsErrors
1818

19-
var SetModFlag = func(config interface{}, value string) {}
19+
var SetModFlag = func(config any, value string) {}
2020
var SetModFile = func(config interface{}, value string) {}

0 commit comments

Comments
 (0)