diff options
Diffstat (limited to 'gopls/internal/lsp/safetoken/safetoken_test.go')
-rw-r--r-- | gopls/internal/lsp/safetoken/safetoken_test.go | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/gopls/internal/lsp/safetoken/safetoken_test.go b/gopls/internal/lsp/safetoken/safetoken_test.go new file mode 100644 index 000000000..afd569472 --- /dev/null +++ b/gopls/internal/lsp/safetoken/safetoken_test.go @@ -0,0 +1,121 @@ +// Copyright 2021 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 safetoken_test + +import ( + "fmt" + "go/parser" + "go/token" + "go/types" + "os" + "testing" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/internal/testenv" +) + +func TestWorkaroundIssue57490(t *testing.T) { + // During error recovery the parser synthesizes various close + // tokens at EOF, causing the End position of incomplete + // syntax nodes, computed as Rbrace+len("}"), to be beyond EOF. + src := `package p; func f() { var x struct` + fset := token.NewFileSet() + file, _ := parser.ParseFile(fset, "a.go", src, 0) + tf := fset.File(file.Pos()) + + // Add another file to the FileSet. + file2, _ := parser.ParseFile(fset, "b.go", "package q", 0) + + // This is the ambiguity of #57490... + if file.End() != file2.Pos() { + t.Errorf("file.End() %d != %d file2.Pos()", file.End(), file2.Pos()) + } + // ...which causes these statements to panic. + if false { + tf.Offset(file.End()) // panic: invalid Pos value 36 (should be in [1, 35]) + tf.Position(file.End()) // panic: invalid Pos value 36 (should be in [1, 35]) + } + + // The offset of the EOF position is the file size. + offset, err := safetoken.Offset(tf, file.End()-1) + if err != nil || offset != tf.Size() { + t.Errorf("Offset(EOF) = (%d, %v), want token.File.Size %d", offset, err, tf.Size()) + } + + // The offset of the file.End() position, 1 byte beyond EOF, + // is also the size of the file. + offset, err = safetoken.Offset(tf, file.End()) + if err != nil || offset != tf.Size() { + t.Errorf("Offset(ast.File.End()) = (%d, %v), want token.File.Size %d", offset, err, tf.Size()) + } + + if got, want := safetoken.Position(tf, file.End()).String(), "a.go:1:35"; got != want { + t.Errorf("Position(ast.File.End()) = %s, want %s", got, want) + } + + if got, want := safetoken.EndPosition(fset, file.End()).String(), "a.go:1:35"; got != want { + t.Errorf("EndPosition(ast.File.End()) = %s, want %s", got, want) + } + + // Note that calling StartPosition on an end may yield the wrong file: + if got, want := safetoken.StartPosition(fset, file.End()).String(), "b.go:1:1"; got != want { + t.Errorf("StartPosition(ast.File.End()) = %s, want %s", got, want) + } +} + +// To reduce the risk of panic, or bugs for which this package +// provides a workaround, this test statically reports references to +// forbidden methods of token.File or FileSet throughout gopls and +// suggests alternatives. +func TestGoplsSourceDoesNotCallTokenFileMethods(t *testing.T) { + testenv.NeedsGoPackages(t) + + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName | packages.NeedModule | packages.NeedCompiledGoFiles | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps, + }, "go/token", "golang.org/x/tools/gopls/...") + if err != nil { + t.Fatal(err) + } + var tokenPkg *packages.Package + for _, pkg := range pkgs { + if pkg.PkgPath == "go/token" { + tokenPkg = pkg + break + } + } + if tokenPkg == nil { + t.Fatal("missing package go/token") + } + + File := tokenPkg.Types.Scope().Lookup("File") + FileSet := tokenPkg.Types.Scope().Lookup("FileSet") + + alternative := make(map[types.Object]string) + setAlternative := func(recv types.Object, old, new string) { + oldMethod, _, _ := types.LookupFieldOrMethod(recv.Type(), true, recv.Pkg(), old) + alternative[oldMethod] = new + } + setAlternative(File, "Offset", "safetoken.Offset") + setAlternative(File, "Position", "safetoken.Position") + setAlternative(File, "PositionFor", "safetoken.Position") + setAlternative(FileSet, "Position", "safetoken.StartPosition or EndPosition") + setAlternative(FileSet, "PositionFor", "safetoken.StartPosition or EndPosition") + + for _, pkg := range pkgs { + switch pkg.PkgPath { + case "go/token", "golang.org/x/tools/gopls/internal/lsp/safetoken": + continue // allow calls within these packages + } + + for ident, obj := range pkg.TypesInfo.Uses { + if alt, ok := alternative[obj]; ok { + posn := safetoken.StartPosition(pkg.Fset, ident.Pos()) + fmt.Fprintf(os.Stderr, "%s: forbidden use of %v; use %s instead.\n", posn, obj, alt) + t.Fail() + } + } + } +} |