cuelang.org/go@v0.10.1/internal/golangorgx/gopls/cache/session.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  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"sync/atomic"
    18  	"time"
    19  
    20  	"cuelang.org/go/internal/golangorgx/gopls/cache/metadata"
    21  	"cuelang.org/go/internal/golangorgx/gopls/cache/typerefs"
    22  	"cuelang.org/go/internal/golangorgx/gopls/file"
    23  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    24  	"cuelang.org/go/internal/golangorgx/gopls/util/bug"
    25  	"cuelang.org/go/internal/golangorgx/gopls/util/persistent"
    26  	"cuelang.org/go/internal/golangorgx/gopls/util/slices"
    27  	"cuelang.org/go/internal/golangorgx/tools/event"
    28  	"cuelang.org/go/internal/golangorgx/tools/gocommand"
    29  	"cuelang.org/go/internal/golangorgx/tools/imports"
    30  	"cuelang.org/go/internal/golangorgx/tools/memoize"
    31  	"cuelang.org/go/internal/golangorgx/tools/xcontext"
    32  )
    33  
    34  // NewSession creates a new gopls session with the given cache.
    35  func NewSession(ctx context.Context, c *Cache) *Session {
    36  	index := atomic.AddInt64(&sessionIndex, 1)
    37  	s := &Session{
    38  		id:          strconv.FormatInt(index, 10),
    39  		cache:       c,
    40  		gocmdRunner: &gocommand.Runner{},
    41  		overlayFS:   newOverlayFS(c),
    42  		parseCache:  newParseCache(1 * time.Minute), // keep recently parsed files for a minute, to optimize typing CPU
    43  		viewMap:     make(map[protocol.DocumentURI]*View),
    44  	}
    45  	event.Log(ctx, "New session", KeyCreateSession.Of(s))
    46  	return s
    47  }
    48  
    49  // A Session holds the state (views, file contents, parse cache,
    50  // memoized computations) of a gopls server process.
    51  //
    52  // It implements the file.Source interface.
    53  type Session struct {
    54  	// Unique identifier for this session.
    55  	id string
    56  
    57  	// Immutable attributes shared across views.
    58  	cache       *Cache            // shared cache
    59  	gocmdRunner *gocommand.Runner // limits go command concurrency
    60  
    61  	viewMu  sync.Mutex
    62  	views   []*View
    63  	viewMap map[protocol.DocumentURI]*View // file->best view; nil after shutdown
    64  
    65  	// snapshots is a counting semaphore that records the number
    66  	// of unreleased snapshots associated with this session.
    67  	// Shutdown waits for it to fall to zero.
    68  	snapshotWG sync.WaitGroup
    69  
    70  	parseCache *parseCache
    71  
    72  	*overlayFS
    73  }
    74  
    75  // ID returns the unique identifier for this session on this server.
    76  func (s *Session) ID() string     { return s.id }
    77  func (s *Session) String() string { return s.id }
    78  
    79  // GoCommandRunner returns the gocommand Runner for this session.
    80  func (s *Session) GoCommandRunner() *gocommand.Runner {
    81  	return s.gocmdRunner
    82  }
    83  
    84  // Shutdown the session and all views it has created.
    85  func (s *Session) Shutdown(ctx context.Context) {
    86  	var views []*View
    87  	s.viewMu.Lock()
    88  	views = append(views, s.views...)
    89  	s.views = nil
    90  	s.viewMap = nil
    91  	s.viewMu.Unlock()
    92  	for _, view := range views {
    93  		view.shutdown()
    94  	}
    95  	s.parseCache.stop()
    96  	s.snapshotWG.Wait() // wait for all work on associated snapshots to finish
    97  	event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s))
    98  }
    99  
   100  // Cache returns the cache that created this session, for debugging only.
   101  func (s *Session) Cache() *Cache {
   102  	return s.cache
   103  }
   104  
   105  // TODO(rfindley): is the logic surrounding this error actually necessary?
   106  var ErrViewExists = errors.New("view already exists for session")
   107  
   108  // NewView creates a new View, returning it and its first snapshot. If a
   109  // non-empty tempWorkspace directory is provided, the View will record a copy
   110  // of its gopls workspace module in that directory, so that client tooling
   111  // can execute in the same main module.  On success it also returns a release
   112  // function that must be called when the Snapshot is no longer needed.
   113  func (s *Session) NewView(ctx context.Context, folder *Folder) (*View, *Snapshot, func(), error) {
   114  	s.viewMu.Lock()
   115  	defer s.viewMu.Unlock()
   116  
   117  	// Querying the file system to check whether
   118  	// two folders denote the same existing directory.
   119  	if inode1, err := os.Stat(filepath.FromSlash(folder.Dir.Path())); err == nil {
   120  		for _, view := range s.views {
   121  			inode2, err := os.Stat(filepath.FromSlash(view.folder.Dir.Path()))
   122  			if err == nil && os.SameFile(inode1, inode2) {
   123  				return nil, nil, nil, ErrViewExists
   124  			}
   125  		}
   126  	}
   127  
   128  	def, err := defineView(ctx, s, folder, nil)
   129  	if err != nil {
   130  		return nil, nil, nil, err
   131  	}
   132  	view, snapshot, release := s.createView(ctx, def)
   133  	s.views = append(s.views, view)
   134  	// we always need to drop the view map
   135  	s.viewMap = make(map[protocol.DocumentURI]*View)
   136  	return view, snapshot, release, nil
   137  }
   138  
   139  // createView creates a new view, with an initial snapshot that retains the
   140  // supplied context, detached from events and cancelation.
   141  //
   142  // The caller is responsible for calling the release function once.
   143  func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *Snapshot, func()) {
   144  	index := atomic.AddInt64(&viewIndex, 1)
   145  
   146  	// We want a true background context and not a detached context here
   147  	// the spans need to be unrelated and no tag values should pollute it.
   148  	baseCtx := event.Detach(xcontext.Detach(ctx))
   149  	backgroundCtx, cancel := context.WithCancel(baseCtx)
   150  
   151  	// Compute a skip function to use for module cache scanning.
   152  	//
   153  	// Note that unlike other filtering operations, we definitely don't want to
   154  	// exclude the gomodcache here, even if it is contained in the workspace
   155  	// folder.
   156  	//
   157  	// TODO(rfindley): consolidate with relPathExcludedByFilter(Func), Filterer,
   158  	// View.filterFunc.
   159  	var skipPath func(string) bool
   160  	{
   161  		// Compute a prefix match, respecting segment boundaries, by ensuring
   162  		// the pattern (dir) has a trailing slash.
   163  		dirPrefix := strings.TrimSuffix(string(def.folder.Dir), "/") + "/"
   164  		filterer := NewFilterer(def.folder.Options.DirectoryFilters)
   165  		skipPath = func(dir string) bool {
   166  			uri := strings.TrimSuffix(string(protocol.URIFromPath(dir)), "/")
   167  			// Note that the logic below doesn't handle the case where uri ==
   168  			// v.folder.Dir, because there is no point in excluding the entire
   169  			// workspace folder!
   170  			if rel := strings.TrimPrefix(uri, dirPrefix); rel != uri {
   171  				return filterer.Disallow(rel)
   172  			}
   173  			return false
   174  		}
   175  	}
   176  
   177  	var ignoreFilter *ignoreFilter
   178  	{
   179  		var dirs []string
   180  		if len(def.workspaceModFiles) == 0 {
   181  			for _, entry := range filepath.SplitList(def.folder.Env.GOPATH) {
   182  				dirs = append(dirs, filepath.Join(entry, "src"))
   183  			}
   184  		} else {
   185  			dirs = append(dirs, def.folder.Env.GOMODCACHE)
   186  			for m := range def.workspaceModFiles {
   187  				dirs = append(dirs, filepath.Dir(m.Path()))
   188  			}
   189  		}
   190  		ignoreFilter = newIgnoreFilter(dirs)
   191  	}
   192  
   193  	var pe *imports.ProcessEnv
   194  	{
   195  		env := make(map[string]string)
   196  		envSlice := slices.Concat(os.Environ(), def.folder.Options.EnvSlice(), []string{"GO111MODULE=" + def.adjustedGO111MODULE()})
   197  		for _, kv := range envSlice {
   198  			if k, v, ok := strings.Cut(kv, "="); ok {
   199  				env[k] = v
   200  			}
   201  		}
   202  		pe = &imports.ProcessEnv{
   203  			GocmdRunner: s.gocmdRunner,
   204  			BuildFlags:  slices.Clone(def.folder.Options.BuildFlags),
   205  			// TODO(rfindley): an old comment said "processEnv operations should not mutate the modfile"
   206  			// But shouldn't we honor the default behavior of mod vendoring?
   207  			ModFlag:        "readonly",
   208  			SkipPathInScan: skipPath,
   209  			Env:            env,
   210  			WorkingDir:     def.root.Path(),
   211  			ModCache:       s.cache.modCache.dirCache(def.folder.Env.GOMODCACHE),
   212  		}
   213  		if def.folder.Options.VerboseOutput {
   214  			pe.Logf = func(format string, args ...interface{}) {
   215  				event.Log(ctx, fmt.Sprintf(format, args...))
   216  			}
   217  		}
   218  	}
   219  
   220  	v := &View{
   221  		id:                   strconv.FormatInt(index, 10),
   222  		gocmdRunner:          s.gocmdRunner,
   223  		initialWorkspaceLoad: make(chan struct{}),
   224  		initializationSema:   make(chan struct{}, 1),
   225  		baseCtx:              baseCtx,
   226  		parseCache:           s.parseCache,
   227  		ignoreFilter:         ignoreFilter,
   228  		fs:                   s.overlayFS,
   229  		viewDefinition:       def,
   230  		importsState:         newImportsState(backgroundCtx, s.cache.modCache, pe),
   231  	}
   232  
   233  	s.snapshotWG.Add(1)
   234  	v.snapshot = &Snapshot{
   235  		view:             v,
   236  		backgroundCtx:    backgroundCtx,
   237  		cancel:           cancel,
   238  		store:            s.cache.store,
   239  		refcount:         1, // Snapshots are born referenced.
   240  		done:             s.snapshotWG.Done,
   241  		packages:         new(persistent.Map[PackageID, *packageHandle]),
   242  		meta:             new(metadata.Graph),
   243  		files:            newFileMap(),
   244  		activePackages:   new(persistent.Map[PackageID, *Package]),
   245  		symbolizeHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]),
   246  		shouldLoad:       new(persistent.Map[PackageID, []PackagePath]),
   247  		unloadableFiles:  new(persistent.Set[protocol.DocumentURI]),
   248  		parseModHandles:  new(persistent.Map[protocol.DocumentURI, *memoize.Promise]),
   249  		parseWorkHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]),
   250  		modTidyHandles:   new(persistent.Map[protocol.DocumentURI, *memoize.Promise]),
   251  		modWhyHandles:    new(persistent.Map[protocol.DocumentURI, *memoize.Promise]),
   252  		pkgIndex:         typerefs.NewPackageIndex(),
   253  		moduleUpgrades:   new(persistent.Map[protocol.DocumentURI, map[string]string]),
   254  	}
   255  
   256  	// Snapshots must observe all open files, as there are some caching
   257  	// heuristics that change behavior depending on open files.
   258  	for _, o := range s.overlayFS.Overlays() {
   259  		_, _ = v.snapshot.ReadFile(ctx, o.URI())
   260  	}
   261  
   262  	// Record the environment of the newly created view in the log.
   263  	event.Log(ctx, viewEnv(v))
   264  
   265  	// Initialize the view without blocking.
   266  	initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
   267  	v.cancelInitialWorkspaceLoad = initCancel
   268  	snapshot := v.snapshot
   269  
   270  	// Pass a second reference to the background goroutine.
   271  	bgRelease := snapshot.Acquire()
   272  	go func() {
   273  		defer bgRelease()
   274  		snapshot.initialize(initCtx, true)
   275  	}()
   276  
   277  	// Return a third reference to the caller.
   278  	return v, snapshot, snapshot.Acquire()
   279  }
   280  
   281  // RemoveView removes from the session the view rooted at the specified directory.
   282  // It reports whether a view of that directory was removed.
   283  func (s *Session) RemoveView(dir protocol.DocumentURI) bool {
   284  	s.viewMu.Lock()
   285  	defer s.viewMu.Unlock()
   286  	for _, view := range s.views {
   287  		if view.folder.Dir == dir {
   288  			i := s.dropView(view)
   289  			if i == -1 {
   290  				return false // can't happen
   291  			}
   292  			// delete this view... we don't care about order but we do want to make
   293  			// sure we can garbage collect the view
   294  			s.views = removeElement(s.views, i)
   295  			return true
   296  		}
   297  	}
   298  	return false
   299  }
   300  
   301  // View returns the view with a matching id, if present.
   302  func (s *Session) View(id string) (*View, error) {
   303  	s.viewMu.Lock()
   304  	defer s.viewMu.Unlock()
   305  	for _, view := range s.views {
   306  		if view.ID() == id {
   307  			return view, nil
   308  		}
   309  	}
   310  	return nil, fmt.Errorf("no view with ID %q", id)
   311  }
   312  
   313  // SnapshotOf returns a Snapshot corresponding to the given URI.
   314  //
   315  // In the case where the file can be  can be associated with a View by
   316  // bestViewForURI (based on directory information alone, without package
   317  // metadata), SnapshotOf returns the current Snapshot for that View. Otherwise,
   318  // it awaits loading package metadata and returns a Snapshot for the first View
   319  // containing a real (=not command-line-arguments) package for the file.
   320  //
   321  // If that also fails to find a View, SnapshotOf returns a Snapshot for the
   322  // first view in s.views that is not shut down (i.e. s.views[0] unless we lose
   323  // a race), for determinism in tests and so that we tend to aggregate the
   324  // resulting command-line-arguments packages into a single view.
   325  //
   326  // SnapshotOf returns an error if a failure occurs along the way (most likely due
   327  // to context cancellation), or if there are no Views in the Session.
   328  //
   329  // On success, the caller must call the returned function to release the snapshot.
   330  func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Snapshot, func(), error) {
   331  	// Fast path: if the uri has a static association with a view, return it.
   332  	s.viewMu.Lock()
   333  	v, err := s.viewOfLocked(ctx, uri)
   334  	s.viewMu.Unlock()
   335  
   336  	if err != nil {
   337  		return nil, nil, err
   338  	}
   339  
   340  	if v != nil {
   341  		snapshot, release, err := v.Snapshot()
   342  		if err == nil {
   343  			return snapshot, release, nil
   344  		}
   345  		// View is shut down. Forget this association.
   346  		s.viewMu.Lock()
   347  		if s.viewMap[uri] == v {
   348  			delete(s.viewMap, uri)
   349  		}
   350  		s.viewMu.Unlock()
   351  	}
   352  
   353  	// Fall-back: none of the views could be associated with uri based on
   354  	// directory information alone.
   355  	//
   356  	// Don't memoize the view association in viewMap, as it is not static: Views
   357  	// may change as metadata changes.
   358  	//
   359  	// TODO(rfindley): we could perhaps optimize this case by peeking at existing
   360  	// metadata before awaiting the load (after all, a load only adds metadata).
   361  	// But that seems potentially tricky, when in the common case no loading
   362  	// should be required.
   363  	views := s.Views()
   364  	for _, v := range views {
   365  		snapshot, release, err := v.Snapshot()
   366  		if err != nil {
   367  			continue // view was shut down
   368  		}
   369  		_ = snapshot.awaitLoaded(ctx) // ignore error
   370  		g := snapshot.MetadataGraph()
   371  		// We don't check the error from awaitLoaded, because a load failure (that
   372  		// doesn't result from context cancelation) should not prevent us from
   373  		// continuing to search for the best view.
   374  		if ctx.Err() != nil {
   375  			release()
   376  			return nil, nil, ctx.Err()
   377  		}
   378  		// Special handling for the builtin file, since it doesn't have packages.
   379  		if snapshot.IsBuiltin(uri) {
   380  			return snapshot, release, nil
   381  		}
   382  		// Only match this view if it loaded a real package for the file.
   383  		//
   384  		// Any view can load a command-line-arguments package; aggregate those into
   385  		// views[0] below.
   386  		for _, id := range g.IDs[uri] {
   387  			if !metadata.IsCommandLineArguments(id) || g.Packages[id].Standalone {
   388  				return snapshot, release, nil
   389  			}
   390  		}
   391  		release()
   392  	}
   393  
   394  	for _, v := range views {
   395  		snapshot, release, err := v.Snapshot()
   396  		if err == nil {
   397  			return snapshot, release, nil // first valid snapshot
   398  		}
   399  	}
   400  	return nil, nil, errNoViews
   401  }
   402  
   403  // errNoViews is sought by orphaned file diagnostics, to detect the case where
   404  // we have no view containing a file.
   405  var errNoViews = errors.New("no views")
   406  
   407  // viewOfLocked wraps bestViewForURI, memoizing its result.
   408  //
   409  // Precondition: caller holds s.viewMu lock.
   410  //
   411  // May return (nil, nil).
   412  func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*View, error) {
   413  	v, hit := s.viewMap[uri]
   414  	if !hit {
   415  		// Cache miss: compute (and memoize) the best view.
   416  		fh, err := s.ReadFile(ctx, uri)
   417  		if err != nil {
   418  			return nil, err
   419  		}
   420  		v, err = bestView(ctx, s, fh, s.views)
   421  		if err != nil {
   422  			return nil, err
   423  		}
   424  		if s.viewMap == nil {
   425  			return nil, errors.New("session is shut down")
   426  		}
   427  		s.viewMap[uri] = v
   428  	}
   429  	return v, nil
   430  }
   431  
   432  func (s *Session) Views() []*View {
   433  	s.viewMu.Lock()
   434  	defer s.viewMu.Unlock()
   435  	result := make([]*View, len(s.views))
   436  	copy(result, s.views)
   437  	return result
   438  }
   439  
   440  // selectViewDefs constructs the best set of views covering the provided workspace
   441  // folders and open files.
   442  //
   443  // This implements the zero-config algorithm of golang/go#57979.
   444  func selectViewDefs(ctx context.Context, fs file.Source, folders []*Folder, openFiles []protocol.DocumentURI) ([]*viewDefinition, error) {
   445  	var defs []*viewDefinition
   446  
   447  	// First, compute a default view for each workspace folder.
   448  	// TODO(golang/go#57979): technically, this is path dependent, since
   449  	// DidChangeWorkspaceFolders could introduce a path-dependent ordering on
   450  	// folders. We should keep folders sorted, or sort them here.
   451  	for _, folder := range folders {
   452  		def, err := defineView(ctx, fs, folder, nil)
   453  		if err != nil {
   454  			return nil, err
   455  		}
   456  		defs = append(defs, def)
   457  	}
   458  
   459  	// Next, ensure that the set of views covers all open files contained in a
   460  	// workspace folder.
   461  	//
   462  	// We only do this for files contained in a workspace folder, because other
   463  	// open files are most likely the result of jumping to a definition from a
   464  	// workspace file; we don't want to create additional views in those cases:
   465  	// they should be resolved after initialization.
   466  
   467  	folderForFile := func(uri protocol.DocumentURI) *Folder {
   468  		var longest *Folder
   469  		for _, folder := range folders {
   470  			if (longest == nil || len(folder.Dir) > len(longest.Dir)) && folder.Dir.Encloses(uri) {
   471  				longest = folder
   472  			}
   473  		}
   474  		return longest
   475  	}
   476  
   477  checkFiles:
   478  	for _, uri := range openFiles {
   479  		folder := folderForFile(uri)
   480  		if folder == nil || !folder.Options.ZeroConfig {
   481  			continue // only guess views for open files
   482  		}
   483  		fh, err := fs.ReadFile(ctx, uri)
   484  		if err != nil {
   485  			return nil, err
   486  		}
   487  		def, err := bestView(ctx, fs, fh, defs)
   488  		if err != nil {
   489  			// We should never call selectViewDefs with a cancellable context, so
   490  			// this should never fail.
   491  			return nil, bug.Errorf("failed to find best view for open file: %v", err)
   492  		}
   493  		if def != nil {
   494  			continue // file covered by an existing view
   495  		}
   496  		def, err = defineView(ctx, fs, folder, fh)
   497  		if err != nil {
   498  			// We should never call selectViewDefs with a cancellable context, so
   499  			// this should never fail.
   500  			return nil, bug.Errorf("failed to define view for open file: %v", err)
   501  		}
   502  		// It need not strictly be the case that the best view for a file is
   503  		// distinct from other views, as the logic of getViewDefinition and
   504  		// bestViewForURI does not align perfectly. This is not necessarily a bug:
   505  		// there may be files for which we can't construct a valid view.
   506  		//
   507  		// Nevertheless, we should not create redundant views.
   508  		for _, alt := range defs {
   509  			if viewDefinitionsEqual(alt, def) {
   510  				continue checkFiles
   511  			}
   512  		}
   513  		defs = append(defs, def)
   514  	}
   515  
   516  	return defs, nil
   517  }
   518  
   519  // The viewDefiner interface allows the bestView algorithm to operate on both
   520  // Views and viewDefinitions.
   521  type viewDefiner interface{ definition() *viewDefinition }
   522  
   523  // bestView returns the best View or viewDefinition that contains the
   524  // given file, or (nil, nil) if no matching view is found.
   525  //
   526  // bestView only returns an error in the event of context cancellation.
   527  //
   528  // Making this function generic is convenient so that we can avoid mapping view
   529  // definitions back to views inside Session.DidModifyFiles, where performance
   530  // matters. It is, however, not the cleanest application of generics.
   531  //
   532  // Note: keep this function in sync with defineView.
   533  func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle, views []V) (V, error) {
   534  	var zero V
   535  
   536  	if len(views) == 0 {
   537  		return zero, nil // avoid the call to findRootPattern
   538  	}
   539  	uri := fh.URI()
   540  	dir := uri.Dir()
   541  	modURI, err := findRootPattern(ctx, dir, "go.mod", fs)
   542  	if err != nil {
   543  		return zero, err
   544  	}
   545  
   546  	// Prefer GoWork > GoMod > GOPATH > GoPackages > AdHoc.
   547  	var (
   548  		goPackagesViews []V // prefer longest
   549  		workViews       []V // prefer longest
   550  		modViews        []V // exact match
   551  		gopathViews     []V // prefer longest
   552  		adHocViews      []V // exact match
   553  	)
   554  
   555  	// pushView updates the views slice with the matching view v, using the
   556  	// heuristic that views with a longer root are preferable. Accordingly,
   557  	// pushView may be a no op if v's root is shorter than the roots in the views
   558  	// slice.
   559  	//
   560  	// Invariant: the length of all roots in views is the same.
   561  	pushView := func(views *[]V, v V) {
   562  		if len(*views) == 0 {
   563  			*views = []V{v}
   564  			return
   565  		}
   566  		better := func(l, r V) bool {
   567  			return len(l.definition().root) > len(r.definition().root)
   568  		}
   569  		existing := (*views)[0]
   570  		switch {
   571  		case better(existing, v):
   572  		case better(v, existing):
   573  			*views = []V{v}
   574  		default:
   575  			*views = append(*views, v)
   576  		}
   577  	}
   578  
   579  	for _, view := range views {
   580  		switch def := view.definition(); def.Type() {
   581  		case GoPackagesDriverView:
   582  			if def.root.Encloses(dir) {
   583  				pushView(&goPackagesViews, view)
   584  			}
   585  		case GoWorkView:
   586  			if _, ok := def.workspaceModFiles[modURI]; ok || uri == def.gowork {
   587  				pushView(&workViews, view)
   588  			}
   589  		case GoModView:
   590  			if _, ok := def.workspaceModFiles[modURI]; ok {
   591  				modViews = append(modViews, view)
   592  			}
   593  		case GOPATHView:
   594  			if def.root.Encloses(dir) {
   595  				pushView(&gopathViews, view)
   596  			}
   597  		case AdHocView:
   598  			if def.root == dir {
   599  				adHocViews = append(adHocViews, view)
   600  			}
   601  		}
   602  	}
   603  
   604  	// Now that we've collected matching views, choose the best match,
   605  	// considering ports.
   606  	//
   607  	// We only consider one type of view, since the matching view created by
   608  	// defineView should be of the best type.
   609  	var bestViews []V
   610  	switch {
   611  	case len(workViews) > 0:
   612  		bestViews = workViews
   613  	case len(modViews) > 0:
   614  		bestViews = modViews
   615  	case len(gopathViews) > 0:
   616  		bestViews = gopathViews
   617  	case len(goPackagesViews) > 0:
   618  		bestViews = goPackagesViews
   619  	case len(adHocViews) > 0:
   620  		bestViews = adHocViews
   621  	default:
   622  		return zero, nil
   623  	}
   624  
   625  	content, err := fh.Content()
   626  	// Port matching doesn't apply to non-go files, or files that no longer exist.
   627  	// Note that the behavior here on non-existent files shouldn't matter much,
   628  	// since there will be a subsequent failure. But it is simpler to preserve
   629  	// the invariant that bestView only fails on context cancellation.
   630  	if fileKind(fh) != file.Go || err != nil {
   631  		return bestViews[0], nil
   632  	}
   633  
   634  	// Find the first view that matches constraints.
   635  	// Content trimming is nontrivial, so do this outside of the loop below.
   636  	path := fh.URI().Path()
   637  	content = trimContentForPortMatch(content)
   638  	for _, v := range bestViews {
   639  		def := v.definition()
   640  		viewPort := port{def.GOOS(), def.GOARCH()}
   641  		if viewPort.matches(path, content) {
   642  			return v, nil
   643  		}
   644  	}
   645  
   646  	return zero, nil // no view found
   647  }
   648  
   649  // updateViewLocked recreates the view with the given options.
   650  //
   651  // If the resulting error is non-nil, the view may or may not have already been
   652  // dropped from the session.
   653  func (s *Session) updateViewLocked(ctx context.Context, view *View, def *viewDefinition) (*View, error) {
   654  	i := s.dropView(view)
   655  	if i == -1 {
   656  		return nil, fmt.Errorf("view %q not found", view.id)
   657  	}
   658  
   659  	view, _, release := s.createView(ctx, def)
   660  	defer release()
   661  
   662  	// substitute the new view into the array where the old view was
   663  	s.views[i] = view
   664  	s.viewMap = make(map[protocol.DocumentURI]*View)
   665  	return view, nil
   666  }
   667  
   668  // removeElement removes the ith element from the slice replacing it with the last element.
   669  // TODO(adonovan): generics, someday.
   670  func removeElement(slice []*View, index int) []*View {
   671  	last := len(slice) - 1
   672  	slice[index] = slice[last]
   673  	slice[last] = nil // aid GC
   674  	return slice[:last]
   675  }
   676  
   677  // dropView removes v from the set of views for the receiver s and calls
   678  // v.shutdown, returning the index of v in s.views (if found), or -1 if v was
   679  // not found. s.viewMu must be held while calling this function.
   680  func (s *Session) dropView(v *View) int {
   681  	// we always need to drop the view map
   682  	s.viewMap = make(map[protocol.DocumentURI]*View)
   683  	for i := range s.views {
   684  		if v == s.views[i] {
   685  			// we found the view, drop it and return the index it was found at
   686  			s.views[i] = nil
   687  			v.shutdown()
   688  			return i
   689  		}
   690  	}
   691  	// TODO(rfindley): it looks wrong that we don't shutdown v in this codepath.
   692  	// We should never get here.
   693  	bug.Reportf("tried to drop nonexistent view %q", v.id)
   694  	return -1
   695  }
   696  
   697  // ResetView resets the best view for the given URI.
   698  func (s *Session) ResetView(ctx context.Context, uri protocol.DocumentURI) (*View, error) {
   699  	s.viewMu.Lock()
   700  	defer s.viewMu.Unlock()
   701  	v, err := s.viewOfLocked(ctx, uri)
   702  	if err != nil {
   703  		return nil, err
   704  	}
   705  	return s.updateViewLocked(ctx, v, v.viewDefinition)
   706  }
   707  
   708  // DidModifyFiles reports a file modification to the session. It returns
   709  // the new snapshots after the modifications have been applied, paired with
   710  // the affected file URIs for those snapshots.
   711  // On success, it returns a release function that
   712  // must be called when the snapshots are no longer needed.
   713  //
   714  // TODO(rfindley): what happens if this function fails? It must leave us in a
   715  // broken state, which we should surface to the user, probably as a request to
   716  // restart gopls.
   717  func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modification) (map[*View][]protocol.DocumentURI, error) {
   718  	s.viewMu.Lock()
   719  	defer s.viewMu.Unlock()
   720  
   721  	// Update overlays.
   722  	//
   723  	// This is done while holding viewMu because the set of open files affects
   724  	// the set of views, and to prevent views from seeing updated file content
   725  	// before they have processed invalidations.
   726  	replaced, err := s.updateOverlays(ctx, modifications)
   727  	if err != nil {
   728  		return nil, err
   729  	}
   730  
   731  	// checkViews controls whether the set of views needs to be recomputed, for
   732  	// example because a go.mod file was created or deleted, or a go.work file
   733  	// changed on disk.
   734  	checkViews := false
   735  
   736  	changed := make(map[protocol.DocumentURI]file.Handle)
   737  	for _, c := range modifications {
   738  		fh := mustReadFile(ctx, s, c.URI)
   739  		changed[c.URI] = fh
   740  
   741  		// Any change to the set of open files causes views to be recomputed.
   742  		if c.Action == file.Open || c.Action == file.Close {
   743  			checkViews = true
   744  		}
   745  
   746  		// Any on-disk change to a go.work or go.mod file causes recomputing views.
   747  		//
   748  		// TODO(rfindley): go.work files need not be named "go.work" -- we need to
   749  		// check each view's source to handle the case of an explicit GOWORK value.
   750  		// Write a test that fails, and fix this.
   751  		if (isGoWork(c.URI) || isGoMod(c.URI)) && (c.Action == file.Save || c.OnDisk) {
   752  			checkViews = true
   753  		}
   754  
   755  		// Any change to the set of supported ports in a file may affect view
   756  		// selection. This is perhaps more subtle than it first seems: since the
   757  		// algorithm for selecting views considers open files in a deterministic
   758  		// order, a change in supported ports may cause a different port to be
   759  		// chosen, even if all open files still match an existing View!
   760  		//
   761  		// We endeavor to avoid that sort of path dependence, so must re-run the
   762  		// view selection algorithm whenever any input changes.
   763  		//
   764  		// However, extracting the build comment is nontrivial, so we don't want to
   765  		// pay this cost when e.g. processing a bunch of on-disk changes due to a
   766  		// branch change. Be careful to only do this if both files are open Go
   767  		// files.
   768  		if old, ok := replaced[c.URI]; ok && !checkViews && fileKind(fh) == file.Go {
   769  			if new, ok := fh.(*overlay); ok {
   770  				if buildComment(old.content) != buildComment(new.content) {
   771  					checkViews = true
   772  				}
   773  			}
   774  		}
   775  	}
   776  
   777  	if checkViews {
   778  		// Hack: collect folders from existing views.
   779  		// TODO(golang/go#57979): we really should track folders independent of
   780  		// Views, but since we always have a default View for each folder, this
   781  		// works for now.
   782  		var folders []*Folder // preserve folder order
   783  		seen := make(map[*Folder]unit)
   784  		for _, v := range s.views {
   785  			if _, ok := seen[v.folder]; ok {
   786  				continue
   787  			}
   788  			seen[v.folder] = unit{}
   789  			folders = append(folders, v.folder)
   790  		}
   791  
   792  		var openFiles []protocol.DocumentURI
   793  		for _, o := range s.Overlays() {
   794  			openFiles = append(openFiles, o.URI())
   795  		}
   796  		// Sort for determinism.
   797  		sort.Slice(openFiles, func(i, j int) bool {
   798  			return openFiles[i] < openFiles[j]
   799  		})
   800  
   801  		// TODO(rfindley): can we avoid running the go command (go env)
   802  		// synchronously to change processing? Can we assume that the env did not
   803  		// change, and derive go.work using a combination of the configured
   804  		// GOWORK value and filesystem?
   805  		defs, err := selectViewDefs(ctx, s, folders, openFiles)
   806  		if err != nil {
   807  			// Catastrophic failure, equivalent to a failure of session
   808  			// initialization and therefore should almost never happen. One
   809  			// scenario where this failure mode could occur is if some file
   810  			// permissions have changed preventing us from reading go.mod
   811  			// files.
   812  			//
   813  			// TODO(rfindley): consider surfacing this error more loudly. We
   814  			// could report a bug, but it's not really a bug.
   815  			event.Error(ctx, "selecting new views", err)
   816  		} else {
   817  			kept := make(map[*View]unit)
   818  			var newViews []*View
   819  			for _, def := range defs {
   820  				var newView *View
   821  				// Reuse existing view?
   822  				for _, v := range s.views {
   823  					if viewDefinitionsEqual(def, v.viewDefinition) {
   824  						newView = v
   825  						kept[v] = unit{}
   826  						break
   827  					}
   828  				}
   829  				if newView == nil {
   830  					v, _, release := s.createView(ctx, def)
   831  					release()
   832  					newView = v
   833  				}
   834  				newViews = append(newViews, newView)
   835  			}
   836  			for _, v := range s.views {
   837  				if _, ok := kept[v]; !ok {
   838  					v.shutdown()
   839  				}
   840  			}
   841  			s.views = newViews
   842  			s.viewMap = make(map[protocol.DocumentURI]*View)
   843  		}
   844  	}
   845  
   846  	// We only want to run fast-path diagnostics (i.e. diagnoseChangedFiles) once
   847  	// for each changed file, in its best view.
   848  	viewsToDiagnose := map[*View][]protocol.DocumentURI{}
   849  	for _, mod := range modifications {
   850  		v, err := s.viewOfLocked(ctx, mod.URI)
   851  		if err != nil {
   852  			// bestViewForURI only returns an error in the event of context
   853  			// cancellation. Since state changes should occur on an uncancellable
   854  			// context, an error here is a bug.
   855  			bug.Reportf("finding best view for change: %v", err)
   856  			continue
   857  		}
   858  		if v != nil {
   859  			viewsToDiagnose[v] = append(viewsToDiagnose[v], mod.URI)
   860  		}
   861  	}
   862  
   863  	// ...but changes may be relevant to other views, for example if they are
   864  	// changes to a shared package.
   865  	for _, v := range s.views {
   866  		_, release, needsDiagnosis := s.invalidateViewLocked(ctx, v, StateChange{Modifications: modifications, Files: changed})
   867  		release()
   868  
   869  		if needsDiagnosis || checkViews {
   870  			if _, ok := viewsToDiagnose[v]; !ok {
   871  				viewsToDiagnose[v] = nil
   872  			}
   873  		}
   874  	}
   875  
   876  	return viewsToDiagnose, nil
   877  }
   878  
   879  // ExpandModificationsToDirectories returns the set of changes with the
   880  // directory changes removed and expanded to include all of the files in
   881  // the directory.
   882  func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []file.Modification) []file.Modification {
   883  	var snapshots []*Snapshot
   884  	s.viewMu.Lock()
   885  	for _, v := range s.views {
   886  		snapshot, release, err := v.Snapshot()
   887  		if err != nil {
   888  			continue // view is shut down; continue with others
   889  		}
   890  		defer release()
   891  		snapshots = append(snapshots, snapshot)
   892  	}
   893  	s.viewMu.Unlock()
   894  
   895  	// Expand the modification to any file we could care about, which we define
   896  	// to be any file observed by any of the snapshots.
   897  	//
   898  	// There may be other files in the directory, but if we haven't read them yet
   899  	// we don't need to invalidate them.
   900  	var result []file.Modification
   901  	for _, c := range changes {
   902  		expanded := make(map[protocol.DocumentURI]bool)
   903  		for _, snapshot := range snapshots {
   904  			for _, uri := range snapshot.filesInDir(c.URI) {
   905  				expanded[uri] = true
   906  			}
   907  		}
   908  		if len(expanded) == 0 {
   909  			result = append(result, c)
   910  		} else {
   911  			for uri := range expanded {
   912  				result = append(result, file.Modification{
   913  					URI:        uri,
   914  					Action:     c.Action,
   915  					LanguageID: "",
   916  					OnDisk:     c.OnDisk,
   917  					// changes to directories cannot include text or versions
   918  				})
   919  			}
   920  		}
   921  	}
   922  	return result
   923  }
   924  
   925  // updateOverlays updates the set of overlays and returns a map of any existing
   926  // overlay values that were replaced.
   927  //
   928  // Precondition: caller holds s.viewMu lock.
   929  // TODO(rfindley): move this to fs_overlay.go.
   930  func (fs *overlayFS) updateOverlays(ctx context.Context, changes []file.Modification) (map[protocol.DocumentURI]*overlay, error) {
   931  	fs.mu.Lock()
   932  	defer fs.mu.Unlock()
   933  
   934  	replaced := make(map[protocol.DocumentURI]*overlay)
   935  	for _, c := range changes {
   936  		o, ok := fs.overlays[c.URI]
   937  		if ok {
   938  			replaced[c.URI] = o
   939  		}
   940  
   941  		// If the file is not opened in an overlay and the change is on disk,
   942  		// there's no need to update an overlay. If there is an overlay, we
   943  		// may need to update the overlay's saved value.
   944  		if !ok && c.OnDisk {
   945  			continue
   946  		}
   947  
   948  		// Determine the file kind on open, otherwise, assume it has been cached.
   949  		var kind file.Kind
   950  		switch c.Action {
   951  		case file.Open:
   952  			kind = file.KindForLang(c.LanguageID)
   953  		default:
   954  			if !ok {
   955  				return nil, fmt.Errorf("updateOverlays: modifying unopened overlay %v", c.URI)
   956  			}
   957  			kind = o.kind
   958  		}
   959  
   960  		// Closing a file just deletes its overlay.
   961  		if c.Action == file.Close {
   962  			delete(fs.overlays, c.URI)
   963  			continue
   964  		}
   965  
   966  		// If the file is on disk, check if its content is the same as in the
   967  		// overlay. Saves and on-disk file changes don't come with the file's
   968  		// content.
   969  		text := c.Text
   970  		if text == nil && (c.Action == file.Save || c.OnDisk) {
   971  			if !ok {
   972  				return nil, fmt.Errorf("no known content for overlay for %s", c.Action)
   973  			}
   974  			text = o.content
   975  		}
   976  		// On-disk changes don't come with versions.
   977  		version := c.Version
   978  		if c.OnDisk || c.Action == file.Save {
   979  			version = o.version
   980  		}
   981  		hash := file.HashOf(text)
   982  		var sameContentOnDisk bool
   983  		switch c.Action {
   984  		case file.Delete:
   985  			// Do nothing. sameContentOnDisk should be false.
   986  		case file.Save:
   987  			// Make sure the version and content (if present) is the same.
   988  			if false && o.version != version { // Client no longer sends the version
   989  				return nil, fmt.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
   990  			}
   991  			if c.Text != nil && o.hash != hash {
   992  				return nil, fmt.Errorf("updateOverlays: overlay %s changed on save", c.URI)
   993  			}
   994  			sameContentOnDisk = true
   995  		default:
   996  			fh := mustReadFile(ctx, fs.delegate, c.URI)
   997  			_, readErr := fh.Content()
   998  			sameContentOnDisk = (readErr == nil && fh.Identity().Hash == hash)
   999  		}
  1000  		o = &overlay{
  1001  			uri:     c.URI,
  1002  			version: version,
  1003  			content: text,
  1004  			kind:    kind,
  1005  			hash:    hash,
  1006  			saved:   sameContentOnDisk,
  1007  		}
  1008  
  1009  		// NOTE: previous versions of this code checked here that the overlay had a
  1010  		// view and file kind (but we don't know why).
  1011  
  1012  		fs.overlays[c.URI] = o
  1013  	}
  1014  
  1015  	return replaced, nil
  1016  }
  1017  
  1018  func mustReadFile(ctx context.Context, fs file.Source, uri protocol.DocumentURI) file.Handle {
  1019  	ctx = xcontext.Detach(ctx)
  1020  	fh, err := fs.ReadFile(ctx, uri)
  1021  	if err != nil {
  1022  		// ReadFile cannot fail with an uncancellable context.
  1023  		bug.Reportf("reading file failed unexpectedly: %v", err)
  1024  		return brokenFile{uri, err}
  1025  	}
  1026  	return fh
  1027  }
  1028  
  1029  // A brokenFile represents an unexpected failure to read a file.
  1030  type brokenFile struct {
  1031  	uri protocol.DocumentURI
  1032  	err error
  1033  }
  1034  
  1035  func (b brokenFile) URI() protocol.DocumentURI { return b.uri }
  1036  func (b brokenFile) Identity() file.Identity   { return file.Identity{URI: b.uri} }
  1037  func (b brokenFile) SameContentsOnDisk() bool  { return false }
  1038  func (b brokenFile) Version() int32            { return 0 }
  1039  func (b brokenFile) Content() ([]byte, error)  { return nil, b.err }
  1040  
  1041  // FileWatchingGlobPatterns returns a set of glob patterns that the client is
  1042  // required to watch for changes, and notify the server of them, in order to
  1043  // keep the server's state up to date.
  1044  //
  1045  // This set includes
  1046  //  1. all go.mod and go.work files in the workspace; and
  1047  //  2. for each Snapshot, its modules (or directory for ad-hoc views). In
  1048  //     module mode, this is the set of active modules (and for VS Code, all
  1049  //     workspace directories within them, due to golang/go#42348).
  1050  //
  1051  // The watch for workspace go.work and go.mod files in (1) is sufficient to
  1052  // capture changes to the repo structure that may affect the set of views.
  1053  // Whenever this set changes, we reload the workspace and invalidate memoized
  1054  // files.
  1055  //
  1056  // The watch for workspace directories in (2) should keep each View up to date,
  1057  // as it should capture any newly added/modified/deleted Go files.
  1058  //
  1059  // Patterns are returned as a set of protocol.RelativePatterns, since they can
  1060  // always be later translated to glob patterns (i.e. strings) if the client
  1061  // lacks relative pattern support. By convention, any pattern returned with
  1062  // empty baseURI should be served as a glob pattern.
  1063  //
  1064  // In general, we prefer to serve relative patterns, as they work better on
  1065  // most clients that support both, and do not have issues with Windows driver
  1066  // letter casing:
  1067  // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#relativePattern
  1068  //
  1069  // TODO(golang/go#57979): we need to reset the memoizedFS when a view changes.
  1070  // Consider the case where we incidentally read a file, then it moved outside
  1071  // of an active module, and subsequently changed: we would still observe the
  1072  // original file state.
  1073  func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[protocol.RelativePattern]unit {
  1074  	s.viewMu.Lock()
  1075  	defer s.viewMu.Unlock()
  1076  
  1077  	// Always watch files that may change the set of views.
  1078  	patterns := map[protocol.RelativePattern]unit{
  1079  		{Pattern: "**/*.{mod,work}"}: {},
  1080  	}
  1081  
  1082  	for _, view := range s.views {
  1083  		snapshot, release, err := view.Snapshot()
  1084  		if err != nil {
  1085  			continue // view is shut down; continue with others
  1086  		}
  1087  		for k, v := range snapshot.fileWatchingGlobPatterns() {
  1088  			patterns[k] = v
  1089  		}
  1090  		release()
  1091  	}
  1092  	return patterns
  1093  }
  1094  
  1095  // OrphanedFileDiagnostics reports diagnostics describing why open files have
  1096  // no packages or have only command-line-arguments packages.
  1097  //
  1098  // If the resulting diagnostic is nil, the file is either not orphaned or we
  1099  // can't produce a good diagnostic.
  1100  //
  1101  // The caller must not mutate the result.
  1102  func (s *Session) OrphanedFileDiagnostics(ctx context.Context) (map[protocol.DocumentURI][]*Diagnostic, error) {
  1103  	// Note: diagnostics holds a slice for consistency with other diagnostic
  1104  	// funcs.
  1105  	diagnostics := make(map[protocol.DocumentURI][]*Diagnostic)
  1106  
  1107  	byView := make(map[*View][]*overlay)
  1108  	for _, o := range s.Overlays() {
  1109  		uri := o.URI()
  1110  		snapshot, release, err := s.SnapshotOf(ctx, uri)
  1111  		if err != nil {
  1112  			// TODO(golang/go#57979): we have to use the .go suffix as an approximation for
  1113  			// file kind here, because we don't have access to Options if no View was
  1114  			// matched.
  1115  			//
  1116  			// But Options are really a property of Folder, not View, and we could
  1117  			// match a folder here.
  1118  			//
  1119  			// Refactor so that Folders are tracked independently of Views, and use
  1120  			// the correct options here to get the most accurate file kind.
  1121  			//
  1122  			// TODO(golang/go#57979): once we switch entirely to the zeroconfig
  1123  			// logic, we should use this diagnostic for the fallback case of
  1124  			// s.views[0] in the ViewOf logic.
  1125  			if errors.Is(err, errNoViews) {
  1126  				if strings.HasSuffix(string(uri), ".go") {
  1127  					if _, rng, ok := orphanedFileDiagnosticRange(ctx, s.parseCache, o); ok {
  1128  						diagnostics[uri] = []*Diagnostic{{
  1129  							URI:      uri,
  1130  							Range:    rng,
  1131  							Severity: protocol.SeverityWarning,
  1132  							Source:   ListError,
  1133  							Message:  fmt.Sprintf("No active builds contain %s: consider opening a new workspace folder containing it", uri.Path()),
  1134  						}}
  1135  					}
  1136  				}
  1137  				continue
  1138  			}
  1139  			return nil, err
  1140  		}
  1141  		v := snapshot.View()
  1142  		release()
  1143  		byView[v] = append(byView[v], o)
  1144  	}
  1145  
  1146  	for view, overlays := range byView {
  1147  		snapshot, release, err := view.Snapshot()
  1148  		if err != nil {
  1149  			continue // view is shutting down
  1150  		}
  1151  		defer release()
  1152  		diags, err := snapshot.orphanedFileDiagnostics(ctx, overlays)
  1153  		if err != nil {
  1154  			return nil, err
  1155  		}
  1156  		for _, d := range diags {
  1157  			diagnostics[d.URI] = append(diagnostics[d.URI], d)
  1158  		}
  1159  	}
  1160  	return diagnostics, nil
  1161  }