blob: 9a4c5b378118ca5e0c10ec5004031ad2fcc815e1 [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"
13 "go/token"
14 "go/types"
15 "io"
16 "io/ioutil"
17 "log"
18 "os"
19 "path/filepath"
20 "regexp"
21 "runtime"
22 "sort"
23 "strconv"
24 "strings"
25 "sync"
26 "sync/atomic"
27 "unsafe"
28
29 "golang.org/x/mod/modfile"
30 "golang.org/x/mod/module"
31 "golang.org/x/mod/semver"
32 "golang.org/x/sync/errgroup"
33 "golang.org/x/tools/go/packages"
Robert Findleyb15dac22022-08-30 14:40:12 -040034 "golang.org/x/tools/gopls/internal/lsp/source"
Alan Donovan26a95e62022-10-07 10:40:32 -040035 "golang.org/x/tools/gopls/internal/span"
Robert Findley9c639112022-09-28 12:03:51 -040036 "golang.org/x/tools/internal/bug"
37 "golang.org/x/tools/internal/event"
38 "golang.org/x/tools/internal/event/tag"
39 "golang.org/x/tools/internal/gocommand"
Robert Findleyb15dac22022-08-30 14:40:12 -040040 "golang.org/x/tools/internal/memoize"
41 "golang.org/x/tools/internal/packagesinternal"
42 "golang.org/x/tools/internal/persistent"
Robert Findleyb15dac22022-08-30 14:40:12 -040043 "golang.org/x/tools/internal/typesinternal"
44)
45
46type snapshot struct {
Robert Findley0c71b562022-11-14 11:58:07 -050047 sequenceID uint64
48 globalID source.GlobalSnapshotID
49 view *View
Robert Findleyb15dac22022-08-30 14:40:12 -040050
51 cancel func()
52 backgroundCtx context.Context
53
54 store *memoize.Store // cache of handles shared by all snapshots
55
56 refcount sync.WaitGroup // number of references
57 destroyedBy *string // atomically set to non-nil in Destroy once refcount = 0
58
59 // initialized reports whether the snapshot has been initialized. Concurrent
60 // initialization is guarded by the view.initializationSema. Each snapshot is
61 // initialized at most once: concurrent initialization is guarded by
62 // view.initializationSema.
63 initialized bool
64 // initializedErr holds the last error resulting from initialization. If
65 // initialization fails, we only retry when the the workspace modules change,
66 // to avoid too many go/packages calls.
67 initializedErr *source.CriticalError
68
69 // mu guards all of the maps in the snapshot, as well as the builtin URI.
70 mu sync.Mutex
71
72 // builtin pins the AST and package for builtin.go in memory.
73 builtin span.URI
74
75 // meta holds loaded metadata.
76 //
77 // meta is guarded by mu, but the metadataGraph itself is immutable.
78 // TODO(rfindley): in many places we hold mu while operating on meta, even
79 // though we only need to hold mu while reading the pointer.
80 meta *metadataGraph
81
82 // files maps file URIs to their corresponding FileHandles.
83 // It may invalidated when a file's content changes.
84 files filesMap
85
86 // parsedGoFiles maps a parseKey to the handle of the future result of parsing it.
87 parsedGoFiles *persistent.Map // from parseKey to *memoize.Promise[parseGoResult]
88
89 // parseKeysByURI records the set of keys of parsedGoFiles that
90 // need to be invalidated for each URI.
91 // TODO(adonovan): opt: parseKey = ParseMode + URI, so this could
92 // be just a set of ParseModes, or we could loop over AllParseModes.
93 parseKeysByURI parseKeysByURIMap
94
95 // symbolizeHandles maps each file URI to a handle for the future
96 // result of computing the symbols declared in that file.
97 symbolizeHandles *persistent.Map // from span.URI to *memoize.Promise[symbolizeResult]
98
99 // packages maps a packageKey to a *packageHandle.
100 // It may be invalidated when a file's content changes.
101 //
102 // Invariants to preserve:
103 // - packages.Get(id).m.Metadata == meta.metadata[id].Metadata for all ids
104 // - if a package is in packages, then all of its dependencies should also
105 // be in packages, unless there is a missing import
106 packages *persistent.Map // from packageKey to *memoize.Promise[*packageHandle]
107
108 // isActivePackageCache maps package ID to the cached value if it is active or not.
109 // It may be invalidated when metadata changes or a new file is opened or closed.
110 isActivePackageCache isActivePackageCacheMap
111
112 // actions maps an actionKey to the handle for the future
113 // result of execution an analysis pass on a package.
114 actions *persistent.Map // from actionKey to *actionHandle
115
116 // workspacePackages contains the workspace's packages, which are loaded
117 // when the view is created.
118 workspacePackages map[PackageID]PackagePath
119
120 // shouldLoad tracks packages that need to be reloaded, mapping a PackageID
121 // to the package paths that should be used to reload it
122 //
123 // When we try to load a package, we clear it from the shouldLoad map
124 // regardless of whether the load succeeded, to prevent endless loads.
125 shouldLoad map[PackageID][]PackagePath
126
127 // unloadableFiles keeps track of files that we've failed to load.
128 unloadableFiles map[span.URI]struct{}
129
130 // parseModHandles keeps track of any parseModHandles for the snapshot.
131 // The handles need not refer to only the view's go.mod file.
132 parseModHandles *persistent.Map // from span.URI to *memoize.Promise[parseModResult]
133
134 // parseWorkHandles keeps track of any parseWorkHandles for the snapshot.
135 // The handles need not refer to only the view's go.work file.
136 parseWorkHandles *persistent.Map // from span.URI to *memoize.Promise[parseWorkResult]
137
138 // Preserve go.mod-related handles to avoid garbage-collecting the results
139 // of various calls to the go command. The handles need not refer to only
140 // the view's go.mod file.
141 modTidyHandles *persistent.Map // from span.URI to *memoize.Promise[modTidyResult]
142 modWhyHandles *persistent.Map // from span.URI to *memoize.Promise[modWhyResult]
Robert Findley7cda55e2022-11-22 12:09:11 -0500143 modVulnHandles *persistent.Map // from span.URI to *memoize.Promise[modVulnResult]
Robert Findleyb15dac22022-08-30 14:40:12 -0400144
145 workspace *workspace // (not guarded by mu)
146
147 // The cached result of makeWorkspaceDir, created on demand and deleted by Snapshot.Destroy.
148 workspaceDir string
149 workspaceDirErr error
150
151 // knownSubdirs is the set of subdirectories in the workspace, used to
152 // create glob patterns for file watching.
153 knownSubdirs knownDirsSet
154 knownSubdirsPatternCache string
155 // unprocessedSubdirChanges are any changes that might affect the set of
156 // subdirectories in the workspace. They are not reflected to knownSubdirs
157 // during the snapshot cloning step as it can slow down cloning.
158 unprocessedSubdirChanges []*fileChange
159}
160
Robert Findley0c71b562022-11-14 11:58:07 -0500161var globalSnapshotID uint64
162
163func nextSnapshotID() source.GlobalSnapshotID {
164 return source.GlobalSnapshotID(atomic.AddUint64(&globalSnapshotID, 1))
165}
166
Robert Findleyb15dac22022-08-30 14:40:12 -0400167var _ memoize.RefCounted = (*snapshot)(nil) // snapshots are reference-counted
168
169// Acquire prevents the snapshot from being destroyed until the returned function is called.
170//
171// (s.Acquire().release() could instead be expressed as a pair of
172// method calls s.IncRef(); s.DecRef(). The latter has the advantage
173// that the DecRefs are fungible and don't require holding anything in
174// addition to the refcounted object s, but paradoxically that is also
175// an advantage of the current approach, which forces the caller to
176// consider the release function at every stage, making a reference
177// leak more obvious.)
178func (s *snapshot) Acquire() func() {
179 type uP = unsafe.Pointer
180 if destroyedBy := atomic.LoadPointer((*uP)(uP(&s.destroyedBy))); destroyedBy != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500181 log.Panicf("%d: acquire() after Destroy(%q)", s.globalID, *(*string)(destroyedBy))
Robert Findleyb15dac22022-08-30 14:40:12 -0400182 }
183 s.refcount.Add(1)
184 return s.refcount.Done
185}
186
187func (s *snapshot) awaitPromise(ctx context.Context, p *memoize.Promise) (interface{}, error) {
188 return p.Get(ctx, s)
189}
190
191// destroy waits for all leases on the snapshot to expire then releases
192// any resources (reference counts and files) associated with it.
193// Snapshots being destroyed can be awaited using v.destroyWG.
194//
195// TODO(adonovan): move this logic into the release function returned
196// by Acquire when the reference count becomes zero. (This would cost
197// us the destroyedBy debug info, unless we add it to the signature of
198// memoize.RefCounted.Acquire.)
199//
200// The destroyedBy argument is used for debugging.
201//
202// v.snapshotMu must be held while calling this function, in order to preserve
203// the invariants described by the the docstring for v.snapshot.
204func (v *View) destroy(s *snapshot, destroyedBy string) {
205 v.snapshotWG.Add(1)
206 go func() {
207 defer v.snapshotWG.Done()
208 s.destroy(destroyedBy)
209 }()
210}
211
212func (s *snapshot) destroy(destroyedBy string) {
213 // Wait for all leases to end before commencing destruction.
214 s.refcount.Wait()
215
216 // Report bad state as a debugging aid.
217 // Not foolproof: another thread could acquire() at this moment.
218 type uP = unsafe.Pointer // looking forward to generics...
219 if old := atomic.SwapPointer((*uP)(uP(&s.destroyedBy)), uP(&destroyedBy)); old != nil {
Robert Findley0c71b562022-11-14 11:58:07 -0500220 log.Panicf("%d: Destroy(%q) after Destroy(%q)", s.globalID, destroyedBy, *(*string)(old))
Robert Findleyb15dac22022-08-30 14:40:12 -0400221 }
222
223 s.packages.Destroy()
224 s.isActivePackageCache.Destroy()
225 s.actions.Destroy()
226 s.files.Destroy()
227 s.parsedGoFiles.Destroy()
228 s.parseKeysByURI.Destroy()
229 s.knownSubdirs.Destroy()
230 s.symbolizeHandles.Destroy()
231 s.parseModHandles.Destroy()
232 s.parseWorkHandles.Destroy()
233 s.modTidyHandles.Destroy()
Robert Findley7cda55e2022-11-22 12:09:11 -0500234 s.modVulnHandles.Destroy()
Robert Findleyb15dac22022-08-30 14:40:12 -0400235 s.modWhyHandles.Destroy()
236
237 if s.workspaceDir != "" {
238 if err := os.RemoveAll(s.workspaceDir); err != nil {
239 event.Error(context.Background(), "cleaning workspace dir", err)
240 }
241 }
242}
243
Robert Findley0c71b562022-11-14 11:58:07 -0500244func (s *snapshot) SequenceID() uint64 {
245 return s.sequenceID
246}
247
248func (s *snapshot) GlobalID() source.GlobalSnapshotID {
249 return s.globalID
Robert Findleyb15dac22022-08-30 14:40:12 -0400250}
251
252func (s *snapshot) View() source.View {
253 return s.view
254}
255
256func (s *snapshot) BackgroundContext() context.Context {
257 return s.backgroundCtx
258}
259
260func (s *snapshot) FileSet() *token.FileSet {
261 return s.view.session.cache.fset
262}
263
264func (s *snapshot) ModFiles() []span.URI {
265 var uris []span.URI
Robert Findley39c2fd82022-11-04 13:14:16 -0400266 for modURI := range s.workspace.ActiveModFiles() {
Robert Findleyb15dac22022-08-30 14:40:12 -0400267 uris = append(uris, modURI)
268 }
269 return uris
270}
271
272func (s *snapshot) WorkFile() span.URI {
273 return s.workspace.workFile
274}
275
276func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle {
277 s.mu.Lock()
278 defer s.mu.Unlock()
279
280 tmpls := map[span.URI]source.VersionedFileHandle{}
281 s.files.Range(func(k span.URI, fh source.VersionedFileHandle) {
282 if s.view.FileKind(fh) == source.Tmpl {
283 tmpls[k] = fh
284 }
285 })
286 return tmpls
287}
288
289func (s *snapshot) ValidBuildConfiguration() bool {
290 // Since we only really understand the `go` command, if the user has a
291 // different GOPACKAGESDRIVER, assume that their configuration is valid.
292 if s.view.hasGopackagesDriver {
293 return true
294 }
295 // Check if the user is working within a module or if we have found
296 // multiple modules in the workspace.
Robert Findley39c2fd82022-11-04 13:14:16 -0400297 if len(s.workspace.ActiveModFiles()) > 0 {
Robert Findleyb15dac22022-08-30 14:40:12 -0400298 return true
299 }
300 // The user may have a multiple directories in their GOPATH.
301 // Check if the workspace is within any of them.
302 for _, gp := range filepath.SplitList(s.view.gopath) {
303 if source.InDir(filepath.Join(gp, "src"), s.view.rootURI.Filename()) {
304 return true
305 }
306 }
307 return false
308}
309
310// workspaceMode describes the way in which the snapshot's workspace should
311// be loaded.
312func (s *snapshot) workspaceMode() workspaceMode {
313 var mode workspaceMode
314
315 // If the view has an invalid configuration, don't build the workspace
316 // module.
317 validBuildConfiguration := s.ValidBuildConfiguration()
318 if !validBuildConfiguration {
319 return mode
320 }
321 // If the view is not in a module and contains no modules, but still has a
322 // valid workspace configuration, do not create the workspace module.
323 // It could be using GOPATH or a different build system entirely.
Robert Findley39c2fd82022-11-04 13:14:16 -0400324 if len(s.workspace.ActiveModFiles()) == 0 && validBuildConfiguration {
Robert Findleyb15dac22022-08-30 14:40:12 -0400325 return mode
326 }
327 mode |= moduleMode
328 options := s.view.Options()
329 // The -modfile flag is available for Go versions >= 1.14.
330 if options.TempModfile && s.view.workspaceInformation.goversion >= 14 {
331 mode |= tempModfile
332 }
333 return mode
334}
335
336// config returns the configuration used for the snapshot's interaction with
337// the go/packages API. It uses the given working directory.
338//
339// TODO(rstambler): go/packages requires that we do not provide overlays for
340// multiple modules in on config, so buildOverlay needs to filter overlays by
341// module.
342func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
343 s.view.optionsMu.Lock()
344 verboseOutput := s.view.options.VerboseOutput
345 s.view.optionsMu.Unlock()
346
347 cfg := &packages.Config{
348 Context: ctx,
349 Dir: inv.WorkingDir,
350 Env: inv.Env,
351 BuildFlags: inv.BuildFlags,
352 Mode: packages.NeedName |
353 packages.NeedFiles |
354 packages.NeedCompiledGoFiles |
355 packages.NeedImports |
356 packages.NeedDeps |
357 packages.NeedTypesSizes |
358 packages.NeedModule |
359 packages.LoadMode(packagesinternal.DepsErrors) |
360 packages.LoadMode(packagesinternal.ForTest),
Alan Donovan6f993662022-11-10 15:07:47 -0500361 Fset: nil, // we do our own parsing
Robert Findleyb15dac22022-08-30 14:40:12 -0400362 Overlay: s.buildOverlay(),
363 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
364 panic("go/packages must not be used to parse files")
365 },
366 Logf: func(format string, args ...interface{}) {
367 if verboseOutput {
368 event.Log(ctx, fmt.Sprintf(format, args...))
369 }
370 },
371 Tests: true,
372 }
373 packagesinternal.SetModFile(cfg, inv.ModFile)
374 packagesinternal.SetModFlag(cfg, inv.ModFlag)
375 // We want to type check cgo code if go/types supports it.
376 if typesinternal.SetUsesCgo(&types.Config{}) {
377 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
378 }
379 packagesinternal.SetGoCmdRunner(cfg, s.view.session.gocmdRunner)
380 return cfg
381}
382
383func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
384 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
385 if err != nil {
386 return nil, err
387 }
388 defer cleanup()
389
390 return s.view.session.gocmdRunner.Run(ctx, *inv)
391}
392
393func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
394 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
395 if err != nil {
396 return err
397 }
398 defer cleanup()
399 return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
400}
401
402func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) {
403 var flags source.InvocationFlags
404 if s.workspaceMode()&tempModfile != 0 {
405 flags = source.WriteTemporaryModFile
406 } else {
407 flags = source.Normal
408 }
409 if allowNetwork {
410 flags |= source.AllowNetwork
411 }
412 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd})
413 if err != nil {
414 return false, nil, nil, err
415 }
416 defer cleanup()
417 invoke := func(args ...string) (*bytes.Buffer, error) {
418 inv.Verb = args[0]
419 inv.Args = args[1:]
420 return s.view.session.gocmdRunner.Run(ctx, *inv)
421 }
422 if err := run(invoke); err != nil {
423 return false, nil, nil, err
424 }
425 if flags.Mode() != source.WriteTemporaryModFile {
426 return false, nil, nil, nil
427 }
428 var modBytes, sumBytes []byte
429 modBytes, err = ioutil.ReadFile(tmpURI.Filename())
430 if err != nil && !os.IsNotExist(err) {
431 return false, nil, nil, err
432 }
433 sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum")
434 if err != nil && !os.IsNotExist(err) {
435 return false, nil, nil, err
436 }
437 return true, modBytes, sumBytes, nil
438}
439
440// goCommandInvocation populates inv with configuration for running go commands on the snapshot.
441//
442// TODO(rfindley): refactor this function to compose the required configuration
443// explicitly, rather than implicitly deriving it from flags and inv.
444//
445// TODO(adonovan): simplify cleanup mechanism. It's hard to see, but
446// it used only after call to tempModFile. Clarify that it is only
447// non-nil on success.
448func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
449 s.view.optionsMu.Lock()
450 allowModfileModificationOption := s.view.options.AllowModfileModifications
451 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
452
453 // TODO(rfindley): this is very hard to follow, and may not even be doing the
454 // right thing: should inv.Env really trample view.options? Do we ever invoke
455 // this with a non-empty inv.Env?
456 //
457 // We should refactor to make it clearer that the correct env is being used.
Robert Findleyc7ed4b32022-11-16 13:52:25 -0500458 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.GO111MODULE())
Robert Findleyb15dac22022-08-30 14:40:12 -0400459 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
460 s.view.optionsMu.Unlock()
461 cleanup = func() {} // fallback
462
463 // All logic below is for module mode.
464 if s.workspaceMode()&moduleMode == 0 {
465 return "", inv, cleanup, nil
466 }
467
468 mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
469 if !allowNetwork && !allowNetworkOption {
470 inv.Env = append(inv.Env, "GOPROXY=off")
471 }
472
473 // What follows is rather complicated logic for how to actually run the go
474 // command. A word of warning: this is the result of various incremental
475 // features added to gopls, and varying behavior of the Go command across Go
476 // versions. It can surely be cleaned up significantly, but tread carefully.
477 //
478 // Roughly speaking we need to resolve four things:
479 // - the working directory.
480 // - the -mod flag
481 // - the -modfile flag
482 //
483 // These are dependent on a number of factors: whether we need to run in a
484 // synthetic workspace, whether flags are supported at the current go
485 // version, and what we're actually trying to achieve (the
486 // source.InvocationFlags).
487
488 var modURI span.URI
489 // Select the module context to use.
490 // If we're type checking, we need to use the workspace context, meaning
491 // the main (workspace) module. Otherwise, we should use the module for
492 // the passed-in working dir.
493 if mode == source.LoadWorkspace {
494 switch s.workspace.moduleSource {
495 case legacyWorkspace:
Robert Findley39c2fd82022-11-04 13:14:16 -0400496 for m := range s.workspace.ActiveModFiles() { // range to access the only element
Robert Findleyb15dac22022-08-30 14:40:12 -0400497 modURI = m
498 }
499 case goWorkWorkspace:
500 if s.view.goversion >= 18 {
501 break
502 }
503 // Before go 1.18, the Go command did not natively support go.work files,
504 // so we 'fake' them with a workspace module.
505 fallthrough
506 case fileSystemWorkspace, goplsModWorkspace:
507 var tmpDir span.URI
508 var err error
509 tmpDir, err = s.getWorkspaceDir(ctx)
510 if err != nil {
511 return "", nil, cleanup, err
512 }
513 inv.WorkingDir = tmpDir.Filename()
514 modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod"))
515 }
516 } else {
517 modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir))
518 }
519
520 var modContent []byte
521 if modURI != "" {
522 modFH, err := s.GetFile(ctx, modURI)
523 if err != nil {
524 return "", nil, cleanup, err
525 }
526 modContent, err = modFH.Read()
527 if err != nil {
528 return "", nil, cleanup, err
529 }
530 }
531
532 // TODO(rfindley): in the case of go.work mode, modURI is empty and we fall
533 // back on the default behavior of vendorEnabled with an empty modURI. Figure
534 // out what is correct here and implement it explicitly.
535 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent)
536 if err != nil {
537 return "", nil, cleanup, err
538 }
539
540 mutableModFlag := ""
541 // If the mod flag isn't set, populate it based on the mode and workspace.
542 if inv.ModFlag == "" {
543 if s.view.goversion >= 16 {
544 mutableModFlag = "mod"
545 }
546
547 switch mode {
548 case source.LoadWorkspace, source.Normal:
549 if vendorEnabled {
550 inv.ModFlag = "vendor"
551 } else if !allowModfileModificationOption {
552 inv.ModFlag = "readonly"
553 } else {
554 inv.ModFlag = mutableModFlag
555 }
556 case source.WriteTemporaryModFile:
557 inv.ModFlag = mutableModFlag
558 // -mod must be readonly when using go.work files - see issue #48941
559 inv.Env = append(inv.Env, "GOWORK=off")
560 }
561 }
562
563 // Only use a temp mod file if the modfile can actually be mutated.
564 needTempMod := inv.ModFlag == mutableModFlag
565 useTempMod := s.workspaceMode()&tempModfile != 0
566 if needTempMod && !useTempMod {
567 return "", nil, cleanup, source.ErrTmpModfileUnsupported
568 }
569
570 // We should use -modfile if:
571 // - the workspace mode supports it
572 // - we're using a go.work file on go1.18+, or we need a temp mod file (for
573 // example, if running go mod tidy in a go.work workspace)
574 //
575 // TODO(rfindley): this is very hard to follow. Refactor.
576 useWorkFile := !needTempMod && s.workspace.moduleSource == goWorkWorkspace && s.view.goversion >= 18
577 if useWorkFile {
578 // Since we're running in the workspace root, the go command will resolve GOWORK automatically.
579 } else if useTempMod {
580 if modURI == "" {
581 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
582 }
583 modFH, err := s.GetFile(ctx, modURI)
584 if err != nil {
585 return "", nil, cleanup, err
586 }
587 // Use the go.sum if it happens to be available.
588 gosum := s.goSum(ctx, modURI)
589 tmpURI, cleanup, err = tempModFile(modFH, gosum)
590 if err != nil {
591 return "", nil, cleanup, err
592 }
593 inv.ModFile = tmpURI.Filename()
594 }
595
596 return tmpURI, inv, cleanup, nil
597}
598
599// usesWorkspaceDir reports whether the snapshot should use a synthetic
600// workspace directory for running workspace go commands such as go list.
601//
602// TODO(rfindley): this logic is duplicated with goCommandInvocation. Clean up
603// the latter, and deduplicate.
604func (s *snapshot) usesWorkspaceDir() bool {
605 switch s.workspace.moduleSource {
606 case legacyWorkspace:
607 return false
608 case goWorkWorkspace:
609 if s.view.goversion >= 18 {
610 return false
611 }
612 // Before go 1.18, the Go command did not natively support go.work files,
613 // so we 'fake' them with a workspace module.
614 }
615 return true
616}
617
618func (s *snapshot) buildOverlay() map[string][]byte {
619 s.mu.Lock()
620 defer s.mu.Unlock()
621
622 overlays := make(map[string][]byte)
623 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
624 overlay, ok := fh.(*overlay)
625 if !ok {
626 return
627 }
628 if overlay.saved {
629 return
630 }
631 // TODO(rstambler): Make sure not to send overlays outside of the current view.
632 overlays[uri.Filename()] = overlay.text
633 })
634 return overlays
635}
636
637func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]source.Package, error) {
638 ctx = event.Label(ctx, tag.URI.Of(uri))
639
640 phs, err := s.packageHandlesForFile(ctx, uri, mode, includeTestVariants)
641 if err != nil {
642 return nil, err
643 }
644 var pkgs []source.Package
645 for _, ph := range phs {
646 pkg, err := ph.await(ctx, s)
647 if err != nil {
648 return nil, err
649 }
650 pkgs = append(pkgs, pkg)
651 }
652 return pkgs, nil
653}
654
655func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) {
656 ctx = event.Label(ctx, tag.URI.Of(uri))
657
658 phs, err := s.packageHandlesForFile(ctx, uri, mode, false)
659 if err != nil {
660 return nil, err
661 }
662
663 if len(phs) < 1 {
664 return nil, fmt.Errorf("no packages")
665 }
666
667 ph := phs[0]
668 for _, handle := range phs[1:] {
669 switch pkgPolicy {
670 case source.WidestPackage:
671 if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) {
672 ph = handle
673 }
674 case source.NarrowestPackage:
675 if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) {
676 ph = handle
677 }
678 }
679 }
680 if ph == nil {
681 return nil, fmt.Errorf("no packages in input")
682 }
683
684 return ph.await(ctx, s)
685}
686
Robert Findleyff4ff8b2022-10-04 09:19:27 -0400687func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, withIntermediateTestVariants bool) ([]*packageHandle, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400688 // TODO(rfindley): why can't/shouldn't we awaitLoaded here? It seems that if
689 // we ask for package handles for a file, we should wait for pending loads.
690 // Else we will reload orphaned files before the initial load completes.
691
692 // Check if we should reload metadata for the file. We don't invalidate IDs
693 // (though we should), so the IDs will be a better source of truth than the
694 // metadata. If there are no IDs for the file, then we should also reload.
695 fh, err := s.GetFile(ctx, uri)
696 if err != nil {
697 return nil, err
698 }
699 if kind := s.view.FileKind(fh); kind != source.Go {
700 return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind)
701 }
702 knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
703 if err != nil {
704 return nil, err
705 }
706
707 var phs []*packageHandle
708 for _, id := range knownIDs {
709 // Filter out any intermediate test variants. We typically aren't
710 // interested in these packages for file= style queries.
Robert Findleyb280e272022-10-04 09:49:34 -0400711 if m := s.getMetadata(id); m != nil && m.IsIntermediateTestVariant() && !withIntermediateTestVariants {
Robert Findleyb15dac22022-08-30 14:40:12 -0400712 continue
713 }
Robert Findley906c7332022-10-04 15:01:17 -0400714 parseMode := source.ParseFull
715 if mode == source.TypecheckWorkspace {
716 parseMode = s.workspaceParseMode(id)
Robert Findleyb15dac22022-08-30 14:40:12 -0400717 }
718
Robert Findley906c7332022-10-04 15:01:17 -0400719 ph, err := s.buildPackageHandle(ctx, id, parseMode)
720 if err != nil {
721 return nil, err
Robert Findleyb15dac22022-08-30 14:40:12 -0400722 }
Robert Findley906c7332022-10-04 15:01:17 -0400723 phs = append(phs, ph)
Robert Findleyb15dac22022-08-30 14:40:12 -0400724 }
725 return phs, nil
726}
727
Robert Findleyb2533142022-10-10 13:50:45 -0400728// getOrLoadIDsForURI returns package IDs associated with the file uri. If no
729// such packages exist or if they are known to be stale, it reloads the file.
730//
731// If experimentalUseInvalidMetadata is set, this function may return package
732// IDs with invalid metadata.
Robert Findleyb15dac22022-08-30 14:40:12 -0400733func (s *snapshot) getOrLoadIDsForURI(ctx context.Context, uri span.URI) ([]PackageID, error) {
734 s.mu.Lock()
Robert Findleyb2533142022-10-10 13:50:45 -0400735
736 // Start with the set of package associations derived from the last load.
Robert Findleyb15dac22022-08-30 14:40:12 -0400737 ids := s.meta.ids[uri]
Robert Findleyb2533142022-10-10 13:50:45 -0400738
Robert Findleyb2533142022-10-10 13:50:45 -0400739 shouldLoad := false // whether any packages containing uri are marked 'shouldLoad'
Robert Findleyb15dac22022-08-30 14:40:12 -0400740 for _, id := range ids {
Robert Findleyb2533142022-10-10 13:50:45 -0400741 if len(s.shouldLoad[id]) > 0 {
742 shouldLoad = true
743 }
Robert Findleyb15dac22022-08-30 14:40:12 -0400744 }
Robert Findleyb2533142022-10-10 13:50:45 -0400745
746 // Check if uri is known to be unloadable.
Robert Findleyb2533142022-10-10 13:50:45 -0400747 _, unloadable := s.unloadableFiles[uri]
748
Robert Findleyb15dac22022-08-30 14:40:12 -0400749 s.mu.Unlock()
750
Robert Findleyb2533142022-10-10 13:50:45 -0400751 // Reload if loading is likely to improve the package associations for uri:
752 // - uri is not contained in any valid packages
753 // - ...or one of the packages containing uri is marked 'shouldLoad'
754 // - ...but uri is not unloadable
Robert Findley23056f62022-11-03 19:06:31 -0400755 if (shouldLoad || len(ids) == 0) && !unloadable {
Robert Findleyb2533142022-10-10 13:50:45 -0400756 scope := fileLoadScope(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -0400757 err := s.load(ctx, false, scope)
758
Robert Findleyb2533142022-10-10 13:50:45 -0400759 // Guard against failed loads due to context cancellation.
Robert Findleyb15dac22022-08-30 14:40:12 -0400760 //
Robert Findleyb2533142022-10-10 13:50:45 -0400761 // Return the context error here as the current operation is no longer
762 // valid.
763 if ctxErr := ctx.Err(); ctxErr != nil {
764 return nil, ctxErr
Robert Findleyb15dac22022-08-30 14:40:12 -0400765 }
766
Robert Findleyb2533142022-10-10 13:50:45 -0400767 // We must clear scopes after loading.
768 //
769 // TODO(rfindley): unlike reloadWorkspace, this is simply marking loaded
770 // packages as loaded. We could do this from snapshot.load and avoid
771 // raciness.
772 s.clearShouldLoad(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -0400773
Robert Findleyb2533142022-10-10 13:50:45 -0400774 // Don't return an error here, as we may still return stale IDs.
775 // Furthermore, the result of getOrLoadIDsForURI should be consistent upon
776 // subsequent calls, even if the file is marked as unloadable.
777 if err != nil && !errors.Is(err, errNoPackages) {
778 event.Error(ctx, "getOrLoadIDsForURI", err)
Robert Findleyb15dac22022-08-30 14:40:12 -0400779 }
780 }
781
Robert Findleyb2533142022-10-10 13:50:45 -0400782 s.mu.Lock()
783 ids = s.meta.ids[uri]
Robert Findley23056f62022-11-03 19:06:31 -0400784 // metadata is only ever added by loading, so if we get here and still have
785 // no ids, uri is unloadable.
786 if !unloadable && len(ids) == 0 {
787 s.unloadableFiles[uri] = struct{}{}
Robert Findleyb2533142022-10-10 13:50:45 -0400788 }
789 s.mu.Unlock()
790
Robert Findleyb15dac22022-08-30 14:40:12 -0400791 return ids, nil
792}
793
Alan Donovan3c3713e2022-11-10 13:02:38 -0500794func (s *snapshot) GetReverseDependencies(ctx context.Context, id PackageID) ([]source.Package, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -0400795 if err := s.awaitLoaded(ctx); err != nil {
796 return nil, err
797 }
798 s.mu.Lock()
799 meta := s.meta
800 s.mu.Unlock()
Robert Findley5a4eba52022-11-03 18:28:39 -0400801 ids := meta.reverseTransitiveClosure(id)
Robert Findleyb15dac22022-08-30 14:40:12 -0400802
803 // Make sure to delete the original package ID from the map.
Alan Donovan3c3713e2022-11-10 13:02:38 -0500804 delete(ids, id)
Robert Findleyb15dac22022-08-30 14:40:12 -0400805
806 var pkgs []source.Package
807 for id := range ids {
808 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
809 if err != nil {
810 return nil, err
811 }
812 pkgs = append(pkgs, pkg)
813 }
814 return pkgs, nil
815}
816
817func (s *snapshot) checkedPackage(ctx context.Context, id PackageID, mode source.ParseMode) (*pkg, error) {
818 ph, err := s.buildPackageHandle(ctx, id, mode)
819 if err != nil {
820 return nil, err
821 }
822 return ph.await(ctx, s)
823}
824
825func (s *snapshot) getImportedBy(id PackageID) []PackageID {
826 s.mu.Lock()
827 defer s.mu.Unlock()
828 return s.meta.importedBy[id]
829}
830
831func (s *snapshot) workspacePackageIDs() (ids []PackageID) {
832 s.mu.Lock()
833 defer s.mu.Unlock()
834
835 for id := range s.workspacePackages {
836 ids = append(ids, id)
837 }
838 return ids
839}
840
841func (s *snapshot) activePackageIDs() (ids []PackageID) {
842 if s.view.Options().MemoryMode == source.ModeNormal {
843 return s.workspacePackageIDs()
844 }
845
846 s.mu.Lock()
847 defer s.mu.Unlock()
848
849 for id := range s.workspacePackages {
850 if s.isActiveLocked(id) {
851 ids = append(ids, id)
852 }
853 }
854 return ids
855}
856
857func (s *snapshot) isActiveLocked(id PackageID) (active bool) {
858 if seen, ok := s.isActivePackageCache.Get(id); ok {
859 return seen
860 }
861 defer func() {
862 s.isActivePackageCache.Set(id, active)
863 }()
864 m, ok := s.meta.metadata[id]
865 if !ok {
866 return false
867 }
868 for _, cgf := range m.CompiledGoFiles {
869 if s.isOpenLocked(cgf) {
870 return true
871 }
872 }
873 // TODO(rfindley): it looks incorrect that we don't also check GoFiles here.
874 // If a CGo file is open, we want to consider the package active.
Alan Donovan21f61272022-10-20 14:32:57 -0400875 for _, dep := range m.DepsByPkgPath {
Robert Findleyb15dac22022-08-30 14:40:12 -0400876 if s.isActiveLocked(dep) {
877 return true
878 }
879 }
880 return false
881}
882
883func (s *snapshot) resetIsActivePackageLocked() {
884 s.isActivePackageCache.Destroy()
885 s.isActivePackageCache = newIsActivePackageCacheMap()
886}
887
888const fileExtensions = "go,mod,sum,work"
889
890func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
891 extensions := fileExtensions
892 for _, ext := range s.View().Options().TemplateExtensions {
893 extensions += "," + ext
894 }
895 // Work-around microsoft/vscode#100870 by making sure that we are,
896 // at least, watching the user's entire workspace. This will still be
897 // applied to every folder in the workspace.
898 patterns := map[string]struct{}{
899 fmt.Sprintf("**/*.{%s}", extensions): {},
900 }
901
902 if s.view.explicitGowork != "" {
903 patterns[s.view.explicitGowork.Filename()] = struct{}{}
904 }
905
906 // Add a pattern for each Go module in the workspace that is not within the view.
907 dirs := s.workspace.dirs(ctx, s)
908 for _, dir := range dirs {
909 dirName := dir.Filename()
910
911 // If the directory is within the view's folder, we're already watching
912 // it with the pattern above.
913 if source.InDir(s.view.folder.Filename(), dirName) {
914 continue
915 }
916 // TODO(rstambler): If microsoft/vscode#3025 is resolved before
917 // microsoft/vscode#101042, we will need a work-around for Windows
918 // drive letter casing.
919 patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{}
920 }
921
922 // Some clients do not send notifications for changes to directories that
923 // contain Go code (golang/go#42348). To handle this, explicitly watch all
924 // of the directories in the workspace. We find them by adding the
925 // directories of every file in the snapshot's workspace directories.
926 // There may be thousands.
927 if pattern := s.getKnownSubdirsPattern(dirs); pattern != "" {
928 patterns[pattern] = struct{}{}
929 }
930
931 return patterns
932}
933
934func (s *snapshot) getKnownSubdirsPattern(wsDirs []span.URI) string {
935 s.mu.Lock()
936 defer s.mu.Unlock()
937
938 // First, process any pending changes and update the set of known
939 // subdirectories.
940 // It may change list of known subdirs and therefore invalidate the cache.
941 s.applyKnownSubdirsChangesLocked(wsDirs)
942
943 if s.knownSubdirsPatternCache == "" {
944 var builder strings.Builder
945 s.knownSubdirs.Range(func(uri span.URI) {
946 if builder.Len() == 0 {
947 builder.WriteString("{")
948 } else {
949 builder.WriteString(",")
950 }
951 builder.WriteString(uri.Filename())
952 })
953 if builder.Len() > 0 {
954 builder.WriteString("}")
955 s.knownSubdirsPatternCache = builder.String()
956 }
957 }
958
959 return s.knownSubdirsPatternCache
960}
961
962// collectAllKnownSubdirs collects all of the subdirectories within the
963// snapshot's workspace directories. None of the workspace directories are
964// included.
965func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) {
966 dirs := s.workspace.dirs(ctx, s)
967
968 s.mu.Lock()
969 defer s.mu.Unlock()
970
971 s.knownSubdirs.Destroy()
972 s.knownSubdirs = newKnownDirsSet()
973 s.knownSubdirsPatternCache = ""
974 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
975 s.addKnownSubdirLocked(uri, dirs)
976 })
977}
978
979func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) knownDirsSet {
980 s.mu.Lock()
981 defer s.mu.Unlock()
982
983 // First, process any pending changes and update the set of known
984 // subdirectories.
985 s.applyKnownSubdirsChangesLocked(wsDirs)
986
987 return s.knownSubdirs.Clone()
988}
989
990func (s *snapshot) applyKnownSubdirsChangesLocked(wsDirs []span.URI) {
991 for _, c := range s.unprocessedSubdirChanges {
992 if c.isUnchanged {
993 continue
994 }
995 if !c.exists {
996 s.removeKnownSubdirLocked(c.fileHandle.URI())
997 } else {
998 s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs)
999 }
1000 }
1001 s.unprocessedSubdirChanges = nil
1002}
1003
1004func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) {
1005 dir := filepath.Dir(uri.Filename())
1006 // First check if the directory is already known, because then we can
1007 // return early.
1008 if s.knownSubdirs.Contains(span.URIFromPath(dir)) {
1009 return
1010 }
1011 var matched span.URI
1012 for _, wsDir := range dirs {
1013 if source.InDir(wsDir.Filename(), dir) {
1014 matched = wsDir
1015 break
1016 }
1017 }
1018 // Don't watch any directory outside of the workspace directories.
1019 if matched == "" {
1020 return
1021 }
1022 for {
1023 if dir == "" || dir == matched.Filename() {
1024 break
1025 }
1026 uri := span.URIFromPath(dir)
1027 if s.knownSubdirs.Contains(uri) {
1028 break
1029 }
1030 s.knownSubdirs.Insert(uri)
1031 dir = filepath.Dir(dir)
1032 s.knownSubdirsPatternCache = ""
1033 }
1034}
1035
1036func (s *snapshot) removeKnownSubdirLocked(uri span.URI) {
1037 dir := filepath.Dir(uri.Filename())
1038 for dir != "" {
1039 uri := span.URIFromPath(dir)
1040 if !s.knownSubdirs.Contains(uri) {
1041 break
1042 }
1043 if info, _ := os.Stat(dir); info == nil {
1044 s.knownSubdirs.Remove(uri)
1045 s.knownSubdirsPatternCache = ""
1046 }
1047 dir = filepath.Dir(dir)
1048 }
1049}
1050
1051// knownFilesInDir returns the files known to the given snapshot that are in
1052// the given directory. It does not respect symlinks.
1053func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI {
1054 var files []span.URI
1055 s.mu.Lock()
1056 defer s.mu.Unlock()
1057
1058 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
1059 if source.InDir(dir.Filename(), uri.Filename()) {
1060 files = append(files, uri)
1061 }
1062 })
1063 return files
1064}
1065
1066func (s *snapshot) ActivePackages(ctx context.Context) ([]source.Package, error) {
1067 phs, err := s.activePackageHandles(ctx)
1068 if err != nil {
1069 return nil, err
1070 }
1071 var pkgs []source.Package
1072 for _, ph := range phs {
1073 pkg, err := ph.await(ctx, s)
1074 if err != nil {
1075 return nil, err
1076 }
1077 pkgs = append(pkgs, pkg)
1078 }
1079 return pkgs, nil
1080}
1081
1082func (s *snapshot) activePackageHandles(ctx context.Context) ([]*packageHandle, error) {
1083 if err := s.awaitLoaded(ctx); err != nil {
1084 return nil, err
1085 }
1086 var phs []*packageHandle
1087 for _, pkgID := range s.activePackageIDs() {
1088 ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID))
1089 if err != nil {
1090 return nil, err
1091 }
1092 phs = append(phs, ph)
1093 }
1094 return phs, nil
1095}
1096
1097// Symbols extracts and returns the symbols for each file in all the snapshot's views.
1098func (s *snapshot) Symbols(ctx context.Context) map[span.URI][]source.Symbol {
Alan Donovan19fb30d2022-11-18 16:29:11 -05001099 // Read the set of Go files out of the snapshot.
1100 var goFiles []source.VersionedFileHandle
1101 s.mu.Lock()
1102 s.files.Range(func(uri span.URI, f source.VersionedFileHandle) {
1103 if s.View().FileKind(f) == source.Go {
1104 goFiles = append(goFiles, f)
1105 }
1106 })
1107 s.mu.Unlock()
1108
1109 // Symbolize them in parallel.
Robert Findleyb15dac22022-08-30 14:40:12 -04001110 var (
1111 group errgroup.Group
Alan Donovan19fb30d2022-11-18 16:29:11 -05001112 nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
Robert Findleyb15dac22022-08-30 14:40:12 -04001113 resultMu sync.Mutex
1114 result = make(map[span.URI][]source.Symbol)
1115 )
Alan Donovan19fb30d2022-11-18 16:29:11 -05001116 group.SetLimit(nprocs)
1117 for _, f := range goFiles {
1118 f := f
Robert Findleyb15dac22022-08-30 14:40:12 -04001119 group.Go(func() error {
Robert Findleyb15dac22022-08-30 14:40:12 -04001120 symbols, err := s.symbolize(ctx, f)
1121 if err != nil {
1122 return err
1123 }
1124 resultMu.Lock()
Alan Donovan19fb30d2022-11-18 16:29:11 -05001125 result[f.URI()] = symbols
Robert Findleyb15dac22022-08-30 14:40:12 -04001126 resultMu.Unlock()
1127 return nil
1128 })
Alan Donovan19fb30d2022-11-18 16:29:11 -05001129 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001130 // Keep going on errors, but log the first failure.
1131 // Partial results are better than no symbol results.
1132 if err := group.Wait(); err != nil {
1133 event.Error(ctx, "getting snapshot symbols", err)
1134 }
1135 return result
1136}
1137
Alan Donovan85bf7a82022-11-18 12:03:11 -05001138func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001139 knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
1140 if err != nil {
1141 return nil, err
1142 }
Alan Donovan85bf7a82022-11-18 12:03:11 -05001143 var mds []*source.Metadata
Robert Findleyb15dac22022-08-30 14:40:12 -04001144 for _, id := range knownIDs {
1145 md := s.getMetadata(id)
1146 // TODO(rfindley): knownIDs and metadata should be in sync, but existing
1147 // code is defensive of nil metadata.
1148 if md != nil {
1149 mds = append(mds, md)
1150 }
1151 }
1152 return mds, nil
1153}
1154
1155func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
1156 if err := s.awaitLoaded(ctx); err != nil {
1157 return nil, err
1158 }
1159
Alan Donovan2592a852022-11-17 12:02:44 -05001160 ids := make([]source.PackageID, 0, len(s.meta.metadata))
Robert Findleyb15dac22022-08-30 14:40:12 -04001161 s.mu.Lock()
1162 for id := range s.meta.metadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001163 ids = append(ids, id)
1164 }
1165 s.mu.Unlock()
1166
Alan Donovan2592a852022-11-17 12:02:44 -05001167 pkgs := make([]source.Package, 0, len(ids))
Robert Findleyb15dac22022-08-30 14:40:12 -04001168 for _, id := range ids {
1169 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
1170 if err != nil {
1171 return nil, err
1172 }
1173 pkgs = append(pkgs, pkg)
1174 }
1175 return pkgs, nil
1176}
1177
Alan Donovan85bf7a82022-11-18 12:03:11 -05001178func (s *snapshot) AllValidMetadata(ctx context.Context) ([]*source.Metadata, error) {
Robert Findley61280302022-10-17 16:35:50 -04001179 if err := s.awaitLoaded(ctx); err != nil {
1180 return nil, err
1181 }
1182
1183 s.mu.Lock()
1184 defer s.mu.Unlock()
1185
Alan Donovan85bf7a82022-11-18 12:03:11 -05001186 var meta []*source.Metadata
Robert Findley61280302022-10-17 16:35:50 -04001187 for _, m := range s.meta.metadata {
Robert Findley5a4eba52022-11-03 18:28:39 -04001188 meta = append(meta, m)
Robert Findley61280302022-10-17 16:35:50 -04001189 }
1190 return meta, nil
1191}
1192
Alan Donovan3c3713e2022-11-10 13:02:38 -05001193func (s *snapshot) WorkspacePackageByID(ctx context.Context, id PackageID) (source.Package, error) {
1194 return s.checkedPackage(ctx, id, s.workspaceParseMode(id))
Robert Findley61280302022-10-17 16:35:50 -04001195}
1196
Alan Donovan3c3713e2022-11-10 13:02:38 -05001197func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]source.Package, error) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001198 // Don't reload workspace package metadata.
1199 // This function is meant to only return currently cached information.
1200 s.AwaitInitialized(ctx)
1201
1202 s.mu.Lock()
1203 defer s.mu.Unlock()
1204
Alan Donovan3c3713e2022-11-10 13:02:38 -05001205 results := map[PackagePath]source.Package{}
Robert Findleyb15dac22022-08-30 14:40:12 -04001206 s.packages.Range(func(_, v interface{}) {
1207 cachedPkg, err := v.(*packageHandle).cached()
1208 if err != nil {
1209 return
1210 }
Alan Donovan21f61272022-10-20 14:32:57 -04001211 for _, newPkg := range cachedPkg.deps {
1212 pkgPath := newPkg.PkgPath()
1213 if oldPkg, ok := results[pkgPath]; ok {
Robert Findleyb15dac22022-08-30 14:40:12 -04001214 // Using the same trick as NarrowestPackage, prefer non-variants.
1215 if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) {
Alan Donovan21f61272022-10-20 14:32:57 -04001216 results[pkgPath] = newPkg
Robert Findleyb15dac22022-08-30 14:40:12 -04001217 }
1218 } else {
Alan Donovan21f61272022-10-20 14:32:57 -04001219 results[pkgPath] = newPkg
Robert Findleyb15dac22022-08-30 14:40:12 -04001220 }
1221 }
1222 })
1223 return results, nil
1224}
1225
1226func (s *snapshot) GoModForFile(uri span.URI) span.URI {
1227 return moduleForURI(s.workspace.activeModFiles, uri)
1228}
1229
1230func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI {
1231 var match span.URI
1232 for modURI := range modFiles {
1233 if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) {
1234 continue
1235 }
1236 if len(modURI) > len(match) {
1237 match = modURI
1238 }
1239 }
1240 return match
1241}
1242
Alan Donovan85bf7a82022-11-18 12:03:11 -05001243func (s *snapshot) getMetadata(id PackageID) *source.Metadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001244 s.mu.Lock()
1245 defer s.mu.Unlock()
1246
1247 return s.meta.metadata[id]
1248}
1249
1250// clearShouldLoad clears package IDs that no longer need to be reloaded after
1251// scopes has been loaded.
Robert Findleyb2533142022-10-10 13:50:45 -04001252func (s *snapshot) clearShouldLoad(scopes ...loadScope) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001253 s.mu.Lock()
1254 defer s.mu.Unlock()
1255
1256 for _, scope := range scopes {
1257 switch scope := scope.(type) {
Robert Findleyb2533142022-10-10 13:50:45 -04001258 case packageLoadScope:
1259 scopePath := PackagePath(scope)
Robert Findleyb15dac22022-08-30 14:40:12 -04001260 var toDelete []PackageID
1261 for id, pkgPaths := range s.shouldLoad {
1262 for _, pkgPath := range pkgPaths {
Robert Findleyb2533142022-10-10 13:50:45 -04001263 if pkgPath == scopePath {
Robert Findleyb15dac22022-08-30 14:40:12 -04001264 toDelete = append(toDelete, id)
1265 }
1266 }
1267 }
1268 for _, id := range toDelete {
1269 delete(s.shouldLoad, id)
1270 }
Robert Findleyb2533142022-10-10 13:50:45 -04001271 case fileLoadScope:
Robert Findleyb15dac22022-08-30 14:40:12 -04001272 uri := span.URI(scope)
1273 ids := s.meta.ids[uri]
1274 for _, id := range ids {
1275 delete(s.shouldLoad, id)
1276 }
1277 }
1278 }
1279}
1280
1281// noValidMetadataForURILocked reports whether there is any valid metadata for
1282// the given URI.
1283func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool {
1284 ids, ok := s.meta.ids[uri]
1285 if !ok {
1286 return true
1287 }
1288 for _, id := range ids {
Robert Findley5a4eba52022-11-03 18:28:39 -04001289 if _, ok := s.meta.metadata[id]; ok {
Robert Findleyb15dac22022-08-30 14:40:12 -04001290 return false
1291 }
1292 }
1293 return true
1294}
1295
1296func (s *snapshot) isWorkspacePackage(id PackageID) bool {
1297 s.mu.Lock()
1298 defer s.mu.Unlock()
1299
1300 _, ok := s.workspacePackages[id]
1301 return ok
1302}
1303
1304func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle {
1305 f := s.view.getFile(uri)
1306
1307 s.mu.Lock()
1308 defer s.mu.Unlock()
1309
1310 result, _ := s.files.Get(f.URI())
1311 return result
1312}
1313
1314// GetVersionedFile returns a File for the given URI. If the file is unknown it
1315// is added to the managed set.
1316//
1317// GetVersionedFile succeeds even if the file does not exist. A non-nil error return
1318// indicates some type of internal error, for example if ctx is cancelled.
1319func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) {
1320 f := s.view.getFile(uri)
1321
1322 s.mu.Lock()
1323 defer s.mu.Unlock()
1324 return s.getFileLocked(ctx, f)
1325}
1326
1327// GetFile implements the fileSource interface by wrapping GetVersionedFile.
1328func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
1329 return s.GetVersionedFile(ctx, uri)
1330}
1331
1332func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) {
1333 if fh, ok := s.files.Get(f.URI()); ok {
1334 return fh, nil
1335 }
1336
1337 fh, err := s.view.session.cache.getFile(ctx, f.URI()) // read the file
1338 if err != nil {
1339 return nil, err
1340 }
1341 closed := &closedFile{fh}
1342 s.files.Set(f.URI(), closed)
1343 return closed, nil
1344}
1345
1346func (s *snapshot) IsOpen(uri span.URI) bool {
1347 s.mu.Lock()
1348 defer s.mu.Unlock()
1349 return s.isOpenLocked(uri)
1350
1351}
1352
1353func (s *snapshot) openFiles() []source.VersionedFileHandle {
1354 s.mu.Lock()
1355 defer s.mu.Unlock()
1356
1357 var open []source.VersionedFileHandle
1358 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
1359 if isFileOpen(fh) {
1360 open = append(open, fh)
1361 }
1362 })
1363 return open
1364}
1365
1366func (s *snapshot) isOpenLocked(uri span.URI) bool {
1367 fh, _ := s.files.Get(uri)
1368 return isFileOpen(fh)
1369}
1370
1371func isFileOpen(fh source.VersionedFileHandle) bool {
1372 _, open := fh.(*overlay)
1373 return open
1374}
1375
1376func (s *snapshot) awaitLoaded(ctx context.Context) error {
1377 loadErr := s.awaitLoadedAllErrors(ctx)
1378
Robert Findley5a4eba52022-11-03 18:28:39 -04001379 // TODO(rfindley): eliminate this function as part of simplifying
1380 // CriticalErrors.
Robert Findleyb15dac22022-08-30 14:40:12 -04001381 if loadErr != nil {
1382 return loadErr.MainError
1383 }
1384 return nil
1385}
1386
1387func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError {
1388 if wsErr := s.workspace.criticalError(ctx, s); wsErr != nil {
1389 return wsErr
1390 }
1391
1392 loadErr := s.awaitLoadedAllErrors(ctx)
1393 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) {
1394 return nil
1395 }
1396
1397 // Even if packages didn't fail to load, we still may want to show
1398 // additional warnings.
1399 if loadErr == nil {
1400 wsPkgs, _ := s.ActivePackages(ctx)
1401 if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" {
1402 return &source.CriticalError{
1403 MainError: errors.New(msg),
1404 }
1405 }
1406 // Even if workspace packages were returned, there still may be an error
1407 // with the user's workspace layout. Workspace packages that only have the
1408 // ID "command-line-arguments" are usually a symptom of a bad workspace
1409 // configuration.
1410 //
1411 // TODO(rfindley): re-evaluate this heuristic.
1412 if containsCommandLineArguments(wsPkgs) {
1413 return s.workspaceLayoutError(ctx)
1414 }
1415 return nil
1416 }
1417
1418 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") {
1419 return s.workspaceLayoutError(ctx)
1420 }
1421 return loadErr
1422}
1423
1424const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src.
1425If you are using modules, please open your editor to a directory in your module.
1426If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.`
1427
1428func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string {
1429 if snapshot.ValidBuildConfiguration() {
1430 return ""
1431 }
1432 for _, pkg := range pkgs {
1433 if len(pkg.MissingDependencies()) > 0 {
1434 return adHocPackagesWarning
1435 }
1436 }
1437 return ""
1438}
1439
1440func containsCommandLineArguments(pkgs []source.Package) bool {
1441 for _, pkg := range pkgs {
1442 if source.IsCommandLineArguments(pkg.ID()) {
1443 return true
1444 }
1445 }
1446 return false
1447}
1448
1449func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError {
1450 // Do not return results until the snapshot's view has been initialized.
1451 s.AwaitInitialized(ctx)
1452
1453 // TODO(rfindley): Should we be more careful about returning the
1454 // initialization error? Is it possible for the initialization error to be
1455 // corrected without a successful reinitialization?
1456 s.mu.Lock()
1457 initializedErr := s.initializedErr
1458 s.mu.Unlock()
1459
1460 if initializedErr != nil {
1461 return initializedErr
1462 }
1463
1464 // TODO(rfindley): revisit this handling. Calling reloadWorkspace with a
1465 // cancelled context should have the same effect, so this preemptive handling
1466 // should not be necessary.
1467 //
1468 // Also: GetCriticalError ignores context cancellation errors. Should we be
1469 // returning nil here?
1470 if ctx.Err() != nil {
1471 return &source.CriticalError{MainError: ctx.Err()}
1472 }
1473
1474 // TODO(rfindley): reloading is not idempotent: if we try to reload or load
1475 // orphaned files below and fail, we won't try again. For that reason, we
1476 // could get different results from subsequent calls to this function, which
1477 // may cause critical errors to be suppressed.
1478
1479 if err := s.reloadWorkspace(ctx); err != nil {
1480 diags := s.extractGoCommandErrors(ctx, err)
1481 return &source.CriticalError{
1482 MainError: err,
1483 Diagnostics: diags,
1484 }
1485 }
1486
1487 if err := s.reloadOrphanedFiles(ctx); err != nil {
1488 diags := s.extractGoCommandErrors(ctx, err)
1489 return &source.CriticalError{
1490 MainError: err,
1491 Diagnostics: diags,
1492 }
1493 }
1494 return nil
1495}
1496
Alan Donovan051f03f2022-10-21 11:10:37 -04001497func (s *snapshot) getInitializationError() *source.CriticalError {
Robert Findleyb15dac22022-08-30 14:40:12 -04001498 s.mu.Lock()
1499 defer s.mu.Unlock()
1500
1501 return s.initializedErr
1502}
1503
1504func (s *snapshot) AwaitInitialized(ctx context.Context) {
1505 select {
1506 case <-ctx.Done():
1507 return
1508 case <-s.view.initialWorkspaceLoad:
1509 }
1510 // We typically prefer to run something as intensive as the IWL without
1511 // blocking. I'm not sure if there is a way to do that here.
1512 s.initialize(ctx, false)
1513}
1514
1515// reloadWorkspace reloads the metadata for all invalidated workspace packages.
1516func (s *snapshot) reloadWorkspace(ctx context.Context) error {
Robert Findleyb2533142022-10-10 13:50:45 -04001517 var scopes []loadScope
Robert Findleyb15dac22022-08-30 14:40:12 -04001518 var seen map[PackagePath]bool
1519 s.mu.Lock()
1520 for _, pkgPaths := range s.shouldLoad {
1521 for _, pkgPath := range pkgPaths {
1522 if seen == nil {
1523 seen = make(map[PackagePath]bool)
1524 }
1525 if seen[pkgPath] {
1526 continue
1527 }
1528 seen[pkgPath] = true
Robert Findleyb2533142022-10-10 13:50:45 -04001529 scopes = append(scopes, packageLoadScope(pkgPath))
Robert Findleyb15dac22022-08-30 14:40:12 -04001530 }
1531 }
1532 s.mu.Unlock()
1533
1534 if len(scopes) == 0 {
1535 return nil
1536 }
1537
1538 // If the view's build configuration is invalid, we cannot reload by
1539 // package path. Just reload the directory instead.
1540 if !s.ValidBuildConfiguration() {
Robert Findleyb2533142022-10-10 13:50:45 -04001541 scopes = []loadScope{viewLoadScope("LOAD_INVALID_VIEW")}
Robert Findleyb15dac22022-08-30 14:40:12 -04001542 }
1543
1544 err := s.load(ctx, false, scopes...)
1545
1546 // Unless the context was canceled, set "shouldLoad" to false for all
1547 // of the metadata we attempted to load.
1548 if !errors.Is(err, context.Canceled) {
1549 s.clearShouldLoad(scopes...)
1550 }
1551
1552 return err
1553}
1554
1555func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
1556 // When we load ./... or a package path directly, we may not get packages
1557 // that exist only in overlays. As a workaround, we search all of the files
1558 // available in the snapshot and reload their metadata individually using a
1559 // file= query if the metadata is unavailable.
1560 files := s.orphanedFiles()
1561
1562 // Files without a valid package declaration can't be loaded. Don't try.
Robert Findleyb2533142022-10-10 13:50:45 -04001563 var scopes []loadScope
Robert Findleyb15dac22022-08-30 14:40:12 -04001564 for _, file := range files {
1565 pgf, err := s.ParseGo(ctx, file, source.ParseHeader)
1566 if err != nil {
1567 continue
1568 }
1569 if !pgf.File.Package.IsValid() {
1570 continue
1571 }
Robert Findleyb2533142022-10-10 13:50:45 -04001572
1573 scopes = append(scopes, fileLoadScope(file.URI()))
Robert Findleyb15dac22022-08-30 14:40:12 -04001574 }
1575
1576 if len(scopes) == 0 {
1577 return nil
1578 }
1579
1580 // The regtests match this exact log message, keep them in sync.
1581 event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes))
1582 err := s.load(ctx, false, scopes...)
1583
1584 // If we failed to load some files, i.e. they have no metadata,
1585 // mark the failures so we don't bother retrying until the file's
1586 // content changes.
1587 //
1588 // TODO(rstambler): This may be an overestimate if the load stopped
1589 // early for an unrelated errors. Add a fallback?
1590 //
1591 // Check for context cancellation so that we don't incorrectly mark files
1592 // as unloadable, but don't return before setting all workspace packages.
1593 if ctx.Err() == nil && err != nil {
1594 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes))
1595 s.mu.Lock()
1596 for _, scope := range scopes {
Robert Findleyb2533142022-10-10 13:50:45 -04001597 uri := span.URI(scope.(fileLoadScope))
Robert Findleyb15dac22022-08-30 14:40:12 -04001598 if s.noValidMetadataForURILocked(uri) {
1599 s.unloadableFiles[uri] = struct{}{}
1600 }
1601 }
1602 s.mu.Unlock()
1603 }
1604 return nil
1605}
1606
1607func (s *snapshot) orphanedFiles() []source.VersionedFileHandle {
1608 s.mu.Lock()
1609 defer s.mu.Unlock()
1610
1611 var files []source.VersionedFileHandle
1612 s.files.Range(func(uri span.URI, fh source.VersionedFileHandle) {
1613 // Don't try to reload metadata for go.mod files.
1614 if s.view.FileKind(fh) != source.Go {
1615 return
1616 }
1617 // If the URI doesn't belong to this view, then it's not in a workspace
1618 // package and should not be reloaded directly.
1619 if !source.InDir(s.view.folder.Filename(), uri.Filename()) {
1620 return
1621 }
1622 // If the file is not open and is in a vendor directory, don't treat it
1623 // like a workspace package.
1624 if _, ok := fh.(*overlay); !ok && inVendor(uri) {
1625 return
1626 }
1627 // Don't reload metadata for files we've already deemed unloadable.
1628 if _, ok := s.unloadableFiles[uri]; ok {
1629 return
1630 }
1631 if s.noValidMetadataForURILocked(uri) {
1632 files = append(files, fh)
1633 }
1634 })
1635 return files
1636}
1637
Robert Findleyb15dac22022-08-30 14:40:12 -04001638// TODO(golang/go#53756): this function needs to consider more than just the
1639// absolute URI, for example:
1640// - the position of /vendor/ with respect to the relevant module root
1641// - whether or not go.work is in use (as vendoring isn't supported in workspace mode)
1642//
1643// Most likely, each call site of inVendor needs to be reconsidered to
1644// understand and correctly implement the desired behavior.
1645func inVendor(uri span.URI) bool {
1646 if !strings.Contains(string(uri), "/vendor/") {
1647 return false
1648 }
1649 // Only packages in _subdirectories_ of /vendor/ are considered vendored
1650 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
1651 split := strings.Split(string(uri), "/vendor/")
1652 if len(split) < 2 {
1653 return false
1654 }
1655 return strings.Contains(split[1], "/")
1656}
1657
1658// unappliedChanges is a file source that handles an uncloned snapshot.
1659type unappliedChanges struct {
1660 originalSnapshot *snapshot
1661 changes map[span.URI]*fileChange
1662}
1663
1664func (ac *unappliedChanges) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
1665 if c, ok := ac.changes[uri]; ok {
1666 return c.fileHandle, nil
1667 }
1668 return ac.originalSnapshot.GetFile(ctx, uri)
1669}
1670
1671func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, func()) {
1672 ctx, done := event.Start(ctx, "snapshot.clone")
1673 defer done()
1674
1675 newWorkspace, reinit := s.workspace.Clone(ctx, changes, &unappliedChanges{
1676 originalSnapshot: s,
1677 changes: changes,
1678 })
1679
1680 s.mu.Lock()
1681 defer s.mu.Unlock()
1682
1683 // If there is an initialization error and a vendor directory changed, try to
1684 // reinit.
1685 if s.initializedErr != nil {
1686 for uri := range changes {
1687 if inVendor(uri) {
1688 reinit = true
1689 break
1690 }
1691 }
1692 }
1693
1694 bgCtx, cancel := context.WithCancel(bgCtx)
1695 result := &snapshot{
Robert Findley0c71b562022-11-14 11:58:07 -05001696 sequenceID: s.sequenceID + 1,
1697 globalID: nextSnapshotID(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001698 store: s.store,
1699 view: s.view,
1700 backgroundCtx: bgCtx,
1701 cancel: cancel,
1702 builtin: s.builtin,
1703 initialized: s.initialized,
1704 initializedErr: s.initializedErr,
1705 packages: s.packages.Clone(),
1706 isActivePackageCache: s.isActivePackageCache.Clone(),
1707 actions: s.actions.Clone(),
1708 files: s.files.Clone(),
1709 parsedGoFiles: s.parsedGoFiles.Clone(),
1710 parseKeysByURI: s.parseKeysByURI.Clone(),
1711 symbolizeHandles: s.symbolizeHandles.Clone(),
1712 workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)),
1713 unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)),
1714 parseModHandles: s.parseModHandles.Clone(),
1715 parseWorkHandles: s.parseWorkHandles.Clone(),
1716 modTidyHandles: s.modTidyHandles.Clone(),
1717 modWhyHandles: s.modWhyHandles.Clone(),
Robert Findley7cda55e2022-11-22 12:09:11 -05001718 modVulnHandles: s.modVulnHandles.Clone(),
Robert Findleyb15dac22022-08-30 14:40:12 -04001719 knownSubdirs: s.knownSubdirs.Clone(),
1720 workspace: newWorkspace,
1721 }
1722
1723 // The snapshot should be initialized if either s was uninitialized, or we've
1724 // detected a change that triggers reinitialization.
1725 if reinit {
1726 result.initialized = false
1727 }
1728
1729 // Create a lease on the new snapshot.
1730 // (Best to do this early in case the code below hides an
1731 // incref/decref operation that might destroy it prematurely.)
1732 release := result.Acquire()
1733
1734 // Copy the set of unloadable files.
1735 //
1736 // TODO(rfindley): this looks wrong. Shouldn't we clear unloadableFiles on
1737 // changes to environment or workspace layout, or more generally on any
1738 // metadata change?
Robert Findley23056f62022-11-03 19:06:31 -04001739 //
1740 // Maybe not, as major configuration changes cause a new view.
Robert Findleyb15dac22022-08-30 14:40:12 -04001741 for k, v := range s.unloadableFiles {
1742 result.unloadableFiles[k] = v
1743 }
1744
1745 // TODO(adonovan): merge loops over "changes".
1746 for uri := range changes {
1747 keys, ok := result.parseKeysByURI.Get(uri)
1748 if ok {
1749 for _, key := range keys {
1750 result.parsedGoFiles.Delete(key)
1751 }
1752 result.parseKeysByURI.Delete(uri)
1753 }
1754
1755 // Invalidate go.mod-related handles.
1756 result.modTidyHandles.Delete(uri)
1757 result.modWhyHandles.Delete(uri)
Robert Findley7cda55e2022-11-22 12:09:11 -05001758 result.modVulnHandles.Delete(uri)
Robert Findleyb15dac22022-08-30 14:40:12 -04001759
1760 // Invalidate handles for cached symbols.
1761 result.symbolizeHandles.Delete(uri)
1762 }
1763
1764 // Add all of the known subdirectories, but don't update them for the
1765 // changed files. We need to rebuild the workspace module to know the
1766 // true set of known subdirectories, but we don't want to do that in clone.
1767 result.knownSubdirs = s.knownSubdirs.Clone()
1768 result.knownSubdirsPatternCache = s.knownSubdirsPatternCache
1769 for _, c := range changes {
1770 result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c)
1771 }
1772
1773 // directIDs keeps track of package IDs that have directly changed.
1774 // It maps id->invalidateMetadata.
1775 directIDs := map[PackageID]bool{}
1776
1777 // Invalidate all package metadata if the workspace module has changed.
1778 if reinit {
1779 for k := range s.meta.metadata {
1780 directIDs[k] = true
1781 }
1782 }
1783
1784 // Compute invalidations based on file changes.
1785 anyImportDeleted := false // import deletions can resolve cycles
1786 anyFileOpenedOrClosed := false // opened files affect workspace packages
1787 anyFileAdded := false // adding a file can resolve missing dependencies
1788
1789 for uri, change := range changes {
1790 // The original FileHandle for this URI is cached on the snapshot.
1791 originalFH, _ := s.files.Get(uri)
1792 var originalOpen, newOpen bool
1793 _, originalOpen = originalFH.(*overlay)
1794 _, newOpen = change.fileHandle.(*overlay)
1795 anyFileOpenedOrClosed = anyFileOpenedOrClosed || (originalOpen != newOpen)
1796 anyFileAdded = anyFileAdded || (originalFH == nil && change.fileHandle != nil)
1797
1798 // If uri is a Go file, check if it has changed in a way that would
1799 // invalidate metadata. Note that we can't use s.view.FileKind here,
1800 // because the file type that matters is not what the *client* tells us,
1801 // but what the Go command sees.
1802 var invalidateMetadata, pkgFileChanged, importDeleted bool
1803 if strings.HasSuffix(uri.Filename(), ".go") {
1804 invalidateMetadata, pkgFileChanged, importDeleted = metadataChanges(ctx, s, originalFH, change.fileHandle)
1805 }
1806
1807 invalidateMetadata = invalidateMetadata || forceReloadMetadata || reinit
1808 anyImportDeleted = anyImportDeleted || importDeleted
1809
1810 // Mark all of the package IDs containing the given file.
1811 filePackageIDs := invalidatedPackageIDs(uri, s.meta.ids, pkgFileChanged)
1812 for id := range filePackageIDs {
1813 directIDs[id] = directIDs[id] || invalidateMetadata
1814 }
1815
1816 // Invalidate the previous modTidyHandle if any of the files have been
1817 // saved or if any of the metadata has been invalidated.
1818 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
1819 // TODO(maybe): Only delete mod handles for
1820 // which the withoutURI is relevant.
1821 // Requires reverse-engineering the go command. (!)
Robert Findleyb15dac22022-08-30 14:40:12 -04001822 result.modTidyHandles.Clear()
1823 result.modWhyHandles.Clear()
Robert Findley7cda55e2022-11-22 12:09:11 -05001824 result.modVulnHandles.Clear()
Robert Findleyb15dac22022-08-30 14:40:12 -04001825 }
1826
1827 result.parseModHandles.Delete(uri)
1828 result.parseWorkHandles.Delete(uri)
1829 // Handle the invalidated file; it may have new contents or not exist.
1830 if !change.exists {
1831 result.files.Delete(uri)
1832 } else {
1833 result.files.Set(uri, change.fileHandle)
1834 }
1835
1836 // Make sure to remove the changed file from the unloadable set.
1837 delete(result.unloadableFiles, uri)
1838 }
1839
1840 // Deleting an import can cause list errors due to import cycles to be
1841 // resolved. The best we can do without parsing the list error message is to
1842 // hope that list errors may have been resolved by a deleted import.
1843 //
1844 // We could do better by parsing the list error message. We already do this
1845 // to assign a better range to the list error, but for such critical
1846 // functionality as metadata, it's better to be conservative until it proves
1847 // impractical.
1848 //
1849 // We could also do better by looking at which imports were deleted and
1850 // trying to find cycles they are involved in. This fails when the file goes
1851 // from an unparseable state to a parseable state, as we don't have a
1852 // starting point to compare with.
1853 if anyImportDeleted {
1854 for id, metadata := range s.meta.metadata {
1855 if len(metadata.Errors) > 0 {
1856 directIDs[id] = true
1857 }
1858 }
1859 }
1860
1861 // Adding a file can resolve missing dependencies from existing packages.
1862 //
1863 // We could be smart here and try to guess which packages may have been
1864 // fixed, but until that proves necessary, just invalidate metadata for any
1865 // package with missing dependencies.
1866 if anyFileAdded {
1867 for id, metadata := range s.meta.metadata {
Alan Donovan21f61272022-10-20 14:32:57 -04001868 for _, impID := range metadata.DepsByImpPath {
1869 if impID == "" { // missing import
1870 directIDs[id] = true
1871 break
1872 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001873 }
1874 }
1875 }
1876
1877 // Invalidate reverse dependencies too.
1878 // idsToInvalidate keeps track of transitive reverse dependencies.
1879 // If an ID is present in the map, invalidate its types.
1880 // If an ID's value is true, invalidate its metadata too.
1881 idsToInvalidate := map[PackageID]bool{}
1882 var addRevDeps func(PackageID, bool)
1883 addRevDeps = func(id PackageID, invalidateMetadata bool) {
1884 current, seen := idsToInvalidate[id]
1885 newInvalidateMetadata := current || invalidateMetadata
1886
1887 // If we've already seen this ID, and the value of invalidate
1888 // metadata has not changed, we can return early.
1889 if seen && current == newInvalidateMetadata {
1890 return
1891 }
1892 idsToInvalidate[id] = newInvalidateMetadata
1893 for _, rid := range s.meta.importedBy[id] {
1894 addRevDeps(rid, invalidateMetadata)
1895 }
1896 }
1897 for id, invalidateMetadata := range directIDs {
1898 addRevDeps(id, invalidateMetadata)
1899 }
1900
Robert Findleycd0288f2022-08-01 14:17:38 -04001901 result.invalidatePackagesLocked(idsToInvalidate)
Robert Findleyb15dac22022-08-30 14:40:12 -04001902
1903 // If a file has been deleted, we must delete metadata for all packages
1904 // containing that file.
1905 //
1906 // TODO(rfindley): why not keep invalid metadata in this case? If we
1907 // otherwise allow operate on invalid metadata, why not continue to do so,
1908 // skipping the missing file?
1909 skipID := map[PackageID]bool{}
1910 for _, c := range changes {
1911 if c.exists {
1912 continue
1913 }
1914 // The file has been deleted.
1915 if ids, ok := s.meta.ids[c.fileHandle.URI()]; ok {
1916 for _, id := range ids {
1917 skipID[id] = true
1918 }
1919 }
1920 }
1921
1922 // Any packages that need loading in s still need loading in the new
1923 // snapshot.
1924 for k, v := range s.shouldLoad {
1925 if result.shouldLoad == nil {
1926 result.shouldLoad = make(map[PackageID][]PackagePath)
1927 }
1928 result.shouldLoad[k] = v
1929 }
1930
Robert Findleyb15dac22022-08-30 14:40:12 -04001931 // Compute which metadata updates are required. We only need to invalidate
1932 // packages directly containing the affected file, and only if it changed in
1933 // a relevant way.
Alan Donovan85bf7a82022-11-18 12:03:11 -05001934 metadataUpdates := make(map[PackageID]*source.Metadata)
Robert Findleyb15dac22022-08-30 14:40:12 -04001935 for k, v := range s.meta.metadata {
1936 invalidateMetadata := idsToInvalidate[k]
1937
1938 // For metadata that has been newly invalidated, capture package paths
1939 // requiring reloading in the shouldLoad map.
Alan Donovan3c3713e2022-11-10 13:02:38 -05001940 if invalidateMetadata && !source.IsCommandLineArguments(v.ID) {
Robert Findleyb15dac22022-08-30 14:40:12 -04001941 if result.shouldLoad == nil {
1942 result.shouldLoad = make(map[PackageID][]PackagePath)
1943 }
1944 needsReload := []PackagePath{v.PkgPath}
1945 if v.ForTest != "" && v.ForTest != v.PkgPath {
1946 // When reloading test variants, always reload their ForTest package as
1947 // well. Otherwise, we may miss test variants in the resulting load.
1948 //
1949 // TODO(rfindley): is this actually sufficient? Is it possible that
1950 // other test variants may be invalidated? Either way, we should
1951 // determine exactly what needs to be reloaded here.
1952 needsReload = append(needsReload, v.ForTest)
1953 }
1954 result.shouldLoad[k] = needsReload
1955 }
1956
1957 // Check whether the metadata should be deleted.
Robert Findley5a4eba52022-11-03 18:28:39 -04001958 if skipID[k] || invalidateMetadata {
Robert Findleyb15dac22022-08-30 14:40:12 -04001959 metadataUpdates[k] = nil
1960 continue
1961 }
Robert Findleyb15dac22022-08-30 14:40:12 -04001962 }
1963
1964 // Update metadata, if necessary.
1965 result.meta = s.meta.Clone(metadataUpdates)
1966
1967 // Update workspace and active packages, if necessary.
1968 if result.meta != s.meta || anyFileOpenedOrClosed {
1969 result.workspacePackages = computeWorkspacePackagesLocked(result, result.meta)
1970 result.resetIsActivePackageLocked()
1971 } else {
1972 result.workspacePackages = s.workspacePackages
1973 }
1974
1975 // Don't bother copying the importedBy graph,
1976 // as it changes each time we update metadata.
1977
Robert Findley5a4eba52022-11-03 18:28:39 -04001978 // TODO(rfindley): consolidate the this workspace mode detection with
1979 // workspace invalidation.
1980 workspaceModeChanged := s.workspaceMode() != result.workspaceMode()
1981
Robert Findleyb15dac22022-08-30 14:40:12 -04001982 // If the snapshot's workspace mode has changed, the packages loaded using
1983 // the previous mode are no longer relevant, so clear them out.
1984 if workspaceModeChanged {
1985 result.workspacePackages = map[PackageID]PackagePath{}
1986 }
1987 result.dumpWorkspace("clone")
1988 return result, release
1989}
1990
1991// invalidatedPackageIDs returns all packages invalidated by a change to uri.
1992// If we haven't seen this URI before, we guess based on files in the same
1993// directory. This is of course incorrect in build systems where packages are
1994// not organized by directory.
1995//
1996// If packageFileChanged is set, the file is either a new file, or has a new
1997// package name. In this case, all known packages in the directory will be
1998// invalidated.
1999func invalidatedPackageIDs(uri span.URI, known map[span.URI][]PackageID, packageFileChanged bool) map[PackageID]struct{} {
2000 invalidated := make(map[PackageID]struct{})
2001
2002 // At a minimum, we invalidate packages known to contain uri.
2003 for _, id := range known[uri] {
2004 invalidated[id] = struct{}{}
2005 }
2006
2007 // If the file didn't move to a new package, we should only invalidate the
2008 // packages it is currently contained inside.
2009 if !packageFileChanged && len(invalidated) > 0 {
2010 return invalidated
2011 }
2012
2013 // This is a file we don't yet know about, or which has moved packages. Guess
2014 // relevant packages by considering files in the same directory.
2015
2016 // Cache of FileInfo to avoid unnecessary stats for multiple files in the
2017 // same directory.
2018 stats := make(map[string]struct {
2019 os.FileInfo
2020 error
2021 })
2022 getInfo := func(dir string) (os.FileInfo, error) {
2023 if res, ok := stats[dir]; ok {
2024 return res.FileInfo, res.error
2025 }
2026 fi, err := os.Stat(dir)
2027 stats[dir] = struct {
2028 os.FileInfo
2029 error
2030 }{fi, err}
2031 return fi, err
2032 }
2033 dir := filepath.Dir(uri.Filename())
2034 fi, err := getInfo(dir)
2035 if err == nil {
2036 // Aggregate all possibly relevant package IDs.
2037 for knownURI, ids := range known {
2038 knownDir := filepath.Dir(knownURI.Filename())
2039 knownFI, err := getInfo(knownDir)
2040 if err != nil {
2041 continue
2042 }
2043 if os.SameFile(fi, knownFI) {
2044 for _, id := range ids {
2045 invalidated[id] = struct{}{}
2046 }
2047 }
2048 }
2049 }
2050 return invalidated
2051}
2052
Robert Findleycd0288f2022-08-01 14:17:38 -04002053// invalidatePackagesLocked deletes data associated with the given package IDs.
2054//
2055// Note: all keys in the ids map are invalidated, regardless of the
2056// corresponding value.
2057//
2058// s.mu must be held while calling this function.
2059func (s *snapshot) invalidatePackagesLocked(ids map[PackageID]bool) {
2060 // Delete invalidated package type information.
2061 for id := range ids {
2062 for _, mode := range source.AllParseModes {
2063 key := packageKey{mode, id}
2064 s.packages.Delete(key)
2065 }
2066 }
2067
2068 // Copy actions.
2069 // TODO(adonovan): opt: avoid iteration over s.actions.
2070 var actionsToDelete []actionKey
2071 s.actions.Range(func(k, _ interface{}) {
2072 key := k.(actionKey)
2073 if _, ok := ids[key.pkgid]; ok {
2074 actionsToDelete = append(actionsToDelete, key)
2075 }
2076 })
2077 for _, key := range actionsToDelete {
2078 s.actions.Delete(key)
2079 }
2080}
2081
Robert Findleyb15dac22022-08-30 14:40:12 -04002082// fileWasSaved reports whether the FileHandle passed in has been saved. It
2083// accomplishes this by checking to see if the original and current FileHandles
2084// are both overlays, and if the current FileHandle is saved while the original
2085// FileHandle was not saved.
2086func fileWasSaved(originalFH, currentFH source.FileHandle) bool {
2087 c, ok := currentFH.(*overlay)
2088 if !ok || c == nil {
2089 return true
2090 }
2091 o, ok := originalFH.(*overlay)
2092 if !ok || o == nil {
2093 return c.saved
2094 }
2095 return !o.saved && c.saved
2096}
2097
2098// metadataChanges detects features of the change from oldFH->newFH that may
2099// affect package metadata.
2100//
2101// It uses lockedSnapshot to access cached parse information. lockedSnapshot
2102// must be locked.
2103//
2104// The result parameters have the following meaning:
2105// - invalidate means that package metadata for packages containing the file
2106// should be invalidated.
2107// - pkgFileChanged means that the file->package associates for the file have
2108// changed (possibly because the file is new, or because its package name has
2109// changed).
2110// - importDeleted means that an import has been deleted, or we can't
2111// determine if an import was deleted due to errors.
2112func metadataChanges(ctx context.Context, lockedSnapshot *snapshot, oldFH, newFH source.FileHandle) (invalidate, pkgFileChanged, importDeleted bool) {
2113 if oldFH == nil || newFH == nil { // existential changes
2114 changed := (oldFH == nil) != (newFH == nil)
2115 return changed, changed, (newFH == nil) // we don't know if an import was deleted
2116 }
2117
2118 // If the file hasn't changed, there's no need to reload.
2119 if oldFH.FileIdentity() == newFH.FileIdentity() {
2120 return false, false, false
2121 }
2122
2123 // Parse headers to compare package names and imports.
2124 oldHead, oldErr := peekOrParse(ctx, lockedSnapshot, oldFH, source.ParseHeader)
2125 newHead, newErr := peekOrParse(ctx, lockedSnapshot, newFH, source.ParseHeader)
2126
2127 if oldErr != nil || newErr != nil {
2128 // TODO(rfindley): we can get here if newFH does not exists. There is
2129 // asymmetry here, in that newFH may be non-nil even if the underlying file
2130 // does not exist.
2131 //
2132 // We should not produce a non-nil filehandle for a file that does not exist.
2133 errChanged := (oldErr == nil) != (newErr == nil)
2134 return errChanged, errChanged, (newErr != nil) // we don't know if an import was deleted
2135 }
2136
2137 // `go list` fails completely if the file header cannot be parsed. If we go
2138 // from a non-parsing state to a parsing state, we should reload.
2139 if oldHead.ParseErr != nil && newHead.ParseErr == nil {
2140 return true, true, true // We don't know what changed, so fall back on full invalidation.
2141 }
2142
2143 // If a package name has changed, the set of package imports may have changed
2144 // in ways we can't detect here. Assume an import has been deleted.
2145 if oldHead.File.Name.Name != newHead.File.Name.Name {
2146 return true, true, true
2147 }
2148
2149 // Check whether package imports have changed. Only consider potentially
2150 // valid imports paths.
2151 oldImports := validImports(oldHead.File.Imports)
2152 newImports := validImports(newHead.File.Imports)
2153
2154 for path := range newImports {
2155 if _, ok := oldImports[path]; ok {
2156 delete(oldImports, path)
2157 } else {
2158 invalidate = true // a new, potentially valid import was added
2159 }
2160 }
2161
2162 if len(oldImports) > 0 {
2163 invalidate = true
2164 importDeleted = true
2165 }
2166
2167 // If the change does not otherwise invalidate metadata, get the full ASTs in
2168 // order to check magic comments.
2169 //
2170 // Note: if this affects performance we can probably avoid parsing in the
2171 // common case by first scanning the source for potential comments.
2172 if !invalidate {
2173 origFull, oldErr := peekOrParse(ctx, lockedSnapshot, oldFH, source.ParseFull)
2174 currFull, newErr := peekOrParse(ctx, lockedSnapshot, newFH, source.ParseFull)
2175 if oldErr == nil && newErr == nil {
2176 invalidate = magicCommentsChanged(origFull.File, currFull.File)
2177 } else {
2178 // At this point, we shouldn't ever fail to produce a ParsedGoFile, as
2179 // we're already past header parsing.
2180 bug.Reportf("metadataChanges: unparseable file %v (old error: %v, new error: %v)", oldFH.URI(), oldErr, newErr)
2181 }
2182 }
2183
2184 return invalidate, pkgFileChanged, importDeleted
2185}
2186
2187// peekOrParse returns the cached ParsedGoFile if it exists,
2188// otherwise parses without populating the cache.
2189//
2190// It returns an error if the file could not be read (note that parsing errors
2191// are stored in ParsedGoFile.ParseErr).
2192//
2193// lockedSnapshot must be locked.
2194func peekOrParse(ctx context.Context, lockedSnapshot *snapshot, fh source.FileHandle, mode source.ParseMode) (*source.ParsedGoFile, error) {
2195 // Peek in the cache without populating it.
2196 // We do this to reduce retained heap, not work.
2197 if parsed, _ := lockedSnapshot.peekParseGoLocked(fh, mode); parsed != nil {
2198 return parsed, nil // cache hit
2199 }
2200 return parseGoImpl(ctx, token.NewFileSet(), fh, mode)
2201}
2202
2203func magicCommentsChanged(original *ast.File, current *ast.File) bool {
2204 oldComments := extractMagicComments(original)
2205 newComments := extractMagicComments(current)
2206 if len(oldComments) != len(newComments) {
2207 return true
2208 }
2209 for i := range oldComments {
2210 if oldComments[i] != newComments[i] {
2211 return true
2212 }
2213 }
2214 return false
2215}
2216
2217// validImports extracts the set of valid import paths from imports.
2218func validImports(imports []*ast.ImportSpec) map[string]struct{} {
2219 m := make(map[string]struct{})
2220 for _, spec := range imports {
2221 if path := spec.Path.Value; validImportPath(path) {
2222 m[path] = struct{}{}
2223 }
2224 }
2225 return m
2226}
2227
2228func validImportPath(path string) bool {
2229 path, err := strconv.Unquote(path)
2230 if err != nil {
2231 return false
2232 }
2233 if path == "" {
2234 return false
2235 }
2236 if path[len(path)-1] == '/' {
2237 return false
2238 }
2239 return true
2240}
2241
2242var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`)
2243
2244// extractMagicComments finds magic comments that affect metadata in f.
2245func extractMagicComments(f *ast.File) []string {
2246 var results []string
2247 for _, cg := range f.Comments {
2248 for _, c := range cg.List {
2249 if buildConstraintOrEmbedRe.MatchString(c.Text) {
2250 results = append(results, c.Text)
2251 }
2252 }
2253 }
2254 return results
2255}
2256
2257func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) {
2258 s.AwaitInitialized(ctx)
2259
2260 s.mu.Lock()
2261 builtin := s.builtin
2262 s.mu.Unlock()
2263
2264 if builtin == "" {
2265 return nil, fmt.Errorf("no builtin package for view %s", s.view.name)
2266 }
2267
2268 fh, err := s.GetFile(ctx, builtin)
2269 if err != nil {
2270 return nil, err
2271 }
2272 return s.ParseGo(ctx, fh, source.ParseFull)
2273}
2274
2275func (s *snapshot) IsBuiltin(ctx context.Context, uri span.URI) bool {
2276 s.mu.Lock()
2277 defer s.mu.Unlock()
2278 // We should always get the builtin URI in a canonical form, so use simple
2279 // string comparison here. span.CompareURI is too expensive.
2280 return uri == s.builtin
2281}
2282
2283func (s *snapshot) setBuiltin(path string) {
2284 s.mu.Lock()
2285 defer s.mu.Unlock()
2286
2287 s.builtin = span.URIFromPath(path)
2288}
2289
2290// BuildGoplsMod generates a go.mod file for all modules in the workspace. It
2291// bypasses any existing gopls.mod.
2292func (s *snapshot) BuildGoplsMod(ctx context.Context) (*modfile.File, error) {
2293 allModules, err := findModules(s.view.folder, pathExcludedByFilterFunc(s.view.rootURI.Filename(), s.view.gomodcache, s.View().Options()), 0)
2294 if err != nil {
2295 return nil, err
2296 }
2297 return buildWorkspaceModFile(ctx, allModules, s)
2298}
2299
2300// TODO(rfindley): move this to workspace.go
2301func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) {
2302 file := &modfile.File{}
2303 file.AddModuleStmt("gopls-workspace")
2304 // Track the highest Go version, to be set on the workspace module.
2305 // Fall back to 1.12 -- old versions insist on having some version.
2306 goVersion := "1.12"
2307
2308 paths := map[string]span.URI{}
2309 excludes := map[string][]string{}
2310 var sortedModURIs []span.URI
2311 for uri := range modFiles {
2312 sortedModURIs = append(sortedModURIs, uri)
2313 }
2314 sort.Slice(sortedModURIs, func(i, j int) bool {
2315 return sortedModURIs[i] < sortedModURIs[j]
2316 })
2317 for _, modURI := range sortedModURIs {
2318 fh, err := fs.GetFile(ctx, modURI)
2319 if err != nil {
2320 return nil, err
2321 }
2322 content, err := fh.Read()
2323 if err != nil {
2324 return nil, err
2325 }
2326 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
2327 if err != nil {
2328 return nil, err
2329 }
2330 if file == nil || parsed.Module == nil {
2331 return nil, fmt.Errorf("no module declaration for %s", modURI)
2332 }
2333 // Prepend "v" to go versions to make them valid semver.
2334 if parsed.Go != nil && semver.Compare("v"+goVersion, "v"+parsed.Go.Version) < 0 {
2335 goVersion = parsed.Go.Version
2336 }
2337 path := parsed.Module.Mod.Path
2338 if seen, ok := paths[path]; ok {
2339 return nil, fmt.Errorf("found module %q multiple times in the workspace, at:\n\t%q\n\t%q", path, seen, modURI)
2340 }
2341 paths[path] = modURI
2342 // If the module's path includes a major version, we expect it to have
2343 // a matching major version.
2344 _, majorVersion, _ := module.SplitPathVersion(path)
2345 if majorVersion == "" {
2346 majorVersion = "/v0"
2347 }
2348 majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions
2349 file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false)
2350 if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil {
2351 return nil, err
2352 }
2353 for _, exclude := range parsed.Exclude {
2354 excludes[exclude.Mod.Path] = append(excludes[exclude.Mod.Path], exclude.Mod.Version)
2355 }
2356 }
2357 if goVersion != "" {
2358 file.AddGoStmt(goVersion)
2359 }
2360 // Go back through all of the modules to handle any of their replace
2361 // statements.
2362 for _, modURI := range sortedModURIs {
2363 fh, err := fs.GetFile(ctx, modURI)
2364 if err != nil {
2365 return nil, err
2366 }
2367 content, err := fh.Read()
2368 if err != nil {
2369 return nil, err
2370 }
2371 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
2372 if err != nil {
2373 return nil, err
2374 }
2375 // If any of the workspace modules have replace directives, they need
2376 // to be reflected in the workspace module.
2377 for _, rep := range parsed.Replace {
2378 // Don't replace any modules that are in our workspace--we should
2379 // always use the version in the workspace.
2380 if _, ok := paths[rep.Old.Path]; ok {
2381 continue
2382 }
2383 newPath := rep.New.Path
2384 newVersion := rep.New.Version
2385 // If a replace points to a module in the workspace, make sure we
2386 // direct it to version of the module in the workspace.
2387 if m, ok := paths[rep.New.Path]; ok {
2388 newPath = dirURI(m).Filename()
2389 newVersion = ""
2390 } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) {
2391 // Make any relative paths absolute.
2392 newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path)
2393 }
2394 if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil {
2395 return nil, err
2396 }
2397 }
2398 }
2399 for path, versions := range excludes {
2400 for _, version := range versions {
2401 file.AddExclude(path, version)
2402 }
2403 }
2404 file.SortBlocks()
2405 return file, nil
2406}
2407
2408func buildWorkspaceSumFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) ([]byte, error) {
2409 allSums := map[module.Version][]string{}
2410 for modURI := range modFiles {
2411 // TODO(rfindley): factor out this pattern into a uripath package.
2412 sumURI := span.URIFromPath(filepath.Join(filepath.Dir(modURI.Filename()), "go.sum"))
2413 fh, err := fs.GetFile(ctx, sumURI)
2414 if err != nil {
2415 continue
2416 }
2417 data, err := fh.Read()
2418 if os.IsNotExist(err) {
2419 continue
2420 }
2421 if err != nil {
2422 return nil, fmt.Errorf("reading go sum: %w", err)
2423 }
2424 if err := readGoSum(allSums, sumURI.Filename(), data); err != nil {
2425 return nil, err
2426 }
2427 }
2428 // This logic to write go.sum is copied (with minor modifications) from
2429 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=631;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
2430 var mods []module.Version
2431 for m := range allSums {
2432 mods = append(mods, m)
2433 }
2434 module.Sort(mods)
2435
2436 var buf bytes.Buffer
2437 for _, m := range mods {
2438 list := allSums[m]
2439 sort.Strings(list)
2440 // Note (rfindley): here we add all sum lines without verification, because
2441 // the assumption is that if they come from a go.sum file, they are
2442 // trusted.
2443 for _, h := range list {
2444 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h)
2445 }
2446 }
2447 return buf.Bytes(), nil
2448}
2449
2450// readGoSum is copied (with minor modifications) from
2451// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=398;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0
2452func readGoSum(dst map[module.Version][]string, file string, data []byte) error {
2453 lineno := 0
2454 for len(data) > 0 {
2455 var line []byte
2456 lineno++
2457 i := bytes.IndexByte(data, '\n')
2458 if i < 0 {
2459 line, data = data, nil
2460 } else {
2461 line, data = data[:i], data[i+1:]
2462 }
2463 f := strings.Fields(string(line))
2464 if len(f) == 0 {
2465 // blank line; skip it
2466 continue
2467 }
2468 if len(f) != 3 {
2469 return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f))
2470 }
2471 mod := module.Version{Path: f[0], Version: f[1]}
2472 dst[mod] = append(dst[mod], f[2])
2473 }
2474 return nil
2475}