github.com/jd-ly/tools@v0.5.7/internal/lsp/cache/check.go (about)

     1  // Copyright 2019 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 cache
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/types"
    13  	"path"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  	"sync"
    18  
    19  	"golang.org/x/mod/module"
    20  	"github.com/jd-ly/tools/go/packages"
    21  	"github.com/jd-ly/tools/internal/event"
    22  	"github.com/jd-ly/tools/internal/lsp/debug/tag"
    23  	"github.com/jd-ly/tools/internal/lsp/source"
    24  	"github.com/jd-ly/tools/internal/memoize"
    25  	"github.com/jd-ly/tools/internal/span"
    26  	"github.com/jd-ly/tools/internal/typesinternal"
    27  	errors "golang.org/x/xerrors"
    28  )
    29  
    30  type packageHandleKey string
    31  
    32  type packageHandle struct {
    33  	handle *memoize.Handle
    34  
    35  	goFiles, compiledGoFiles []*parseGoHandle
    36  
    37  	// mode is the mode the files were parsed in.
    38  	mode source.ParseMode
    39  
    40  	// m is the metadata associated with the package.
    41  	m *metadata
    42  
    43  	// key is the hashed key for the package.
    44  	key packageHandleKey
    45  }
    46  
    47  func (ph *packageHandle) packageKey() packageKey {
    48  	return packageKey{
    49  		id:   ph.m.id,
    50  		mode: ph.mode,
    51  	}
    52  }
    53  
    54  // packageData contains the data produced by type-checking a package.
    55  type packageData struct {
    56  	pkg *pkg
    57  	err error
    58  }
    59  
    60  // buildPackageHandle returns a packageHandle for a given package and mode.
    61  func (s *snapshot) buildPackageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, error) {
    62  	if ph := s.getPackage(id, mode); ph != nil {
    63  		return ph, nil
    64  	}
    65  
    66  	// Build the packageHandle for this ID and its dependencies.
    67  	ph, deps, err := s.buildKey(ctx, id, mode)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	// Do not close over the packageHandle or the snapshot in the Bind function.
    73  	// This creates a cycle, which causes the finalizers to never run on the handles.
    74  	// The possible cycles are:
    75  	//
    76  	//     packageHandle.h.function -> packageHandle
    77  	//     packageHandle.h.function -> snapshot -> packageHandle
    78  	//
    79  
    80  	m := ph.m
    81  	key := ph.key
    82  
    83  	h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
    84  		snapshot := arg.(*snapshot)
    85  
    86  		// Begin loading the direct dependencies, in parallel.
    87  		var wg sync.WaitGroup
    88  		for _, dep := range deps {
    89  			wg.Add(1)
    90  			go func(dep *packageHandle) {
    91  				dep.check(ctx, snapshot)
    92  				wg.Done()
    93  			}(dep)
    94  		}
    95  
    96  		data := &packageData{}
    97  		data.pkg, data.err = typeCheck(ctx, snapshot, m, mode, deps)
    98  		// Make sure that the workers above have finished before we return,
    99  		// especially in case of cancellation.
   100  		wg.Wait()
   101  
   102  		return data
   103  	}, nil)
   104  	ph.handle = h
   105  
   106  	// Cache the handle in the snapshot. If a package handle has already
   107  	// been cached, addPackage will return the cached value. This is fine,
   108  	// since the original package handle above will have no references and be
   109  	// garbage collected.
   110  	ph = s.addPackageHandle(ph)
   111  
   112  	return ph, nil
   113  }
   114  
   115  // buildKey computes the key for a given packageHandle.
   116  func (s *snapshot) buildKey(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, map[packagePath]*packageHandle, error) {
   117  	m := s.getMetadata(id)
   118  	if m == nil {
   119  		return nil, nil, errors.Errorf("no metadata for %s", id)
   120  	}
   121  	goFiles, err := s.parseGoHandles(ctx, m.goFiles, mode)
   122  	if err != nil {
   123  		return nil, nil, err
   124  	}
   125  	compiledGoFiles, err := s.parseGoHandles(ctx, m.compiledGoFiles, mode)
   126  	if err != nil {
   127  		return nil, nil, err
   128  	}
   129  	ph := &packageHandle{
   130  		m:               m,
   131  		goFiles:         goFiles,
   132  		compiledGoFiles: compiledGoFiles,
   133  		mode:            mode,
   134  	}
   135  	// Make sure all of the depList are sorted.
   136  	depList := append([]packageID{}, m.deps...)
   137  	sort.Slice(depList, func(i, j int) bool {
   138  		return depList[i] < depList[j]
   139  	})
   140  
   141  	deps := make(map[packagePath]*packageHandle)
   142  
   143  	// Begin computing the key by getting the depKeys for all dependencies.
   144  	var depKeys []packageHandleKey
   145  	for _, depID := range depList {
   146  		depHandle, err := s.buildPackageHandle(ctx, depID, s.workspaceParseMode(depID))
   147  		if err != nil {
   148  			event.Error(ctx, fmt.Sprintf("%s: no dep handle for %s", id, depID), err, tag.Snapshot.Of(s.id))
   149  			if ctx.Err() != nil {
   150  				return nil, nil, ctx.Err()
   151  			}
   152  			// One bad dependency should not prevent us from checking the entire package.
   153  			// Add a special key to mark a bad dependency.
   154  			depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", id)))
   155  			continue
   156  		}
   157  		deps[depHandle.m.pkgPath] = depHandle
   158  		depKeys = append(depKeys, depHandle.key)
   159  	}
   160  	experimentalKey := s.View().Options().ExperimentalPackageCacheKey
   161  	ph.key = checkPackageKey(ctx, ph.m.id, compiledGoFiles, m.config, depKeys, mode, experimentalKey)
   162  	return ph, deps, nil
   163  }
   164  
   165  func (s *snapshot) workspaceParseMode(id packageID) source.ParseMode {
   166  	if _, ws := s.isWorkspacePackage(id); ws {
   167  		return source.ParseFull
   168  	} else {
   169  		return source.ParseExported
   170  	}
   171  }
   172  
   173  func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode, experimentalKey bool) packageHandleKey {
   174  	b := bytes.NewBuffer(nil)
   175  	b.WriteString(string(id))
   176  	if !experimentalKey {
   177  		// cfg was used to produce the other hashed inputs (package ID, parsed Go
   178  		// files, and deps). It should not otherwise affect the inputs to the type
   179  		// checker, so this experiment omits it. This should increase cache hits on
   180  		// the daemon as cfg contains the environment and working directory.
   181  		b.WriteString(hashConfig(cfg))
   182  	}
   183  	b.WriteByte(byte(mode))
   184  	for _, dep := range deps {
   185  		b.WriteString(string(dep))
   186  	}
   187  	for _, cgf := range pghs {
   188  		b.WriteString(cgf.file.FileIdentity().String())
   189  	}
   190  	return packageHandleKey(hashContents(b.Bytes()))
   191  }
   192  
   193  // hashEnv returns a hash of the snapshot's configuration.
   194  func hashEnv(s *snapshot) string {
   195  	s.view.optionsMu.Lock()
   196  	env := s.view.options.EnvSlice()
   197  	s.view.optionsMu.Unlock()
   198  
   199  	b := &bytes.Buffer{}
   200  	for _, e := range env {
   201  		b.WriteString(e)
   202  	}
   203  	return hashContents(b.Bytes())
   204  }
   205  
   206  // hashConfig returns the hash for the *packages.Config.
   207  func hashConfig(config *packages.Config) string {
   208  	b := bytes.NewBuffer(nil)
   209  
   210  	// Dir, Mode, Env, BuildFlags are the parts of the config that can change.
   211  	b.WriteString(config.Dir)
   212  	b.WriteString(string(rune(config.Mode)))
   213  
   214  	for _, e := range config.Env {
   215  		b.WriteString(e)
   216  	}
   217  	for _, f := range config.BuildFlags {
   218  		b.WriteString(f)
   219  	}
   220  	return hashContents(b.Bytes())
   221  }
   222  
   223  func (ph *packageHandle) Check(ctx context.Context, s source.Snapshot) (source.Package, error) {
   224  	return ph.check(ctx, s.(*snapshot))
   225  }
   226  
   227  func (ph *packageHandle) check(ctx context.Context, s *snapshot) (*pkg, error) {
   228  	v, err := ph.handle.Get(ctx, s.generation, s)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  	data := v.(*packageData)
   233  	return data.pkg, data.err
   234  }
   235  
   236  func (ph *packageHandle) CompiledGoFiles() []span.URI {
   237  	return ph.m.compiledGoFiles
   238  }
   239  
   240  func (ph *packageHandle) ID() string {
   241  	return string(ph.m.id)
   242  }
   243  
   244  func (ph *packageHandle) cached(g *memoize.Generation) (*pkg, error) {
   245  	v := ph.handle.Cached(g)
   246  	if v == nil {
   247  		return nil, errors.Errorf("no cached type information for %s", ph.m.pkgPath)
   248  	}
   249  	data := v.(*packageData)
   250  	return data.pkg, data.err
   251  }
   252  
   253  func (s *snapshot) parseGoHandles(ctx context.Context, files []span.URI, mode source.ParseMode) ([]*parseGoHandle, error) {
   254  	pghs := make([]*parseGoHandle, 0, len(files))
   255  	for _, uri := range files {
   256  		fh, err := s.GetFile(ctx, uri)
   257  		if err != nil {
   258  			return nil, err
   259  		}
   260  		pghs = append(pghs, s.parseGoHandle(ctx, fh, mode))
   261  	}
   262  	return pghs, nil
   263  }
   264  
   265  func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source.ParseMode, deps map[packagePath]*packageHandle) (*pkg, error) {
   266  	ctx, done := event.Start(ctx, "cache.importer.typeCheck", tag.Package.Of(string(m.id)))
   267  	defer done()
   268  
   269  	var rawErrors []error
   270  	for _, err := range m.errors {
   271  		rawErrors = append(rawErrors, err)
   272  	}
   273  
   274  	fset := snapshot.view.session.cache.fset
   275  	pkg := &pkg{
   276  		m:               m,
   277  		mode:            mode,
   278  		goFiles:         make([]*source.ParsedGoFile, len(m.goFiles)),
   279  		compiledGoFiles: make([]*source.ParsedGoFile, len(m.compiledGoFiles)),
   280  		imports:         make(map[packagePath]*pkg),
   281  		typesSizes:      m.typesSizes,
   282  		typesInfo: &types.Info{
   283  			Types:      make(map[ast.Expr]types.TypeAndValue),
   284  			Defs:       make(map[*ast.Ident]types.Object),
   285  			Uses:       make(map[*ast.Ident]types.Object),
   286  			Implicits:  make(map[ast.Node]types.Object),
   287  			Selections: make(map[*ast.SelectorExpr]*types.Selection),
   288  			Scopes:     make(map[ast.Node]*types.Scope),
   289  		},
   290  	}
   291  	// If this is a replaced module in the workspace, the version is
   292  	// meaningless, and we don't want clients to access it.
   293  	if m.module != nil {
   294  		version := m.module.Version
   295  		if source.IsWorkspaceModuleVersion(version) {
   296  			version = ""
   297  		}
   298  		pkg.version = &module.Version{
   299  			Path:    m.module.Path,
   300  			Version: version,
   301  		}
   302  	}
   303  	var (
   304  		files        = make([]*ast.File, len(m.compiledGoFiles))
   305  		parseErrors  = make([]error, len(m.compiledGoFiles))
   306  		actualErrors = make([]error, len(m.compiledGoFiles))
   307  		wg           sync.WaitGroup
   308  
   309  		mu             sync.Mutex
   310  		skipTypeErrors bool
   311  	)
   312  	for i, cgf := range m.compiledGoFiles {
   313  		wg.Add(1)
   314  		go func(i int, cgf span.URI) {
   315  			defer wg.Done()
   316  			fh, err := snapshot.GetFile(ctx, cgf)
   317  			if err != nil {
   318  				actualErrors[i] = err
   319  				return
   320  			}
   321  			pgh := snapshot.parseGoHandle(ctx, fh, mode)
   322  			pgf, fixed, err := snapshot.parseGo(ctx, pgh)
   323  			if err != nil {
   324  				actualErrors[i] = err
   325  				return
   326  			}
   327  			pkg.compiledGoFiles[i] = pgf
   328  			files[i], parseErrors[i], actualErrors[i] = pgf.File, pgf.ParseErr, err
   329  
   330  			mu.Lock()
   331  			skipTypeErrors = skipTypeErrors || fixed
   332  			mu.Unlock()
   333  		}(i, cgf)
   334  	}
   335  	for i, gf := range m.goFiles {
   336  		wg.Add(1)
   337  		// We need to parse the non-compiled go files, but we don't care about their errors.
   338  		go func(i int, gf span.URI) {
   339  			defer wg.Done()
   340  			fh, err := snapshot.GetFile(ctx, gf)
   341  			if err != nil {
   342  				return
   343  			}
   344  			pgf, _ := snapshot.ParseGo(ctx, fh, mode)
   345  			pkg.goFiles[i] = pgf
   346  		}(i, gf)
   347  	}
   348  	wg.Wait()
   349  	for _, err := range actualErrors {
   350  		if err != nil {
   351  			return nil, err
   352  		}
   353  	}
   354  
   355  	for _, e := range parseErrors {
   356  		if e != nil {
   357  			rawErrors = append(rawErrors, e)
   358  		}
   359  	}
   360  
   361  	var i int
   362  	for _, f := range files {
   363  		if f != nil {
   364  			files[i] = f
   365  			i++
   366  		}
   367  	}
   368  	files = files[:i]
   369  
   370  	// Use the default type information for the unsafe package.
   371  	if pkg.m.pkgPath == "unsafe" {
   372  		pkg.types = types.Unsafe
   373  		// Don't type check Unsafe: it's unnecessary, and doing so exposes a data
   374  		// race to Unsafe.completed.
   375  		return pkg, nil
   376  	} else if len(files) == 0 { // not the unsafe package, no parsed files
   377  		// Try to attach errors messages to the file as much as possible.
   378  		var found bool
   379  		for _, e := range rawErrors {
   380  			srcErr, err := sourceError(ctx, snapshot, pkg, e)
   381  			if err != nil {
   382  				continue
   383  			}
   384  			found = true
   385  			pkg.errors = append(pkg.errors, srcErr)
   386  		}
   387  		if found {
   388  			return pkg, nil
   389  		}
   390  		return nil, errors.Errorf("no parsed files for package %s, expected: %v, list errors: %v", pkg.m.pkgPath, pkg.compiledGoFiles, rawErrors)
   391  	} else {
   392  		pkg.types = types.NewPackage(string(m.pkgPath), string(m.name))
   393  	}
   394  
   395  	cfg := &types.Config{
   396  		Error: func(e error) {
   397  			// If we have fixed parse errors in any of the files,
   398  			// we should hide type errors, as they may be completely nonsensical.
   399  			if skipTypeErrors {
   400  				return
   401  			}
   402  			rawErrors = append(rawErrors, e)
   403  		},
   404  		Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
   405  			// If the context was cancelled, we should abort.
   406  			if ctx.Err() != nil {
   407  				return nil, ctx.Err()
   408  			}
   409  			dep := resolveImportPath(pkgPath, pkg, deps)
   410  			if dep == nil {
   411  				return nil, snapshot.missingPkgError(pkgPath)
   412  			}
   413  			if !isValidImport(m.pkgPath, dep.m.pkgPath) {
   414  				return nil, errors.Errorf("invalid use of internal package %s", pkgPath)
   415  			}
   416  			depPkg, err := dep.check(ctx, snapshot)
   417  			if err != nil {
   418  				return nil, err
   419  			}
   420  			pkg.imports[depPkg.m.pkgPath] = depPkg
   421  			return depPkg.types, nil
   422  		}),
   423  	}
   424  	// We want to type check cgo code if go/types supports it.
   425  	// We passed typecheckCgo to go/packages when we Loaded.
   426  	typesinternal.SetUsesCgo(cfg)
   427  
   428  	check := types.NewChecker(cfg, fset, pkg.types, pkg.typesInfo)
   429  
   430  	// Type checking errors are handled via the config, so ignore them here.
   431  	_ = check.Files(files)
   432  	// If the context was cancelled, we may have returned a ton of transient
   433  	// errors to the type checker. Swallow them.
   434  	if ctx.Err() != nil {
   435  		return nil, ctx.Err()
   436  	}
   437  
   438  	// We don't care about a package's errors unless we have parsed it in full.
   439  	if mode == source.ParseFull {
   440  		expandErrors(rawErrors)
   441  		for _, e := range rawErrors {
   442  			srcErr, err := sourceError(ctx, snapshot, pkg, e)
   443  			if err != nil {
   444  				event.Error(ctx, "unable to compute error positions", err, tag.Package.Of(pkg.ID()))
   445  				continue
   446  			}
   447  			pkg.errors = append(pkg.errors, srcErr)
   448  			if err, ok := e.(extendedError); ok {
   449  				pkg.typeErrors = append(pkg.typeErrors, err.primary)
   450  			}
   451  		}
   452  	}
   453  
   454  	return pkg, nil
   455  }
   456  
   457  // missingPkgError returns an error message for a missing package that varies
   458  // based on the user's workspace mode.
   459  func (s *snapshot) missingPkgError(pkgPath string) error {
   460  	if s.workspaceMode()&moduleMode != 0 {
   461  		return fmt.Errorf("no required module provides package %q", pkgPath)
   462  	}
   463  	gorootSrcPkg := filepath.FromSlash(filepath.Join(s.view.goroot, "src", pkgPath))
   464  
   465  	var b strings.Builder
   466  	b.WriteString(fmt.Sprintf("cannot find package %q in any of \n\t%s (from $GOROOT)", pkgPath, gorootSrcPkg))
   467  
   468  	for _, gopath := range strings.Split(s.view.gopath, ":") {
   469  		gopathSrcPkg := filepath.FromSlash(filepath.Join(gopath, "src", pkgPath))
   470  		b.WriteString(fmt.Sprintf("\n\t%s (from $GOPATH)", gopathSrcPkg))
   471  	}
   472  	return errors.New(b.String())
   473  }
   474  
   475  type extendedError struct {
   476  	primary     types.Error
   477  	secondaries []types.Error
   478  }
   479  
   480  func (e extendedError) Error() string {
   481  	return e.primary.Error()
   482  }
   483  
   484  // expandErrors duplicates "secondary" errors by mapping them to their main
   485  // error. Some errors returned by the type checker are followed by secondary
   486  // errors which give more information about the error. These are errors in
   487  // their own right, and they are marked by starting with \t. For instance, when
   488  // there is a multiply-defined function, the secondary error points back to the
   489  // definition first noticed.
   490  //
   491  // This code associates the secondary error with its primary error, which can
   492  // then be used as RelatedInformation when the error becomes a diagnostic.
   493  func expandErrors(errs []error) []error {
   494  	for i := 0; i < len(errs); {
   495  		e, ok := errs[i].(types.Error)
   496  		if !ok {
   497  			i++
   498  			continue
   499  		}
   500  		enew := extendedError{
   501  			primary: e,
   502  		}
   503  		j := i + 1
   504  		for ; j < len(errs); j++ {
   505  			spl, ok := errs[j].(types.Error)
   506  			if !ok || len(spl.Msg) == 0 || spl.Msg[0] != '\t' {
   507  				break
   508  			}
   509  			enew.secondaries = append(enew.secondaries, spl)
   510  		}
   511  		errs[i] = enew
   512  		i = j
   513  	}
   514  	return errs
   515  }
   516  
   517  // resolveImportPath resolves an import path in pkg to a package from deps.
   518  // It should produce the same results as resolveImportPath:
   519  // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/load/pkg.go;drc=641918ee09cb44d282a30ee8b66f99a0b63eaef9;l=990.
   520  func resolveImportPath(importPath string, pkg *pkg, deps map[packagePath]*packageHandle) *packageHandle {
   521  	if dep := deps[packagePath(importPath)]; dep != nil {
   522  		return dep
   523  	}
   524  	// We may be in GOPATH mode, in which case we need to check vendor dirs.
   525  	searchDir := path.Dir(pkg.PkgPath())
   526  	for {
   527  		vdir := packagePath(path.Join(searchDir, "vendor", importPath))
   528  		if vdep := deps[vdir]; vdep != nil {
   529  			return vdep
   530  		}
   531  
   532  		// Search until Dir doesn't take us anywhere new, e.g. "." or "/".
   533  		next := path.Dir(searchDir)
   534  		if searchDir == next {
   535  			break
   536  		}
   537  		searchDir = next
   538  	}
   539  
   540  	// Vendor didn't work. Let's try minimal module compatibility mode.
   541  	// In MMC, the packagePath is the canonical (.../vN/...) path, which
   542  	// is hard to calculate. But the go command has already resolved the ID
   543  	// to the non-versioned path, and we can take advantage of that.
   544  	for _, dep := range deps {
   545  		if dep.ID() == importPath {
   546  			return dep
   547  		}
   548  	}
   549  	return nil
   550  }
   551  
   552  func isValidImport(pkgPath, importPkgPath packagePath) bool {
   553  	i := strings.LastIndex(string(importPkgPath), "/internal/")
   554  	if i == -1 {
   555  		return true
   556  	}
   557  	if pkgPath == "command-line-arguments" {
   558  		return true
   559  	}
   560  	return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
   561  }
   562  
   563  // An importFunc is an implementation of the single-method
   564  // types.Importer interface based on a function value.
   565  type importerFunc func(path string) (*types.Package, error)
   566  
   567  func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }