golang.org/x/tools/gopls@v0.15.3/internal/cache/snapshot.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cache 6 7 import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "go/ast" 13 "go/build/constraint" 14 "go/parser" 15 "go/token" 16 "go/types" 17 "io" 18 "os" 19 "path" 20 "path/filepath" 21 "regexp" 22 "runtime" 23 "sort" 24 "strconv" 25 "strings" 26 "sync" 27 28 "golang.org/x/sync/errgroup" 29 "golang.org/x/tools/go/packages" 30 "golang.org/x/tools/go/types/objectpath" 31 "golang.org/x/tools/gopls/internal/cache/metadata" 32 "golang.org/x/tools/gopls/internal/cache/methodsets" 33 "golang.org/x/tools/gopls/internal/cache/typerefs" 34 "golang.org/x/tools/gopls/internal/cache/xrefs" 35 "golang.org/x/tools/gopls/internal/file" 36 "golang.org/x/tools/gopls/internal/filecache" 37 "golang.org/x/tools/gopls/internal/protocol" 38 "golang.org/x/tools/gopls/internal/protocol/command" 39 "golang.org/x/tools/gopls/internal/settings" 40 "golang.org/x/tools/gopls/internal/util/bug" 41 "golang.org/x/tools/gopls/internal/util/constraints" 42 "golang.org/x/tools/gopls/internal/util/immutable" 43 "golang.org/x/tools/gopls/internal/util/pathutil" 44 "golang.org/x/tools/gopls/internal/util/persistent" 45 "golang.org/x/tools/gopls/internal/util/slices" 46 "golang.org/x/tools/gopls/internal/vulncheck" 47 "golang.org/x/tools/internal/event" 48 "golang.org/x/tools/internal/event/label" 49 "golang.org/x/tools/internal/event/tag" 50 "golang.org/x/tools/internal/gocommand" 51 "golang.org/x/tools/internal/memoize" 52 "golang.org/x/tools/internal/packagesinternal" 53 "golang.org/x/tools/internal/typesinternal" 54 ) 55 56 // A Snapshot represents the current state for a given view. 57 // 58 // It is first and foremost an idempotent implementation of file.Source whose 59 // ReadFile method returns consistent information about the existence and 60 // content of each file throughout its lifetime. 61 // 62 // However, the snapshot also manages additional state (such as parsed files 63 // and packages) that are derived from file content. 64 // 65 // Snapshots are responsible for bookkeeping and invalidation of this state, 66 // implemented in Snapshot.clone. 67 type Snapshot struct { 68 // sequenceID is the monotonically increasing ID of this snapshot within its View. 69 // 70 // Sequence IDs for Snapshots from different Views cannot be compared. 71 sequenceID uint64 72 73 // TODO(rfindley): the snapshot holding a reference to the view poses 74 // lifecycle problems: a view may be shut down and waiting for work 75 // associated with this snapshot to complete. While most accesses of the view 76 // are benign (options or workspace information), this is not formalized and 77 // it is wrong for the snapshot to use a shutdown view. 78 // 79 // Fix this by passing options and workspace information to the snapshot, 80 // both of which should be immutable for the snapshot. 81 view *View 82 83 cancel func() 84 backgroundCtx context.Context 85 86 store *memoize.Store // cache of handles shared by all snapshots 87 88 refMu sync.Mutex 89 90 // refcount holds the number of outstanding references to the current 91 // Snapshot. When refcount is decremented to 0, the Snapshot maps are 92 // destroyed and the done function is called. 93 // 94 // TODO(rfindley): use atomic.Int32 on Go 1.19+. 95 refcount int 96 done func() // for implementing Session.Shutdown 97 98 // mu guards all of the maps in the snapshot, as well as the builtin URI and 99 // initialized. 100 mu sync.Mutex 101 102 // initialized reports whether the snapshot has been initialized. Concurrent 103 // initialization is guarded by the view.initializationSema. Each snapshot is 104 // initialized at most once: concurrent initialization is guarded by 105 // view.initializationSema. 106 initialized bool 107 108 // initialErr holds the last error resulting from initialization. If 109 // initialization fails, we only retry when the workspace modules change, 110 // to avoid too many go/packages calls. 111 // If initialized is false, initialErr stil holds the error resulting from 112 // the previous initialization. 113 // TODO(rfindley): can we unify the lifecycle of initialized and initialErr. 114 initialErr *InitializationError 115 116 // builtin is the location of builtin.go in GOROOT. 117 // 118 // TODO(rfindley): would it make more sense to eagerly parse builtin, and 119 // instead store a *ParsedGoFile here? 120 builtin protocol.DocumentURI 121 122 // meta holds loaded metadata. 123 // 124 // meta is guarded by mu, but the Graph itself is immutable. 125 // 126 // TODO(rfindley): in many places we hold mu while operating on meta, even 127 // though we only need to hold mu while reading the pointer. 128 meta *metadata.Graph 129 130 // files maps file URIs to their corresponding FileHandles. 131 // It may invalidated when a file's content changes. 132 files *fileMap 133 134 // symbolizeHandles maps each file URI to a handle for the future 135 // result of computing the symbols declared in that file. 136 symbolizeHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[symbolizeResult] 137 138 // packages maps a packageKey to a *packageHandle. 139 // It may be invalidated when a file's content changes. 140 // 141 // Invariants to preserve: 142 // - packages.Get(id).meta == meta.metadata[id] for all ids 143 // - if a package is in packages, then all of its dependencies should also 144 // be in packages, unless there is a missing import 145 packages *persistent.Map[PackageID, *packageHandle] 146 147 // activePackages maps a package ID to a memoized active package, or nil if 148 // the package is known not to be open. 149 // 150 // IDs not contained in the map are not known to be open or not open. 151 activePackages *persistent.Map[PackageID, *Package] 152 153 // workspacePackages contains the workspace's packages, which are loaded 154 // when the view is created. It does not contain intermediate test variants. 155 workspacePackages immutable.Map[PackageID, PackagePath] 156 157 // shouldLoad tracks packages that need to be reloaded, mapping a PackageID 158 // to the package paths that should be used to reload it 159 // 160 // When we try to load a package, we clear it from the shouldLoad map 161 // regardless of whether the load succeeded, to prevent endless loads. 162 shouldLoad *persistent.Map[PackageID, []PackagePath] 163 164 // unloadableFiles keeps track of files that we've failed to load. 165 unloadableFiles *persistent.Set[protocol.DocumentURI] 166 167 // TODO(rfindley): rename the handles below to "promises". A promise is 168 // different from a handle (we mutate the package handle.) 169 170 // parseModHandles keeps track of any parseModHandles for the snapshot. 171 // The handles need not refer to only the view's go.mod file. 172 parseModHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[parseModResult] 173 174 // parseWorkHandles keeps track of any parseWorkHandles for the snapshot. 175 // The handles need not refer to only the view's go.work file. 176 parseWorkHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[parseWorkResult] 177 178 // Preserve go.mod-related handles to avoid garbage-collecting the results 179 // of various calls to the go command. The handles need not refer to only 180 // the view's go.mod file. 181 modTidyHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modTidyResult] 182 modWhyHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modWhyResult] 183 modVulnHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modVulnResult] 184 185 // importGraph holds a shared import graph to use for type-checking. Adding 186 // more packages to this import graph can speed up type checking, at the 187 // expense of in-use memory. 188 // 189 // See getImportGraph for additional documentation. 190 importGraphDone chan struct{} // closed when importGraph is set; may be nil 191 importGraph *importGraph // copied from preceding snapshot and re-evaluated 192 193 // pkgIndex is an index of package IDs, for efficient storage of typerefs. 194 pkgIndex *typerefs.PackageIndex 195 196 // moduleUpgrades tracks known upgrades for module paths in each modfile. 197 // Each modfile has a map of module name to upgrade version. 198 moduleUpgrades *persistent.Map[protocol.DocumentURI, map[string]string] 199 200 // vulns maps each go.mod file's URI to its known vulnerabilities. 201 vulns *persistent.Map[protocol.DocumentURI, *vulncheck.Result] 202 203 // gcOptimizationDetails describes the packages for which we want 204 // optimization details to be included in the diagnostics. 205 gcOptimizationDetails map[metadata.PackageID]unit 206 } 207 208 var _ memoize.RefCounted = (*Snapshot)(nil) // snapshots are reference-counted 209 210 func (s *Snapshot) awaitPromise(ctx context.Context, p *memoize.Promise) (interface{}, error) { 211 return p.Get(ctx, s) 212 } 213 214 // Acquire prevents the snapshot from being destroyed until the returned 215 // function is called. 216 // 217 // (s.Acquire().release() could instead be expressed as a pair of 218 // method calls s.IncRef(); s.DecRef(). The latter has the advantage 219 // that the DecRefs are fungible and don't require holding anything in 220 // addition to the refcounted object s, but paradoxically that is also 221 // an advantage of the current approach, which forces the caller to 222 // consider the release function at every stage, making a reference 223 // leak more obvious.) 224 func (s *Snapshot) Acquire() func() { 225 s.refMu.Lock() 226 defer s.refMu.Unlock() 227 assert(s.refcount > 0, "non-positive refs") 228 s.refcount++ 229 230 return s.decref 231 } 232 233 // decref should only be referenced by Acquire, and by View when it frees its 234 // reference to View.snapshot. 235 func (s *Snapshot) decref() { 236 s.refMu.Lock() 237 defer s.refMu.Unlock() 238 239 assert(s.refcount > 0, "non-positive refs") 240 s.refcount-- 241 if s.refcount == 0 { 242 s.packages.Destroy() 243 s.activePackages.Destroy() 244 s.files.destroy() 245 s.symbolizeHandles.Destroy() 246 s.parseModHandles.Destroy() 247 s.parseWorkHandles.Destroy() 248 s.modTidyHandles.Destroy() 249 s.modVulnHandles.Destroy() 250 s.modWhyHandles.Destroy() 251 s.unloadableFiles.Destroy() 252 s.moduleUpgrades.Destroy() 253 s.vulns.Destroy() 254 s.done() 255 } 256 } 257 258 // SequenceID is the sequence id of this snapshot within its containing 259 // view. 260 // 261 // Relative to their view sequence ids are monotonically increasing, but this 262 // does not hold globally: when new views are created their initial snapshot 263 // has sequence ID 0. 264 func (s *Snapshot) SequenceID() uint64 { 265 return s.sequenceID 266 } 267 268 // SnapshotLabels returns a new slice of labels that should be used for events 269 // related to a snapshot. 270 func (s *Snapshot) Labels() []label.Label { 271 return []label.Label{tag.Snapshot.Of(s.SequenceID()), tag.Directory.Of(s.Folder())} 272 } 273 274 // Folder returns the folder at the base of this snapshot. 275 func (s *Snapshot) Folder() protocol.DocumentURI { 276 return s.view.folder.Dir 277 } 278 279 // View returns the View associated with this snapshot. 280 func (s *Snapshot) View() *View { 281 return s.view 282 } 283 284 // FileKind returns the kind of a file. 285 // 286 // We can't reliably deduce the kind from the file name alone, 287 // as some editors can be told to interpret a buffer as 288 // language different from the file name heuristic, e.g. that 289 // an .html file actually contains Go "html/template" syntax, 290 // or even that a .go file contains Python. 291 func (s *Snapshot) FileKind(fh file.Handle) file.Kind { 292 if k := fileKind(fh); k != file.UnknownKind { 293 return k 294 } 295 fext := filepath.Ext(fh.URI().Path()) 296 exts := s.Options().TemplateExtensions 297 for _, ext := range exts { 298 if fext == ext || fext == "."+ext { 299 return file.Tmpl 300 } 301 } 302 303 // and now what? This should never happen, but it does for cgo before go1.15 304 // 305 // TODO(rfindley): this doesn't look right. We should default to UnknownKind. 306 // Also, I don't understand the comment above, though I'd guess before go1.15 307 // we encountered cgo files without the .go extension. 308 return file.Go 309 } 310 311 // fileKind returns the default file kind for a file, before considering 312 // template file extensions. See [Snapshot.FileKind]. 313 func fileKind(fh file.Handle) file.Kind { 314 // The kind of an unsaved buffer comes from the 315 // TextDocumentItem.LanguageID field in the didChange event, 316 // not from the file name. They may differ. 317 if o, ok := fh.(*overlay); ok { 318 if o.kind != file.UnknownKind { 319 return o.kind 320 } 321 } 322 323 fext := filepath.Ext(fh.URI().Path()) 324 switch fext { 325 case ".go": 326 return file.Go 327 case ".mod": 328 return file.Mod 329 case ".sum": 330 return file.Sum 331 case ".work": 332 return file.Work 333 } 334 return file.UnknownKind 335 } 336 337 // Options returns the options associated with this snapshot. 338 func (s *Snapshot) Options() *settings.Options { 339 return s.view.folder.Options 340 } 341 342 // BackgroundContext returns a context used for all background processing 343 // on behalf of this snapshot. 344 func (s *Snapshot) BackgroundContext() context.Context { 345 return s.backgroundCtx 346 } 347 348 // Templates returns the .tmpl files. 349 func (s *Snapshot) Templates() map[protocol.DocumentURI]file.Handle { 350 s.mu.Lock() 351 defer s.mu.Unlock() 352 353 tmpls := map[protocol.DocumentURI]file.Handle{} 354 s.files.foreach(func(k protocol.DocumentURI, fh file.Handle) { 355 if s.FileKind(fh) == file.Tmpl { 356 tmpls[k] = fh 357 } 358 }) 359 return tmpls 360 } 361 362 // config returns the configuration used for the snapshot's interaction with 363 // the go/packages API. It uses the given working directory. 364 // 365 // TODO(rstambler): go/packages requires that we do not provide overlays for 366 // multiple modules in on config, so buildOverlay needs to filter overlays by 367 // module. 368 func (s *Snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config { 369 370 cfg := &packages.Config{ 371 Context: ctx, 372 Dir: inv.WorkingDir, 373 Env: inv.Env, 374 BuildFlags: inv.BuildFlags, 375 Mode: packages.NeedName | 376 packages.NeedFiles | 377 packages.NeedCompiledGoFiles | 378 packages.NeedImports | 379 packages.NeedDeps | 380 packages.NeedTypesSizes | 381 packages.NeedModule | 382 packages.NeedEmbedFiles | 383 packages.LoadMode(packagesinternal.DepsErrors) | 384 packages.LoadMode(packagesinternal.ForTest), 385 Fset: nil, // we do our own parsing 386 Overlay: s.buildOverlay(), 387 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) { 388 panic("go/packages must not be used to parse files") 389 }, 390 Logf: func(format string, args ...interface{}) { 391 if s.Options().VerboseOutput { 392 event.Log(ctx, fmt.Sprintf(format, args...)) 393 } 394 }, 395 Tests: true, 396 } 397 packagesinternal.SetModFile(cfg, inv.ModFile) 398 packagesinternal.SetModFlag(cfg, inv.ModFlag) 399 // We want to type check cgo code if go/types supports it. 400 if typesinternal.SetUsesCgo(&types.Config{}) { 401 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo) 402 } 403 return cfg 404 } 405 406 // InvocationFlags represents the settings of a particular go command invocation. 407 // It is a mode, plus a set of flag bits. 408 type InvocationFlags int 409 410 const ( 411 // Normal is appropriate for commands that might be run by a user and don't 412 // deliberately modify go.mod files, e.g. `go test`. 413 Normal InvocationFlags = iota 414 // WriteTemporaryModFile is for commands that need information from a 415 // modified version of the user's go.mod file, e.g. `go mod tidy` used to 416 // generate diagnostics. 417 WriteTemporaryModFile 418 // LoadWorkspace is for packages.Load, and other operations that should 419 // consider the whole workspace at once. 420 LoadWorkspace 421 // AllowNetwork is a flag bit that indicates the invocation should be 422 // allowed to access the network. 423 AllowNetwork InvocationFlags = 1 << 10 424 ) 425 426 func (m InvocationFlags) Mode() InvocationFlags { 427 return m & (AllowNetwork - 1) 428 } 429 430 func (m InvocationFlags) AllowNetwork() bool { 431 return m&AllowNetwork != 0 432 } 433 434 // RunGoCommandDirect runs the given `go` command. Verb, Args, and 435 // WorkingDir must be specified. 436 func (s *Snapshot) RunGoCommandDirect(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) { 437 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 438 if err != nil { 439 return nil, err 440 } 441 defer cleanup() 442 443 return s.view.gocmdRunner.Run(ctx, *inv) 444 } 445 446 // RunGoCommandPiped runs the given `go` command, writing its output 447 // to stdout and stderr. Verb, Args, and WorkingDir must be specified. 448 // 449 // RunGoCommandPiped runs the command serially using gocommand.RunPiped, 450 // enforcing that this command executes exclusively to other commands on the 451 // server. 452 func (s *Snapshot) RunGoCommandPiped(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error { 453 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 454 if err != nil { 455 return err 456 } 457 defer cleanup() 458 return s.view.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr) 459 } 460 461 // RunGoModUpdateCommands runs a series of `go` commands that updates the go.mod 462 // and go.sum file for wd, and returns their updated contents. 463 // 464 // TODO(rfindley): the signature of RunGoModUpdateCommands is very confusing. 465 // Simplify it. 466 func (s *Snapshot) RunGoModUpdateCommands(ctx context.Context, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) ([]byte, []byte, error) { 467 flags := WriteTemporaryModFile | AllowNetwork 468 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd}) 469 if err != nil { 470 return nil, nil, err 471 } 472 defer cleanup() 473 invoke := func(args ...string) (*bytes.Buffer, error) { 474 inv.Verb = args[0] 475 inv.Args = args[1:] 476 return s.view.gocmdRunner.Run(ctx, *inv) 477 } 478 if err := run(invoke); err != nil { 479 return nil, nil, err 480 } 481 if flags.Mode() != WriteTemporaryModFile { 482 return nil, nil, nil 483 } 484 var modBytes, sumBytes []byte 485 modBytes, err = os.ReadFile(tmpURI.Path()) 486 if err != nil && !os.IsNotExist(err) { 487 return nil, nil, err 488 } 489 sumBytes, err = os.ReadFile(strings.TrimSuffix(tmpURI.Path(), ".mod") + ".sum") 490 if err != nil && !os.IsNotExist(err) { 491 return nil, nil, err 492 } 493 return modBytes, sumBytes, nil 494 } 495 496 // goCommandInvocation populates inv with configuration for running go commands on the snapshot. 497 // 498 // TODO(rfindley): refactor this function to compose the required configuration 499 // explicitly, rather than implicitly deriving it from flags and inv. 500 // 501 // TODO(adonovan): simplify cleanup mechanism. It's hard to see, but 502 // it used only after call to tempModFile. 503 func (s *Snapshot) goCommandInvocation(ctx context.Context, flags InvocationFlags, inv *gocommand.Invocation) (tmpURI protocol.DocumentURI, updatedInv *gocommand.Invocation, cleanup func(), err error) { 504 allowModfileModificationOption := s.Options().AllowModfileModifications 505 allowNetworkOption := s.Options().AllowImplicitNetworkAccess 506 507 // TODO(rfindley): it's not clear that this is doing the right thing. 508 // Should inv.Env really overwrite view.options? Should s.view.envOverlay 509 // overwrite inv.Env? (Do we ever invoke this with a non-empty inv.Env?) 510 // 511 // We should survey existing uses and write down rules for how env is 512 // applied. 513 inv.Env = slices.Concat( 514 os.Environ(), 515 s.Options().EnvSlice(), 516 inv.Env, 517 []string{"GO111MODULE=" + s.view.adjustedGO111MODULE()}, 518 s.view.EnvOverlay(), 519 ) 520 inv.BuildFlags = append([]string{}, s.Options().BuildFlags...) 521 cleanup = func() {} // fallback 522 523 // All logic below is for module mode. 524 if len(s.view.workspaceModFiles) == 0 { 525 return "", inv, cleanup, nil 526 } 527 528 mode, allowNetwork := flags.Mode(), flags.AllowNetwork() 529 if !allowNetwork && !allowNetworkOption { 530 inv.Env = append(inv.Env, "GOPROXY=off") 531 } 532 533 // What follows is rather complicated logic for how to actually run the go 534 // command. A word of warning: this is the result of various incremental 535 // features added to gopls, and varying behavior of the Go command across Go 536 // versions. It can surely be cleaned up significantly, but tread carefully. 537 // 538 // Roughly speaking we need to resolve four things: 539 // - the working directory. 540 // - the -mod flag 541 // - the -modfile flag 542 // 543 // These are dependent on a number of factors: whether we need to run in a 544 // synthetic workspace, whether flags are supported at the current go 545 // version, and what we're actually trying to achieve (the 546 // InvocationFlags). 547 // 548 // TODO(rfindley): should we set -overlays here? 549 550 const mutableModFlag = "mod" 551 552 // If the mod flag isn't set, populate it based on the mode and workspace. 553 // 554 // (As noted in various TODOs throughout this function, this is very 555 // confusing and not obviously correct, but tests pass and we will eventually 556 // rewrite this entire function.) 557 if inv.ModFlag == "" { 558 switch mode { 559 case LoadWorkspace, Normal: 560 if allowModfileModificationOption { 561 inv.ModFlag = mutableModFlag 562 } 563 case WriteTemporaryModFile: 564 inv.ModFlag = mutableModFlag 565 // -mod must be readonly when using go.work files - see issue #48941 566 inv.Env = append(inv.Env, "GOWORK=off") 567 } 568 } 569 570 // TODO(rfindley): if inv.ModFlag was already set to "mod", we may not have 571 // set GOWORK=off here. But that doesn't happen. Clean up this entire API so 572 // that we don't have this mutation of the invocation, which is quite hard to 573 // follow. 574 575 // If the invocation needs to mutate the modfile, we must use a temp mod. 576 if inv.ModFlag == mutableModFlag { 577 var modURI protocol.DocumentURI 578 // Select the module context to use. 579 // If we're type checking, we need to use the workspace context, meaning 580 // the main (workspace) module. Otherwise, we should use the module for 581 // the passed-in working dir. 582 if mode == LoadWorkspace { 583 // TODO(rfindley): this seems unnecessary and overly complicated. Remove 584 // this along with 'allowModFileModifications'. 585 if s.view.typ == GoModView { 586 modURI = s.view.gomod 587 } 588 } else { 589 modURI = s.GoModForFile(protocol.URIFromPath(inv.WorkingDir)) 590 } 591 592 var modContent []byte 593 if modURI != "" { 594 modFH, err := s.ReadFile(ctx, modURI) 595 if err != nil { 596 return "", nil, cleanup, err 597 } 598 modContent, err = modFH.Content() 599 if err != nil { 600 return "", nil, cleanup, err 601 } 602 } 603 if modURI == "" { 604 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir) 605 } 606 // Use the go.sum if it happens to be available. 607 gosum := s.goSum(ctx, modURI) 608 tmpURI, cleanup, err = tempModFile(modURI, modContent, gosum) 609 if err != nil { 610 return "", nil, cleanup, err 611 } 612 inv.ModFile = tmpURI.Path() 613 } 614 615 return tmpURI, inv, cleanup, nil 616 } 617 618 func (s *Snapshot) buildOverlay() map[string][]byte { 619 overlays := make(map[string][]byte) 620 for _, overlay := range s.Overlays() { 621 if overlay.saved { 622 continue 623 } 624 // TODO(rfindley): previously, there was a todo here to make sure we don't 625 // send overlays outside of the current view. IMO we should instead make 626 // sure this doesn't matter. 627 overlays[overlay.URI().Path()] = overlay.content 628 } 629 return overlays 630 } 631 632 // Overlays returns the set of overlays at this snapshot. 633 // 634 // Note that this may differ from the set of overlays on the server, if the 635 // snapshot observed a historical state. 636 func (s *Snapshot) Overlays() []*overlay { 637 s.mu.Lock() 638 defer s.mu.Unlock() 639 640 return s.files.getOverlays() 641 } 642 643 // Package data kinds, identifying various package data that may be stored in 644 // the file cache. 645 const ( 646 xrefsKind = "xrefs" 647 methodSetsKind = "methodsets" 648 exportDataKind = "export" 649 diagnosticsKind = "diagnostics" 650 typerefsKind = "typerefs" 651 ) 652 653 // PackageDiagnostics returns diagnostics for files contained in specified 654 // packages. 655 // 656 // If these diagnostics cannot be loaded from cache, the requested packages 657 // may be type-checked. 658 func (s *Snapshot) PackageDiagnostics(ctx context.Context, ids ...PackageID) (map[protocol.DocumentURI][]*Diagnostic, error) { 659 ctx, done := event.Start(ctx, "cache.snapshot.PackageDiagnostics") 660 defer done() 661 662 var mu sync.Mutex 663 perFile := make(map[protocol.DocumentURI][]*Diagnostic) 664 collect := func(diags []*Diagnostic) { 665 mu.Lock() 666 defer mu.Unlock() 667 for _, diag := range diags { 668 perFile[diag.URI] = append(perFile[diag.URI], diag) 669 } 670 } 671 pre := func(_ int, ph *packageHandle) bool { 672 data, err := filecache.Get(diagnosticsKind, ph.key) 673 if err == nil { // hit 674 collect(ph.loadDiagnostics) 675 collect(decodeDiagnostics(data)) 676 return false 677 } else if err != filecache.ErrNotFound { 678 event.Error(ctx, "reading diagnostics from filecache", err) 679 } 680 return true 681 } 682 post := func(_ int, pkg *Package) { 683 collect(pkg.loadDiagnostics) 684 collect(pkg.pkg.diagnostics) 685 } 686 return perFile, s.forEachPackage(ctx, ids, pre, post) 687 } 688 689 // References returns cross-reference indexes for the specified packages. 690 // 691 // If these indexes cannot be loaded from cache, the requested packages may 692 // be type-checked. 693 func (s *Snapshot) References(ctx context.Context, ids ...PackageID) ([]xrefIndex, error) { 694 ctx, done := event.Start(ctx, "cache.snapshot.References") 695 defer done() 696 697 indexes := make([]xrefIndex, len(ids)) 698 pre := func(i int, ph *packageHandle) bool { 699 data, err := filecache.Get(xrefsKind, ph.key) 700 if err == nil { // hit 701 indexes[i] = xrefIndex{mp: ph.mp, data: data} 702 return false 703 } else if err != filecache.ErrNotFound { 704 event.Error(ctx, "reading xrefs from filecache", err) 705 } 706 return true 707 } 708 post := func(i int, pkg *Package) { 709 indexes[i] = xrefIndex{mp: pkg.metadata, data: pkg.pkg.xrefs()} 710 } 711 return indexes, s.forEachPackage(ctx, ids, pre, post) 712 } 713 714 // An xrefIndex is a helper for looking up references in a given package. 715 type xrefIndex struct { 716 mp *metadata.Package 717 data []byte 718 } 719 720 func (index xrefIndex) Lookup(targets map[PackagePath]map[objectpath.Path]struct{}) []protocol.Location { 721 return xrefs.Lookup(index.mp, index.data, targets) 722 } 723 724 // MethodSets returns method-set indexes for the specified packages. 725 // 726 // If these indexes cannot be loaded from cache, the requested packages may 727 // be type-checked. 728 func (s *Snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methodsets.Index, error) { 729 ctx, done := event.Start(ctx, "cache.snapshot.MethodSets") 730 defer done() 731 732 indexes := make([]*methodsets.Index, len(ids)) 733 pre := func(i int, ph *packageHandle) bool { 734 data, err := filecache.Get(methodSetsKind, ph.key) 735 if err == nil { // hit 736 indexes[i] = methodsets.Decode(data) 737 return false 738 } else if err != filecache.ErrNotFound { 739 event.Error(ctx, "reading methodsets from filecache", err) 740 } 741 return true 742 } 743 post := func(i int, pkg *Package) { 744 indexes[i] = pkg.pkg.methodsets() 745 } 746 return indexes, s.forEachPackage(ctx, ids, pre, post) 747 } 748 749 // MetadataForFile returns a new slice containing metadata for each 750 // package containing the Go file identified by uri, ordered by the 751 // number of CompiledGoFiles (i.e. "narrowest" to "widest" package), 752 // and secondarily by IsIntermediateTestVariant (false < true). 753 // The result may include tests and intermediate test variants of 754 // importable packages. 755 // It returns an error if the context was cancelled. 756 func (s *Snapshot) MetadataForFile(ctx context.Context, uri protocol.DocumentURI) ([]*metadata.Package, error) { 757 if s.view.typ == AdHocView { 758 // As described in golang/go#57209, in ad-hoc workspaces (where we load ./ 759 // rather than ./...), preempting the directory load with file loads can 760 // lead to an inconsistent outcome, where certain files are loaded with 761 // command-line-arguments packages and others are loaded only in the ad-hoc 762 // package. Therefore, ensure that the workspace is loaded before doing any 763 // file loads. 764 if err := s.awaitLoaded(ctx); err != nil { 765 return nil, err 766 } 767 } 768 769 s.mu.Lock() 770 771 // Start with the set of package associations derived from the last load. 772 ids := s.meta.IDs[uri] 773 774 shouldLoad := false // whether any packages containing uri are marked 'shouldLoad' 775 for _, id := range ids { 776 if pkgs, _ := s.shouldLoad.Get(id); len(pkgs) > 0 { 777 shouldLoad = true 778 } 779 } 780 781 // Check if uri is known to be unloadable. 782 unloadable := s.unloadableFiles.Contains(uri) 783 784 s.mu.Unlock() 785 786 // Reload if loading is likely to improve the package associations for uri: 787 // - uri is not contained in any valid packages 788 // - ...or one of the packages containing uri is marked 'shouldLoad' 789 // - ...but uri is not unloadable 790 if (shouldLoad || len(ids) == 0) && !unloadable { 791 scope := fileLoadScope(uri) 792 err := s.load(ctx, false, scope) 793 794 // 795 // Return the context error here as the current operation is no longer 796 // valid. 797 if err != nil { 798 // Guard against failed loads due to context cancellation. We don't want 799 // to mark loads as completed if they failed due to context cancellation. 800 if ctx.Err() != nil { 801 return nil, ctx.Err() 802 } 803 804 // Don't return an error here, as we may still return stale IDs. 805 // Furthermore, the result of MetadataForFile should be consistent upon 806 // subsequent calls, even if the file is marked as unloadable. 807 if !errors.Is(err, errNoPackages) { 808 event.Error(ctx, "MetadataForFile", err) 809 } 810 } 811 812 // We must clear scopes after loading. 813 // 814 // TODO(rfindley): unlike reloadWorkspace, this is simply marking loaded 815 // packages as loaded. We could do this from snapshot.load and avoid 816 // raciness. 817 s.clearShouldLoad(scope) 818 } 819 820 // Retrieve the metadata. 821 s.mu.Lock() 822 defer s.mu.Unlock() 823 ids = s.meta.IDs[uri] 824 metas := make([]*metadata.Package, len(ids)) 825 for i, id := range ids { 826 metas[i] = s.meta.Packages[id] 827 if metas[i] == nil { 828 panic("nil metadata") 829 } 830 } 831 // Metadata is only ever added by loading, 832 // so if we get here and still have 833 // no IDs, uri is unloadable. 834 if !unloadable && len(ids) == 0 { 835 s.unloadableFiles.Add(uri) 836 } 837 838 // Sort packages "narrowest" to "widest" (in practice: 839 // non-tests before tests), and regular packages before 840 // their intermediate test variants (which have the same 841 // files but different imports). 842 sort.Slice(metas, func(i, j int) bool { 843 x, y := metas[i], metas[j] 844 xfiles, yfiles := len(x.CompiledGoFiles), len(y.CompiledGoFiles) 845 if xfiles != yfiles { 846 return xfiles < yfiles 847 } 848 return boolLess(x.IsIntermediateTestVariant(), y.IsIntermediateTestVariant()) 849 }) 850 851 return metas, nil 852 } 853 854 func boolLess(x, y bool) bool { return !x && y } // false < true 855 856 // ReverseDependencies returns a new mapping whose entries are 857 // the ID and Metadata of each package in the workspace that 858 // directly or transitively depend on the package denoted by id, 859 // excluding id itself. 860 func (s *Snapshot) ReverseDependencies(ctx context.Context, id PackageID, transitive bool) (map[PackageID]*metadata.Package, error) { 861 if err := s.awaitLoaded(ctx); err != nil { 862 return nil, err 863 } 864 865 meta := s.MetadataGraph() 866 var rdeps map[PackageID]*metadata.Package 867 if transitive { 868 rdeps = meta.ReverseReflexiveTransitiveClosure(id) 869 870 // Remove the original package ID from the map. 871 // (Callers all want irreflexivity but it's easier 872 // to compute reflexively then subtract.) 873 delete(rdeps, id) 874 875 } else { 876 // direct reverse dependencies 877 rdeps = make(map[PackageID]*metadata.Package) 878 for _, rdepID := range meta.ImportedBy[id] { 879 if rdep := meta.Packages[rdepID]; rdep != nil { 880 rdeps[rdepID] = rdep 881 } 882 } 883 } 884 885 return rdeps, nil 886 } 887 888 // -- Active package tracking -- 889 // 890 // We say a package is "active" if any of its files are open. 891 // This is an optimization: the "active" concept is an 892 // implementation detail of the cache and is not exposed 893 // in the source or Snapshot API. 894 // After type-checking we keep active packages in memory. 895 // The activePackages persistent map does bookkeeping for 896 // the set of active packages. 897 898 // getActivePackage returns a the memoized active package for id, if it exists. 899 // If id is not active or has not yet been type-checked, it returns nil. 900 func (s *Snapshot) getActivePackage(id PackageID) *Package { 901 s.mu.Lock() 902 defer s.mu.Unlock() 903 904 if value, ok := s.activePackages.Get(id); ok { 905 return value 906 } 907 return nil 908 } 909 910 // setActivePackage checks if pkg is active, and if so either records it in 911 // the active packages map or returns the existing memoized active package for id. 912 func (s *Snapshot) setActivePackage(id PackageID, pkg *Package) { 913 s.mu.Lock() 914 defer s.mu.Unlock() 915 916 if _, ok := s.activePackages.Get(id); ok { 917 return // already memoized 918 } 919 920 if containsOpenFileLocked(s, pkg.Metadata()) { 921 s.activePackages.Set(id, pkg, nil) 922 } else { 923 s.activePackages.Set(id, (*Package)(nil), nil) // remember that pkg is not open 924 } 925 } 926 927 func (s *Snapshot) resetActivePackagesLocked() { 928 s.activePackages.Destroy() 929 s.activePackages = new(persistent.Map[PackageID, *Package]) 930 } 931 932 // See Session.FileWatchingGlobPatterns for a description of gopls' file 933 // watching heuristic. 934 func (s *Snapshot) fileWatchingGlobPatterns() map[protocol.RelativePattern]unit { 935 // Always watch files that may change the view definition. 936 patterns := make(map[protocol.RelativePattern]unit) 937 938 // If GOWORK is outside the folder, ensure we are watching it. 939 if s.view.gowork != "" && !s.view.folder.Dir.Encloses(s.view.gowork) { 940 workPattern := protocol.RelativePattern{ 941 BaseURI: s.view.gowork.Dir(), 942 Pattern: path.Base(string(s.view.gowork)), 943 } 944 patterns[workPattern] = unit{} 945 } 946 947 extensions := "go,mod,sum,work" 948 for _, ext := range s.Options().TemplateExtensions { 949 extensions += "," + ext 950 } 951 watchGoFiles := fmt.Sprintf("**/*.{%s}", extensions) 952 953 var dirs []string 954 if s.view.moduleMode() { 955 if s.view.typ == GoWorkView { 956 workVendorDir := filepath.Join(s.view.gowork.Dir().Path(), "vendor") 957 workVendorURI := protocol.URIFromPath(workVendorDir) 958 patterns[protocol.RelativePattern{BaseURI: workVendorURI, Pattern: watchGoFiles}] = unit{} 959 } 960 961 // In module mode, watch directories containing active modules, and collect 962 // these dirs for later filtering the set of known directories. 963 // 964 // The assumption is that the user is not actively editing non-workspace 965 // modules, so don't pay the price of file watching. 966 for modFile := range s.view.workspaceModFiles { 967 dir := filepath.Dir(modFile.Path()) 968 dirs = append(dirs, dir) 969 970 // TODO(golang/go#64724): thoroughly test these patterns, particularly on 971 // on Windows. 972 // 973 // Note that glob patterns should use '/' on Windows: 974 // https://code.visualstudio.com/docs/editor/glob-patterns 975 patterns[protocol.RelativePattern{BaseURI: modFile.Dir(), Pattern: watchGoFiles}] = unit{} 976 } 977 } else { 978 // In non-module modes (GOPATH or AdHoc), we just watch the workspace root. 979 dirs = []string{s.view.root.Path()} 980 patterns[protocol.RelativePattern{Pattern: watchGoFiles}] = unit{} 981 } 982 983 if s.watchSubdirs() { 984 // Some clients (e.g. VS Code) do not send notifications for changes to 985 // directories that contain Go code (golang/go#42348). To handle this, 986 // explicitly watch all of the directories in the workspace. We find them 987 // by adding the directories of every file in the snapshot's workspace 988 // directories. There may be thousands of patterns, each a single 989 // directory. 990 // 991 // We compute this set by looking at files that we've previously observed. 992 // This may miss changed to directories that we haven't observed, but that 993 // shouldn't matter as there is nothing to invalidate (if a directory falls 994 // in forest, etc). 995 // 996 // (A previous iteration created a single glob pattern holding a union of 997 // all the directories, but this was found to cause VS Code to get stuck 998 // for several minutes after a buffer was saved twice in a workspace that 999 // had >8000 watched directories.) 1000 // 1001 // Some clients (notably coc.nvim, which uses watchman for globs) perform 1002 // poorly with a large list of individual directories. 1003 s.addKnownSubdirs(patterns, dirs) 1004 } 1005 1006 return patterns 1007 } 1008 1009 func (s *Snapshot) addKnownSubdirs(patterns map[protocol.RelativePattern]unit, wsDirs []string) { 1010 s.mu.Lock() 1011 defer s.mu.Unlock() 1012 1013 s.files.getDirs().Range(func(dir string) { 1014 for _, wsDir := range wsDirs { 1015 if pathutil.InDir(wsDir, dir) { 1016 patterns[protocol.RelativePattern{Pattern: filepath.ToSlash(dir)}] = unit{} 1017 } 1018 } 1019 }) 1020 } 1021 1022 // watchSubdirs reports whether gopls should request separate file watchers for 1023 // each relevant subdirectory. This is necessary only for clients (namely VS 1024 // Code) that do not send notifications for individual files in a directory 1025 // when the entire directory is deleted. 1026 func (s *Snapshot) watchSubdirs() bool { 1027 switch p := s.Options().SubdirWatchPatterns; p { 1028 case settings.SubdirWatchPatternsOn: 1029 return true 1030 case settings.SubdirWatchPatternsOff: 1031 return false 1032 case settings.SubdirWatchPatternsAuto: 1033 // See the documentation of InternalOptions.SubdirWatchPatterns for an 1034 // explanation of why VS Code gets a different default value here. 1035 // 1036 // Unfortunately, there is no authoritative list of client names, nor any 1037 // requirements that client names do not change. We should update the VS 1038 // Code extension to set a default value of "subdirWatchPatterns" to "on", 1039 // so that this workaround is only temporary. 1040 if s.Options().ClientInfo != nil && s.Options().ClientInfo.Name == "Visual Studio Code" { 1041 return true 1042 } 1043 return false 1044 default: 1045 bug.Reportf("invalid subdirWatchPatterns: %q", p) 1046 return false 1047 } 1048 } 1049 1050 // filesInDir returns all files observed by the snapshot that are contained in 1051 // a directory with the provided URI. 1052 func (s *Snapshot) filesInDir(uri protocol.DocumentURI) []protocol.DocumentURI { 1053 s.mu.Lock() 1054 defer s.mu.Unlock() 1055 1056 dir := uri.Path() 1057 if !s.files.getDirs().Contains(dir) { 1058 return nil 1059 } 1060 var files []protocol.DocumentURI 1061 s.files.foreach(func(uri protocol.DocumentURI, _ file.Handle) { 1062 if pathutil.InDir(dir, uri.Path()) { 1063 files = append(files, uri) 1064 } 1065 }) 1066 return files 1067 } 1068 1069 // WorkspaceMetadata returns a new, unordered slice containing 1070 // metadata for all ordinary and test packages (but not 1071 // intermediate test variants) in the workspace. 1072 // 1073 // The workspace is the set of modules typically defined by a 1074 // go.work file. It is not transitively closed: for example, 1075 // the standard library is not usually part of the workspace 1076 // even though every module in the workspace depends on it. 1077 // 1078 // Operations that must inspect all the dependencies of the 1079 // workspace packages should instead use AllMetadata. 1080 func (s *Snapshot) WorkspaceMetadata(ctx context.Context) ([]*metadata.Package, error) { 1081 if err := s.awaitLoaded(ctx); err != nil { 1082 return nil, err 1083 } 1084 1085 s.mu.Lock() 1086 defer s.mu.Unlock() 1087 1088 meta := make([]*metadata.Package, 0, s.workspacePackages.Len()) 1089 s.workspacePackages.Range(func(id PackageID, _ PackagePath) { 1090 meta = append(meta, s.meta.Packages[id]) 1091 }) 1092 return meta, nil 1093 } 1094 1095 // isWorkspacePackage reports whether the given package ID refers to a 1096 // workspace package for the snapshot. 1097 func (s *Snapshot) isWorkspacePackage(id PackageID) bool { 1098 s.mu.Lock() 1099 defer s.mu.Unlock() 1100 _, ok := s.workspacePackages.Value(id) 1101 return ok 1102 } 1103 1104 // Symbols extracts and returns symbol information for every file contained in 1105 // a loaded package. It awaits snapshot loading. 1106 // 1107 // If workspaceOnly is set, this only includes symbols from files in a 1108 // workspace package. Otherwise, it returns symbols from all loaded packages. 1109 // 1110 // TODO(rfindley): move to symbols.go. 1111 func (s *Snapshot) Symbols(ctx context.Context, workspaceOnly bool) (map[protocol.DocumentURI][]Symbol, error) { 1112 var ( 1113 meta []*metadata.Package 1114 err error 1115 ) 1116 if workspaceOnly { 1117 meta, err = s.WorkspaceMetadata(ctx) 1118 } else { 1119 meta, err = s.AllMetadata(ctx) 1120 } 1121 if err != nil { 1122 return nil, fmt.Errorf("loading metadata: %v", err) 1123 } 1124 1125 goFiles := make(map[protocol.DocumentURI]struct{}) 1126 for _, mp := range meta { 1127 for _, uri := range mp.GoFiles { 1128 goFiles[uri] = struct{}{} 1129 } 1130 for _, uri := range mp.CompiledGoFiles { 1131 goFiles[uri] = struct{}{} 1132 } 1133 } 1134 1135 // Symbolize them in parallel. 1136 var ( 1137 group errgroup.Group 1138 nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU 1139 resultMu sync.Mutex 1140 result = make(map[protocol.DocumentURI][]Symbol) 1141 ) 1142 group.SetLimit(nprocs) 1143 for uri := range goFiles { 1144 uri := uri 1145 group.Go(func() error { 1146 symbols, err := s.symbolize(ctx, uri) 1147 if err != nil { 1148 return err 1149 } 1150 resultMu.Lock() 1151 result[uri] = symbols 1152 resultMu.Unlock() 1153 return nil 1154 }) 1155 } 1156 // Keep going on errors, but log the first failure. 1157 // Partial results are better than no symbol results. 1158 if err := group.Wait(); err != nil { 1159 event.Error(ctx, "getting snapshot symbols", err) 1160 } 1161 return result, nil 1162 } 1163 1164 // AllMetadata returns a new unordered array of metadata for 1165 // all packages known to this snapshot, which includes the 1166 // packages of all workspace modules plus their transitive 1167 // import dependencies. 1168 // 1169 // It may also contain ad-hoc packages for standalone files. 1170 // It includes all test variants. 1171 // 1172 // TODO(rfindley): Replace this with s.MetadataGraph(). 1173 func (s *Snapshot) AllMetadata(ctx context.Context) ([]*metadata.Package, error) { 1174 if err := s.awaitLoaded(ctx); err != nil { 1175 return nil, err 1176 } 1177 1178 g := s.MetadataGraph() 1179 1180 meta := make([]*metadata.Package, 0, len(g.Packages)) 1181 for _, mp := range g.Packages { 1182 meta = append(meta, mp) 1183 } 1184 return meta, nil 1185 } 1186 1187 // GoModForFile returns the URI of the go.mod file for the given URI. 1188 // 1189 // TODO(rfindley): clarify that this is only active modules. Or update to just 1190 // use findRootPattern. 1191 func (s *Snapshot) GoModForFile(uri protocol.DocumentURI) protocol.DocumentURI { 1192 return moduleForURI(s.view.workspaceModFiles, uri) 1193 } 1194 1195 func moduleForURI(modFiles map[protocol.DocumentURI]struct{}, uri protocol.DocumentURI) protocol.DocumentURI { 1196 var match protocol.DocumentURI 1197 for modURI := range modFiles { 1198 if !modURI.Dir().Encloses(uri) { 1199 continue 1200 } 1201 if len(modURI) > len(match) { 1202 match = modURI 1203 } 1204 } 1205 return match 1206 } 1207 1208 // nearestModFile finds the nearest go.mod file contained in the directory 1209 // containing uri, or a parent of that directory. 1210 // 1211 // The given uri must be a file, not a directory. 1212 func nearestModFile(ctx context.Context, uri protocol.DocumentURI, fs file.Source) (protocol.DocumentURI, error) { 1213 dir := filepath.Dir(uri.Path()) 1214 return findRootPattern(ctx, protocol.URIFromPath(dir), "go.mod", fs) 1215 } 1216 1217 // Metadata returns the metadata for the specified package, 1218 // or nil if it was not found. 1219 func (s *Snapshot) Metadata(id PackageID) *metadata.Package { 1220 s.mu.Lock() 1221 defer s.mu.Unlock() 1222 return s.meta.Packages[id] 1223 } 1224 1225 // clearShouldLoad clears package IDs that no longer need to be reloaded after 1226 // scopes has been loaded. 1227 func (s *Snapshot) clearShouldLoad(scopes ...loadScope) { 1228 s.mu.Lock() 1229 defer s.mu.Unlock() 1230 1231 for _, scope := range scopes { 1232 switch scope := scope.(type) { 1233 case packageLoadScope: 1234 scopePath := PackagePath(scope) 1235 var toDelete []PackageID 1236 s.shouldLoad.Range(func(id PackageID, pkgPaths []PackagePath) { 1237 for _, pkgPath := range pkgPaths { 1238 if pkgPath == scopePath { 1239 toDelete = append(toDelete, id) 1240 } 1241 } 1242 }) 1243 for _, id := range toDelete { 1244 s.shouldLoad.Delete(id) 1245 } 1246 case fileLoadScope: 1247 uri := protocol.DocumentURI(scope) 1248 ids := s.meta.IDs[uri] 1249 for _, id := range ids { 1250 s.shouldLoad.Delete(id) 1251 } 1252 } 1253 } 1254 } 1255 1256 // FindFile returns the FileHandle for the given URI, if it is already 1257 // in the given snapshot. 1258 // TODO(adonovan): delete this operation; use ReadFile instead. 1259 func (s *Snapshot) FindFile(uri protocol.DocumentURI) file.Handle { 1260 s.mu.Lock() 1261 defer s.mu.Unlock() 1262 1263 result, _ := s.files.get(uri) 1264 return result 1265 } 1266 1267 // ReadFile returns a File for the given URI. If the file is unknown it is added 1268 // to the managed set. 1269 // 1270 // ReadFile succeeds even if the file does not exist. A non-nil error return 1271 // indicates some type of internal error, for example if ctx is cancelled. 1272 func (s *Snapshot) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) { 1273 s.mu.Lock() 1274 defer s.mu.Unlock() 1275 1276 return lockedSnapshot{s}.ReadFile(ctx, uri) 1277 } 1278 1279 // lockedSnapshot implements the file.Source interface, while holding s.mu. 1280 // 1281 // TODO(rfindley): This unfortunate type had been eliminated, but it had to be 1282 // restored to fix golang/go#65801. We should endeavor to remove it again. 1283 type lockedSnapshot struct { 1284 s *Snapshot 1285 } 1286 1287 func (s lockedSnapshot) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) { 1288 fh, ok := s.s.files.get(uri) 1289 if !ok { 1290 var err error 1291 fh, err = s.s.view.fs.ReadFile(ctx, uri) 1292 if err != nil { 1293 return nil, err 1294 } 1295 s.s.files.set(uri, fh) 1296 } 1297 return fh, nil 1298 } 1299 1300 // preloadFiles delegates to the view FileSource to read the requested uris in 1301 // parallel, without holding the snapshot lock. 1302 func (s *Snapshot) preloadFiles(ctx context.Context, uris []protocol.DocumentURI) { 1303 files := make([]file.Handle, len(uris)) 1304 var wg sync.WaitGroup 1305 iolimit := make(chan struct{}, 20) // I/O concurrency limiting semaphore 1306 for i, uri := range uris { 1307 wg.Add(1) 1308 iolimit <- struct{}{} 1309 go func(i int, uri protocol.DocumentURI) { 1310 defer wg.Done() 1311 fh, err := s.view.fs.ReadFile(ctx, uri) 1312 <-iolimit 1313 if err != nil && ctx.Err() == nil { 1314 event.Error(ctx, fmt.Sprintf("reading %s", uri), err) 1315 return 1316 } 1317 files[i] = fh 1318 }(i, uri) 1319 } 1320 wg.Wait() 1321 1322 s.mu.Lock() 1323 defer s.mu.Unlock() 1324 1325 for i, fh := range files { 1326 if fh == nil { 1327 continue // error logged above 1328 } 1329 uri := uris[i] 1330 if _, ok := s.files.get(uri); !ok { 1331 s.files.set(uri, fh) 1332 } 1333 } 1334 } 1335 1336 // IsOpen returns whether the editor currently has a file open. 1337 func (s *Snapshot) IsOpen(uri protocol.DocumentURI) bool { 1338 s.mu.Lock() 1339 defer s.mu.Unlock() 1340 1341 fh, _ := s.files.get(uri) 1342 _, open := fh.(*overlay) 1343 return open 1344 } 1345 1346 // MetadataGraph returns the current metadata graph for the Snapshot. 1347 func (s *Snapshot) MetadataGraph() *metadata.Graph { 1348 s.mu.Lock() 1349 defer s.mu.Unlock() 1350 return s.meta 1351 } 1352 1353 // InitializationError returns the last error from initialization. 1354 func (s *Snapshot) InitializationError() *InitializationError { 1355 s.mu.Lock() 1356 defer s.mu.Unlock() 1357 return s.initialErr 1358 } 1359 1360 // awaitLoaded awaits initialization and package reloading, and returns 1361 // ctx.Err(). 1362 func (s *Snapshot) awaitLoaded(ctx context.Context) error { 1363 // Do not return results until the snapshot's view has been initialized. 1364 s.AwaitInitialized(ctx) 1365 s.reloadWorkspace(ctx) 1366 return ctx.Err() 1367 } 1368 1369 // AwaitInitialized waits until the snapshot's view is initialized. 1370 func (s *Snapshot) AwaitInitialized(ctx context.Context) { 1371 select { 1372 case <-ctx.Done(): 1373 return 1374 case <-s.view.initialWorkspaceLoad: 1375 } 1376 // We typically prefer to run something as intensive as the IWL without 1377 // blocking. I'm not sure if there is a way to do that here. 1378 s.initialize(ctx, false) 1379 } 1380 1381 // reloadWorkspace reloads the metadata for all invalidated workspace packages. 1382 func (s *Snapshot) reloadWorkspace(ctx context.Context) { 1383 var scopes []loadScope 1384 var seen map[PackagePath]bool 1385 s.mu.Lock() 1386 s.shouldLoad.Range(func(_ PackageID, pkgPaths []PackagePath) { 1387 for _, pkgPath := range pkgPaths { 1388 if seen == nil { 1389 seen = make(map[PackagePath]bool) 1390 } 1391 if seen[pkgPath] { 1392 continue 1393 } 1394 seen[pkgPath] = true 1395 scopes = append(scopes, packageLoadScope(pkgPath)) 1396 } 1397 }) 1398 s.mu.Unlock() 1399 1400 if len(scopes) == 0 { 1401 return 1402 } 1403 1404 // For an ad-hoc view, we cannot reload by package path. Just reload the view. 1405 if s.view.typ == AdHocView { 1406 scopes = []loadScope{viewLoadScope{}} 1407 } 1408 1409 err := s.load(ctx, false, scopes...) 1410 1411 // Unless the context was canceled, set "shouldLoad" to false for all 1412 // of the metadata we attempted to load. 1413 if !errors.Is(err, context.Canceled) { 1414 s.clearShouldLoad(scopes...) 1415 if err != nil { 1416 event.Error(ctx, "reloading workspace", err, s.Labels()...) 1417 } 1418 } 1419 } 1420 1421 func (s *Snapshot) orphanedFileDiagnostics(ctx context.Context, overlays []*overlay) ([]*Diagnostic, error) { 1422 if err := s.awaitLoaded(ctx); err != nil { 1423 return nil, err 1424 } 1425 1426 var diagnostics []*Diagnostic 1427 var orphaned []*overlay 1428 searchOverlays: 1429 for _, o := range overlays { 1430 uri := o.URI() 1431 if s.IsBuiltin(uri) || s.FileKind(o) != file.Go { 1432 continue 1433 } 1434 mps, err := s.MetadataForFile(ctx, uri) 1435 if err != nil { 1436 return nil, err 1437 } 1438 for _, mp := range mps { 1439 if !metadata.IsCommandLineArguments(mp.ID) || mp.Standalone { 1440 continue searchOverlays 1441 } 1442 } 1443 metadata.RemoveIntermediateTestVariants(&mps) 1444 1445 // With zero-config gopls (golang/go#57979), orphaned file diagnostics 1446 // include diagnostics for orphaned files -- not just diagnostics relating 1447 // to the reason the files are opened. 1448 // 1449 // This is because orphaned files are never considered part of a workspace 1450 // package: if they are loaded by a view, that view is arbitrary, and they 1451 // may be loaded by multiple views. If they were to be diagnosed by 1452 // multiple views, their diagnostics may become inconsistent. 1453 if len(mps) > 0 { 1454 diags, err := s.PackageDiagnostics(ctx, mps[0].ID) 1455 if err != nil { 1456 return nil, err 1457 } 1458 diagnostics = append(diagnostics, diags[uri]...) 1459 } 1460 orphaned = append(orphaned, o) 1461 } 1462 1463 if len(orphaned) == 0 { 1464 return nil, nil 1465 } 1466 1467 loadedModFiles := make(map[protocol.DocumentURI]struct{}) // all mod files, including dependencies 1468 ignoredFiles := make(map[protocol.DocumentURI]bool) // files reported in packages.Package.IgnoredFiles 1469 1470 g := s.MetadataGraph() 1471 for _, meta := range g.Packages { 1472 if meta.Module != nil && meta.Module.GoMod != "" { 1473 gomod := protocol.URIFromPath(meta.Module.GoMod) 1474 loadedModFiles[gomod] = struct{}{} 1475 } 1476 for _, ignored := range meta.IgnoredFiles { 1477 ignoredFiles[ignored] = true 1478 } 1479 } 1480 1481 initialErr := s.InitializationError() 1482 1483 for _, fh := range orphaned { 1484 pgf, rng, ok := orphanedFileDiagnosticRange(ctx, s.view.parseCache, fh) 1485 if !ok { 1486 continue // e.g. cancellation or parse error 1487 } 1488 1489 var ( 1490 msg string // if non-empty, report a diagnostic with this message 1491 suggestedFixes []SuggestedFix // associated fixes, if any 1492 ) 1493 if initialErr != nil { 1494 msg = fmt.Sprintf("initialization failed: %v", initialErr.MainError) 1495 } else if goMod, err := nearestModFile(ctx, fh.URI(), s); err == nil && goMod != "" { 1496 // If we have a relevant go.mod file, check whether the file is orphaned 1497 // due to its go.mod file being inactive. We could also offer a 1498 // prescriptive diagnostic in the case that there is no go.mod file, but it 1499 // is harder to be precise in that case, and less important. 1500 if _, ok := loadedModFiles[goMod]; !ok { 1501 modDir := filepath.Dir(goMod.Path()) 1502 viewDir := s.view.folder.Dir.Path() 1503 1504 // When the module is underneath the view dir, we offer 1505 // "use all modules" quick-fixes. 1506 inDir := pathutil.InDir(viewDir, modDir) 1507 1508 if rel, err := filepath.Rel(viewDir, modDir); err == nil { 1509 modDir = rel 1510 } 1511 1512 var fix string 1513 if s.view.folder.Env.GoVersion >= 18 { 1514 if s.view.gowork != "" { 1515 fix = fmt.Sprintf("To fix this problem, you can add this module to your go.work file (%s)", s.view.gowork) 1516 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use`", command.RunGoWorkArgs{ 1517 ViewID: s.view.ID(), 1518 Args: []string{"use", modDir}, 1519 }); err == nil { 1520 suggestedFixes = append(suggestedFixes, SuggestedFix{ 1521 Title: "Use this module in your go.work file", 1522 Command: &cmd, 1523 ActionKind: protocol.QuickFix, 1524 }) 1525 } 1526 1527 if inDir { 1528 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work use -r`", command.RunGoWorkArgs{ 1529 ViewID: s.view.ID(), 1530 Args: []string{"use", "-r", "."}, 1531 }); err == nil { 1532 suggestedFixes = append(suggestedFixes, SuggestedFix{ 1533 Title: "Use all modules in your workspace", 1534 Command: &cmd, 1535 ActionKind: protocol.QuickFix, 1536 }) 1537 } 1538 } 1539 } else { 1540 fix = "To fix this problem, you can add a go.work file that uses this directory." 1541 1542 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use`", command.RunGoWorkArgs{ 1543 ViewID: s.view.ID(), 1544 InitFirst: true, 1545 Args: []string{"use", modDir}, 1546 }); err == nil { 1547 suggestedFixes = []SuggestedFix{ 1548 { 1549 Title: "Add a go.work file using this module", 1550 Command: &cmd, 1551 ActionKind: protocol.QuickFix, 1552 }, 1553 } 1554 } 1555 1556 if inDir { 1557 if cmd, err := command.NewRunGoWorkCommandCommand("Run `go work init && go work use -r`", command.RunGoWorkArgs{ 1558 ViewID: s.view.ID(), 1559 InitFirst: true, 1560 Args: []string{"use", "-r", "."}, 1561 }); err == nil { 1562 suggestedFixes = append(suggestedFixes, SuggestedFix{ 1563 Title: "Add a go.work file using all modules in your workspace", 1564 Command: &cmd, 1565 ActionKind: protocol.QuickFix, 1566 }) 1567 } 1568 } 1569 } 1570 } else { 1571 fix = `To work with multiple modules simultaneously, please upgrade to Go 1.18 or 1572 later, reinstall gopls, and use a go.work file.` 1573 } 1574 1575 msg = fmt.Sprintf(`This file is within module %q, which is not included in your workspace. 1576 %s 1577 See the documentation for more information on setting up your workspace: 1578 https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`, modDir, fix) 1579 } 1580 } 1581 1582 if msg == "" { 1583 if ignoredFiles[fh.URI()] { 1584 // TODO(rfindley): use the constraint package to check if the file 1585 // _actually_ satisfies the current build context. 1586 hasConstraint := false 1587 walkConstraints(pgf.File, func(constraint.Expr) bool { 1588 hasConstraint = true 1589 return false 1590 }) 1591 var fix string 1592 if hasConstraint { 1593 fix = `This file may be excluded due to its build tags; try adding "-tags=<build tag>" to your gopls "buildFlags" configuration 1594 See the documentation for more information on working with build tags: 1595 https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags-string.` 1596 } else if strings.Contains(filepath.Base(fh.URI().Path()), "_") { 1597 fix = `This file may be excluded due to its GOOS/GOARCH, or other build constraints.` 1598 } else { 1599 fix = `This file is ignored by your gopls build.` // we don't know why 1600 } 1601 msg = fmt.Sprintf("No packages found for open file %s.\n%s", fh.URI().Path(), fix) 1602 } else { 1603 // Fall back: we're not sure why the file is orphaned. 1604 // TODO(rfindley): we could do better here, diagnosing the lack of a 1605 // go.mod file and malformed file names (see the perc%ent marker test). 1606 msg = fmt.Sprintf("No packages found for open file %s.", fh.URI().Path()) 1607 } 1608 } 1609 1610 if msg != "" { 1611 d := &Diagnostic{ 1612 URI: fh.URI(), 1613 Range: rng, 1614 Severity: protocol.SeverityWarning, 1615 Source: ListError, 1616 Message: msg, 1617 SuggestedFixes: suggestedFixes, 1618 } 1619 if ok := bundleQuickFixes(d); !ok { 1620 bug.Reportf("failed to bundle quick fixes for %v", d) 1621 } 1622 // Only report diagnostics if we detect an actual exclusion. 1623 diagnostics = append(diagnostics, d) 1624 } 1625 } 1626 return diagnostics, nil 1627 } 1628 1629 // orphanedFileDiagnosticRange returns the position to use for orphaned file diagnostics. 1630 // We only warn about an orphaned file if it is well-formed enough to actually 1631 // be part of a package. Otherwise, we need more information. 1632 func orphanedFileDiagnosticRange(ctx context.Context, cache *parseCache, fh file.Handle) (*ParsedGoFile, protocol.Range, bool) { 1633 pgfs, err := cache.parseFiles(ctx, token.NewFileSet(), ParseHeader, false, fh) 1634 if err != nil { 1635 return nil, protocol.Range{}, false 1636 } 1637 pgf := pgfs[0] 1638 if !pgf.File.Name.Pos().IsValid() { 1639 return nil, protocol.Range{}, false 1640 } 1641 rng, err := pgf.PosRange(pgf.File.Name.Pos(), pgf.File.Name.End()) 1642 if err != nil { 1643 return nil, protocol.Range{}, false 1644 } 1645 return pgf, rng, true 1646 } 1647 1648 // TODO(golang/go#53756): this function needs to consider more than just the 1649 // absolute URI, for example: 1650 // - the position of /vendor/ with respect to the relevant module root 1651 // - whether or not go.work is in use (as vendoring isn't supported in workspace mode) 1652 // 1653 // Most likely, each call site of inVendor needs to be reconsidered to 1654 // understand and correctly implement the desired behavior. 1655 func inVendor(uri protocol.DocumentURI) bool { 1656 _, after, found := strings.Cut(string(uri), "/vendor/") 1657 // Only subdirectories of /vendor/ are considered vendored 1658 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not). 1659 return found && strings.Contains(after, "/") 1660 } 1661 1662 // clone copies state from the receiver into a new Snapshot, applying the given 1663 // state changes. 1664 // 1665 // The caller of clone must call Snapshot.decref on the returned 1666 // snapshot when they are finished using it. 1667 // 1668 // The resulting bool reports whether the change invalidates any derived 1669 // diagnostics for the snapshot, for example because it invalidates Packages or 1670 // parsed go.mod files. This is used to mark a view as needing diagnosis in the 1671 // server. 1672 // 1673 // TODO(rfindley): long term, it may be better to move responsibility for 1674 // diagnostics into the Snapshot (e.g. a Snapshot.Diagnostics method), at which 1675 // point the Snapshot could be responsible for tracking and forwarding a 1676 // 'viewsToDiagnose' field. As is, this field is instead externalized in the 1677 // server.viewsToDiagnose map. Moving it to the snapshot would entirely 1678 // eliminate any 'relevance' heuristics from Session.DidModifyFiles, but would 1679 // also require more strictness about diagnostic dependencies. For example, 1680 // template.Diagnostics currently re-parses every time: there is no Snapshot 1681 // data responsible for providing these diagnostics. 1682 func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done func()) (*Snapshot, bool) { 1683 changedFiles := changed.Files 1684 ctx, stop := event.Start(ctx, "cache.snapshot.clone") 1685 defer stop() 1686 1687 s.mu.Lock() 1688 defer s.mu.Unlock() 1689 1690 // TODO(rfindley): reorganize this function to make the derivation of 1691 // needsDiagnosis clearer. 1692 needsDiagnosis := len(changed.GCDetails) > 0 || len(changed.ModuleUpgrades) > 0 || len(changed.Vulns) > 0 1693 1694 bgCtx, cancel := context.WithCancel(bgCtx) 1695 result := &Snapshot{ 1696 sequenceID: s.sequenceID + 1, 1697 store: s.store, 1698 refcount: 1, // Snapshots are born referenced. 1699 done: done, 1700 view: s.view, 1701 backgroundCtx: bgCtx, 1702 cancel: cancel, 1703 builtin: s.builtin, 1704 initialized: s.initialized, 1705 initialErr: s.initialErr, 1706 packages: s.packages.Clone(), 1707 activePackages: s.activePackages.Clone(), 1708 files: s.files.clone(changedFiles), 1709 symbolizeHandles: cloneWithout(s.symbolizeHandles, changedFiles, nil), 1710 workspacePackages: s.workspacePackages, 1711 shouldLoad: s.shouldLoad.Clone(), // not cloneWithout: shouldLoad is cleared on loads 1712 unloadableFiles: s.unloadableFiles.Clone(), // not cloneWithout: typing in a file doesn't necessarily make it loadable 1713 parseModHandles: cloneWithout(s.parseModHandles, changedFiles, &needsDiagnosis), 1714 parseWorkHandles: cloneWithout(s.parseWorkHandles, changedFiles, &needsDiagnosis), 1715 modTidyHandles: cloneWithout(s.modTidyHandles, changedFiles, &needsDiagnosis), 1716 modWhyHandles: cloneWithout(s.modWhyHandles, changedFiles, &needsDiagnosis), 1717 modVulnHandles: cloneWithout(s.modVulnHandles, changedFiles, &needsDiagnosis), 1718 importGraph: s.importGraph, 1719 pkgIndex: s.pkgIndex, 1720 moduleUpgrades: cloneWith(s.moduleUpgrades, changed.ModuleUpgrades), 1721 vulns: cloneWith(s.vulns, changed.Vulns), 1722 } 1723 1724 // Compute the new set of packages for which we want gc details, after 1725 // applying changed.GCDetails. 1726 if len(s.gcOptimizationDetails) > 0 || len(changed.GCDetails) > 0 { 1727 newGCDetails := make(map[metadata.PackageID]unit) 1728 for id := range s.gcOptimizationDetails { 1729 if _, ok := changed.GCDetails[id]; !ok { 1730 newGCDetails[id] = unit{} // no change 1731 } 1732 } 1733 for id, want := range changed.GCDetails { 1734 if want { 1735 newGCDetails[id] = unit{} 1736 } 1737 } 1738 if len(newGCDetails) > 0 { 1739 result.gcOptimizationDetails = newGCDetails 1740 } 1741 } 1742 1743 reinit := false 1744 1745 // Changes to vendor tree may require reinitialization, 1746 // either because of an initialization error 1747 // (e.g. "inconsistent vendoring detected"), or because 1748 // one or more modules may have moved into or out of the 1749 // vendor tree after 'go mod vendor' or 'rm -fr vendor/'. 1750 // 1751 // In this case, we consider the actual modification to see if was a creation 1752 // or deletion. 1753 // 1754 // TODO(rfindley): revisit the location of this check. 1755 for _, mod := range changed.Modifications { 1756 if inVendor(mod.URI) && (mod.Action == file.Create || mod.Action == file.Delete) || 1757 strings.HasSuffix(string(mod.URI), "/vendor/modules.txt") { 1758 1759 reinit = true 1760 break 1761 } 1762 } 1763 1764 // Collect observed file handles for changed URIs from the old snapshot, if 1765 // they exist. Importantly, we don't call ReadFile here: consider the case 1766 // where a file is added on disk; we don't want to read the newly added file 1767 // into the old snapshot, as that will break our change detection below. 1768 // 1769 // TODO(rfindley): it may be more accurate to rely on the modification type 1770 // here, similarly to what we do for vendored files above. If we happened not 1771 // to have read a file in the previous snapshot, that's not the same as it 1772 // actually being created. 1773 oldFiles := make(map[protocol.DocumentURI]file.Handle) 1774 for uri := range changedFiles { 1775 if fh, ok := s.files.get(uri); ok { 1776 oldFiles[uri] = fh 1777 } 1778 } 1779 // changedOnDisk determines if the new file handle may have changed on disk. 1780 // It over-approximates, returning true if the new file is saved and either 1781 // the old file wasn't saved, or the on-disk contents changed. 1782 // 1783 // oldFH may be nil. 1784 changedOnDisk := func(oldFH, newFH file.Handle) bool { 1785 if !newFH.SameContentsOnDisk() { 1786 return false 1787 } 1788 if oe, ne := (oldFH != nil && fileExists(oldFH)), fileExists(newFH); !oe || !ne { 1789 return oe != ne 1790 } 1791 return !oldFH.SameContentsOnDisk() || oldFH.Identity() != newFH.Identity() 1792 } 1793 1794 // Reinitialize if any workspace mod file has changed on disk. 1795 for uri, newFH := range changedFiles { 1796 if _, ok := result.view.workspaceModFiles[uri]; ok && changedOnDisk(oldFiles[uri], newFH) { 1797 reinit = true 1798 } 1799 } 1800 1801 // Finally, process sumfile changes that may affect loading. 1802 for uri, newFH := range changedFiles { 1803 if !changedOnDisk(oldFiles[uri], newFH) { 1804 continue // like with go.mod files, we only reinit when things change on disk 1805 } 1806 dir, base := filepath.Split(uri.Path()) 1807 if base == "go.work.sum" && s.view.typ == GoWorkView && dir == filepath.Dir(s.view.gowork.Path()) { 1808 reinit = true 1809 } 1810 if base == "go.sum" { 1811 modURI := protocol.URIFromPath(filepath.Join(dir, "go.mod")) 1812 if _, active := result.view.workspaceModFiles[modURI]; active { 1813 reinit = true 1814 } 1815 } 1816 } 1817 1818 // The snapshot should be initialized if either s was uninitialized, or we've 1819 // detected a change that triggers reinitialization. 1820 if reinit { 1821 result.initialized = false 1822 needsDiagnosis = true 1823 } 1824 1825 // directIDs keeps track of package IDs that have directly changed. 1826 // Note: this is not a set, it's a map from id to invalidateMetadata. 1827 directIDs := map[PackageID]bool{} 1828 1829 // Invalidate all package metadata if the workspace module has changed. 1830 if reinit { 1831 for k := range s.meta.Packages { 1832 // TODO(rfindley): this seems brittle; can we just start over? 1833 directIDs[k] = true 1834 } 1835 } 1836 1837 // Compute invalidations based on file changes. 1838 anyImportDeleted := false // import deletions can resolve cycles 1839 anyFileOpenedOrClosed := false // opened files affect workspace packages 1840 anyFileAdded := false // adding a file can resolve missing dependencies 1841 1842 for uri, newFH := range changedFiles { 1843 // The original FileHandle for this URI is cached on the snapshot. 1844 oldFH := oldFiles[uri] // may be nil 1845 _, oldOpen := oldFH.(*overlay) 1846 _, newOpen := newFH.(*overlay) 1847 1848 anyFileOpenedOrClosed = anyFileOpenedOrClosed || (oldOpen != newOpen) 1849 anyFileAdded = anyFileAdded || (oldFH == nil || !fileExists(oldFH)) && fileExists(newFH) 1850 1851 // If uri is a Go file, check if it has changed in a way that would 1852 // invalidate metadata. Note that we can't use s.view.FileKind here, 1853 // because the file type that matters is not what the *client* tells us, 1854 // but what the Go command sees. 1855 var invalidateMetadata, pkgFileChanged, importDeleted bool 1856 if strings.HasSuffix(uri.Path(), ".go") { 1857 invalidateMetadata, pkgFileChanged, importDeleted = metadataChanges(ctx, s, oldFH, newFH) 1858 } 1859 if invalidateMetadata { 1860 // If this is a metadata-affecting change, perhaps a reload will succeed. 1861 result.unloadableFiles.Remove(uri) 1862 needsDiagnosis = true 1863 } 1864 1865 invalidateMetadata = invalidateMetadata || reinit 1866 anyImportDeleted = anyImportDeleted || importDeleted 1867 1868 // Mark all of the package IDs containing the given file. 1869 filePackageIDs := invalidatedPackageIDs(uri, s.meta.IDs, pkgFileChanged) 1870 for id := range filePackageIDs { 1871 directIDs[id] = directIDs[id] || invalidateMetadata // may insert 'false' 1872 } 1873 1874 // Invalidate the previous modTidyHandle if any of the files have been 1875 // saved or if any of the metadata has been invalidated. 1876 // 1877 // TODO(rfindley): this seems like too-aggressive invalidation of mod 1878 // results. We should instead thread through overlays to the Go command 1879 // invocation and only run this if invalidateMetadata (and perhaps then 1880 // still do it less frequently). 1881 if invalidateMetadata || fileWasSaved(oldFH, newFH) { 1882 // Only invalidate mod tidy results for the most relevant modfile in the 1883 // workspace. This is a potentially lossy optimization for workspaces 1884 // with many modules (such as google-cloud-go, which has 145 modules as 1885 // of writing). 1886 // 1887 // While it is theoretically possible that a change in workspace module A 1888 // could affect the mod-tidiness of workspace module B (if B transitively 1889 // requires A), such changes are probably unlikely and not worth the 1890 // penalty of re-running go mod tidy for everything. Note that mod tidy 1891 // ignores GOWORK, so the two modules would have to be related by a chain 1892 // of replace directives. 1893 // 1894 // We could improve accuracy by inspecting replace directives, using 1895 // overlays in go mod tidy, and/or checking for metadata changes from the 1896 // on-disk content. 1897 // 1898 // Note that we iterate the modTidyHandles map here, rather than e.g. 1899 // using nearestModFile, because we don't have access to an accurate 1900 // FileSource at this point in the snapshot clone. 1901 const onlyInvalidateMostRelevant = true 1902 if onlyInvalidateMostRelevant { 1903 deleteMostRelevantModFile(result.modTidyHandles, uri) 1904 } else { 1905 result.modTidyHandles.Clear() 1906 } 1907 1908 // TODO(rfindley): should we apply the above heuristic to mod vuln or mod 1909 // why handles as well? 1910 // 1911 // TODO(rfindley): no tests fail if I delete the line below. 1912 result.modWhyHandles.Clear() 1913 result.modVulnHandles.Clear() 1914 } 1915 } 1916 1917 // Deleting an import can cause list errors due to import cycles to be 1918 // resolved. The best we can do without parsing the list error message is to 1919 // hope that list errors may have been resolved by a deleted import. 1920 // 1921 // We could do better by parsing the list error message. We already do this 1922 // to assign a better range to the list error, but for such critical 1923 // functionality as metadata, it's better to be conservative until it proves 1924 // impractical. 1925 // 1926 // We could also do better by looking at which imports were deleted and 1927 // trying to find cycles they are involved in. This fails when the file goes 1928 // from an unparseable state to a parseable state, as we don't have a 1929 // starting point to compare with. 1930 if anyImportDeleted { 1931 for id, mp := range s.meta.Packages { 1932 if len(mp.Errors) > 0 { 1933 directIDs[id] = true 1934 } 1935 } 1936 } 1937 1938 // Adding a file can resolve missing dependencies from existing packages. 1939 // 1940 // We could be smart here and try to guess which packages may have been 1941 // fixed, but until that proves necessary, just invalidate metadata for any 1942 // package with missing dependencies. 1943 if anyFileAdded { 1944 for id, mp := range s.meta.Packages { 1945 for _, impID := range mp.DepsByImpPath { 1946 if impID == "" { // missing import 1947 directIDs[id] = true 1948 break 1949 } 1950 } 1951 } 1952 } 1953 1954 // Invalidate reverse dependencies too. 1955 // idsToInvalidate keeps track of transitive reverse dependencies. 1956 // If an ID is present in the map, invalidate its types. 1957 // If an ID's value is true, invalidate its metadata too. 1958 idsToInvalidate := map[PackageID]bool{} 1959 var addRevDeps func(PackageID, bool) 1960 addRevDeps = func(id PackageID, invalidateMetadata bool) { 1961 current, seen := idsToInvalidate[id] 1962 newInvalidateMetadata := current || invalidateMetadata 1963 1964 // If we've already seen this ID, and the value of invalidate 1965 // metadata has not changed, we can return early. 1966 if seen && current == newInvalidateMetadata { 1967 return 1968 } 1969 idsToInvalidate[id] = newInvalidateMetadata 1970 for _, rid := range s.meta.ImportedBy[id] { 1971 addRevDeps(rid, invalidateMetadata) 1972 } 1973 } 1974 for id, invalidateMetadata := range directIDs { 1975 addRevDeps(id, invalidateMetadata) 1976 } 1977 1978 // Invalidated package information. 1979 for id, invalidateMetadata := range idsToInvalidate { 1980 if _, ok := directIDs[id]; ok || invalidateMetadata { 1981 if result.packages.Delete(id) { 1982 needsDiagnosis = true 1983 } 1984 } else { 1985 if entry, hit := result.packages.Get(id); hit { 1986 needsDiagnosis = true 1987 ph := entry.clone(false) 1988 result.packages.Set(id, ph, nil) 1989 } 1990 } 1991 if result.activePackages.Delete(id) { 1992 needsDiagnosis = true 1993 } 1994 } 1995 1996 // Compute which metadata updates are required. We only need to invalidate 1997 // packages directly containing the affected file, and only if it changed in 1998 // a relevant way. 1999 metadataUpdates := make(map[PackageID]*metadata.Package) 2000 for id, mp := range s.meta.Packages { 2001 invalidateMetadata := idsToInvalidate[id] 2002 2003 // For metadata that has been newly invalidated, capture package paths 2004 // requiring reloading in the shouldLoad map. 2005 if invalidateMetadata && !metadata.IsCommandLineArguments(mp.ID) { 2006 needsReload := []PackagePath{mp.PkgPath} 2007 if mp.ForTest != "" && mp.ForTest != mp.PkgPath { 2008 // When reloading test variants, always reload their ForTest package as 2009 // well. Otherwise, we may miss test variants in the resulting load. 2010 // 2011 // TODO(rfindley): is this actually sufficient? Is it possible that 2012 // other test variants may be invalidated? Either way, we should 2013 // determine exactly what needs to be reloaded here. 2014 needsReload = append(needsReload, mp.ForTest) 2015 } 2016 result.shouldLoad.Set(id, needsReload, nil) 2017 } 2018 2019 // Check whether the metadata should be deleted. 2020 if invalidateMetadata { 2021 needsDiagnosis = true 2022 metadataUpdates[id] = nil 2023 continue 2024 } 2025 } 2026 2027 // Update metadata, if necessary. 2028 result.meta = s.meta.Update(metadataUpdates) 2029 2030 // Update workspace and active packages, if necessary. 2031 if result.meta != s.meta || anyFileOpenedOrClosed { 2032 needsDiagnosis = true 2033 result.workspacePackages = computeWorkspacePackagesLocked(ctx, result, result.meta) 2034 result.resetActivePackagesLocked() 2035 } else { 2036 result.workspacePackages = s.workspacePackages 2037 } 2038 2039 return result, needsDiagnosis 2040 } 2041 2042 // cloneWithout clones m then deletes from it the keys of changes. 2043 // 2044 // The optional didDelete variable is set to true if there were deletions. 2045 func cloneWithout[K constraints.Ordered, V1, V2 any](m *persistent.Map[K, V1], changes map[K]V2, didDelete *bool) *persistent.Map[K, V1] { 2046 m2 := m.Clone() 2047 for k := range changes { 2048 if m2.Delete(k) && didDelete != nil { 2049 *didDelete = true 2050 } 2051 } 2052 return m2 2053 } 2054 2055 // cloneWith clones m then inserts the changes into it. 2056 func cloneWith[K constraints.Ordered, V any](m *persistent.Map[K, V], changes map[K]V) *persistent.Map[K, V] { 2057 m2 := m.Clone() 2058 for k, v := range changes { 2059 m2.Set(k, v, nil) 2060 } 2061 return m2 2062 } 2063 2064 // deleteMostRelevantModFile deletes the mod file most likely to be the mod 2065 // file for the changed URI, if it exists. 2066 // 2067 // Specifically, this is the longest mod file path in a directory containing 2068 // changed. This might not be accurate if there is another mod file closer to 2069 // changed that happens not to be present in the map, but that's OK: the goal 2070 // of this function is to guarantee that IF the nearest mod file is present in 2071 // the map, it is invalidated. 2072 func deleteMostRelevantModFile(m *persistent.Map[protocol.DocumentURI, *memoize.Promise], changed protocol.DocumentURI) { 2073 var mostRelevant protocol.DocumentURI 2074 changedFile := changed.Path() 2075 2076 m.Range(func(modURI protocol.DocumentURI, _ *memoize.Promise) { 2077 if len(modURI) > len(mostRelevant) { 2078 if pathutil.InDir(filepath.Dir(modURI.Path()), changedFile) { 2079 mostRelevant = modURI 2080 } 2081 } 2082 }) 2083 if mostRelevant != "" { 2084 m.Delete(mostRelevant) 2085 } 2086 } 2087 2088 // invalidatedPackageIDs returns all packages invalidated by a change to uri. 2089 // If we haven't seen this URI before, we guess based on files in the same 2090 // directory. This is of course incorrect in build systems where packages are 2091 // not organized by directory. 2092 // 2093 // If packageFileChanged is set, the file is either a new file, or has a new 2094 // package name. In this case, all known packages in the directory will be 2095 // invalidated. 2096 func invalidatedPackageIDs(uri protocol.DocumentURI, known map[protocol.DocumentURI][]PackageID, packageFileChanged bool) map[PackageID]struct{} { 2097 invalidated := make(map[PackageID]struct{}) 2098 2099 // At a minimum, we invalidate packages known to contain uri. 2100 for _, id := range known[uri] { 2101 invalidated[id] = struct{}{} 2102 } 2103 2104 // If the file didn't move to a new package, we should only invalidate the 2105 // packages it is currently contained inside. 2106 if !packageFileChanged && len(invalidated) > 0 { 2107 return invalidated 2108 } 2109 2110 // This is a file we don't yet know about, or which has moved packages. Guess 2111 // relevant packages by considering files in the same directory. 2112 2113 // Cache of FileInfo to avoid unnecessary stats for multiple files in the 2114 // same directory. 2115 stats := make(map[string]struct { 2116 os.FileInfo 2117 error 2118 }) 2119 getInfo := func(dir string) (os.FileInfo, error) { 2120 if res, ok := stats[dir]; ok { 2121 return res.FileInfo, res.error 2122 } 2123 fi, err := os.Stat(dir) 2124 stats[dir] = struct { 2125 os.FileInfo 2126 error 2127 }{fi, err} 2128 return fi, err 2129 } 2130 dir := filepath.Dir(uri.Path()) 2131 fi, err := getInfo(dir) 2132 if err == nil { 2133 // Aggregate all possibly relevant package IDs. 2134 for knownURI, ids := range known { 2135 knownDir := filepath.Dir(knownURI.Path()) 2136 knownFI, err := getInfo(knownDir) 2137 if err != nil { 2138 continue 2139 } 2140 if os.SameFile(fi, knownFI) { 2141 for _, id := range ids { 2142 invalidated[id] = struct{}{} 2143 } 2144 } 2145 } 2146 } 2147 return invalidated 2148 } 2149 2150 // fileWasSaved reports whether the FileHandle passed in has been saved. It 2151 // accomplishes this by checking to see if the original and current FileHandles 2152 // are both overlays, and if the current FileHandle is saved while the original 2153 // FileHandle was not saved. 2154 func fileWasSaved(originalFH, currentFH file.Handle) bool { 2155 c, ok := currentFH.(*overlay) 2156 if !ok || c == nil { 2157 return true 2158 } 2159 o, ok := originalFH.(*overlay) 2160 if !ok || o == nil { 2161 return c.saved 2162 } 2163 return !o.saved && c.saved 2164 } 2165 2166 // metadataChanges detects features of the change from oldFH->newFH that may 2167 // affect package metadata. 2168 // 2169 // It uses lockedSnapshot to access cached parse information. lockedSnapshot 2170 // must be locked. 2171 // 2172 // The result parameters have the following meaning: 2173 // - invalidate means that package metadata for packages containing the file 2174 // should be invalidated. 2175 // - pkgFileChanged means that the file->package associates for the file have 2176 // changed (possibly because the file is new, or because its package name has 2177 // changed). 2178 // - importDeleted means that an import has been deleted, or we can't 2179 // determine if an import was deleted due to errors. 2180 func metadataChanges(ctx context.Context, lockedSnapshot *Snapshot, oldFH, newFH file.Handle) (invalidate, pkgFileChanged, importDeleted bool) { 2181 if oe, ne := oldFH != nil && fileExists(oldFH), fileExists(newFH); !oe || !ne { // existential changes 2182 changed := oe != ne 2183 return changed, changed, !ne // we don't know if an import was deleted 2184 } 2185 2186 // If the file hasn't changed, there's no need to reload. 2187 if oldFH.Identity() == newFH.Identity() { 2188 return false, false, false 2189 } 2190 2191 fset := token.NewFileSet() 2192 // Parse headers to compare package names and imports. 2193 oldHeads, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseHeader, false, oldFH) 2194 newHeads, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseHeader, false, newFH) 2195 2196 if oldErr != nil || newErr != nil { 2197 errChanged := (oldErr == nil) != (newErr == nil) 2198 return errChanged, errChanged, (newErr != nil) // we don't know if an import was deleted 2199 } 2200 2201 oldHead := oldHeads[0] 2202 newHead := newHeads[0] 2203 2204 // `go list` fails completely if the file header cannot be parsed. If we go 2205 // from a non-parsing state to a parsing state, we should reload. 2206 if oldHead.ParseErr != nil && newHead.ParseErr == nil { 2207 return true, true, true // We don't know what changed, so fall back on full invalidation. 2208 } 2209 2210 // If a package name has changed, the set of package imports may have changed 2211 // in ways we can't detect here. Assume an import has been deleted. 2212 if oldHead.File.Name.Name != newHead.File.Name.Name { 2213 return true, true, true 2214 } 2215 2216 // Check whether package imports have changed. Only consider potentially 2217 // valid imports paths. 2218 oldImports := validImports(oldHead.File.Imports) 2219 newImports := validImports(newHead.File.Imports) 2220 2221 for path := range newImports { 2222 if _, ok := oldImports[path]; ok { 2223 delete(oldImports, path) 2224 } else { 2225 invalidate = true // a new, potentially valid import was added 2226 } 2227 } 2228 2229 if len(oldImports) > 0 { 2230 invalidate = true 2231 importDeleted = true 2232 } 2233 2234 // If the change does not otherwise invalidate metadata, get the full ASTs in 2235 // order to check magic comments. 2236 // 2237 // Note: if this affects performance we can probably avoid parsing in the 2238 // common case by first scanning the source for potential comments. 2239 if !invalidate { 2240 origFulls, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseFull, false, oldFH) 2241 newFulls, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, ParseFull, false, newFH) 2242 if oldErr == nil && newErr == nil { 2243 invalidate = magicCommentsChanged(origFulls[0].File, newFulls[0].File) 2244 } else { 2245 // At this point, we shouldn't ever fail to produce a ParsedGoFile, as 2246 // we're already past header parsing. 2247 bug.Reportf("metadataChanges: unparseable file %v (old error: %v, new error: %v)", oldFH.URI(), oldErr, newErr) 2248 } 2249 } 2250 2251 return invalidate, pkgFileChanged, importDeleted 2252 } 2253 2254 func magicCommentsChanged(original *ast.File, current *ast.File) bool { 2255 oldComments := extractMagicComments(original) 2256 newComments := extractMagicComments(current) 2257 if len(oldComments) != len(newComments) { 2258 return true 2259 } 2260 for i := range oldComments { 2261 if oldComments[i] != newComments[i] { 2262 return true 2263 } 2264 } 2265 return false 2266 } 2267 2268 // validImports extracts the set of valid import paths from imports. 2269 func validImports(imports []*ast.ImportSpec) map[string]struct{} { 2270 m := make(map[string]struct{}) 2271 for _, spec := range imports { 2272 if path := spec.Path.Value; validImportPath(path) { 2273 m[path] = struct{}{} 2274 } 2275 } 2276 return m 2277 } 2278 2279 func validImportPath(path string) bool { 2280 path, err := strconv.Unquote(path) 2281 if err != nil { 2282 return false 2283 } 2284 if path == "" { 2285 return false 2286 } 2287 if path[len(path)-1] == '/' { 2288 return false 2289 } 2290 return true 2291 } 2292 2293 var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`) 2294 2295 // extractMagicComments finds magic comments that affect metadata in f. 2296 func extractMagicComments(f *ast.File) []string { 2297 var results []string 2298 for _, cg := range f.Comments { 2299 for _, c := range cg.List { 2300 if buildConstraintOrEmbedRe.MatchString(c.Text) { 2301 results = append(results, c.Text) 2302 } 2303 } 2304 } 2305 return results 2306 } 2307 2308 // BuiltinFile returns information about the special builtin package. 2309 func (s *Snapshot) BuiltinFile(ctx context.Context) (*ParsedGoFile, error) { 2310 s.AwaitInitialized(ctx) 2311 2312 s.mu.Lock() 2313 builtin := s.builtin 2314 s.mu.Unlock() 2315 2316 if builtin == "" { 2317 return nil, fmt.Errorf("no builtin package for view %s", s.view.folder.Name) 2318 } 2319 2320 fh, err := s.ReadFile(ctx, builtin) 2321 if err != nil { 2322 return nil, err 2323 } 2324 // For the builtin file only, we need syntactic object resolution 2325 // (since we can't type check). 2326 mode := ParseFull &^ parser.SkipObjectResolution 2327 pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), mode, false, fh) 2328 if err != nil { 2329 return nil, err 2330 } 2331 return pgfs[0], nil 2332 } 2333 2334 // IsBuiltin reports whether uri is part of the builtin package. 2335 func (s *Snapshot) IsBuiltin(uri protocol.DocumentURI) bool { 2336 s.mu.Lock() 2337 defer s.mu.Unlock() 2338 // We should always get the builtin URI in a canonical form, so use simple 2339 // string comparison here. span.CompareURI is too expensive. 2340 return uri == s.builtin 2341 } 2342 2343 func (s *Snapshot) setBuiltin(path string) { 2344 s.mu.Lock() 2345 defer s.mu.Unlock() 2346 2347 s.builtin = protocol.URIFromPath(path) 2348 } 2349 2350 // WantGCDetails reports whether to compute GC optimization details for the 2351 // specified package. 2352 func (s *Snapshot) WantGCDetails(id metadata.PackageID) bool { 2353 _, ok := s.gcOptimizationDetails[id] 2354 return ok 2355 }