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