blob: 948912a549a950afa07974e3ca6f783c7916b3c0 [file] [log] [blame]
Robert Findleyb15dac22022-08-30 14:40:12 -04001// 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 "bytes"
9 "context"
10 "errors"
11 "fmt"
12 "go/ast"
Rob Findley1c9fe3f2023-05-11 14:33:05 -040013 "go/build/constraint"
Robert Findleyb15dac22022-08-30 14:40:12 -040014 "go/token"
15 "go/types"
16 "io"
17 "io/ioutil"
18 "log"
19 "os"
20 "path/filepath"
21 "regexp"
22 "runtime"
23 "sort"
24 "strconv"
25 "strings"
26 "sync"
27 "sync/atomic"
28 "unsafe"
29
Robert Findleyb15dac22022-08-30 14:40:12 -040030 "golang.org/x/sync/errgroup"
31 "golang.org/x/tools/go/packages"
Robert Findley21d22562023-02-21 12:26:27 -050032 "golang.org/x/tools/go/types/objectpath"
Alan Donovan4baa3dc2023-04-25 10:21:06 -040033 "golang.org/x/tools/gopls/internal/bug"
Rob Findleya13793e2023-05-12 14:22:10 -040034 "golang.org/x/tools/gopls/internal/lsp/command"
Robert Findleyacaae982023-03-16 12:21:36 -040035 "golang.org/x/tools/gopls/internal/lsp/filecache"
Robert Findley21d22562023-02-21 12:26:27 -050036 "golang.org/x/tools/gopls/internal/lsp/protocol"
Robert Findleyb15dac22022-08-30 14:40:12 -040037 "golang.org/x/tools/gopls/internal/lsp/source"
Robert Findley21d22562023-02-21 12:26:27 -050038 "golang.org/x/tools/gopls/internal/lsp/source/methodsets"
Robert Findley1b2d1bd2023-03-16 18:52:13 -040039 "golang.org/x/tools/gopls/internal/lsp/source/typerefs"
Robert Findley21d22562023-02-21 12:26:27 -050040 "golang.org/x/tools/gopls/internal/lsp/source/xrefs"
Alan Donovan26a95e62022-10-07 10:40:32 -040041 "golang.org/x/tools/gopls/internal/span"
Robert Findley9c639112022-09-28 12:03:51 -040042 "golang.org/x/tools/internal/event"
43 "golang.org/x/tools/internal/event/tag"
44 "golang.org/x/tools/internal/gocommand"
Robert Findleyb15dac22022-08-30 14:40:12 -040045 "golang.org/x/tools/internal/memoize"
46 "golang.org/x/tools/internal/packagesinternal"
47 "golang.org/x/tools/internal/persistent"
Robert Findleyb15dac22022-08-30 14:40:12 -040048 "golang.org/x/tools/internal/typesinternal"
49)
50
51type snapshot struct {
Robert Findley0c71b562022-11-14 11:58:07 -050052 sequenceID uint64
53 globalID source.GlobalSnapshotID
Robert Findleyc4c6aa62023-01-19 20:24:55 -050054
55 // TODO(rfindley): the snapshot holding a reference to the view poses
56 // lifecycle problems: a view may be shut down and waiting for work
57 // associated with this snapshot to complete. While most accesses of the view
58 // are benign (options or workspace information), this is not formalized and
59 // it is wrong for the snapshot to use a shutdown view.
60 //
61 // Fix this by passing options and workspace information to the snapshot,
62 // both of which should be immutable for the snapshot.
63 view *View
Robert Findleyb15dac22022-08-30 14:40:12 -040064
65 cancel func()
66 backgroundCtx context.Context
67
68 store *memoize.Store // cache of handles shared by all snapshots
69
70 refcount sync.WaitGroup // number of references
71 destroyedBy *string // atomically set to non-nil in Destroy once refcount = 0
72
73 // initialized reports whether the snapshot has been initialized. Concurrent
74 // initialization is guarded by the view.initializationSema. Each snapshot is
75 // initialized at most once: concurrent initialization is guarded by
76 // view.initializationSema.
77 initialized bool
78 // initializedErr holds the last error resulting from initialization. If
cui fliter165099b2023-04-27 20:32:55 +080079 // initialization fails, we only retry when the workspace modules change,
Robert Findleyb15dac22022-08-30 14:40:12 -040080 // to avoid too many go/packages calls.
81 initializedErr *source.CriticalError
82
83 // mu guards all of the maps in the snapshot, as well as the builtin URI.
84 mu sync.Mutex
85
Robert Findley4b112032023-03-28 20:06:14 -040086 // builtin is the location of builtin.go in GOROOT.
87 //
88 // TODO(rfindley): would it make more sense to eagerly parse builtin, and
89 // instead store a *ParsedGoFile here?
Robert Findleyb15dac22022-08-30 14:40:12 -040090 builtin span.URI
91
92 // meta holds loaded metadata.
93 //
94 // meta is guarded by mu, but the metadataGraph itself is immutable.
95 // TODO(rfindley): in many places we hold mu while operating on meta, even
96 // though we only need to hold mu while reading the pointer.
97 meta *metadataGraph
98
99 // files maps file URIs to their corresponding FileHandles.
100 // It may invalidated when a file's content changes.
101 files filesMap
102
Robert Findleyae056092023-02-19 21:14:18 -0500103 // parseCache holds an LRU cache of recently parsed files.
104 parseCache *parseCache
Robert Findleyb15dac22022-08-30 14:40:12 -0400105
106 // symbolizeHandles maps each file URI to a handle for the future
107 // result of computing the symbols declared in that file.
108 symbolizeHandles *persistent.Map // from span.URI to *memoize.Promise[symbolizeResult]
109
110 // packages maps a packageKey to a *packageHandle.
111 // It may be invalidated when a file's content changes.
112 //
113 // Invariants to preserve:
Alan Donovanff22fab2022-11-18 14:47:36 -0500114 // - packages.Get(id).meta == meta.metadata[id] for all ids
Robert Findleyb15dac22022-08-30 14:40:12 -0400115 // - if a package is in packages, then all of its dependencies should also
116 // be in packages, unless there is a missing import
Robert Findley21d22562023-02-21 12:26:27 -0500117 packages *persistent.Map // from packageID to *packageHandle
Robert Findleyb15dac22022-08-30 14:40:12 -0400118
Robert Findley21d22562023-02-21 12:26:27 -0500119 // activePackages maps a package ID to a memoized active package, or nil if
120 // the package is known not to be open.
121 //
122 // IDs not contained in the map are not known to be open or not open.
123 activePackages *persistent.Map // from packageID to *Package
Robert Findleyb15dac22022-08-30 14:40:12 -0400124
Alan Donovan61e2d3f2022-10-14 17:58:15 -0400125 // analyses maps an analysisKey (which identifies a package
126 // and a set of analyzers) to the handle for the future result
127 // of loading the package and analyzing it.
Robert Findleyf10e7d52023-01-12 13:51:49 -0500128 analyses *persistent.Map // from analysisKey to analysisPromise
Robert Findleyb15dac22022-08-30 14:40:12 -0400129
130 // workspacePackages contains the workspace's packages, which are loaded
Alan Donovand59a28f2023-04-21 16:40:05 -0400131 // when the view is created. It contains no intermediate test variants.
Robert Findleyb15dac22022-08-30 14:40:12 -0400132 workspacePackages map[PackageID]PackagePath
133
134 // shouldLoad tracks packages that need to be reloaded, mapping a PackageID
135 // to the package paths that should be used to reload it
136 //
137 // When we try to load a package, we clear it from the shouldLoad map
138 // regardless of whether the load succeeded, to prevent endless loads.
139 shouldLoad map[PackageID][]PackagePath
140
141 // unloadableFiles keeps track of files that we've failed to load.
142 unloadableFiles map[span.URI]struct{}
143
Robert Findley21d22562023-02-21 12:26:27 -0500144 // TODO(rfindley): rename the handles below to "promises". A promise is
145 // different from a handle (we mutate the package handle.)
146
Robert Findleyb15dac22022-08-30 14:40:12 -0400147 // parseModHandles keeps track of any parseModHandles for the snapshot.
148 // The handles need not refer to only the view's go.mod file.
149 parseModHandles *persistent.Map // from span.URI to *memoize.Promise[parseModResult]
150
151 // parseWorkHandles keeps track of any parseWorkHandles for the snapshot.
152 // The handles need not refer to only the view's go.work file.
153 parseWorkHandles *persistent.Map // from span.URI to *memoize.Promise[parseWorkResult]
154
155 // Preserve go.mod-related handles to avoid garbage-collecting the results
156 // of various calls to the go command. The handles need not refer to only
157 // the view's go.mod file.
158 modTidyHandles *persistent.Map // from span.URI to *memoize.Promise[modTidyResult]
159 modWhyHandles *persistent.Map // from span.URI to *memoize.Promise[modWhyResult]
Robert Findley7cda55e2022-11-22 12:09:11 -0500160 modVulnHandles *persistent.Map // from span.URI to *memoize.Promise[modVulnResult]
Robert Findleyb15dac22022-08-30 14:40:12 -0400161
Alan Donovan2ec42992023-05-18 13:45:43 -0400162 // knownSubdirs is the set of subdirectory URIs in the workspace,
163 // used to create glob patterns for file watching.
164 knownSubdirs knownDirsSet
165 knownSubdirsCache map[string]struct{} // memo of knownSubdirs as a set of filenames
Robert Findleyb15dac22022-08-30 14:40:12 -0400166 // unprocessedSubdirChanges are any changes that might affect the set of
167 // subdirectories in the workspace. They are not reflected to knownSubdirs
168 // during the snapshot cloning step as it can slow down cloning.
169 unprocessedSubdirChanges []*fileChange
Robert Findley80879112022-12-16 09:11:12 -0500170
171 // workspaceModFiles holds the set of mod files active in this snapshot.
172 //
173 // This is either empty, a single entry for the workspace go.mod file, or the
174 // set of mod files used by the workspace go.work file.
175 //
176 // This set is immutable inside the snapshot, and therefore is not guarded by mu.
177 workspaceModFiles map[span.URI]struct{}
178 workspaceModFilesErr error // error encountered computing workspaceModFiles
Robert Findley488ba862023-03-23 12:18:06 -0400179
180 // importGraph holds a shared import graph to use for type-checking. Adding
181 // more packages to this import graph can speed up type checking, at the
182 // expense of in-use memory.
183 //
184 // See getImportGraph for additional documentation.
185 importGraphDone chan struct{} // closed when importGraph is set; may be nil
186 importGraph *importGraph // copied from preceding snapshot and re-evaluated
Robert Findley1b2d1bd2023-03-16 18:52:13 -0400187
188 // pkgIndex is an index of package IDs, for efficient storage of typerefs.
189 pkgIndex *typerefs.PackageIndex
Rob Findley787e7202023-05-09 18:57:05 -0400190
191 // Only compute module prefixes once, as they are used with high frequency to
192 // detect ignored files.
193 ignoreFilterOnce sync.Once
194 ignoreFilter *ignoreFilter
Rob Findleya13793e2023-05-12 14:22:10 -0400195
196 // If non-nil, the result of computing orphaned file diagnostics.
197 //
198 // Only the field, not the map itself, is guarded by the mutex. The map must
199 // not be mutated.
200 //
201 // Used to save work across diagnostics+code action passes.
202 // TODO(rfindley): refactor all of this so there's no need to re-evaluate
203 // diagnostics during code-action.
204 orphanedFileDiagnostics map[span.URI]*source.Diagnostic
Robert Findleyb15dac22022-08-30 14:40:12 -0400205}
206
Robert Findley0c71b562022-11-14 11:58:07 -0500207var globalSnapshotID uint64
208
209func nextSnapshotID() source.GlobalSnapshotID {
210 return source.GlobalSnapshotID(atomic.AddUint64(&globalSnapshotID, 1))
211}
212
Robert Findleyb15dac22022-08-30 14:40:12 -0400213var _ memoize.RefCounted = (*snapshot)(nil) // snapshots are reference-counted
214
215// Acquire prevents the snapshot from being destroyed until the returned function is called.
216//
217// (s.Acquire().release() could instead be expressed as a pair of
218// method calls s.IncRef(); s.DecRef(). The latter has the advantage
219// that the DecRefs are fungible and don't require holding anything in
220// addition to the refcounted object s, but paradoxically that is also
221// an advantage of the current approach, which forces the caller to
222// consider the release function at every stage, making a reference
223// leak more obvious.)
224func (s *snapshot) Acquire() func() {
225 type uP = unsafe.Pointer
226 if destroyedBy := atomic.LoadPointer((*uP)(uP(&s.destroyedBy))); destroyedBy != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500227 log.Panicf("%d: acquire() after Destroy(%q)", s.globalID, *(*string)(destroyedBy))
Robert Findleyb15dac22022-08-30 14:40:12 -0400228 }
229 s.refcount.Add(1)
230 return s.refcount.Done
231}
232
233func (s *snapshot) awaitPromise(ctx context.Context, p *memoize.Promise) (interface{}, error) {
234 return p.Get(ctx, s)
235}
236
237// destroy waits for all leases on the snapshot to expire then releases
238// any resources (reference counts and files) associated with it.
239// Snapshots being destroyed can be awaited using v.destroyWG.
240//
241// TODO(adonovan): move this logic into the release function returned
242// by Acquire when the reference count becomes zero. (This would cost
243// us the destroyedBy debug info, unless we add it to the signature of
244// memoize.RefCounted.Acquire.)
245//
246// The destroyedBy argument is used for debugging.
247//
248// v.snapshotMu must be held while calling this function, in order to preserve
cui fliter165099b2023-04-27 20:32:55 +0800249// the invariants described by the docstring for v.snapshot.
Robert Findleyb15dac22022-08-30 14:40:12 -0400250func (v *View) destroy(s *snapshot, destroyedBy string) {
251 v.snapshotWG.Add(1)
252 go func() {
253 defer v.snapshotWG.Done()
254 s.destroy(destroyedBy)
255 }()
256}
257
258func (s *snapshot) destroy(destroyedBy string) {
259 // Wait for all leases to end before commencing destruction.
260 s.refcount.Wait()
261
262 // Report bad state as a debugging aid.
263 // Not foolproof: another thread could acquire() at this moment.
264 type uP = unsafe.Pointer // looking forward to generics...
265 if old := atomic.SwapPointer((*uP)(uP(&s.destroyedBy)), uP(&destroyedBy)); old != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500266 log.Panicf("%d: Destroy(%q) after Destroy(%q)", s.globalID, destroyedBy, *(*string)(old))
Robert Findleyb15dac22022-08-30 14:40:12 -0400267 }
268
269 s.packages.Destroy()
Robert Findley21d22562023-02-21 12:26:27 -0500270 s.activePackages.Destroy()
Alan Donovan61e2d3f2022-10-14 17:58:15 -0400271 s.analyses.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400272 s.files.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400273 s.knownSubdirs.Destroy()
274 s.symbolizeHandles.Destroy()
275 s.parseModHandles.Destroy()
276 s.parseWorkHandles.Destroy()
277 s.modTidyHandles.Destroy()
Robert Findley7cda55e2022-11-22 12:09:11 -0500278 s.modVulnHandles.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400279 s.modWhyHandles.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400280}
281
Robert Findley0c71b562022-11-14 11:58:07 -0500282func (s *snapshot) SequenceID() uint64 {
283 return s.sequenceID
284}
285
286func (s *snapshot) GlobalID() source.GlobalSnapshotID {
287 return s.globalID
Robert Findleyb15dac22022-08-30 14:40:12 -0400288}
289
290func (s *snapshot) View() source.View {
291 return s.view
292}
293
294func (s *snapshot) BackgroundContext() context.Context {
295 return s.backgroundCtx
296}
297
Robert Findleyb15dac22022-08-30 14:40:12 -0400298func (s *snapshot) ModFiles() []span.URI {
299 var uris []span.URI
Robert Findley80879112022-12-16 09:11:12 -0500300 for modURI := range s.workspaceModFiles {
Robert Findleyb15dac22022-08-30 14:40:12 -0400301 uris = append(uris, modURI)
302 }
303 return uris
304}
305
306func (s *snapshot) WorkFile() span.URI {
Rob Findleya13793e2023-05-12 14:22:10 -0400307 gowork, _ := s.view.GOWORK()
308 return gowork
Robert Findleyb15dac22022-08-30 14:40:12 -0400309}
310
Robert Findleya7f033a2023-01-19 18:12:18 -0500311func (s *snapshot) Templates() map[span.URI]source.FileHandle {
Robert Findleyb15dac22022-08-30 14:40:12 -0400312 s.mu.Lock()
313 defer s.mu.Unlock()
314
Robert Findleya7f033a2023-01-19 18:12:18 -0500315 tmpls := map[span.URI]source.FileHandle{}
316 s.files.Range(func(k span.URI, fh source.FileHandle) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400317 if s.view.FileKind(fh) == source.Tmpl {
318 tmpls[k] = fh
319 }
320 })
321 return tmpls
322}
323
Rob Findleye5c8d4d2023-05-15 19:50:02 -0400324func (s *snapshot) validBuildConfiguration() bool {
Robert Findleyb15dac22022-08-30 14:40:12 -0400325 // Since we only really understand the `go` command, if the user has a
326 // different GOPACKAGESDRIVER, assume that their configuration is valid.
327 if s.view.hasGopackagesDriver {
328 return true
329 }
Rob Findley3d53c2d2023-05-15 20:03:00 -0400330
Robert Findleyb15dac22022-08-30 14:40:12 -0400331 // Check if the user is working within a module or if we have found
332 // multiple modules in the workspace.
Robert Findley80879112022-12-16 09:11:12 -0500333 if len(s.workspaceModFiles) > 0 {
Robert Findleyb15dac22022-08-30 14:40:12 -0400334 return true
335 }
Rob Findley3d53c2d2023-05-15 20:03:00 -0400336
Alan Donovan18f76ec2022-12-08 09:48:21 -0500337 // TODO(rfindley): this should probably be subject to "if GO111MODULES = off {...}".
Rob Findley3d53c2d2023-05-15 20:03:00 -0400338 if s.view.inGOPATH {
Robert Findley80879112022-12-16 09:11:12 -0500339 return true
Robert Findley80879112022-12-16 09:11:12 -0500340 }
Rob Findley3d53c2d2023-05-15 20:03:00 -0400341
342 return false
Robert Findley80879112022-12-16 09:11:12 -0500343}
344
Robert Findleyb15dac22022-08-30 14:40:12 -0400345// workspaceMode describes the way in which the snapshot's workspace should
346// be loaded.
Robert Findley80879112022-12-16 09:11:12 -0500347//
348// TODO(rfindley): remove this, in favor of specific methods.
Robert Findleyb15dac22022-08-30 14:40:12 -0400349func (s *snapshot) workspaceMode() workspaceMode {
350 var mode workspaceMode
351
352 // If the view has an invalid configuration, don't build the workspace
353 // module.
Rob Findleye5c8d4d2023-05-15 19:50:02 -0400354 validBuildConfiguration := s.validBuildConfiguration()
Robert Findleyb15dac22022-08-30 14:40:12 -0400355 if !validBuildConfiguration {
356 return mode
357 }
358 // If the view is not in a module and contains no modules, but still has a
359 // valid workspace configuration, do not create the workspace module.
360 // It could be using GOPATH or a different build system entirely.
Robert Findley80879112022-12-16 09:11:12 -0500361 if len(s.workspaceModFiles) == 0 && validBuildConfiguration {
Robert Findleyb15dac22022-08-30 14:40:12 -0400362 return mode
363 }
364 mode |= moduleMode
365 options := s.view.Options()
Robert Findley16b3bf82023-01-06 11:35:54 -0500366 if options.TempModfile {
Robert Findleyb15dac22022-08-30 14:40:12 -0400367 mode |= tempModfile
368 }
369 return mode
370}
371
372// config returns the configuration used for the snapshot's interaction with
373// the go/packages API. It uses the given working directory.
374//
375// TODO(rstambler): go/packages requires that we do not provide overlays for
376// multiple modules in on config, so buildOverlay needs to filter overlays by
377// module.
378func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
379 s.view.optionsMu.Lock()
380 verboseOutput := s.view.options.VerboseOutput
381 s.view.optionsMu.Unlock()
382
383 cfg := &packages.Config{
384 Context: ctx,
385 Dir: inv.WorkingDir,
386 Env: inv.Env,
387 BuildFlags: inv.BuildFlags,
388 Mode: packages.NeedName |
389 packages.NeedFiles |
390 packages.NeedCompiledGoFiles |
391 packages.NeedImports |
392 packages.NeedDeps |
393 packages.NeedTypesSizes |
394 packages.NeedModule |
Frank Viernau3ec30bd2023-02-22 21:48:57 +0000395 packages.NeedEmbedFiles |
Robert Findleyb15dac22022-08-30 14:40:12 -0400396 packages.LoadMode(packagesinternal.DepsErrors) |
397 packages.LoadMode(packagesinternal.ForTest),
Alan Donovan6f993662022-11-10 15:07:47 -0500398 Fset: nil, // we do our own parsing
Robert Findleyb15dac22022-08-30 14:40:12 -0400399 Overlay: s.buildOverlay(),
400 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
401 panic("go/packages must not be used to parse files")
402 },
403 Logf: func(format string, args ...interface{}) {
404 if verboseOutput {
405 event.Log(ctx, fmt.Sprintf(format, args...))
406 }
407 },
408 Tests: true,
409 }
410 packagesinternal.SetModFile(cfg, inv.ModFile)
411 packagesinternal.SetModFlag(cfg, inv.ModFlag)
412 // We want to type check cgo code if go/types supports it.
413 if typesinternal.SetUsesCgo(&types.Config{}) {
414 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
415 }
Robert Findleye8a70a52022-11-28 14:58:06 -0500416 packagesinternal.SetGoCmdRunner(cfg, s.view.gocmdRunner)
Robert Findleyb15dac22022-08-30 14:40:12 -0400417 return cfg
418}
419
420func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
421 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
422 if err != nil {
423 return nil, err
424 }
425 defer cleanup()
426
Robert Findleye8a70a52022-11-28 14:58:06 -0500427 return s.view.gocmdRunner.Run(ctx, *inv)
Robert Findleyb15dac22022-08-30 14:40:12 -0400428}
429
430func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
431 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
432 if err != nil {
433 return err
434 }
435 defer cleanup()
Robert Findleye8a70a52022-11-28 14:58:06 -0500436 return s.view.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
Robert Findleyb15dac22022-08-30 14:40:12 -0400437}
438
439func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) {
440 var flags source.InvocationFlags
441 if s.workspaceMode()&tempModfile != 0 {
442 flags = source.WriteTemporaryModFile
443 } else {
444 flags = source.Normal
445 }
446 if allowNetwork {
447 flags |= source.AllowNetwork
448 }
449 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd})
450 if err != nil {
451 return false, nil, nil, err
452 }
453 defer cleanup()
454 invoke := func(args ...string) (*bytes.Buffer, error) {
455 inv.Verb = args[0]
456 inv.Args = args[1:]
Robert Findleye8a70a52022-11-28 14:58:06 -0500457 return s.view.gocmdRunner.Run(ctx, *inv)
Robert Findleyb15dac22022-08-30 14:40:12 -0400458 }
459 if err := run(invoke); err != nil {
460 return false, nil, nil, err
461 }
462 if flags.Mode() != source.WriteTemporaryModFile {
463 return false, nil, nil, nil
464 }
465 var modBytes, sumBytes []byte
466 modBytes, err = ioutil.ReadFile(tmpURI.Filename())
467 if err != nil && !os.IsNotExist(err) {
468 return false, nil, nil, err
469 }
470 sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum")
471 if err != nil && !os.IsNotExist(err) {
472 return false, nil, nil, err
473 }
474 return true, modBytes, sumBytes, nil
475}
476
477// goCommandInvocation populates inv with configuration for running go commands on the snapshot.
478//
479// TODO(rfindley): refactor this function to compose the required configuration
480// explicitly, rather than implicitly deriving it from flags and inv.
481//
482// TODO(adonovan): simplify cleanup mechanism. It's hard to see, but
483// it used only after call to tempModFile. Clarify that it is only
484// non-nil on success.
485func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
486 s.view.optionsMu.Lock()
487 allowModfileModificationOption := s.view.options.AllowModfileModifications
488 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
489
490 // TODO(rfindley): this is very hard to follow, and may not even be doing the
491 // right thing: should inv.Env really trample view.options? Do we ever invoke
492 // this with a non-empty inv.Env?
493 //
494 // We should refactor to make it clearer that the correct env is being used.
Robert Findleyc7ed4b32022-11-16 13:52:25 -0500495 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.GO111MODULE())
Robert Findleyb15dac22022-08-30 14:40:12 -0400496 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
497 s.view.optionsMu.Unlock()
498 cleanup = func() {} // fallback
499
500 // All logic below is for module mode.
501 if s.workspaceMode()&moduleMode == 0 {
502 return "", inv, cleanup, nil
503 }
504
505 mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
506 if !allowNetwork && !allowNetworkOption {
507 inv.Env = append(inv.Env, "GOPROXY=off")
508 }
509
510 // What follows is rather complicated logic for how to actually run the go
511 // command. A word of warning: this is the result of various incremental
512 // features added to gopls, and varying behavior of the Go command across Go
513 // versions. It can surely be cleaned up significantly, but tread carefully.
514 //
515 // Roughly speaking we need to resolve four things:
516 // - the working directory.
517 // - the -mod flag
518 // - the -modfile flag
519 //
520 // These are dependent on a number of factors: whether we need to run in a
521 // synthetic workspace, whether flags are supported at the current go
522 // version, and what we're actually trying to achieve (the
523 // source.InvocationFlags).
524
525 var modURI span.URI
526 // Select the module context to use.
527 // If we're type checking, we need to use the workspace context, meaning
528 // the main (workspace) module. Otherwise, we should use the module for
529 // the passed-in working dir.
530 if mode == source.LoadWorkspace {
Rob Findleya13793e2023-05-12 14:22:10 -0400531 if gowork, _ := s.view.GOWORK(); gowork == "" && s.view.gomod != "" {
Robert Findley80879112022-12-16 09:11:12 -0500532 modURI = s.view.gomod
Robert Findleyb15dac22022-08-30 14:40:12 -0400533 }
534 } else {
535 modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir))
536 }
537
538 var modContent []byte
539 if modURI != "" {
Alan Donovan36ed0b12023-03-13 14:20:23 -0400540 modFH, err := s.ReadFile(ctx, modURI)
Robert Findleyb15dac22022-08-30 14:40:12 -0400541 if err != nil {
542 return "", nil, cleanup, err
543 }
Alan Donovan786752b2023-03-07 12:14:28 -0500544 modContent, err = modFH.Content()
Robert Findleyb15dac22022-08-30 14:40:12 -0400545 if err != nil {
546 return "", nil, cleanup, err
547 }
548 }
549
550 // TODO(rfindley): in the case of go.work mode, modURI is empty and we fall
551 // back on the default behavior of vendorEnabled with an empty modURI. Figure
552 // out what is correct here and implement it explicitly.
553 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent)
554 if err != nil {
555 return "", nil, cleanup, err
556 }
557
Robert Findley16b3bf82023-01-06 11:35:54 -0500558 const mutableModFlag = "mod"
Robert Findleyb15dac22022-08-30 14:40:12 -0400559 // If the mod flag isn't set, populate it based on the mode and workspace.
Robert Findley80879112022-12-16 09:11:12 -0500560 // TODO(rfindley): this doesn't make sense if we're not in module mode
Robert Findleyb15dac22022-08-30 14:40:12 -0400561 if inv.ModFlag == "" {
Robert Findleyb15dac22022-08-30 14:40:12 -0400562 switch mode {
563 case source.LoadWorkspace, source.Normal:
564 if vendorEnabled {
565 inv.ModFlag = "vendor"
566 } else if !allowModfileModificationOption {
567 inv.ModFlag = "readonly"
568 } else {
569 inv.ModFlag = mutableModFlag
570 }
571 case source.WriteTemporaryModFile:
572 inv.ModFlag = mutableModFlag
573 // -mod must be readonly when using go.work files - see issue #48941
574 inv.Env = append(inv.Env, "GOWORK=off")
575 }
576 }
577
578 // Only use a temp mod file if the modfile can actually be mutated.
579 needTempMod := inv.ModFlag == mutableModFlag
580 useTempMod := s.workspaceMode()&tempModfile != 0
581 if needTempMod && !useTempMod {
582 return "", nil, cleanup, source.ErrTmpModfileUnsupported
583 }
584
585 // We should use -modfile if:
586 // - the workspace mode supports it
587 // - we're using a go.work file on go1.18+, or we need a temp mod file (for
588 // example, if running go mod tidy in a go.work workspace)
589 //
590 // TODO(rfindley): this is very hard to follow. Refactor.
Robert Findley80879112022-12-16 09:11:12 -0500591 if !needTempMod && s.view.gowork != "" {
Robert Findleyb15dac22022-08-30 14:40:12 -0400592 // Since we're running in the workspace root, the go command will resolve GOWORK automatically.
593 } else if useTempMod {
594 if modURI == "" {
595 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
596 }
Alan Donovan36ed0b12023-03-13 14:20:23 -0400597 modFH, err := s.ReadFile(ctx, modURI)
Robert Findleyb15dac22022-08-30 14:40:12 -0400598 if err != nil {
599 return "", nil, cleanup, err
600 }
601 // Use the go.sum if it happens to be available.
602 gosum := s.goSum(ctx, modURI)
603 tmpURI, cleanup, err = tempModFile(modFH, gosum)
604 if err != nil {
605 return "", nil, cleanup, err
606 }
607 inv.ModFile = tmpURI.Filename()
608 }
609
610 return tmpURI, inv, cleanup, nil
611}
612
Robert Findleyb15dac22022-08-30 14:40:12 -0400613func (s *snapshot) buildOverlay() map[string][]byte {
Robert Findley488ba862023-03-23 12:18:06 -0400614 overlays := make(map[string][]byte)
615 for _, overlay := range s.overlays() {
616 if overlay.saved {
617 continue
618 }
619 // TODO(rfindley): previously, there was a todo here to make sure we don't
620 // send overlays outside of the current view. IMO we should instead make
621 // sure this doesn't matter.
622 overlays[overlay.URI().Filename()] = overlay.content
623 }
624 return overlays
625}
626
Robert Findley488ba862023-03-23 12:18:06 -0400627func (s *snapshot) overlays() []*Overlay {
Robert Findleyb15dac22022-08-30 14:40:12 -0400628 s.mu.Lock()
629 defer s.mu.Unlock()
630
Rob Findleya5ef6c32023-05-19 14:43:24 -0400631 return s.files.overlays()
Robert Findleyb15dac22022-08-30 14:40:12 -0400632}
633
Robert Findley21d22562023-02-21 12:26:27 -0500634// Package data kinds, identifying various package data that may be stored in
635// the file cache.
636const (
637 xrefsKind = "xrefs"
638 methodSetsKind = "methodsets"
639 exportDataKind = "export"
640 diagnosticsKind = "diagnostics"
Robert Findley1b2d1bd2023-03-16 18:52:13 -0400641 typerefsKind = "typerefs"
Robert Findley21d22562023-02-21 12:26:27 -0500642)
Alan Donovan1dcc4232022-12-06 13:27:13 -0500643
Robert Findley21d22562023-02-21 12:26:27 -0500644func (s *snapshot) PackageDiagnostics(ctx context.Context, ids ...PackageID) (map[span.URI][]*source.Diagnostic, error) {
Robert Findleyf7963612023-03-28 21:34:10 -0400645 ctx, done := event.Start(ctx, "cache.snapshot.PackageDiagnostics")
646 defer done()
647
Robert Findleyacaae982023-03-16 12:21:36 -0400648 var mu sync.Mutex
Robert Findley21d22562023-02-21 12:26:27 -0500649 perFile := make(map[span.URI][]*source.Diagnostic)
Robert Findleyacaae982023-03-16 12:21:36 -0400650 collect := func(diags []*source.Diagnostic) {
651 mu.Lock()
652 defer mu.Unlock()
653 for _, diag := range diags {
654 perFile[diag.URI] = append(perFile[diag.URI], diag)
Alan Donovan1dcc4232022-12-06 13:27:13 -0500655 }
Alan Donovan1dcc4232022-12-06 13:27:13 -0500656 }
Robert Findleyacaae982023-03-16 12:21:36 -0400657 pre := func(i int, ph *packageHandle) bool {
658 data, err := filecache.Get(diagnosticsKind, ph.key)
659 if err == nil { // hit
660 collect(ph.m.Diagnostics)
661 collect(decodeDiagnostics(data))
662 return false
663 } else if err != filecache.ErrNotFound {
664 event.Error(ctx, "reading diagnostics from filecache", err)
665 }
666 return true
667 }
668 post := func(_ int, pkg *Package) {
Robert Findley1b2d1bd2023-03-16 18:52:13 -0400669 collect(pkg.ph.m.Diagnostics)
Robert Findleyacaae982023-03-16 12:21:36 -0400670 collect(pkg.pkg.diagnostics)
671 }
672 return perFile, s.forEachPackage(ctx, ids, pre, post)
Robert Findley21d22562023-02-21 12:26:27 -0500673}
Alan Donovan1dcc4232022-12-06 13:27:13 -0500674
Robert Findley21d22562023-02-21 12:26:27 -0500675func (s *snapshot) References(ctx context.Context, ids ...PackageID) ([]source.XrefIndex, error) {
Robert Findleyf7963612023-03-28 21:34:10 -0400676 ctx, done := event.Start(ctx, "cache.snapshot.References")
677 defer done()
678
Robert Findley21d22562023-02-21 12:26:27 -0500679 indexes := make([]source.XrefIndex, len(ids))
Robert Findleyacaae982023-03-16 12:21:36 -0400680 pre := func(i int, ph *packageHandle) bool {
681 data, err := filecache.Get(xrefsKind, ph.key)
682 if err == nil { // hit
683 indexes[i] = XrefIndex{m: ph.m, data: data}
684 return false
685 } else if err != filecache.ErrNotFound {
686 event.Error(ctx, "reading xrefs from filecache", err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400687 }
Robert Findleyacaae982023-03-16 12:21:36 -0400688 return true
Robert Findleyb15dac22022-08-30 14:40:12 -0400689 }
Robert Findleyacaae982023-03-16 12:21:36 -0400690 post := func(i int, pkg *Package) {
Robert Findley1b2d1bd2023-03-16 18:52:13 -0400691 indexes[i] = XrefIndex{m: pkg.ph.m, data: pkg.pkg.xrefs}
Robert Findleyacaae982023-03-16 12:21:36 -0400692 }
693 return indexes, s.forEachPackage(ctx, ids, pre, post)
Robert Findley21d22562023-02-21 12:26:27 -0500694}
Robert Findleyf10e7d52023-01-12 13:51:49 -0500695
Robert Findley21d22562023-02-21 12:26:27 -0500696// An XrefIndex is a helper for looking up a package in a given package.
697type XrefIndex struct {
698 m *source.Metadata
699 data []byte
700}
701
702func (index XrefIndex) Lookup(targets map[PackagePath]map[objectpath.Path]struct{}) []protocol.Location {
703 return xrefs.Lookup(index.m, index.data, targets)
704}
705
706func (s *snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methodsets.Index, error) {
Robert Findleyf7963612023-03-28 21:34:10 -0400707 ctx, done := event.Start(ctx, "cache.snapshot.MethodSets")
708 defer done()
709
Robert Findley21d22562023-02-21 12:26:27 -0500710 indexes := make([]*methodsets.Index, len(ids))
Robert Findleyacaae982023-03-16 12:21:36 -0400711 pre := func(i int, ph *packageHandle) bool {
712 data, err := filecache.Get(methodSetsKind, ph.key)
713 if err == nil { // hit
714 indexes[i] = methodsets.Decode(data)
715 return false
716 } else if err != filecache.ErrNotFound {
717 event.Error(ctx, "reading methodsets from filecache", err)
Robert Findley21d22562023-02-21 12:26:27 -0500718 }
Robert Findleyacaae982023-03-16 12:21:36 -0400719 return true
Robert Findley21d22562023-02-21 12:26:27 -0500720 }
Robert Findleyacaae982023-03-16 12:21:36 -0400721 post := func(i int, pkg *Package) {
722 indexes[i] = pkg.pkg.methodsets
723 }
724 return indexes, s.forEachPackage(ctx, ids, pre, post)
Robert Findleyb15dac22022-08-30 14:40:12 -0400725}
726
Alan Donovanff22fab2022-11-18 14:47:36 -0500727func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
Rob Findley3d53c2d2023-05-15 20:03:00 -0400728 if s.view.ViewType() == AdHocView {
729 // As described in golang/go#57209, in ad-hoc workspaces (where we load ./
730 // rather than ./...), preempting the directory load with file loads can
731 // lead to an inconsistent outcome, where certain files are loaded with
732 // command-line-arguments packages and others are loaded only in the ad-hoc
733 // package. Therefore, ensure that the workspace is loaded before doing any
734 // file loads.
735 if err := s.awaitLoaded(ctx); err != nil {
736 return nil, err
737 }
738 }
739
Robert Findleyb15dac22022-08-30 14:40:12 -0400740 s.mu.Lock()
Robert Findleyb2533142022-10-10 13:50:45 -0400741
742 // Start with the set of package associations derived from the last load.
Robert Findleyb15dac22022-08-30 14:40:12 -0400743 ids := s.meta.ids[uri]
Robert Findleyb2533142022-10-10 13:50:45 -0400744
Robert Findleyb2533142022-10-10 13:50:45 -0400745 shouldLoad := false // whether any packages containing uri are marked 'shouldLoad'
Robert Findleyb15dac22022-08-30 14:40:12 -0400746 for _, id := range ids {
Robert Findleyb2533142022-10-10 13:50:45 -0400747 if len(s.shouldLoad[id]) > 0 {
748 shouldLoad = true
749 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400750 }
Robert Findleyb2533142022-10-10 13:50:45 -0400751
752 // Check if uri is known to be unloadable.
Robert Findleyb2533142022-10-10 13:50:45 -0400753 _, unloadable := s.unloadableFiles[uri]
754
Robert Findleyb15dac22022-08-30 14:40:12 -0400755 s.mu.Unlock()
756
Robert Findleyb2533142022-10-10 13:50:45 -0400757 // Reload if loading is likely to improve the package associations for uri:
758 // - uri is not contained in any valid packages
759 // - ...or one of the packages containing uri is marked 'shouldLoad'
760 // - ...but uri is not unloadable
Robert Findley23056f62022-11-03 19:06:31 -0400761 if (shouldLoad || len(ids) == 0) && !unloadable {
Robert Findleyb2533142022-10-10 13:50:45 -0400762 scope := fileLoadScope(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -0400763 err := s.load(ctx, false, scope)
764
Robert Findleyb2533142022-10-10 13:50:45 -0400765 // Guard against failed loads due to context cancellation.
Robert Findleyb15dac22022-08-30 14:40:12 -0400766 //
Robert Findleyb2533142022-10-10 13:50:45 -0400767 // Return the context error here as the current operation is no longer
768 // valid.
769 if ctxErr := ctx.Err(); ctxErr != nil {
770 return nil, ctxErr
Robert Findleyb15dac22022-08-30 14:40:12 -0400771 }
772
Robert Findleyb2533142022-10-10 13:50:45 -0400773 // We must clear scopes after loading.
774 //
775 // TODO(rfindley): unlike reloadWorkspace, this is simply marking loaded
776 // packages as loaded. We could do this from snapshot.load and avoid
777 // raciness.
778 s.clearShouldLoad(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -0400779
Robert Findleyb2533142022-10-10 13:50:45 -0400780 // Don't return an error here, as we may still return stale IDs.
Alan Donovanff22fab2022-11-18 14:47:36 -0500781 // Furthermore, the result of MetadataForFile should be consistent upon
Robert Findleyb2533142022-10-10 13:50:45 -0400782 // subsequent calls, even if the file is marked as unloadable.
783 if err != nil && !errors.Is(err, errNoPackages) {
Alan Donovanff22fab2022-11-18 14:47:36 -0500784 event.Error(ctx, "MetadataForFile", err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400785 }
786 }
787
Alan Donovanff22fab2022-11-18 14:47:36 -0500788 // Retrieve the metadata.
Robert Findleyb2533142022-10-10 13:50:45 -0400789 s.mu.Lock()
Alan Donovanff22fab2022-11-18 14:47:36 -0500790 defer s.mu.Unlock()
Robert Findleyb2533142022-10-10 13:50:45 -0400791 ids = s.meta.ids[uri]
Alan Donovanff22fab2022-11-18 14:47:36 -0500792 metas := make([]*source.Metadata, len(ids))
793 for i, id := range ids {
794 metas[i] = s.meta.metadata[id]
795 if metas[i] == nil {
796 panic("nil metadata")
797 }
798 }
799 // Metadata is only ever added by loading,
800 // so if we get here and still have
801 // no IDs, uri is unloadable.
Robert Findley23056f62022-11-03 19:06:31 -0400802 if !unloadable && len(ids) == 0 {
803 s.unloadableFiles[uri] = struct{}{}
Robert Findleyb2533142022-10-10 13:50:45 -0400804 }
Alan Donovan1dcc4232022-12-06 13:27:13 -0500805
Alan Donovanb35949e2023-04-20 14:53:41 -0400806 // Sort packages "narrowest" to "widest" (in practice:
807 // non-tests before tests), and regular packages before
808 // their intermediate test variants (which have the same
809 // files but different imports).
Alan Donovan1dcc4232022-12-06 13:27:13 -0500810 sort.Slice(metas, func(i, j int) bool {
Alan Donovanb35949e2023-04-20 14:53:41 -0400811 x, y := metas[i], metas[j]
812 xfiles, yfiles := len(x.CompiledGoFiles), len(y.CompiledGoFiles)
813 if xfiles != yfiles {
814 return xfiles < yfiles
815 }
816 return boolLess(x.IsIntermediateTestVariant(), y.IsIntermediateTestVariant())
Alan Donovan1dcc4232022-12-06 13:27:13 -0500817 })
818
Alan Donovanff22fab2022-11-18 14:47:36 -0500819 return metas, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400820}
821
Alan Donovanb35949e2023-04-20 14:53:41 -0400822func boolLess(x, y bool) bool { return !x && y } // false < true
823
Alan Donovan44395ff2022-12-21 12:13:36 -0500824func (s *snapshot) ReverseDependencies(ctx context.Context, id PackageID, transitive bool) (map[PackageID]*source.Metadata, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400825 if err := s.awaitLoaded(ctx); err != nil {
826 return nil, err
827 }
828 s.mu.Lock()
829 meta := s.meta
830 s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -0400831
Alan Donovan44395ff2022-12-21 12:13:36 -0500832 var rdeps map[PackageID]*source.Metadata
833 if transitive {
834 rdeps = meta.reverseReflexiveTransitiveClosure(id)
835
836 // Remove the original package ID from the map.
837 // (Callers all want irreflexivity but it's easier
838 // to compute reflexively then subtract.)
839 delete(rdeps, id)
840
841 } else {
842 // direct reverse dependencies
843 rdeps = make(map[PackageID]*source.Metadata)
844 for _, rdepID := range meta.importedBy[id] {
845 if rdep := meta.metadata[rdepID]; rdep != nil {
846 rdeps[rdepID] = rdep
847 }
848 }
849 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400850
Alan Donovanb2b9dc32022-12-12 11:31:59 -0500851 return rdeps, nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400852}
853
Robert Findley21d22562023-02-21 12:26:27 -0500854// -- Active package tracking --
855//
Alan Donovane8f417a2023-04-21 14:54:28 -0400856// We say a package is "active" if any of its files are open.
857// This is an optimization: the "active" concept is an
858// implementation detail of the cache and is not exposed
859// in the source or Snapshot API.
860// After type-checking we keep active packages in memory.
861// The activePackages persistent map does bookkeeping for
862// the set of active packages.
Robert Findley21d22562023-02-21 12:26:27 -0500863
864// getActivePackage returns a the memoized active package for id, if it exists.
865// If id is not active or has not yet been type-checked, it returns nil.
866func (s *snapshot) getActivePackage(id PackageID) *Package {
867 s.mu.Lock()
868 defer s.mu.Unlock()
869
870 if value, ok := s.activePackages.Get(id); ok {
871 return value.(*Package) // possibly nil, if we have already checked this id.
Robert Findleyb15dac22022-08-30 14:40:12 -0400872 }
Robert Findley21d22562023-02-21 12:26:27 -0500873 return nil
Robert Findleyb15dac22022-08-30 14:40:12 -0400874}
875
Robert Findley21d22562023-02-21 12:26:27 -0500876// memoizeActivePackage checks if pkg is active, and if so either records it in
877// the active packages map or returns the existing memoized active package for id.
878//
879// The resulting package is non-nil if and only if the specified package is open.
880func (s *snapshot) memoizeActivePackage(id PackageID, pkg *Package) (active *Package) {
881 s.mu.Lock()
882 defer s.mu.Unlock()
883
884 if value, ok := s.activePackages.Get(id); ok {
885 return value.(*Package) // possibly nil, if we have already checked this id.
886 }
887
888 defer func() {
889 s.activePackages.Set(id, active, nil) // store the result either way: remember that pkg is not open
890 }()
Rob Findley1c9fe3f2023-05-11 14:33:05 -0400891
892 if containsOpenFileLocked(s, pkg.Metadata()) {
893 return pkg
Robert Findley21d22562023-02-21 12:26:27 -0500894 }
895 return nil
896}
897
898func (s *snapshot) resetActivePackagesLocked() {
899 s.activePackages.Destroy()
900 s.activePackages = persistent.NewMap(packageIDLessInterface)
Robert Findleyb15dac22022-08-30 14:40:12 -0400901}
902
903const fileExtensions = "go,mod,sum,work"
904
905func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
906 extensions := fileExtensions
907 for _, ext := range s.View().Options().TemplateExtensions {
908 extensions += "," + ext
909 }
910 // Work-around microsoft/vscode#100870 by making sure that we are,
911 // at least, watching the user's entire workspace. This will still be
912 // applied to every folder in the workspace.
913 patterns := map[string]struct{}{
914 fmt.Sprintf("**/*.{%s}", extensions): {},
915 }
916
Robert Findley5b300bd2022-12-29 13:39:59 -0500917 // If GOWORK is outside the folder, ensure we are watching it.
Rob Findleya13793e2023-05-12 14:22:10 -0400918 gowork, _ := s.view.GOWORK()
Robert Findley5b300bd2022-12-29 13:39:59 -0500919 if gowork != "" && !source.InDir(s.view.folder.Filename(), gowork.Filename()) {
920 patterns[gowork.Filename()] = struct{}{}
Robert Findleyb15dac22022-08-30 14:40:12 -0400921 }
922
923 // Add a pattern for each Go module in the workspace that is not within the view.
Robert Findley80879112022-12-16 09:11:12 -0500924 dirs := s.dirs(ctx)
Robert Findleyb15dac22022-08-30 14:40:12 -0400925 for _, dir := range dirs {
926 dirName := dir.Filename()
927
928 // If the directory is within the view's folder, we're already watching
Alan Donovane0b516b2022-11-28 23:41:43 -0500929 // it with the first pattern above.
Robert Findleyb15dac22022-08-30 14:40:12 -0400930 if source.InDir(s.view.folder.Filename(), dirName) {
931 continue
932 }
933 // TODO(rstambler): If microsoft/vscode#3025 is resolved before
934 // microsoft/vscode#101042, we will need a work-around for Windows
935 // drive letter casing.
936 patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{}
937 }
938
Alan Donovan2ec42992023-05-18 13:45:43 -0400939 // Some clients (e.g. VSCode) do not send notifications for
940 // changes to directories that contain Go code (golang/go#42348).
941 // To handle this, explicitly watch all of the directories in
942 // the workspace. We find them by adding the directories of
943 // every file in the snapshot's workspace directories.
944 // There may be thousands of patterns, each a single directory.
945 //
946 // (A previous iteration created a single glob pattern holding a
947 // union of all the directories, but this was found to cause
948 // VSCode to get stuck for several minutes after a buffer was
949 // saved twice in a workspace that had >8000 watched directories.)
950 //
951 // Some clients (notably coc.nvim, which uses watchman for
952 // globs) perform poorly with a large list of individual
953 // directories, though they work fine with one large
954 // comma-separated element. Sadly no one size fits all, so we
955 // may have to resort to sniffing the client to determine the
956 // best behavior, though that would set a poor precedent.
957 // TODO(adonovan): improve the nvim situation.
958 s.addKnownSubdirs(patterns, dirs)
Robert Findleyb15dac22022-08-30 14:40:12 -0400959
960 return patterns
961}
962
Alan Donovan2ec42992023-05-18 13:45:43 -0400963func (s *snapshot) addKnownSubdirs(patterns map[string]struct{}, wsDirs []span.URI) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400964 s.mu.Lock()
965 defer s.mu.Unlock()
966
967 // First, process any pending changes and update the set of known
968 // subdirectories.
969 // It may change list of known subdirs and therefore invalidate the cache.
970 s.applyKnownSubdirsChangesLocked(wsDirs)
971
Alan Donovan2ec42992023-05-18 13:45:43 -0400972 // TODO(adonovan): is it still necessary to memoize the Range
973 // and URI.Filename operations?
974 if s.knownSubdirsCache == nil {
975 s.knownSubdirsCache = make(map[string]struct{})
Robert Findleyb15dac22022-08-30 14:40:12 -0400976 s.knownSubdirs.Range(func(uri span.URI) {
Alan Donovan2ec42992023-05-18 13:45:43 -0400977 s.knownSubdirsCache[uri.Filename()] = struct{}{}
Robert Findleyb15dac22022-08-30 14:40:12 -0400978 })
Robert Findleyb15dac22022-08-30 14:40:12 -0400979 }
980
Alan Donovan2ec42992023-05-18 13:45:43 -0400981 for pattern := range s.knownSubdirsCache {
982 patterns[pattern] = struct{}{}
983 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400984}
985
986// collectAllKnownSubdirs collects all of the subdirectories within the
987// snapshot's workspace directories. None of the workspace directories are
988// included.
989func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) {
Robert Findley80879112022-12-16 09:11:12 -0500990 dirs := s.dirs(ctx)
Robert Findleyb15dac22022-08-30 14:40:12 -0400991
992 s.mu.Lock()
993 defer s.mu.Unlock()
994
995 s.knownSubdirs.Destroy()
996 s.knownSubdirs = newKnownDirsSet()
Alan Donovan2ec42992023-05-18 13:45:43 -0400997 s.knownSubdirsCache = nil
Robert Findleya7f033a2023-01-19 18:12:18 -0500998 s.files.Range(func(uri span.URI, fh source.FileHandle) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400999 s.addKnownSubdirLocked(uri, dirs)
1000 })
1001}
1002
1003func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) knownDirsSet {
1004 s.mu.Lock()
1005 defer s.mu.Unlock()
1006
1007 // First, process any pending changes and update the set of known
1008 // subdirectories.
1009 s.applyKnownSubdirsChangesLocked(wsDirs)
1010
1011 return s.knownSubdirs.Clone()
1012}
1013
1014func (s *snapshot) applyKnownSubdirsChangesLocked(wsDirs []span.URI) {
1015 for _, c := range s.unprocessedSubdirChanges {
1016 if c.isUnchanged {
1017 continue
1018 }
1019 if !c.exists {
1020 s.removeKnownSubdirLocked(c.fileHandle.URI())
1021 } else {
1022 s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs)
1023 }
1024 }
1025 s.unprocessedSubdirChanges = nil
1026}
1027
1028func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) {
1029 dir := filepath.Dir(uri.Filename())
1030 // First check if the directory is already known, because then we can
1031 // return early.
1032 if s.knownSubdirs.Contains(span.URIFromPath(dir)) {
1033 return
1034 }
1035 var matched span.URI
1036 for _, wsDir := range dirs {
1037 if source.InDir(wsDir.Filename(), dir) {
1038 matched = wsDir
1039 break
1040 }
1041 }
1042 // Don't watch any directory outside of the workspace directories.
1043 if matched == "" {
1044 return
1045 }
1046 for {
1047 if dir == "" || dir == matched.Filename() {
1048 break
1049 }
1050 uri := span.URIFromPath(dir)
1051 if s.knownSubdirs.Contains(uri) {
1052 break
1053 }
1054 s.knownSubdirs.Insert(uri)
1055 dir = filepath.Dir(dir)
Alan Donovan2ec42992023-05-18 13:45:43 -04001056 s.knownSubdirsCache = nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001057 }
1058}
1059
1060func (s *snapshot) removeKnownSubdirLocked(uri span.URI) {
1061 dir := filepath.Dir(uri.Filename())
1062 for dir != "" {
1063 uri := span.URIFromPath(dir)
1064 if !s.knownSubdirs.Contains(uri) {
1065 break
1066 }
1067 if info, _ := os.Stat(dir); info == nil {
1068 s.knownSubdirs.Remove(uri)
Alan Donovan2ec42992023-05-18 13:45:43 -04001069 s.knownSubdirsCache = nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001070 }
1071 dir = filepath.Dir(dir)
1072 }
1073}
1074
1075// knownFilesInDir returns the files known to the given snapshot that are in
1076// the given directory. It does not respect symlinks.
1077func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI {
1078 var files []span.URI
1079 s.mu.Lock()
1080 defer s.mu.Unlock()
1081
Robert Findleya7f033a2023-01-19 18:12:18 -05001082 s.files.Range(func(uri span.URI, fh source.FileHandle) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001083 if source.InDir(dir.Filename(), uri.Filename()) {
1084 files = append(files, uri)
1085 }
1086 })
1087 return files
1088}
1089
Alan Donovane8f417a2023-04-21 14:54:28 -04001090func (s *snapshot) WorkspaceMetadata(ctx context.Context) ([]*source.Metadata, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001091 if err := s.awaitLoaded(ctx); err != nil {
1092 return nil, err
1093 }
Alan Donovane8f417a2023-04-21 14:54:28 -04001094
1095 s.mu.Lock()
1096 defer s.mu.Unlock()
1097
1098 meta := make([]*source.Metadata, 0, len(s.workspacePackages))
1099 for id := range s.workspacePackages {
1100 meta = append(meta, s.meta.metadata[id])
1101 }
1102 return meta, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001103}
1104
Robert Findleye5b99482023-02-15 22:26:54 -05001105// Symbols extracts and returns symbol information for every file contained in
1106// a loaded package. It awaits snapshot loading.
1107//
1108// TODO(rfindley): move this to the top of cache/symbols.go
Rob Findley8e9b1852023-05-01 12:44:43 -04001109func (s *snapshot) Symbols(ctx context.Context, workspaceOnly bool) (map[span.URI][]source.Symbol, error) {
Robert Findleye5b99482023-02-15 22:26:54 -05001110 if err := s.awaitLoaded(ctx); err != nil {
1111 return nil, err
1112 }
1113
Rob Findley8e9b1852023-05-01 12:44:43 -04001114 var (
1115 meta []*source.Metadata
1116 err error
1117 )
1118 if workspaceOnly {
1119 meta, err = s.WorkspaceMetadata(ctx)
1120 } else {
1121 meta, err = s.AllMetadata(ctx)
1122 }
1123 if err != nil {
1124 return nil, fmt.Errorf("loading metadata: %v", err)
1125 }
Alan Donovan19fb30d2022-11-18 16:29:11 -05001126
Robert Findleye5b99482023-02-15 22:26:54 -05001127 goFiles := make(map[span.URI]struct{})
Rob Findley8e9b1852023-05-01 12:44:43 -04001128 for _, m := range meta {
Robert Findleye5b99482023-02-15 22:26:54 -05001129 for _, uri := range m.GoFiles {
1130 goFiles[uri] = struct{}{}
1131 }
1132 for _, uri := range m.CompiledGoFiles {
1133 goFiles[uri] = struct{}{}
1134 }
1135 }
1136
Alan Donovan19fb30d2022-11-18 16:29:11 -05001137 // Symbolize them in parallel.
Robert Findleyb15dac22022-08-30 14:40:12 -04001138 var (
1139 group errgroup.Group
Alan Donovan19fb30d2022-11-18 16:29:11 -05001140 nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
Robert Findleyb15dac22022-08-30 14:40:12 -04001141 resultMu sync.Mutex
1142 result = make(map[span.URI][]source.Symbol)
1143 )
Alan Donovan19fb30d2022-11-18 16:29:11 -05001144 group.SetLimit(nprocs)
Robert Findleye5b99482023-02-15 22:26:54 -05001145 for uri := range goFiles {
1146 uri := uri
Robert Findleyb15dac22022-08-30 14:40:12 -04001147 group.Go(func() error {
Robert Findleye5b99482023-02-15 22:26:54 -05001148 symbols, err := s.symbolize(ctx, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001149 if err != nil {
1150 return err
1151 }
1152 resultMu.Lock()
Robert Findleye5b99482023-02-15 22:26:54 -05001153 result[uri] = symbols
Robert Findleyb15dac22022-08-30 14:40:12 -04001154 resultMu.Unlock()
1155 return nil
1156 })
Alan Donovan19fb30d2022-11-18 16:29:11 -05001157 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001158 // Keep going on errors, but log the first failure.
1159 // Partial results are better than no symbol results.
1160 if err := group.Wait(); err != nil {
1161 event.Error(ctx, "getting snapshot symbols", err)
1162 }
Robert Findleye5b99482023-02-15 22:26:54 -05001163 return result, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001164}
1165
Alan Donovan763a0302022-12-12 15:07:03 -05001166func (s *snapshot) AllMetadata(ctx context.Context) ([]*source.Metadata, error) {
Robert Findley61280302022-10-17 16:35:50 -04001167 if err := s.awaitLoaded(ctx); err != nil {
1168 return nil, err
1169 }
1170
1171 s.mu.Lock()
Alan Donovanff22fab2022-11-18 14:47:36 -05001172 g := s.meta
1173 s.mu.Unlock()
Robert Findley61280302022-10-17 16:35:50 -04001174
Alan Donovanff22fab2022-11-18 14:47:36 -05001175 meta := make([]*source.Metadata, 0, len(g.metadata))
1176 for _, m := range g.metadata {
Robert Findley5a4eba52022-11-03 18:28:39 -04001177 meta = append(meta, m)
Robert Findley61280302022-10-17 16:35:50 -04001178 }
1179 return meta, nil
1180}
1181
Robert Findleybf5db812022-12-02 17:02:23 -05001182// TODO(rfindley): clarify that this is only active modules. Or update to just
1183// use findRootPattern.
Robert Findleyb15dac22022-08-30 14:40:12 -04001184func (s *snapshot) GoModForFile(uri span.URI) span.URI {
Robert Findley80879112022-12-16 09:11:12 -05001185 return moduleForURI(s.workspaceModFiles, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001186}
1187
1188func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI {
1189 var match span.URI
1190 for modURI := range modFiles {
Rob Findley787e7202023-05-09 18:57:05 -04001191 if !source.InDir(filepath.Dir(modURI.Filename()), uri.Filename()) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001192 continue
1193 }
1194 if len(modURI) > len(match) {
1195 match = modURI
1196 }
1197 }
1198 return match
1199}
1200
Robert Findleyad4fc282023-02-15 23:48:30 -05001201// nearestModFile finds the nearest go.mod file contained in the directory
1202// containing uri, or a parent of that directory.
1203//
1204// The given uri must be a file, not a directory.
1205func nearestModFile(ctx context.Context, uri span.URI, fs source.FileSource) (span.URI, error) {
Robert Findleyad4fc282023-02-15 23:48:30 -05001206 dir := filepath.Dir(uri.Filename())
1207 mod, err := findRootPattern(ctx, dir, "go.mod", fs)
1208 if err != nil {
1209 return "", err
1210 }
1211 return span.URIFromPath(mod), nil
1212}
1213
Alan Donovan09ae2d52022-11-18 15:44:00 -05001214func (s *snapshot) Metadata(id PackageID) *source.Metadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001215 s.mu.Lock()
1216 defer s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -04001217 return s.meta.metadata[id]
1218}
1219
1220// clearShouldLoad clears package IDs that no longer need to be reloaded after
1221// scopes has been loaded.
Robert Findleyb2533142022-10-10 13:50:45 -04001222func (s *snapshot) clearShouldLoad(scopes ...loadScope) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001223 s.mu.Lock()
1224 defer s.mu.Unlock()
1225
1226 for _, scope := range scopes {
1227 switch scope := scope.(type) {
Robert Findleyb2533142022-10-10 13:50:45 -04001228 case packageLoadScope:
1229 scopePath := PackagePath(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -04001230 var toDelete []PackageID
1231 for id, pkgPaths := range s.shouldLoad {
1232 for _, pkgPath := range pkgPaths {
Robert Findleyb2533142022-10-10 13:50:45 -04001233 if pkgPath == scopePath {
Robert Findleyb15dac22022-08-30 14:40:12 -04001234 toDelete = append(toDelete, id)
1235 }
1236 }
1237 }
1238 for _, id := range toDelete {
1239 delete(s.shouldLoad, id)
1240 }
Robert Findleyb2533142022-10-10 13:50:45 -04001241 case fileLoadScope:
Robert Findleyb15dac22022-08-30 14:40:12 -04001242 uri := span.URI(scope)
1243 ids := s.meta.ids[uri]
1244 for _, id := range ids {
1245 delete(s.shouldLoad, id)
1246 }
1247 }
1248 }
1249}
1250
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001251// noRealPackagesForURILocked reports whether there are any
1252// non-command-line-arguments packages containing the given URI.
1253func (s *snapshot) noRealPackagesForURILocked(uri span.URI) bool {
Alan Donovanff22fab2022-11-18 14:47:36 -05001254 for _, id := range s.meta.ids[uri] {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001255 if !source.IsCommandLineArguments(id) || s.meta.metadata[id].Standalone {
Robert Findleyb15dac22022-08-30 14:40:12 -04001256 return false
1257 }
1258 }
1259 return true
1260}
1261
Robert Findleya7f033a2023-01-19 18:12:18 -05001262func (s *snapshot) FindFile(uri span.URI) source.FileHandle {
Robert Findleyae242ec2023-01-19 16:41:08 -05001263 s.view.markKnown(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001264
1265 s.mu.Lock()
1266 defer s.mu.Unlock()
1267
Alan Donovand444fa32022-12-01 11:17:42 -05001268 result, _ := s.files.Get(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001269 return result
1270}
1271
Alan Donovan36ed0b12023-03-13 14:20:23 -04001272// ReadFile returns a File for the given URI. If the file is unknown it is added
Robert Findleya7f033a2023-01-19 18:12:18 -05001273// to the managed set.
Robert Findleyb15dac22022-08-30 14:40:12 -04001274//
Alan Donovan36ed0b12023-03-13 14:20:23 -04001275// ReadFile succeeds even if the file does not exist. A non-nil error return
Robert Findleyb15dac22022-08-30 14:40:12 -04001276// indicates some type of internal error, for example if ctx is cancelled.
Alan Donovan36ed0b12023-03-13 14:20:23 -04001277func (s *snapshot) ReadFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001278 s.mu.Lock()
1279 defer s.mu.Unlock()
Alan Donovand444fa32022-12-01 11:17:42 -05001280
Alan Donovan36ed0b12023-03-13 14:20:23 -04001281 return lockedSnapshot{s}.ReadFile(ctx, uri)
Robert Findleyad4fc282023-02-15 23:48:30 -05001282}
1283
Robert Findleyc3edf5a2023-03-20 18:37:18 -04001284// preloadFiles delegates to the view FileSource to read the requested uris in
1285// parallel, without holding the snapshot lock.
1286func (s *snapshot) preloadFiles(ctx context.Context, uris []span.URI) {
1287 files := make([]source.FileHandle, len(uris))
1288 var wg sync.WaitGroup
1289 iolimit := make(chan struct{}, 20) // I/O concurrency limiting semaphore
1290 for i, uri := range uris {
1291 wg.Add(1)
1292 iolimit <- struct{}{}
1293 go func(i int, uri span.URI) {
1294 defer wg.Done()
1295 fh, err := s.view.fs.ReadFile(ctx, uri)
1296 <-iolimit
1297 if err != nil && ctx.Err() == nil {
1298 event.Error(ctx, fmt.Sprintf("reading %s", uri), err)
1299 return
1300 }
1301 files[i] = fh
1302 }(i, uri)
1303 }
1304 wg.Wait()
1305
1306 s.mu.Lock()
1307 defer s.mu.Unlock()
1308
1309 for i, fh := range files {
1310 if fh == nil {
1311 continue // error logged above
1312 }
1313 uri := uris[i]
1314 if _, ok := s.files.Get(uri); !ok {
1315 s.files.Set(uri, fh)
1316 }
1317 }
1318}
1319
Robert Findleyad4fc282023-02-15 23:48:30 -05001320// A lockedSnapshot implements the source.FileSource interface while holding
1321// the lock for the wrapped snapshot.
1322type lockedSnapshot struct{ wrapped *snapshot }
1323
Alan Donovan36ed0b12023-03-13 14:20:23 -04001324func (s lockedSnapshot) ReadFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
Robert Findleyad4fc282023-02-15 23:48:30 -05001325 s.wrapped.view.markKnown(uri)
1326 if fh, ok := s.wrapped.files.Get(uri); ok {
Alan Donovand444fa32022-12-01 11:17:42 -05001327 return fh, nil
1328 }
1329
Alan Donovan36ed0b12023-03-13 14:20:23 -04001330 fh, err := s.wrapped.view.fs.ReadFile(ctx, uri)
Alan Donovand444fa32022-12-01 11:17:42 -05001331 if err != nil {
1332 return nil, err
1333 }
Robert Findleyad4fc282023-02-15 23:48:30 -05001334 s.wrapped.files.Set(uri, fh)
Robert Findleya7f033a2023-01-19 18:12:18 -05001335 return fh, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001336}
1337
Robert Findleyb15dac22022-08-30 14:40:12 -04001338func (s *snapshot) IsOpen(uri span.URI) bool {
1339 s.mu.Lock()
1340 defer s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -04001341
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001342 fh, _ := s.files.Get(uri)
1343 _, open := fh.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04001344 return open
1345}
1346
Robert Findleya7f033a2023-01-19 18:12:18 -05001347func isFileOpen(fh source.FileHandle) bool {
1348 _, open := fh.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04001349 return open
1350}
1351
Robert Findley488ba862023-03-23 12:18:06 -04001352// TODO(rfindley): it would make sense for awaitLoaded to return metadata.
Robert Findleyb15dac22022-08-30 14:40:12 -04001353func (s *snapshot) awaitLoaded(ctx context.Context) error {
1354 loadErr := s.awaitLoadedAllErrors(ctx)
1355
Robert Findley5a4eba52022-11-03 18:28:39 -04001356 // TODO(rfindley): eliminate this function as part of simplifying
1357 // CriticalErrors.
Robert Findleyb15dac22022-08-30 14:40:12 -04001358 if loadErr != nil {
1359 return loadErr.MainError
1360 }
1361 return nil
1362}
1363
Alan Donovane8f417a2023-04-21 14:54:28 -04001364func (s *snapshot) CriticalError(ctx context.Context) *source.CriticalError {
Robert Findley80879112022-12-16 09:11:12 -05001365 // If we couldn't compute workspace mod files, then the load below is
1366 // invalid.
1367 //
1368 // TODO(rfindley): is this a clear error to present to the user?
1369 if s.workspaceModFilesErr != nil {
1370 return &source.CriticalError{MainError: s.workspaceModFilesErr}
Robert Findleyb15dac22022-08-30 14:40:12 -04001371 }
1372
1373 loadErr := s.awaitLoadedAllErrors(ctx)
1374 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) {
1375 return nil
1376 }
1377
1378 // Even if packages didn't fail to load, we still may want to show
1379 // additional warnings.
1380 if loadErr == nil {
Alan Donovane8f417a2023-04-21 14:54:28 -04001381 active, _ := s.WorkspaceMetadata(ctx)
Alan Donovan18f76ec2022-12-08 09:48:21 -05001382 if msg := shouldShowAdHocPackagesWarning(s, active); msg != "" {
Robert Findleyb15dac22022-08-30 14:40:12 -04001383 return &source.CriticalError{
1384 MainError: errors.New(msg),
1385 }
1386 }
1387 // Even if workspace packages were returned, there still may be an error
1388 // with the user's workspace layout. Workspace packages that only have the
1389 // ID "command-line-arguments" are usually a symptom of a bad workspace
1390 // configuration.
1391 //
Robert Findley4f69bf32022-12-02 13:01:36 -05001392 // This heuristic is path-dependent: we only get command-line-arguments
1393 // packages when we've loaded using file scopes, which only occurs
1394 // on-demand or via orphaned file reloading.
1395 //
Robert Findleyb15dac22022-08-30 14:40:12 -04001396 // TODO(rfindley): re-evaluate this heuristic.
Alan Donovan18f76ec2022-12-08 09:48:21 -05001397 if containsCommandLineArguments(active) {
Robert Findleybf5db812022-12-02 17:02:23 -05001398 err, diags := s.workspaceLayoutError(ctx)
1399 if err != nil {
1400 if ctx.Err() != nil {
1401 return nil // see the API documentation for source.Snapshot
1402 }
1403 return &source.CriticalError{
1404 MainError: err,
1405 Diagnostics: diags,
1406 }
1407 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001408 }
1409 return nil
1410 }
1411
1412 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") {
Robert Findleybf5db812022-12-02 17:02:23 -05001413 err, diags := s.workspaceLayoutError(ctx)
1414 if err != nil {
1415 if ctx.Err() != nil {
1416 return nil // see the API documentation for source.Snapshot
1417 }
1418 return &source.CriticalError{
1419 MainError: err,
1420 Diagnostics: diags,
1421 }
1422 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001423 }
1424 return loadErr
1425}
1426
Alan Donovan84299a02022-12-08 10:18:14 -05001427// A portion of this text is expected by TestBrokenWorkspace_OutsideModule.
Robert Findleyb15dac22022-08-30 14:40:12 -04001428const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
1429If you are using modules, please open your editor to a directory in your module.
1430If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
1431
Rob Findleye5c8d4d2023-05-15 19:50:02 -04001432func shouldShowAdHocPackagesWarning(snapshot *snapshot, active []*source.Metadata) string {
1433 if !snapshot.validBuildConfiguration() {
Alan Donovan18f76ec2022-12-08 09:48:21 -05001434 for _, m := range active {
Alan Donovan84299a02022-12-08 10:18:14 -05001435 // A blank entry in DepsByImpPath
1436 // indicates a missing dependency.
1437 for _, importID := range m.DepsByImpPath {
1438 if importID == "" {
1439 return adHocPackagesWarning
1440 }
Alan Donovand39685a2022-11-22 13:18:01 -05001441 }
1442 }
1443 }
Alan Donovan84299a02022-12-08 10:18:14 -05001444 return ""
Alan Donovand39685a2022-11-22 13:18:01 -05001445}
1446
Alan Donovan18f76ec2022-12-08 09:48:21 -05001447func containsCommandLineArguments(metas []*source.Metadata) bool {
1448 for _, m := range metas {
1449 if source.IsCommandLineArguments(m.ID) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001450 return true
1451 }
1452 }
1453 return false
1454}
1455
1456func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError {
1457 // Do not return results until the snapshot's view has been initialized.
1458 s.AwaitInitialized(ctx)
1459
1460 // TODO(rfindley): Should we be more careful about returning the
1461 // initialization error? Is it possible for the initialization error to be
1462 // corrected without a successful reinitialization?
Alan Donovanff22fab2022-11-18 14:47:36 -05001463 if err := s.getInitializationError(); err != nil {
1464 return err
Robert Findleyb15dac22022-08-30 14:40:12 -04001465 }
1466
1467 // TODO(rfindley): revisit this handling. Calling reloadWorkspace with a
1468 // cancelled context should have the same effect, so this preemptive handling
1469 // should not be necessary.
1470 //
1471 // Also: GetCriticalError ignores context cancellation errors. Should we be
1472 // returning nil here?
1473 if ctx.Err() != nil {
1474 return &source.CriticalError{MainError: ctx.Err()}
1475 }
1476
1477 // TODO(rfindley): reloading is not idempotent: if we try to reload or load
1478 // orphaned files below and fail, we won't try again. For that reason, we
1479 // could get different results from subsequent calls to this function, which
1480 // may cause critical errors to be suppressed.
1481
1482 if err := s.reloadWorkspace(ctx); err != nil {
1483 diags := s.extractGoCommandErrors(ctx, err)
1484 return &source.CriticalError{
1485 MainError: err,
1486 Diagnostics: diags,
1487 }
1488 }
1489
Robert Findley4f69bf32022-12-02 13:01:36 -05001490 if err := s.reloadOrphanedOpenFiles(ctx); err != nil {
Robert Findleyb15dac22022-08-30 14:40:12 -04001491 diags := s.extractGoCommandErrors(ctx, err)
1492 return &source.CriticalError{
1493 MainError: err,
1494 Diagnostics: diags,
1495 }
1496 }
1497 return nil
1498}
1499
Alan Donovan051f03f2022-10-21 11:10:37 -04001500func (s *snapshot) getInitializationError() *source.CriticalError {
Robert Findleyb15dac22022-08-30 14:40:12 -04001501 s.mu.Lock()
1502 defer s.mu.Unlock()
1503
1504 return s.initializedErr
1505}
1506
1507func (s *snapshot) AwaitInitialized(ctx context.Context) {
1508 select {
1509 case <-ctx.Done():
1510 return
1511 case <-s.view.initialWorkspaceLoad:
1512 }
1513 // We typically prefer to run something as intensive as the IWL without
1514 // blocking. I'm not sure if there is a way to do that here.
1515 s.initialize(ctx, false)
1516}
1517
1518// reloadWorkspace reloads the metadata for all invalidated workspace packages.
1519func (s *snapshot) reloadWorkspace(ctx context.Context) error {
Robert Findleyb2533142022-10-10 13:50:45 -04001520 var scopes []loadScope
Robert Findleyb15dac22022-08-30 14:40:12 -04001521 var seen map[PackagePath]bool
1522 s.mu.Lock()
1523 for _, pkgPaths := range s.shouldLoad {
1524 for _, pkgPath := range pkgPaths {
1525 if seen == nil {
1526 seen = make(map[PackagePath]bool)
1527 }
1528 if seen[pkgPath] {
1529 continue
1530 }
1531 seen[pkgPath] = true
Robert Findleyb2533142022-10-10 13:50:45 -04001532 scopes = append(scopes, packageLoadScope(pkgPath))
Robert Findleyb15dac22022-08-30 14:40:12 -04001533 }
1534 }
1535 s.mu.Unlock()
1536
1537 if len(scopes) == 0 {
1538 return nil
1539 }
1540
1541 // If the view's build configuration is invalid, we cannot reload by
1542 // package path. Just reload the directory instead.
Rob Findleye5c8d4d2023-05-15 19:50:02 -04001543 if !s.validBuildConfiguration() {
Robert Findleyb2533142022-10-10 13:50:45 -04001544 scopes = []loadScope{viewLoadScope("LOAD_INVALID_VIEW")}
Robert Findleyb15dac22022-08-30 14:40:12 -04001545 }
1546
1547 err := s.load(ctx, false, scopes...)
1548
1549 // Unless the context was canceled, set "shouldLoad" to false for all
1550 // of the metadata we attempted to load.
1551 if !errors.Is(err, context.Canceled) {
1552 s.clearShouldLoad(scopes...)
1553 }
1554
1555 return err
1556}
1557
Robert Findley04059e12023-03-29 17:05:52 -04001558// reloadOrphanedOpenFiles attempts to load a package for each open file that
1559// does not yet have an associated package. If loading finishes without being
1560// canceled, any files still not contained in a package are marked as unloadable.
1561//
1562// An error is returned if the load is canceled.
Robert Findley4f69bf32022-12-02 13:01:36 -05001563func (s *snapshot) reloadOrphanedOpenFiles(ctx context.Context) error {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001564 s.mu.Lock()
1565 meta := s.meta
1566 s.mu.Unlock()
Robert Findleyb15dac22022-08-30 14:40:12 -04001567 // When we load ./... or a package path directly, we may not get packages
1568 // that exist only in overlays. As a workaround, we search all of the files
1569 // available in the snapshot and reload their metadata individually using a
1570 // file= query if the metadata is unavailable.
Rob Findleya13793e2023-05-12 14:22:10 -04001571 open := s.overlays()
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001572 var files []*Overlay
1573 for _, o := range open {
1574 uri := o.URI()
1575 if s.IsBuiltin(uri) || s.view.FileKind(o) != source.Go {
1576 continue
1577 }
1578 if len(meta.ids[uri]) == 0 {
1579 files = append(files, o)
1580 }
1581 }
Rob Findleycd39d2b2023-05-11 13:05:03 -04001582 if len(files) == 0 {
Robert Findleyb15dac22022-08-30 14:40:12 -04001583 return nil
1584 }
1585
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001586 // Filter to files that are not known to be unloadable.
1587 s.mu.Lock()
1588 loadable := files[:0]
1589 for _, file := range files {
1590 if _, unloadable := s.unloadableFiles[file.URI()]; !unloadable {
1591 loadable = append(loadable, file)
1592 }
1593 }
1594 files = loadable
1595 s.mu.Unlock()
1596
Rob Findleycd39d2b2023-05-11 13:05:03 -04001597 var uris []span.URI
1598 for _, file := range files {
1599 uris = append(uris, file.URI())
1600 }
1601
1602 event.Log(ctx, "reloadOrphanedFiles reloading", tag.Files.Of(uris))
1603
1604 var g errgroup.Group
1605
1606 cpulimit := runtime.GOMAXPROCS(0)
1607 g.SetLimit(cpulimit)
1608
1609 // Load files one-at-a-time. go/packages can return at most one
1610 // command-line-arguments package per query.
1611 for _, file := range files {
1612 file := file
1613 g.Go(func() error {
1614 pgf, err := s.ParseGo(ctx, file, source.ParseHeader)
1615 if err != nil || !pgf.File.Package.IsValid() {
1616 return nil // need a valid header
1617 }
1618 return s.load(ctx, false, fileLoadScope(file.URI()))
1619 })
1620 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001621
1622 // If we failed to load some files, i.e. they have no metadata,
1623 // mark the failures so we don't bother retrying until the file's
1624 // content changes.
1625 //
cui fliter165099b2023-04-27 20:32:55 +08001626 // TODO(rfindley): is it possible that the load stopped early for an
Robert Findley04059e12023-03-29 17:05:52 -04001627 // unrelated errors? If so, add a fallback?
Rob Findleycd39d2b2023-05-11 13:05:03 -04001628
1629 if err := g.Wait(); err != nil {
1630 // Check for context cancellation so that we don't incorrectly mark files
1631 // as unloadable, but don't return before setting all workspace packages.
1632 if ctx.Err() != nil {
1633 return ctx.Err()
1634 }
1635
1636 if !errors.Is(err, errNoPackages) {
1637 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Files.Of(uris))
1638 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001639 }
Robert Findley04059e12023-03-29 17:05:52 -04001640
1641 // If the context was not canceled, we assume that the result of loading
1642 // packages is deterministic (though as we saw in golang/go#59318, it may not
1643 // be in the presence of bugs). Marking all unloaded files as unloadable here
1644 // prevents us from falling into recursive reloading where we only make a bit
1645 // of progress each time.
1646 s.mu.Lock()
Rob Findleycd39d2b2023-05-11 13:05:03 -04001647 defer s.mu.Unlock()
1648 for _, file := range files {
Robert Findley04059e12023-03-29 17:05:52 -04001649 // TODO(rfindley): instead of locking here, we should have load return the
1650 // metadata graph that resulted from loading.
Rob Findleycd39d2b2023-05-11 13:05:03 -04001651 uri := file.URI()
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001652 if len(s.meta.ids) == 0 {
Robert Findley04059e12023-03-29 17:05:52 -04001653 s.unloadableFiles[uri] = struct{}{}
1654 }
1655 }
Robert Findley04059e12023-03-29 17:05:52 -04001656
Robert Findleyb15dac22022-08-30 14:40:12 -04001657 return nil
1658}
1659
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001660// OrphanedFileDiagnostics reports diagnostics describing why open files have
1661// no packages or have only command-line-arguments packages.
1662//
1663// If the resulting diagnostic is nil, the file is either not orphaned or we
1664// can't produce a good diagnostic.
1665//
1666// TODO(rfindley): reconcile the definition of "orphaned" here with
1667// reloadOrphanedFiles. The latter does not include files with
1668// command-line-arguments packages.
Rob Findleya13793e2023-05-12 14:22:10 -04001669func (s *snapshot) OrphanedFileDiagnostics(ctx context.Context) (map[span.URI]*source.Diagnostic, error) {
1670 // Orphaned file diagnostics are queried from code actions to produce
1671 // quick-fixes (and may be queried many times, once for each file).
1672 //
1673 // Because they are non-trivial to compute, record them optimistically to
1674 // avoid most redundant work.
1675 //
1676 // This is a hacky workaround: in the future we should avoid recomputing
1677 // anything when codeActions provide a diagnostic: simply read the published
1678 // diagnostic, if it exists.
Robert Findleyb15dac22022-08-30 14:40:12 -04001679 s.mu.Lock()
Rob Findleya13793e2023-05-12 14:22:10 -04001680 existing := s.orphanedFileDiagnostics
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001681 s.mu.Unlock()
Rob Findleya13793e2023-05-12 14:22:10 -04001682 if existing != nil {
1683 return existing, nil
1684 }
1685
1686 if err := s.awaitLoaded(ctx); err != nil {
1687 return nil, err
1688 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001689
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001690 var files []*Overlay
1691
1692searchOverlays:
1693 for _, o := range s.overlays() {
1694 uri := o.URI()
1695 if s.IsBuiltin(uri) || s.view.FileKind(o) != source.Go {
1696 continue
Robert Findley4f69bf32022-12-02 13:01:36 -05001697 }
Rob Findleya13793e2023-05-12 14:22:10 -04001698 md, err := s.MetadataForFile(ctx, uri)
1699 if err != nil {
1700 return nil, err
1701 }
1702 for _, m := range md {
1703 if !source.IsCommandLineArguments(m.ID) || m.Standalone {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001704 continue searchOverlays
1705 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001706 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001707 files = append(files, o)
1708 }
1709 if len(files) == 0 {
Rob Findleya13793e2023-05-12 14:22:10 -04001710 return nil, nil
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001711 }
1712
Rob Findleya13793e2023-05-12 14:22:10 -04001713 loadedModFiles := make(map[span.URI]struct{}) // all mod files, including dependencies
1714 ignoredFiles := make(map[span.URI]bool) // files reported in packages.Package.IgnoredFiles
1715
1716 meta, err := s.AllMetadata(ctx)
1717 if err != nil {
1718 return nil, err
1719 }
1720
1721 for _, meta := range meta {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001722 if meta.Module != nil && meta.Module.GoMod != "" {
1723 gomod := span.URIFromPath(meta.Module.GoMod)
1724 loadedModFiles[gomod] = struct{}{}
Robert Findleyb15dac22022-08-30 14:40:12 -04001725 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001726 for _, ignored := range meta.IgnoredFiles {
1727 ignoredFiles[ignored] = true
Robert Findleyb15dac22022-08-30 14:40:12 -04001728 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001729 }
1730
1731 diagnostics := make(map[span.URI]*source.Diagnostic)
1732 for _, fh := range files {
1733 // Only warn about orphaned files if the file is well-formed enough to
1734 // actually be part of a package.
1735 //
1736 // Use ParseGo as for open files this is likely to be a cache hit (we'll have )
1737 pgf, err := s.ParseGo(ctx, fh, source.ParseHeader)
1738 if err != nil {
1739 continue
Robert Findleyb15dac22022-08-30 14:40:12 -04001740 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001741 if !pgf.File.Name.Pos().IsValid() {
1742 continue
1743 }
1744 rng, err := pgf.PosRange(pgf.File.Name.Pos(), pgf.File.Name.End())
1745 if err != nil {
1746 continue
1747 }
1748
Rob Findleya13793e2023-05-12 14:22:10 -04001749 var (
1750 msg string // if non-empty, report a diagnostic with this message
1751 suggestedFixes []source.SuggestedFix // associated fixes, if any
1752 )
1753
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001754 // If we have a relevant go.mod file, check whether the file is orphaned
1755 // due to its go.mod file being inactive. We could also offer a
1756 // prescriptive diagnostic in the case that there is no go.mod file, but it
1757 // is harder to be precise in that case, and less important.
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001758 if goMod, err := nearestModFile(ctx, fh.URI(), s); err == nil && goMod != "" {
1759 if _, ok := loadedModFiles[goMod]; !ok {
1760 modDir := filepath.Dir(goMod.Filename())
Rob Findleya13793e2023-05-12 14:22:10 -04001761 viewDir := s.view.folder.Filename()
1762
1763 // When the module is underneath the view dir, we offer
1764 // "use all modules" quick-fixes.
1765 inDir := source.InDir(viewDir, modDir)
1766
1767 if rel, err := filepath.Rel(viewDir, modDir); err == nil {
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001768 modDir = rel
1769 }
1770
1771 var fix string
1772 if s.view.goversion >= 18 {
1773 if s.view.gowork != "" {
1774 fix = fmt.Sprintf("To fix this problem, you can add this module to your go.work file (%s)", s.view.gowork)
Rob Findleya13793e2023-05-12 14:22:10 -04001775 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use`", command.RunGoWorkArgs{
1776 ViewID: s.view.ID(),
1777 Args: []string{"use", modDir},
1778 }); err == nil {
1779 suggestedFixes = append(suggestedFixes, source.SuggestedFix{
1780 Title: "Use this module in your go.work file",
1781 Command: &cmd,
1782 ActionKind: protocol.QuickFix,
1783 })
1784 }
1785
1786 if inDir {
1787 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use -r`", command.RunGoWorkArgs{
1788 ViewID: s.view.ID(),
1789 Args: []string{"use", "-r", "."},
1790 }); err == nil {
1791 suggestedFixes = append(suggestedFixes, source.SuggestedFix{
1792 Title: "Use all modules in your workspace",
1793 Command: &cmd,
1794 ActionKind: protocol.QuickFix,
1795 })
1796 }
1797 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001798 } else {
1799 fix = "To fix this problem, you can add a go.work file that uses this directory."
Rob Findleya13793e2023-05-12 14:22:10 -04001800
1801 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use`", command.RunGoWorkArgs{
1802 ViewID: s.view.ID(),
1803 InitFirst: true,
1804 Args: []string{"use", modDir},
1805 }); err == nil {
1806 suggestedFixes = []source.SuggestedFix{
1807 {
1808 Title: "Add a go.work file using this module",
1809 Command: &cmd,
1810 ActionKind: protocol.QuickFix,
1811 },
1812 }
1813 }
1814
1815 if inDir {
1816 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use -r`", command.RunGoWorkArgs{
1817 ViewID: s.view.ID(),
1818 InitFirst: true,
1819 Args: []string{"use", "-r", "."},
1820 }); err == nil {
1821 suggestedFixes = append(suggestedFixes, source.SuggestedFix{
1822 Title: "Add a go.work file using all modules in your workspace",
1823 Command: &cmd,
1824 ActionKind: protocol.QuickFix,
1825 })
1826 }
1827 }
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001828 }
1829 } else {
1830 fix = `To work with multiple modules simultaneously, please upgrade to Go 1.18 or
1831later, reinstall gopls, and use a go.work file.`
1832 }
Alan Donovan4d663242023-05-19 14:51:17 -04001833 msg = fmt.Sprintf(`This file is within module %q, which is not included in your workspace.
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001834%s
1835See the documentation for more information on setting up your workspace:
1836https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`, modDir, fix)
1837 }
1838 }
1839
1840 if msg == "" && ignoredFiles[fh.URI()] {
1841 // TODO(rfindley): use the constraint package to check if the file
1842 // _actually_ satisfies the current build context.
1843 hasConstraint := false
1844 walkConstraints(pgf.File, func(constraint.Expr) bool {
1845 hasConstraint = true
1846 return false
1847 })
1848 var fix string
1849 if hasConstraint {
1850 fix = `This file may be excluded due to its build tags; try adding "-tags=<build tag>" to your gopls "buildFlags" configuration
1851See the documentation for more information on working with build tags:
1852https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags-string.`
1853 } else if strings.Contains(filepath.Base(fh.URI().Filename()), "_") {
1854 fix = `This file may be excluded due to its GOOS/GOARCH, or other build constraints.`
1855 } else {
1856 fix = `This file is ignored by your gopls build.` // we don't know why
1857 }
1858 msg = fmt.Sprintf("No packages found for open file %s.\n%s", fh.URI().Filename(), fix)
1859 }
1860
1861 if msg != "" {
1862 // Only report diagnostics if we detect an actual exclusion.
1863 diagnostics[fh.URI()] = &source.Diagnostic{
Rob Findleya13793e2023-05-12 14:22:10 -04001864 URI: fh.URI(),
1865 Range: rng,
1866 Severity: protocol.SeverityWarning,
1867 Source: source.ListError,
1868 Message: msg,
1869 SuggestedFixes: suggestedFixes,
Rob Findley1c9fe3f2023-05-11 14:33:05 -04001870 }
1871 }
1872 }
1873
Rob Findleya13793e2023-05-12 14:22:10 -04001874 s.mu.Lock()
1875 defer s.mu.Unlock()
1876 if s.orphanedFileDiagnostics == nil { // another thread may have won the race
1877 s.orphanedFileDiagnostics = diagnostics
1878 }
1879 return s.orphanedFileDiagnostics, nil
Robert Findleyb15dac22022-08-30 14:40:12 -04001880}
1881
Robert Findleyb15dac22022-08-30 14:40:12 -04001882// TODO(golang/go#53756): this function needs to consider more than just the
1883// absolute URI, for example:
1884// - the position of /vendor/ with respect to the relevant module root
1885// - whether or not go.work is in use (as vendoring isn't supported in workspace mode)
1886//
1887// Most likely, each call site of inVendor needs to be reconsidered to
1888// understand and correctly implement the desired behavior.
1889func inVendor(uri span.URI) bool {
Alan Donovane0b516b2022-11-28 23:41:43 -05001890 _, after, found := cut(string(uri), "/vendor/")
1891 // Only subdirectories of /vendor/ are considered vendored
Robert Findleyb15dac22022-08-30 14:40:12 -04001892 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
Alan Donovane0b516b2022-11-28 23:41:43 -05001893 return found && strings.Contains(after, "/")
1894}
1895
1896// TODO(adonovan): replace with strings.Cut when we can assume go1.18.
1897func cut(s, sep string) (before, after string, found bool) {
1898 if i := strings.Index(s, sep); i >= 0 {
1899 return s[:i], s[i+len(sep):], true
Robert Findleyb15dac22022-08-30 14:40:12 -04001900 }
Alan Donovane0b516b2022-11-28 23:41:43 -05001901 return s, "", false
Robert Findleyb15dac22022-08-30 14:40:12 -04001902}
1903
1904// unappliedChanges is a file source that handles an uncloned snapshot.
1905type unappliedChanges struct {
1906 originalSnapshot *snapshot
1907 changes map[span.URI]*fileChange
1908}
1909
Alan Donovan36ed0b12023-03-13 14:20:23 -04001910func (ac *unappliedChanges) ReadFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001911 if c, ok := ac.changes[uri]; ok {
1912 return c.fileHandle, nil
1913 }
Alan Donovan36ed0b12023-03-13 14:20:23 -04001914 return ac.originalSnapshot.ReadFile(ctx, uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001915}
1916
1917func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, func()) {
Robert Findleyf7963612023-03-28 21:34:10 -04001918 ctx, done := event.Start(ctx, "cache.snapshot.clone")
Robert Findleyb15dac22022-08-30 14:40:12 -04001919 defer done()
1920
Robert Findley80879112022-12-16 09:11:12 -05001921 reinit := false
1922 wsModFiles, wsModFilesErr := s.workspaceModFiles, s.workspaceModFilesErr
1923
Rob Findleya13793e2023-05-12 14:22:10 -04001924 if workURI, _ := s.view.GOWORK(); workURI != "" {
Robert Findley80879112022-12-16 09:11:12 -05001925 if change, ok := changes[workURI]; ok {
1926 wsModFiles, wsModFilesErr = computeWorkspaceModFiles(ctx, s.view.gomod, workURI, s.view.effectiveGO111MODULE(), &unappliedChanges{
1927 originalSnapshot: s,
1928 changes: changes,
1929 })
1930 // TODO(rfindley): don't rely on 'isUnchanged' here. Use a content hash instead.
1931 reinit = change.fileHandle.Saved() && !change.isUnchanged
1932 }
1933 }
1934
1935 // Reinitialize if any workspace mod file has changed on disk.
1936 for uri, change := range changes {
1937 if _, ok := wsModFiles[uri]; ok && change.fileHandle.Saved() && !change.isUnchanged {
1938 reinit = true
1939 }
1940 }
1941
1942 // Finally, process sumfile changes that may affect loading.
1943 for uri, change := range changes {
1944 if !change.fileHandle.Saved() {
1945 continue // like with go.mod files, we only reinit when things are saved
1946 }
1947 if filepath.Base(uri.Filename()) == "go.work.sum" && s.view.gowork != "" {
1948 if filepath.Dir(uri.Filename()) == filepath.Dir(s.view.gowork) {
1949 reinit = true
1950 }
1951 }
1952 if filepath.Base(uri.Filename()) == "go.sum" {
1953 dir := filepath.Dir(uri.Filename())
1954 modURI := span.URIFromPath(filepath.Join(dir, "go.mod"))
1955 if _, active := wsModFiles[modURI]; active {
1956 reinit = true
1957 }
1958 }
1959 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001960
1961 s.mu.Lock()
1962 defer s.mu.Unlock()
1963
Alan Donovane0b516b2022-11-28 23:41:43 -05001964 // Changes to vendor tree may require reinitialization,
1965 // either because of an initialization error
1966 // (e.g. "inconsistent vendoring detected"), or because
1967 // one or more modules may have moved into or out of the
1968 // vendor tree after 'go mod vendor' or 'rm -fr vendor/'.
1969 for uri := range changes {
1970 if inVendor(uri) && s.initializedErr != nil ||
1971 strings.HasSuffix(string(uri), "/vendor/modules.txt") {
1972 reinit = true
1973 break
Robert Findleyb15dac22022-08-30 14:40:12 -04001974 }
1975 }
1976
1977 bgCtx, cancel := context.WithCancel(bgCtx)
1978 result := &snapshot{
Robert Findley0c71b562022-11-14 11:58:07 -05001979 sequenceID: s.sequenceID + 1,
1980 globalID: nextSnapshotID(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001981 store: s.store,
1982 view: s.view,
1983 backgroundCtx: bgCtx,
1984 cancel: cancel,
1985 builtin: s.builtin,
1986 initialized: s.initialized,
1987 initializedErr: s.initializedErr,
1988 packages: s.packages.Clone(),
Robert Findley21d22562023-02-21 12:26:27 -05001989 activePackages: s.activePackages.Clone(),
Alan Donovan61e2d3f2022-10-14 17:58:15 -04001990 analyses: s.analyses.Clone(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001991 files: s.files.Clone(),
Robert Findleyae056092023-02-19 21:14:18 -05001992 parseCache: s.parseCache,
Robert Findleyb15dac22022-08-30 14:40:12 -04001993 symbolizeHandles: s.symbolizeHandles.Clone(),
1994 workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)),
1995 unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)),
1996 parseModHandles: s.parseModHandles.Clone(),
1997 parseWorkHandles: s.parseWorkHandles.Clone(),
1998 modTidyHandles: s.modTidyHandles.Clone(),
1999 modWhyHandles: s.modWhyHandles.Clone(),
Robert Findley7cda55e2022-11-22 12:09:11 -05002000 modVulnHandles: s.modVulnHandles.Clone(),
Robert Findleyb15dac22022-08-30 14:40:12 -04002001 knownSubdirs: s.knownSubdirs.Clone(),
Robert Findley80879112022-12-16 09:11:12 -05002002 workspaceModFiles: wsModFiles,
2003 workspaceModFilesErr: wsModFilesErr,
Robert Findley488ba862023-03-23 12:18:06 -04002004 importGraph: s.importGraph,
Robert Findley1b2d1bd2023-03-16 18:52:13 -04002005 pkgIndex: s.pkgIndex,
Robert Findleyb15dac22022-08-30 14:40:12 -04002006 }
2007
2008 // The snapshot should be initialized if either s was uninitialized, or we've
2009 // detected a change that triggers reinitialization.
2010 if reinit {
2011 result.initialized = false
2012 }
2013
2014 // Create a lease on the new snapshot.
2015 // (Best to do this early in case the code below hides an
2016 // incref/decref operation that might destroy it prematurely.)
2017 release := result.Acquire()
2018
2019 // Copy the set of unloadable files.
2020 //
2021 // TODO(rfindley): this looks wrong. Shouldn't we clear unloadableFiles on
2022 // changes to environment or workspace layout, or more generally on any
2023 // metadata change?
Robert Findley23056f62022-11-03 19:06:31 -04002024 //
2025 // Maybe not, as major configuration changes cause a new view.
Robert Findleyb15dac22022-08-30 14:40:12 -04002026 for k, v := range s.unloadableFiles {
2027 result.unloadableFiles[k] = v
2028 }
2029
Robert Findleyb15dac22022-08-30 14:40:12 -04002030 // Add all of the known subdirectories, but don't update them for the
2031 // changed files. We need to rebuild the workspace module to know the
2032 // true set of known subdirectories, but we don't want to do that in clone.
2033 result.knownSubdirs = s.knownSubdirs.Clone()
Alan Donovan2ec42992023-05-18 13:45:43 -04002034 result.knownSubdirsCache = s.knownSubdirsCache
Robert Findleyb15dac22022-08-30 14:40:12 -04002035 for _, c := range changes {
2036 result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c)
2037 }
2038
2039 // directIDs keeps track of package IDs that have directly changed.
Alan Donovane0b516b2022-11-28 23:41:43 -05002040 // Note: this is not a set, it's a map from id to invalidateMetadata.
Robert Findleyb15dac22022-08-30 14:40:12 -04002041 directIDs := map[PackageID]bool{}
2042
2043 // Invalidate all package metadata if the workspace module has changed.
2044 if reinit {
2045 for k := range s.meta.metadata {
2046 directIDs[k] = true
2047 }
2048 }
2049
2050 // Compute invalidations based on file changes.
2051 anyImportDeleted := false // import deletions can resolve cycles
2052 anyFileOpenedOrClosed := false // opened files affect workspace packages
2053 anyFileAdded := false // adding a file can resolve missing dependencies
2054
2055 for uri, change := range changes {
Robert Findley21d22562023-02-21 12:26:27 -05002056 // Invalidate go.mod-related handles.
2057 result.modTidyHandles.Delete(uri)
2058 result.modWhyHandles.Delete(uri)
2059 result.modVulnHandles.Delete(uri)
2060
2061 // Invalidate handles for cached symbols.
2062 result.symbolizeHandles.Delete(uri)
2063
Robert Findleyb15dac22022-08-30 14:40:12 -04002064 // The original FileHandle for this URI is cached on the snapshot.
2065 originalFH, _ := s.files.Get(uri)
2066 var originalOpen, newOpen bool
Robert Findleya7f033a2023-01-19 18:12:18 -05002067 _, originalOpen = originalFH.(*Overlay)
2068 _, newOpen = change.fileHandle.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04002069 anyFileOpenedOrClosed = anyFileOpenedOrClosed || (originalOpen != newOpen)
2070 anyFileAdded = anyFileAdded || (originalFH == nil && change.fileHandle != nil)
2071
2072 // If uri is a Go file, check if it has changed in a way that would
2073 // invalidate metadata. Note that we can't use s.view.FileKind here,
2074 // because the file type that matters is not what the *client* tells us,
2075 // but what the Go command sees.
2076 var invalidateMetadata, pkgFileChanged, importDeleted bool
2077 if strings.HasSuffix(uri.Filename(), ".go") {
2078 invalidateMetadata, pkgFileChanged, importDeleted = metadataChanges(ctx, s, originalFH, change.fileHandle)
2079 }
2080
2081 invalidateMetadata = invalidateMetadata || forceReloadMetadata || reinit
2082 anyImportDeleted = anyImportDeleted || importDeleted
2083
2084 // Mark all of the package IDs containing the given file.
2085 filePackageIDs := invalidatedPackageIDs(uri, s.meta.ids, pkgFileChanged)
2086 for id := range filePackageIDs {
Alan Donovane0b516b2022-11-28 23:41:43 -05002087 directIDs[id] = directIDs[id] || invalidateMetadata // may insert 'false'
Robert Findleyb15dac22022-08-30 14:40:12 -04002088 }
2089
2090 // Invalidate the previous modTidyHandle if any of the files have been
2091 // saved or if any of the metadata has been invalidated.
2092 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
Rob Findley43b02ea2023-05-19 16:18:30 -04002093 // Only invalidate mod tidy results for the most relevant modfile in the
2094 // workspace. This is a potentially lossy optimization for workspaces
2095 // with many modules (such as google-cloud-go, which has 145 modules as
2096 // of writing).
2097 //
2098 // While it is theoretically possible that a change in workspace module A
2099 // could affect the mod-tidiness of workspace module B (if B transitively
2100 // requires A), such changes are probably unlikely and not worth the
2101 // penalty of re-running go mod tidy for everything. Note that mod tidy
2102 // ignores GOWORK, so the two modules would have to be related by a chain
2103 // of replace directives.
2104 //
2105 // We could improve accuracy by inspecting replace directives, using
2106 // overlays in go mod tidy, and/or checking for metadata changes from the
2107 // on-disk content.
2108 //
2109 // Note that we iterate the modTidyHandles map here, rather than e.g.
2110 // using nearestModFile, because we don't have access to an accurate
2111 // FileSource at this point in the snapshot clone.
2112 const onlyInvalidateMostRelevant = true
2113 if onlyInvalidateMostRelevant {
2114 deleteMostRelevantModFile(result.modTidyHandles, uri)
2115 } else {
2116 result.modTidyHandles.Clear()
2117 }
2118
2119 // TODO(rfindley): should we apply the above heuristic to mod vuln
2120 // or mod handles as well?
2121 //
2122 // TODO(rfindley): no tests fail if I delete the below line.
Robert Findleyb15dac22022-08-30 14:40:12 -04002123 result.modWhyHandles.Clear()
Robert Findley7cda55e2022-11-22 12:09:11 -05002124 result.modVulnHandles.Clear()
Robert Findleyb15dac22022-08-30 14:40:12 -04002125 }
2126
2127 result.parseModHandles.Delete(uri)
2128 result.parseWorkHandles.Delete(uri)
2129 // Handle the invalidated file; it may have new contents or not exist.
2130 if !change.exists {
2131 result.files.Delete(uri)
2132 } else {
2133 result.files.Set(uri, change.fileHandle)
2134 }
2135
2136 // Make sure to remove the changed file from the unloadable set.
2137 delete(result.unloadableFiles, uri)
2138 }
2139
2140 // Deleting an import can cause list errors due to import cycles to be
2141 // resolved. The best we can do without parsing the list error message is to
2142 // hope that list errors may have been resolved by a deleted import.
2143 //
2144 // We could do better by parsing the list error message. We already do this
2145 // to assign a better range to the list error, but for such critical
2146 // functionality as metadata, it's better to be conservative until it proves
2147 // impractical.
2148 //
2149 // We could also do better by looking at which imports were deleted and
2150 // trying to find cycles they are involved in. This fails when the file goes
2151 // from an unparseable state to a parseable state, as we don't have a
2152 // starting point to compare with.
2153 if anyImportDeleted {
2154 for id, metadata := range s.meta.metadata {
2155 if len(metadata.Errors) > 0 {
2156 directIDs[id] = true
2157 }
2158 }
2159 }
2160
2161 // Adding a file can resolve missing dependencies from existing packages.
2162 //
2163 // We could be smart here and try to guess which packages may have been
2164 // fixed, but until that proves necessary, just invalidate metadata for any
2165 // package with missing dependencies.
2166 if anyFileAdded {
2167 for id, metadata := range s.meta.metadata {
Alan Donovan21f61272022-10-20 14:32:57 -04002168 for _, impID := range metadata.DepsByImpPath {
2169 if impID == "" { // missing import
2170 directIDs[id] = true
2171 break
2172 }
Robert Findleyb15dac22022-08-30 14:40:12 -04002173 }
2174 }
2175 }
2176
2177 // Invalidate reverse dependencies too.
2178 // idsToInvalidate keeps track of transitive reverse dependencies.
2179 // If an ID is present in the map, invalidate its types.
2180 // If an ID's value is true, invalidate its metadata too.
2181 idsToInvalidate := map[PackageID]bool{}
2182 var addRevDeps func(PackageID, bool)
2183 addRevDeps = func(id PackageID, invalidateMetadata bool) {
2184 current, seen := idsToInvalidate[id]
2185 newInvalidateMetadata := current || invalidateMetadata
2186
2187 // If we've already seen this ID, and the value of invalidate
2188 // metadata has not changed, we can return early.
2189 if seen && current == newInvalidateMetadata {
2190 return
2191 }
2192 idsToInvalidate[id] = newInvalidateMetadata
2193 for _, rid := range s.meta.importedBy[id] {
2194 addRevDeps(rid, invalidateMetadata)
2195 }
2196 }
2197 for id, invalidateMetadata := range directIDs {
2198 addRevDeps(id, invalidateMetadata)
2199 }
2200
Robert Findley1b2d1bd2023-03-16 18:52:13 -04002201 // Invalidated package information.
2202 for id, invalidateMetadata := range idsToInvalidate {
2203 if _, ok := directIDs[id]; ok || invalidateMetadata {
2204 result.packages.Delete(id)
2205 } else {
2206 if entry, hit := result.packages.Get(id); hit {
2207 ph := entry.(*packageHandle).clone(false)
2208 result.packages.Set(id, ph, nil)
2209 }
2210 }
Robert Findley21d22562023-02-21 12:26:27 -05002211 result.activePackages.Delete(id)
Alan Donovanff22fab2022-11-18 14:47:36 -05002212 }
2213
2214 // Delete invalidated analysis actions.
Alan Donovan61e2d3f2022-10-14 17:58:15 -04002215 var actionsToDelete []analysisKey
2216 result.analyses.Range(func(k, _ interface{}) {
2217 key := k.(analysisKey)
Alan Donovanff22fab2022-11-18 14:47:36 -05002218 if _, ok := idsToInvalidate[key.pkgid]; ok {
2219 actionsToDelete = append(actionsToDelete, key)
2220 }
2221 })
2222 for _, key := range actionsToDelete {
Alan Donovan61e2d3f2022-10-14 17:58:15 -04002223 result.analyses.Delete(key)
Alan Donovanff22fab2022-11-18 14:47:36 -05002224 }
Robert Findleyb15dac22022-08-30 14:40:12 -04002225
2226 // If a file has been deleted, we must delete metadata for all packages
2227 // containing that file.
2228 //
2229 // TODO(rfindley): why not keep invalid metadata in this case? If we
2230 // otherwise allow operate on invalid metadata, why not continue to do so,
2231 // skipping the missing file?
2232 skipID := map[PackageID]bool{}
2233 for _, c := range changes {
2234 if c.exists {
2235 continue
2236 }
2237 // The file has been deleted.
2238 if ids, ok := s.meta.ids[c.fileHandle.URI()]; ok {
2239 for _, id := range ids {
2240 skipID[id] = true
2241 }
2242 }
2243 }
2244
2245 // Any packages that need loading in s still need loading in the new
2246 // snapshot.
2247 for k, v := range s.shouldLoad {
2248 if result.shouldLoad == nil {
2249 result.shouldLoad = make(map[PackageID][]PackagePath)
2250 }
2251 result.shouldLoad[k] = v
2252 }
2253
Robert Findleyb15dac22022-08-30 14:40:12 -04002254 // Compute which metadata updates are required. We only need to invalidate
2255 // packages directly containing the affected file, and only if it changed in
2256 // a relevant way.
Alan Donovan85bf7a82022-11-18 12:03:11 -05002257 metadataUpdates := make(map[PackageID]*source.Metadata)
Robert Findleyb15dac22022-08-30 14:40:12 -04002258 for k, v := range s.meta.metadata {
2259 invalidateMetadata := idsToInvalidate[k]
2260
2261 // For metadata that has been newly invalidated, capture package paths
2262 // requiring reloading in the shouldLoad map.
Alan Donovan3c3713e2022-11-10 13:02:38 -05002263 if invalidateMetadata && !source.IsCommandLineArguments(v.ID) {
Robert Findleyb15dac22022-08-30 14:40:12 -04002264 if result.shouldLoad == nil {
2265 result.shouldLoad = make(map[PackageID][]PackagePath)
2266 }
2267 needsReload := []PackagePath{v.PkgPath}
2268 if v.ForTest != "" && v.ForTest != v.PkgPath {
2269 // When reloading test variants, always reload their ForTest package as
2270 // well. Otherwise, we may miss test variants in the resulting load.
2271 //
2272 // TODO(rfindley): is this actually sufficient? Is it possible that
2273 // other test variants may be invalidated? Either way, we should
2274 // determine exactly what needs to be reloaded here.
2275 needsReload = append(needsReload, v.ForTest)
2276 }
2277 result.shouldLoad[k] = needsReload
2278 }
2279
2280 // Check whether the metadata should be deleted.
Robert Findley5a4eba52022-11-03 18:28:39 -04002281 if skipID[k] || invalidateMetadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04002282 metadataUpdates[k] = nil
2283 continue
2284 }
Robert Findleyb15dac22022-08-30 14:40:12 -04002285 }
2286
2287 // Update metadata, if necessary.
2288 result.meta = s.meta.Clone(metadataUpdates)
2289
2290 // Update workspace and active packages, if necessary.
2291 if result.meta != s.meta || anyFileOpenedOrClosed {
2292 result.workspacePackages = computeWorkspacePackagesLocked(result, result.meta)
Robert Findley21d22562023-02-21 12:26:27 -05002293 result.resetActivePackagesLocked()
Robert Findleyb15dac22022-08-30 14:40:12 -04002294 } else {
2295 result.workspacePackages = s.workspacePackages
2296 }
2297
2298 // Don't bother copying the importedBy graph,
2299 // as it changes each time we update metadata.
2300
Robert Findley5a4eba52022-11-03 18:28:39 -04002301 // TODO(rfindley): consolidate the this workspace mode detection with
2302 // workspace invalidation.
2303 workspaceModeChanged := s.workspaceMode() != result.workspaceMode()
2304
Robert Findleyb15dac22022-08-30 14:40:12 -04002305 // If the snapshot's workspace mode has changed, the packages loaded using
2306 // the previous mode are no longer relevant, so clear them out.
2307 if workspaceModeChanged {
2308 result.workspacePackages = map[PackageID]PackagePath{}
2309 }
Robert Findleyb15dac22022-08-30 14:40:12 -04002310 return result, release
2311}
2312
Rob Findley43b02ea2023-05-19 16:18:30 -04002313// deleteMostRelevantModFile deletes the mod file most likely to be the mod
2314// file for the changed URI, if it exists.
2315//
2316// Specifically, this is the longest mod file path in a directory containing
2317// changed. This might not be accurate if there is another mod file closer to
2318// changed that happens not to be present in the map, but that's OK: the goal
2319// of this function is to guarantee that IF the nearest mod file is present in
2320// the map, it is invalidated.
2321func deleteMostRelevantModFile(m *persistent.Map, changed span.URI) {
2322 var mostRelevant span.URI
2323 changedFile := changed.Filename()
2324
2325 m.Range(func(key, value interface{}) {
2326 modURI := key.(span.URI)
2327 if len(modURI) > len(mostRelevant) {
2328 if source.InDir(filepath.Dir(modURI.Filename()), changedFile) {
2329 mostRelevant = modURI
2330 }
2331 }
2332 })
2333 if mostRelevant != "" {
2334 m.Delete(mostRelevant)
2335 }
2336}
2337
Robert Findleyb15dac22022-08-30 14:40:12 -04002338// invalidatedPackageIDs returns all packages invalidated by a change to uri.
2339// If we haven't seen this URI before, we guess based on files in the same
2340// directory. This is of course incorrect in build systems where packages are
2341// not organized by directory.
2342//
2343// If packageFileChanged is set, the file is either a new file, or has a new
2344// package name. In this case, all known packages in the directory will be
2345// invalidated.
2346func invalidatedPackageIDs(uri span.URI, known map[span.URI][]PackageID, packageFileChanged bool) map[PackageID]struct{} {
2347 invalidated := make(map[PackageID]struct{})
2348
2349 // At a minimum, we invalidate packages known to contain uri.
2350 for _, id := range known[uri] {
2351 invalidated[id] = struct{}{}
2352 }
2353
2354 // If the file didn't move to a new package, we should only invalidate the
2355 // packages it is currently contained inside.
2356 if !packageFileChanged && len(invalidated) > 0 {
2357 return invalidated
2358 }
2359
2360 // This is a file we don't yet know about, or which has moved packages. Guess
2361 // relevant packages by considering files in the same directory.
2362
2363 // Cache of FileInfo to avoid unnecessary stats for multiple files in the
2364 // same directory.
2365 stats := make(map[string]struct {
2366 os.FileInfo
2367 error
2368 })
2369 getInfo := func(dir string) (os.FileInfo, error) {
2370 if res, ok := stats[dir]; ok {
2371 return res.FileInfo, res.error
2372 }
2373 fi, err := os.Stat(dir)
2374 stats[dir] = struct {
2375 os.FileInfo
2376 error
2377 }{fi, err}
2378 return fi, err
2379 }
2380 dir := filepath.Dir(uri.Filename())
2381 fi, err := getInfo(dir)
2382 if err == nil {
2383 // Aggregate all possibly relevant package IDs.
2384 for knownURI, ids := range known {
2385 knownDir := filepath.Dir(knownURI.Filename())
2386 knownFI, err := getInfo(knownDir)
2387 if err != nil {
2388 continue
2389 }
2390 if os.SameFile(fi, knownFI) {
2391 for _, id := range ids {
2392 invalidated[id] = struct{}{}
2393 }
2394 }
2395 }
2396 }
2397 return invalidated
2398}
2399
Robert Findleyb15dac22022-08-30 14:40:12 -04002400// fileWasSaved reports whether the FileHandle passed in has been saved. It
2401// accomplishes this by checking to see if the original and current FileHandles
2402// are both overlays, and if the current FileHandle is saved while the original
2403// FileHandle was not saved.
2404func fileWasSaved(originalFH, currentFH source.FileHandle) bool {
Robert Findleya7f033a2023-01-19 18:12:18 -05002405 c, ok := currentFH.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04002406 if !ok || c == nil {
2407 return true
2408 }
Robert Findleya7f033a2023-01-19 18:12:18 -05002409 o, ok := originalFH.(*Overlay)
Robert Findleyb15dac22022-08-30 14:40:12 -04002410 if !ok || o == nil {
2411 return c.saved
2412 }
2413 return !o.saved && c.saved
2414}
2415
2416// metadataChanges detects features of the change from oldFH->newFH that may
2417// affect package metadata.
2418//
2419// It uses lockedSnapshot to access cached parse information. lockedSnapshot
2420// must be locked.
2421//
2422// The result parameters have the following meaning:
2423// - invalidate means that package metadata for packages containing the file
2424// should be invalidated.
2425// - pkgFileChanged means that the file->package associates for the file have
2426// changed (possibly because the file is new, or because its package name has
2427// changed).
2428// - importDeleted means that an import has been deleted, or we can't
2429// determine if an import was deleted due to errors.
2430func metadataChanges(ctx context.Context, lockedSnapshot *snapshot, oldFH, newFH source.FileHandle) (invalidate, pkgFileChanged, importDeleted bool) {
2431 if oldFH == nil || newFH == nil { // existential changes
2432 changed := (oldFH == nil) != (newFH == nil)
2433 return changed, changed, (newFH == nil) // we don't know if an import was deleted
2434 }
2435
2436 // If the file hasn't changed, there's no need to reload.
2437 if oldFH.FileIdentity() == newFH.FileIdentity() {
2438 return false, false, false
2439 }
2440
Robert Findleyb2225202023-03-05 11:23:52 -05002441 fset := token.NewFileSet()
Robert Findleyb15dac22022-08-30 14:40:12 -04002442 // Parse headers to compare package names and imports.
Robert Findleyb2225202023-03-05 11:23:52 -05002443 oldHeads, oldErr := lockedSnapshot.parseCache.parseFiles(ctx, fset, source.ParseHeader, oldFH)
2444 newHeads, newErr := lockedSnapshot.parseCache.parseFiles(ctx, fset, source.ParseHeader, newFH)
Robert Findleyb15dac22022-08-30 14:40:12 -04002445
2446 if oldErr != nil || newErr != nil {
Robert Findleyae056092023-02-19 21:14:18 -05002447 // TODO(rfindley): we can get here if newFH does not exist. There is
2448 // asymmetry, in that newFH may be non-nil even if the underlying file does
2449 // not exist.
Robert Findleyb15dac22022-08-30 14:40:12 -04002450 //
2451 // We should not produce a non-nil filehandle for a file that does not exist.
2452 errChanged := (oldErr == nil) != (newErr == nil)
2453 return errChanged, errChanged, (newErr != nil) // we don't know if an import was deleted
2454 }
2455
Robert Findleyae056092023-02-19 21:14:18 -05002456 oldHead := oldHeads[0]
2457 newHead := newHeads[0]
2458
Robert Findleyb15dac22022-08-30 14:40:12 -04002459 // `go list` fails completely if the file header cannot be parsed. If we go
2460 // from a non-parsing state to a parsing state, we should reload.
2461 if oldHead.ParseErr != nil && newHead.ParseErr == nil {
2462 return true, true, true // We don't know what changed, so fall back on full invalidation.
2463 }
2464
2465 // If a package name has changed, the set of package imports may have changed
2466 // in ways we can't detect here. Assume an import has been deleted.
2467 if oldHead.File.Name.Name != newHead.File.Name.Name {
2468 return true, true, true
2469 }
2470
2471 // Check whether package imports have changed. Only consider potentially
2472 // valid imports paths.
2473 oldImports := validImports(oldHead.File.Imports)
2474 newImports := validImports(newHead.File.Imports)
2475
2476 for path := range newImports {
2477 if _, ok := oldImports[path]; ok {
2478 delete(oldImports, path)
2479 } else {
2480 invalidate = true // a new, potentially valid import was added
2481 }
2482 }
2483
2484 if len(oldImports) > 0 {
2485 invalidate = true
2486 importDeleted = true
2487 }
2488
2489 // If the change does not otherwise invalidate metadata, get the full ASTs in
2490 // order to check magic comments.
2491 //
2492 // Note: if this affects performance we can probably avoid parsing in the
2493 // common case by first scanning the source for potential comments.
2494 if !invalidate {
Robert Findleyb2225202023-03-05 11:23:52 -05002495 origFulls, oldErr := lockedSnapshot.parseCache.parseFiles(ctx, fset, source.ParseFull, oldFH)
2496 newFulls, newErr := lockedSnapshot.parseCache.parseFiles(ctx, fset, source.ParseFull, newFH)
Robert Findleyb15dac22022-08-30 14:40:12 -04002497 if oldErr == nil && newErr == nil {
Robert Findleyae056092023-02-19 21:14:18 -05002498 invalidate = magicCommentsChanged(origFulls[0].File, newFulls[0].File)
Robert Findleyb15dac22022-08-30 14:40:12 -04002499 } else {
2500 // At this point, we shouldn't ever fail to produce a ParsedGoFile, as
2501 // we're already past header parsing.
2502 bug.Reportf("metadataChanges: unparseable file %v (old error: %v, new error: %v)", oldFH.URI(), oldErr, newErr)
2503 }
2504 }
2505
2506 return invalidate, pkgFileChanged, importDeleted
2507}
2508
Robert Findleyb15dac22022-08-30 14:40:12 -04002509func magicCommentsChanged(original *ast.File, current *ast.File) bool {
2510 oldComments := extractMagicComments(original)
2511 newComments := extractMagicComments(current)
2512 if len(oldComments) != len(newComments) {
2513 return true
2514 }
2515 for i := range oldComments {
2516 if oldComments[i] != newComments[i] {
2517 return true
2518 }
2519 }
2520 return false
2521}
2522
2523// validImports extracts the set of valid import paths from imports.
2524func validImports(imports []*ast.ImportSpec) map[string]struct{} {
2525 m := make(map[string]struct{})
2526 for _, spec := range imports {
2527 if path := spec.Path.Value; validImportPath(path) {
2528 m[path] = struct{}{}
2529 }
2530 }
2531 return m
2532}
2533
2534func validImportPath(path string) bool {
2535 path, err := strconv.Unquote(path)
2536 if err != nil {
2537 return false
2538 }
2539 if path == "" {
2540 return false
2541 }
2542 if path[len(path)-1] == '/' {
2543 return false
2544 }
2545 return true
2546}
2547
2548var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`)
2549
2550// extractMagicComments finds magic comments that affect metadata in f.
2551func extractMagicComments(f *ast.File) []string {
2552 var results []string
2553 for _, cg := range f.Comments {
2554 for _, c := range cg.List {
2555 if buildConstraintOrEmbedRe.MatchString(c.Text) {
2556 results = append(results, c.Text)
2557 }
2558 }
2559 }
2560 return results
2561}
2562
2563func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) {
2564 s.AwaitInitialized(ctx)
2565
2566 s.mu.Lock()
2567 builtin := s.builtin
2568 s.mu.Unlock()
2569
2570 if builtin == "" {
2571 return nil, fmt.Errorf("no builtin package for view %s", s.view.name)
2572 }
2573
Alan Donovan36ed0b12023-03-13 14:20:23 -04002574 fh, err := s.ReadFile(ctx, builtin)
Robert Findleyb15dac22022-08-30 14:40:12 -04002575 if err != nil {
2576 return nil, err
2577 }
Robert Findley912861f2023-03-03 20:38:55 -05002578 // For the builtin file only, we need syntactic object resolution
2579 // (since we can't type check).
2580 mode := source.ParseFull &^ source.SkipObjectResolution
Robert Findley4b112032023-03-28 20:06:14 -04002581 pgfs, err := s.parseCache.parseFiles(ctx, token.NewFileSet(), mode, fh)
2582 if err != nil {
2583 return nil, err
2584 }
2585 return pgfs[0], nil
Robert Findleyb15dac22022-08-30 14:40:12 -04002586}
2587
Rob Findley1c9fe3f2023-05-11 14:33:05 -04002588func (s *snapshot) IsBuiltin(uri span.URI) bool {
Robert Findleyb15dac22022-08-30 14:40:12 -04002589 s.mu.Lock()
2590 defer s.mu.Unlock()
2591 // We should always get the builtin URI in a canonical form, so use simple
2592 // string comparison here. span.CompareURI is too expensive.
2593 return uri == s.builtin
2594}
2595
2596func (s *snapshot) setBuiltin(path string) {
2597 s.mu.Lock()
2598 defer s.mu.Unlock()
2599
2600 s.builtin = span.URIFromPath(path)
2601}