golang.org/x/tools/gopls@v0.15.3/internal/util/safetoken/safetoken_test.go (about)

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package safetoken_test
     6  
     7  import (
     8  	"fmt"
     9  	"go/parser"
    10  	"go/token"
    11  	"go/types"
    12  	"os"
    13  	"testing"
    14  
    15  	"golang.org/x/tools/go/packages"
    16  	"golang.org/x/tools/gopls/internal/util/safetoken"
    17  	"golang.org/x/tools/internal/testenv"
    18  )
    19  
    20  func TestWorkaroundIssue57490(t *testing.T) {
    21  	// During error recovery the parser synthesizes various close
    22  	// tokens at EOF, causing the End position of incomplete
    23  	// syntax nodes, computed as Rbrace+len("}"), to be beyond EOF.
    24  	src := `package p; func f() { var x struct`
    25  	fset := token.NewFileSet()
    26  	file, _ := parser.ParseFile(fset, "a.go", src, 0)
    27  	tf := fset.File(file.Pos())
    28  
    29  	// Add another file to the FileSet.
    30  	file2, _ := parser.ParseFile(fset, "b.go", "package q", 0)
    31  
    32  	// This is the ambiguity of #57490...
    33  	if file.End() != file2.Pos() {
    34  		t.Errorf("file.End() %d != %d file2.Pos()", file.End(), file2.Pos())
    35  	}
    36  	// ...which causes these statements to panic.
    37  	if false {
    38  		tf.Offset(file.End())   // panic: invalid Pos value 36 (should be in [1, 35])
    39  		tf.Position(file.End()) // panic: invalid Pos value 36 (should be in [1, 35])
    40  	}
    41  
    42  	// The offset of the EOF position is the file size.
    43  	offset, err := safetoken.Offset(tf, file.End()-1)
    44  	if err != nil || offset != tf.Size() {
    45  		t.Errorf("Offset(EOF) = (%d, %v), want token.File.Size %d", offset, err, tf.Size())
    46  	}
    47  
    48  	// The offset of the file.End() position, 1 byte beyond EOF,
    49  	// is also the size of the file.
    50  	offset, err = safetoken.Offset(tf, file.End())
    51  	if err != nil || offset != tf.Size() {
    52  		t.Errorf("Offset(ast.File.End()) = (%d, %v), want token.File.Size %d", offset, err, tf.Size())
    53  	}
    54  
    55  	if got, want := safetoken.Position(tf, file.End()).String(), "a.go:1:35"; got != want {
    56  		t.Errorf("Position(ast.File.End()) = %s, want %s", got, want)
    57  	}
    58  
    59  	if got, want := safetoken.EndPosition(fset, file.End()).String(), "a.go:1:35"; got != want {
    60  		t.Errorf("EndPosition(ast.File.End()) = %s, want %s", got, want)
    61  	}
    62  
    63  	// Note that calling StartPosition on an end may yield the wrong file:
    64  	if got, want := safetoken.StartPosition(fset, file.End()).String(), "b.go:1:1"; got != want {
    65  		t.Errorf("StartPosition(ast.File.End()) = %s, want %s", got, want)
    66  	}
    67  }
    68  
    69  // To reduce the risk of panic, or bugs for which this package
    70  // provides a workaround, this test statically reports references to
    71  // forbidden methods of token.File or FileSet throughout gopls and
    72  // suggests alternatives.
    73  func TestGoplsSourceDoesNotCallTokenFileMethods(t *testing.T) {
    74  	testenv.NeedsGoPackages(t)
    75  	testenv.NeedsLocalXTools(t)
    76  
    77  	cfg := &packages.Config{
    78  		Mode: packages.NeedName | packages.NeedModule | packages.NeedCompiledGoFiles | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps,
    79  	}
    80  	cfg.Env = os.Environ()
    81  	cfg.Env = append(cfg.Env,
    82  		"GOPACKAGESDRIVER=off",
    83  		"GOWORK=off", // necessary for -mod=mod below
    84  		"GOFLAGS=-mod=mod",
    85  	)
    86  
    87  	pkgs, err := packages.Load(cfg, "go/token", "golang.org/x/tools/gopls/...")
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	var tokenPkg *packages.Package
    92  	for _, pkg := range pkgs {
    93  		if pkg.PkgPath == "go/token" {
    94  			tokenPkg = pkg
    95  			break
    96  		}
    97  	}
    98  	if tokenPkg == nil {
    99  		t.Fatal("missing package go/token")
   100  	}
   101  
   102  	File := tokenPkg.Types.Scope().Lookup("File")
   103  	FileSet := tokenPkg.Types.Scope().Lookup("FileSet")
   104  
   105  	alternative := make(map[types.Object]string)
   106  	setAlternative := func(recv types.Object, old, new string) {
   107  		oldMethod, _, _ := types.LookupFieldOrMethod(recv.Type(), true, recv.Pkg(), old)
   108  		alternative[oldMethod] = new
   109  	}
   110  	setAlternative(File, "Line", "safetoken.Line")
   111  	setAlternative(File, "Offset", "safetoken.Offset")
   112  	setAlternative(File, "Position", "safetoken.Position")
   113  	setAlternative(File, "PositionFor", "safetoken.Position")
   114  	setAlternative(FileSet, "Position", "safetoken.StartPosition or EndPosition")
   115  	setAlternative(FileSet, "PositionFor", "safetoken.StartPosition or EndPosition")
   116  
   117  	for _, pkg := range pkgs {
   118  		switch pkg.PkgPath {
   119  		case "go/token", "golang.org/x/tools/gopls/internal/util/safetoken":
   120  			continue // allow calls within these packages
   121  		}
   122  
   123  		for ident, obj := range pkg.TypesInfo.Uses {
   124  			if alt, ok := alternative[obj]; ok {
   125  				posn := safetoken.StartPosition(pkg.Fset, ident.Pos())
   126  				fmt.Fprintf(os.Stderr, "%s: forbidden use of %v; use %s instead.\n", posn, obj, alt)
   127  				t.Fail()
   128  			}
   129  		}
   130  	}
   131  }