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