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
5package cache
6
7import (
8	"context"
9	"fmt"
10	"strconv"
11	"sync"
12	"sync/atomic"
13
14	"golang.org/x/tools/internal/event"
15	"golang.org/x/tools/internal/gocommand"
16	"golang.org/x/tools/internal/imports"
17	"golang.org/x/tools/internal/lsp/progress"
18	"golang.org/x/tools/internal/lsp/source"
19	"golang.org/x/tools/internal/span"
20	"golang.org/x/tools/internal/xcontext"
21	errors "golang.org/x/xerrors"
22)
23
24type Session struct {
25	cache *Cache
26	id    string
27
28	optionsMu sync.Mutex
29	options   *source.Options
30
31	viewMu  sync.RWMutex
32	views   []*View
33	viewMap map[span.URI]*View // map of URI->best view
34
35	overlayMu sync.Mutex
36	overlays  map[span.URI]*overlay
37
38	// gocmdRunner guards go command calls from concurrency errors.
39	gocmdRunner *gocommand.Runner
40
41	progress *progress.Tracker
42}
43
44type overlay struct {
45	session *Session
46	uri     span.URI
47	text    []byte
48	hash    string
49	version int32
50	kind    source.FileKind
51
52	// saved is true if a file matches the state on disk,
53	// and therefore does not need to be part of the overlay sent to go/packages.
54	saved bool
55}
56
57func (o *overlay) Read() ([]byte, error) {
58	return o.text, nil
59}
60
61func (o *overlay) FileIdentity() source.FileIdentity {
62	return source.FileIdentity{
63		URI:  o.uri,
64		Hash: o.hash,
65		Kind: o.kind,
66	}
67}
68
69func (o *overlay) VersionedFileIdentity() source.VersionedFileIdentity {
70	return source.VersionedFileIdentity{
71		URI:       o.uri,
72		SessionID: o.session.id,
73		Version:   o.version,
74	}
75}
76
77func (o *overlay) Kind() source.FileKind {
78	return o.kind
79}
80
81func (o *overlay) URI() span.URI {
82	return o.uri
83}
84
85func (o *overlay) Version() int32 {
86	return o.version
87}
88
89func (o *overlay) Session() string {
90	return o.session.id
91}
92
93func (o *overlay) Saved() bool {
94	return o.saved
95}
96
97// closedFile implements LSPFile for a file that the editor hasn't told us about.
98type closedFile struct {
99	source.FileHandle
100}
101
102func (c *closedFile) VersionedFileIdentity() source.VersionedFileIdentity {
103	return source.VersionedFileIdentity{
104		URI:       c.FileHandle.URI(),
105		SessionID: "",
106		Version:   0,
107	}
108}
109
110func (c *closedFile) Saved() bool {
111	return true
112}
113
114func (c *closedFile) Session() string {
115	return ""
116}
117
118func (c *closedFile) Version() int32 {
119	return 0
120}
121
122func (s *Session) ID() string     { return s.id }
123func (s *Session) String() string { return s.id }
124
125func (s *Session) Options() *source.Options {
126	s.optionsMu.Lock()
127	defer s.optionsMu.Unlock()
128	return s.options
129}
130
131func (s *Session) SetOptions(options *source.Options) {
132	s.optionsMu.Lock()
133	defer s.optionsMu.Unlock()
134	s.options = options
135}
136
137func (s *Session) SetProgressTracker(tracker *progress.Tracker) {
138	// The progress tracker should be set before any view is initialized.
139	s.progress = tracker
140}
141
142func (s *Session) Shutdown(ctx context.Context) {
143	s.viewMu.Lock()
144	defer s.viewMu.Unlock()
145	for _, view := range s.views {
146		view.shutdown(ctx)
147	}
148	s.views = nil
149	s.viewMap = nil
150	event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s))
151}
152
153func (s *Session) Cache() interface{} {
154	return s.cache
155}
156
157func (s *Session) NewView(ctx context.Context, name string, folder, tempWorkspace span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) {
158	s.viewMu.Lock()
159	defer s.viewMu.Unlock()
160	view, snapshot, release, err := s.createView(ctx, name, folder, tempWorkspace, options, 0)
161	if err != nil {
162		return nil, nil, func() {}, err
163	}
164	s.views = append(s.views, view)
165	// we always need to drop the view map
166	s.viewMap = make(map[span.URI]*View)
167	return view, snapshot, release, nil
168}
169
170func (s *Session) createView(ctx context.Context, name string, folder, tempWorkspace span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
171	index := atomic.AddInt64(&viewIndex, 1)
172
173	if s.cache.options != nil {
174		s.cache.options(options)
175	}
176
177	// Set the module-specific information.
178	ws, err := s.getWorkspaceInformation(ctx, folder, options)
179	if err != nil {
180		return nil, nil, func() {}, err
181	}
182	root := folder
183	if options.ExpandWorkspaceToModule {
184		root, err = findWorkspaceRoot(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), options.ExperimentalWorkspaceModule)
185		if err != nil {
186			return nil, nil, func() {}, err
187		}
188	}
189
190	// Build the gopls workspace, collecting active modules in the view.
191	workspace, err := newWorkspace(ctx, root, s, pathExcludedByFilterFunc(root.Filename(), ws.gomodcache, options), ws.userGo111Module == off, options.ExperimentalWorkspaceModule)
192	if err != nil {
193		return nil, nil, func() {}, err
194	}
195
196	// We want a true background context and not a detached context here
197	// the spans need to be unrelated and no tag values should pollute it.
198	baseCtx := event.Detach(xcontext.Detach(ctx))
199	backgroundCtx, cancel := context.WithCancel(baseCtx)
200
201	v := &View{
202		session:              s,
203		initialWorkspaceLoad: make(chan struct{}),
204		initializationSema:   make(chan struct{}, 1),
205		id:                   strconv.FormatInt(index, 10),
206		options:              options,
207		baseCtx:              baseCtx,
208		name:                 name,
209		folder:               folder,
210		moduleUpgrades:       map[string]string{},
211		filesByURI:           map[span.URI]*fileBase{},
212		filesByBase:          map[string][]*fileBase{},
213		rootURI:              root,
214		workspaceInformation: *ws,
215		tempWorkspace:        tempWorkspace,
216	}
217	v.importsState = &importsState{
218		ctx: backgroundCtx,
219		processEnv: &imports.ProcessEnv{
220			GocmdRunner: s.gocmdRunner,
221		},
222	}
223	v.snapshot = &snapshot{
224		id:                snapshotID,
225		view:              v,
226		backgroundCtx:     backgroundCtx,
227		cancel:            cancel,
228		initializeOnce:    &sync.Once{},
229		generation:        s.cache.store.Generation(generationName(v, 0)),
230		packages:          make(map[packageKey]*packageHandle),
231		ids:               make(map[span.URI][]packageID),
232		metadata:          make(map[packageID]*knownMetadata),
233		files:             make(map[span.URI]source.VersionedFileHandle),
234		goFiles:           make(map[parseKey]*parseGoHandle),
235		importedBy:        make(map[packageID][]packageID),
236		actions:           make(map[actionKey]*actionHandle),
237		workspacePackages: make(map[packageID]packagePath),
238		unloadableFiles:   make(map[span.URI]struct{}),
239		parseModHandles:   make(map[span.URI]*parseModHandle),
240		modTidyHandles:    make(map[span.URI]*modTidyHandle),
241		modWhyHandles:     make(map[span.URI]*modWhyHandle),
242		workspace:         workspace,
243	}
244
245	// Initialize the view without blocking.
246	initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
247	v.initCancelFirstAttempt = initCancel
248	snapshot := v.snapshot
249	release := snapshot.generation.Acquire(initCtx)
250	go func() {
251		defer release()
252		snapshot.initialize(initCtx, true)
253		// Ensure that the view workspace is written at least once following
254		// initialization.
255		if err := v.updateWorkspace(initCtx); err != nil {
256			event.Error(ctx, "copying workspace dir", err)
257		}
258	}()
259	return v, snapshot, snapshot.generation.Acquire(ctx), nil
260}
261
262// View returns the view by name.
263func (s *Session) View(name string) source.View {
264	s.viewMu.RLock()
265	defer s.viewMu.RUnlock()
266	for _, view := range s.views {
267		if view.Name() == name {
268			return view
269		}
270	}
271	return nil
272}
273
274// ViewOf returns a view corresponding to the given URI.
275// If the file is not already associated with a view, pick one using some heuristics.
276func (s *Session) ViewOf(uri span.URI) (source.View, error) {
277	return s.viewOf(uri)
278}
279
280func (s *Session) viewOf(uri span.URI) (*View, error) {
281	s.viewMu.RLock()
282	defer s.viewMu.RUnlock()
283	// Check if we already know this file.
284	if v, found := s.viewMap[uri]; found {
285		return v, nil
286	}
287	// Pick the best view for this file and memoize the result.
288	if len(s.views) == 0 {
289		return nil, fmt.Errorf("no views in session")
290	}
291	s.viewMap[uri] = bestViewForURI(uri, s.views)
292	return s.viewMap[uri], nil
293}
294
295func (s *Session) viewsOf(uri span.URI) []*View {
296	s.viewMu.RLock()
297	defer s.viewMu.RUnlock()
298
299	var views []*View
300	for _, view := range s.views {
301		if source.InDir(view.folder.Filename(), uri.Filename()) {
302			views = append(views, view)
303		}
304	}
305	return views
306}
307
308func (s *Session) Views() []source.View {
309	s.viewMu.RLock()
310	defer s.viewMu.RUnlock()
311	result := make([]source.View, len(s.views))
312	for i, v := range s.views {
313		result[i] = v
314	}
315	return result
316}
317
318// bestViewForURI returns the most closely matching view for the given URI
319// out of the given set of views.
320func bestViewForURI(uri span.URI, views []*View) *View {
321	// we need to find the best view for this file
322	var longest *View
323	for _, view := range views {
324		if longest != nil && len(longest.Folder()) > len(view.Folder()) {
325			continue
326		}
327		if view.contains(uri) {
328			longest = view
329		}
330	}
331	if longest != nil {
332		return longest
333	}
334	// Try our best to return a view that knows the file.
335	for _, view := range views {
336		if view.knownFile(uri) {
337			return view
338		}
339	}
340	// TODO: are there any more heuristics we can use?
341	return views[0]
342}
343
344func (s *Session) removeView(ctx context.Context, view *View) error {
345	s.viewMu.Lock()
346	defer s.viewMu.Unlock()
347	i, err := s.dropView(ctx, view)
348	if err != nil {
349		return err
350	}
351	// delete this view... we don't care about order but we do want to make
352	// sure we can garbage collect the view
353	s.views[i] = s.views[len(s.views)-1]
354	s.views[len(s.views)-1] = nil
355	s.views = s.views[:len(s.views)-1]
356	return nil
357}
358
359func (s *Session) updateView(ctx context.Context, view *View, options *source.Options) (*View, error) {
360	s.viewMu.Lock()
361	defer s.viewMu.Unlock()
362	i, err := s.dropView(ctx, view)
363	if err != nil {
364		return nil, err
365	}
366	// Preserve the snapshot ID if we are recreating the view.
367	view.snapshotMu.Lock()
368	snapshotID := view.snapshot.id
369	view.snapshotMu.Unlock()
370	v, _, release, err := s.createView(ctx, view.name, view.folder, view.tempWorkspace, options, snapshotID)
371	release()
372	if err != nil {
373		// we have dropped the old view, but could not create the new one
374		// this should not happen and is very bad, but we still need to clean
375		// up the view array if it happens
376		s.views[i] = s.views[len(s.views)-1]
377		s.views[len(s.views)-1] = nil
378		s.views = s.views[:len(s.views)-1]
379		return nil, err
380	}
381	// substitute the new view into the array where the old view was
382	s.views[i] = v
383	return v, nil
384}
385
386func (s *Session) dropView(ctx context.Context, v *View) (int, error) {
387	// we always need to drop the view map
388	s.viewMap = make(map[span.URI]*View)
389	for i := range s.views {
390		if v == s.views[i] {
391			// we found the view, drop it and return the index it was found at
392			s.views[i] = nil
393			v.shutdown(ctx)
394			return i, nil
395		}
396	}
397	return -1, errors.Errorf("view %s for %v not found", v.Name(), v.Folder())
398}
399
400func (s *Session) ModifyFiles(ctx context.Context, changes []source.FileModification) error {
401	_, releases, err := s.DidModifyFiles(ctx, changes)
402	for _, release := range releases {
403		release()
404	}
405	return err
406}
407
408type fileChange struct {
409	content    []byte
410	exists     bool
411	fileHandle source.VersionedFileHandle
412
413	// isUnchanged indicates whether the file action is one that does not
414	// change the actual contents of the file. Opens and closes should not
415	// be treated like other changes, since the file content doesn't change.
416	isUnchanged bool
417}
418
419func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) (map[source.Snapshot][]span.URI, []func(), error) {
420	s.viewMu.RLock()
421	defer s.viewMu.RUnlock()
422	views := make(map[*View]map[span.URI]*fileChange)
423	affectedViews := map[span.URI][]*View{}
424
425	overlays, err := s.updateOverlays(ctx, changes)
426	if err != nil {
427		return nil, nil, err
428	}
429	var forceReloadMetadata bool
430	for _, c := range changes {
431		if c.Action == source.InvalidateMetadata {
432			forceReloadMetadata = true
433		}
434
435		// Build the list of affected views.
436		var changedViews []*View
437		for _, view := range s.views {
438			// Don't propagate changes that are outside of the view's scope
439			// or knowledge.
440			if !view.relevantChange(c) {
441				continue
442			}
443			changedViews = append(changedViews, view)
444		}
445		// If the change is not relevant to any view, but the change is
446		// happening in the editor, assign it the most closely matching view.
447		if len(changedViews) == 0 {
448			if c.OnDisk {
449				continue
450			}
451			bestView, err := s.viewOf(c.URI)
452			if err != nil {
453				return nil, nil, err
454			}
455			changedViews = append(changedViews, bestView)
456		}
457		affectedViews[c.URI] = changedViews
458
459		isUnchanged := c.Action == source.Open || c.Action == source.Close
460
461		// Apply the changes to all affected views.
462		for _, view := range changedViews {
463			// Make sure that the file is added to the view.
464			_ = view.getFile(c.URI)
465			if _, ok := views[view]; !ok {
466				views[view] = make(map[span.URI]*fileChange)
467			}
468			if fh, ok := overlays[c.URI]; ok {
469				views[view][c.URI] = &fileChange{
470					content:     fh.text,
471					exists:      true,
472					fileHandle:  fh,
473					isUnchanged: isUnchanged,
474				}
475			} else {
476				fsFile, err := s.cache.getFile(ctx, c.URI)
477				if err != nil {
478					return nil, nil, err
479				}
480				content, err := fsFile.Read()
481				fh := &closedFile{fsFile}
482				views[view][c.URI] = &fileChange{
483					content:     content,
484					exists:      err == nil,
485					fileHandle:  fh,
486					isUnchanged: isUnchanged,
487				}
488			}
489		}
490	}
491
492	var releases []func()
493	viewToSnapshot := map[*View]*snapshot{}
494	for view, changed := range views {
495		snapshot, release := view.invalidateContent(ctx, changed, forceReloadMetadata)
496		releases = append(releases, release)
497		viewToSnapshot[view] = snapshot
498	}
499
500	// We only want to diagnose each changed file once, in the view to which
501	// it "most" belongs. We do this by picking the best view for each URI,
502	// and then aggregating the set of snapshots and their URIs (to avoid
503	// diagnosing the same snapshot multiple times).
504	snapshotURIs := map[source.Snapshot][]span.URI{}
505	for _, mod := range changes {
506		viewSlice, ok := affectedViews[mod.URI]
507		if !ok || len(viewSlice) == 0 {
508			continue
509		}
510		view := bestViewForURI(mod.URI, viewSlice)
511		snapshot, ok := viewToSnapshot[view]
512		if !ok {
513			panic(fmt.Sprintf("no snapshot for view %s", view.Folder()))
514		}
515		snapshotURIs[snapshot] = append(snapshotURIs[snapshot], mod.URI)
516	}
517	return snapshotURIs, releases, nil
518}
519
520func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []source.FileModification) []source.FileModification {
521	s.viewMu.RLock()
522	defer s.viewMu.RUnlock()
523	var snapshots []*snapshot
524	for _, v := range s.views {
525		snapshot, release := v.getSnapshot(ctx)
526		defer release()
527		snapshots = append(snapshots, snapshot)
528	}
529	knownDirs := knownDirectories(ctx, snapshots)
530	var result []source.FileModification
531	for _, c := range changes {
532		if _, ok := knownDirs[c.URI]; !ok {
533			result = append(result, c)
534			continue
535		}
536		affectedFiles := knownFilesInDir(ctx, snapshots, c.URI)
537		var fileChanges []source.FileModification
538		for uri := range affectedFiles {
539			fileChanges = append(fileChanges, source.FileModification{
540				URI:        uri,
541				Action:     c.Action,
542				LanguageID: "",
543				OnDisk:     c.OnDisk,
544				// changes to directories cannot include text or versions
545			})
546		}
547		result = append(result, fileChanges...)
548	}
549	return result
550}
551
552// knownDirectories returns all of the directories known to the given
553// snapshots, including workspace directories and their subdirectories.
554func knownDirectories(ctx context.Context, snapshots []*snapshot) map[span.URI]struct{} {
555	result := map[span.URI]struct{}{}
556	for _, snapshot := range snapshots {
557		dirs := snapshot.workspace.dirs(ctx, snapshot)
558		for _, dir := range dirs {
559			result[dir] = struct{}{}
560		}
561		for _, dir := range snapshot.getKnownSubdirs(dirs) {
562			result[dir] = struct{}{}
563		}
564	}
565	return result
566}
567
568// knownFilesInDir returns the files known to the snapshots in the session.
569// It does not respect symlinks.
570func knownFilesInDir(ctx context.Context, snapshots []*snapshot, dir span.URI) map[span.URI]struct{} {
571	files := map[span.URI]struct{}{}
572
573	for _, snapshot := range snapshots {
574		for _, uri := range snapshot.knownFilesInDir(ctx, dir) {
575			files[uri] = struct{}{}
576		}
577	}
578	return files
579}
580
581func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModification) (map[span.URI]*overlay, error) {
582	s.overlayMu.Lock()
583	defer s.overlayMu.Unlock()
584
585	for _, c := range changes {
586		// Don't update overlays for metadata invalidations.
587		if c.Action == source.InvalidateMetadata {
588			continue
589		}
590
591		o, ok := s.overlays[c.URI]
592
593		// If the file is not opened in an overlay and the change is on disk,
594		// there's no need to update an overlay. If there is an overlay, we
595		// may need to update the overlay's saved value.
596		if !ok && c.OnDisk {
597			continue
598		}
599
600		// Determine the file kind on open, otherwise, assume it has been cached.
601		var kind source.FileKind
602		switch c.Action {
603		case source.Open:
604			kind = source.DetectLanguage(c.LanguageID, c.URI.Filename())
605		default:
606			if !ok {
607				return nil, errors.Errorf("updateOverlays: modifying unopened overlay %v", c.URI)
608			}
609			kind = o.kind
610		}
611		if kind == source.UnknownKind {
612			return nil, errors.Errorf("updateOverlays: unknown file kind for %s", c.URI)
613		}
614
615		// Closing a file just deletes its overlay.
616		if c.Action == source.Close {
617			delete(s.overlays, c.URI)
618			continue
619		}
620
621		// If the file is on disk, check if its content is the same as in the
622		// overlay. Saves and on-disk file changes don't come with the file's
623		// content.
624		text := c.Text
625		if text == nil && (c.Action == source.Save || c.OnDisk) {
626			if !ok {
627				return nil, fmt.Errorf("no known content for overlay for %s", c.Action)
628			}
629			text = o.text
630		}
631		// On-disk changes don't come with versions.
632		version := c.Version
633		if c.OnDisk || c.Action == source.Save {
634			version = o.version
635		}
636		hash := hashContents(text)
637		var sameContentOnDisk bool
638		switch c.Action {
639		case source.Delete:
640			// Do nothing. sameContentOnDisk should be false.
641		case source.Save:
642			// Make sure the version and content (if present) is the same.
643			if false && o.version != version { // Client no longer sends the version
644				return nil, errors.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
645			}
646			if c.Text != nil && o.hash != hash {
647				return nil, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI)
648			}
649			sameContentOnDisk = true
650		default:
651			fh, err := s.cache.getFile(ctx, c.URI)
652			if err != nil {
653				return nil, err
654			}
655			_, readErr := fh.Read()
656			sameContentOnDisk = (readErr == nil && fh.FileIdentity().Hash == hash)
657		}
658		o = &overlay{
659			session: s,
660			uri:     c.URI,
661			version: version,
662			text:    text,
663			kind:    kind,
664			hash:    hash,
665			saved:   sameContentOnDisk,
666		}
667		s.overlays[c.URI] = o
668	}
669
670	// Get the overlays for each change while the session's overlay map is
671	// locked.
672	overlays := make(map[span.URI]*overlay)
673	for _, c := range changes {
674		if o, ok := s.overlays[c.URI]; ok {
675			overlays[c.URI] = o
676		}
677	}
678	return overlays, nil
679}
680
681func (s *Session) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
682	if overlay := s.readOverlay(uri); overlay != nil {
683		return overlay, nil
684	}
685	// Fall back to the cache-level file system.
686	return s.cache.getFile(ctx, uri)
687}
688
689func (s *Session) readOverlay(uri span.URI) *overlay {
690	s.overlayMu.Lock()
691	defer s.overlayMu.Unlock()
692
693	if overlay, ok := s.overlays[uri]; ok {
694		return overlay
695	}
696	return nil
697}
698
699func (s *Session) Overlays() []source.Overlay {
700	s.overlayMu.Lock()
701	defer s.overlayMu.Unlock()
702
703	overlays := make([]source.Overlay, 0, len(s.overlays))
704	for _, overlay := range s.overlays {
705		overlays = append(overlays, overlay)
706	}
707	return overlays
708}
709
710func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
711	s.viewMu.RLock()
712	defer s.viewMu.RUnlock()
713	patterns := map[string]struct{}{}
714	for _, view := range s.views {
715		snapshot, release := view.getSnapshot(ctx)
716		for k, v := range snapshot.fileWatchingGlobPatterns(ctx) {
717			patterns[k] = v
718		}
719		release()
720	}
721	return patterns
722}
723