github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/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 "fmt" 11 "go/ast" 12 "go/token" 13 "go/types" 14 "io" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 "regexp" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 24 "golang.org/x/mod/modfile" 25 "golang.org/x/mod/module" 26 "golang.org/x/mod/semver" 27 "github.com/jhump/golang-x-tools/go/analysis" 28 "github.com/jhump/golang-x-tools/go/packages" 29 "github.com/jhump/golang-x-tools/internal/event" 30 "github.com/jhump/golang-x-tools/internal/gocommand" 31 "github.com/jhump/golang-x-tools/internal/lsp/debug/log" 32 "github.com/jhump/golang-x-tools/internal/lsp/debug/tag" 33 "github.com/jhump/golang-x-tools/internal/lsp/source" 34 "github.com/jhump/golang-x-tools/internal/memoize" 35 "github.com/jhump/golang-x-tools/internal/packagesinternal" 36 "github.com/jhump/golang-x-tools/internal/span" 37 "github.com/jhump/golang-x-tools/internal/typesinternal" 38 errors "golang.org/x/xerrors" 39 ) 40 41 type snapshot struct { 42 memoize.Arg // allow as a memoize.Function arg 43 44 id uint64 45 view *View 46 47 cancel func() 48 backgroundCtx context.Context 49 50 // the cache generation that contains the data for this snapshot. 51 generation *memoize.Generation 52 53 // The snapshot's initialization state is controlled by the fields below. 54 // 55 // initializeOnce guards snapshot initialization. Each snapshot is 56 // initialized at most once: reinitialization is triggered on later snapshots 57 // by invalidating this field. 58 initializeOnce *sync.Once 59 // initializedErr holds the last error resulting from initialization. If 60 // initialization fails, we only retry when the the workspace modules change, 61 // to avoid too many go/packages calls. 62 initializedErr *source.CriticalError 63 64 // mu guards all of the maps in the snapshot, as well as the builtin URI. 65 mu sync.Mutex 66 67 // builtin pins the AST and package for builtin.go in memory. 68 builtin span.URI 69 70 // ids maps file URIs to package IDs. 71 // It may be invalidated on calls to go/packages. 72 ids map[span.URI][]PackageID 73 74 // metadata maps file IDs to their associated metadata. 75 // It may invalidated on calls to go/packages. 76 metadata map[PackageID]*KnownMetadata 77 78 // importedBy maps package IDs to the list of packages that import them. 79 importedBy map[PackageID][]PackageID 80 81 // files maps file URIs to their corresponding FileHandles. 82 // It may invalidated when a file's content changes. 83 files map[span.URI]source.VersionedFileHandle 84 85 // goFiles maps a parseKey to its parseGoHandle. 86 goFiles map[parseKey]*parseGoHandle 87 88 // TODO(rfindley): consider merging this with files to reduce burden on clone. 89 symbols map[span.URI]*symbolHandle 90 91 // packages maps a packageKey to a set of packageHandles to which that file belongs. 92 // It may be invalidated when a file's content changes. 93 packages map[packageKey]*packageHandle 94 95 // actions maps an actionkey to its actionHandle. 96 actions map[actionKey]*actionHandle 97 98 // workspacePackages contains the workspace's packages, which are loaded 99 // when the view is created. 100 workspacePackages map[PackageID]PackagePath 101 102 // unloadableFiles keeps track of files that we've failed to load. 103 unloadableFiles map[span.URI]struct{} 104 105 // parseModHandles keeps track of any parseModHandles for the snapshot. 106 // The handles need not refer to only the view's go.mod file. 107 parseModHandles map[span.URI]*parseModHandle 108 109 // parseWorkHandles keeps track of any parseWorkHandles for the snapshot. 110 // The handles need not refer to only the view's go.work file. 111 parseWorkHandles map[span.URI]*parseWorkHandle 112 113 // Preserve go.mod-related handles to avoid garbage-collecting the results 114 // of various calls to the go command. The handles need not refer to only 115 // the view's go.mod file. 116 modTidyHandles map[span.URI]*modTidyHandle 117 modWhyHandles map[span.URI]*modWhyHandle 118 119 workspace *workspace 120 workspaceDirHandle *memoize.Handle 121 122 // knownSubdirs is the set of subdirectories in the workspace, used to 123 // create glob patterns for file watching. 124 knownSubdirs map[span.URI]struct{} 125 // unprocessedSubdirChanges are any changes that might affect the set of 126 // subdirectories in the workspace. They are not reflected to knownSubdirs 127 // during the snapshot cloning step as it can slow down cloning. 128 unprocessedSubdirChanges []*fileChange 129 } 130 131 type packageKey struct { 132 mode source.ParseMode 133 id PackageID 134 } 135 136 type actionKey struct { 137 pkg packageKey 138 analyzer *analysis.Analyzer 139 } 140 141 func (s *snapshot) ID() uint64 { 142 return s.id 143 } 144 145 func (s *snapshot) View() source.View { 146 return s.view 147 } 148 149 func (s *snapshot) BackgroundContext() context.Context { 150 return s.backgroundCtx 151 } 152 153 func (s *snapshot) FileSet() *token.FileSet { 154 return s.view.session.cache.fset 155 } 156 157 func (s *snapshot) ModFiles() []span.URI { 158 var uris []span.URI 159 for modURI := range s.workspace.getActiveModFiles() { 160 uris = append(uris, modURI) 161 } 162 return uris 163 } 164 165 func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle { 166 s.mu.Lock() 167 defer s.mu.Unlock() 168 169 tmpls := map[span.URI]source.VersionedFileHandle{} 170 for k, fh := range s.files { 171 if s.view.FileKind(fh) == source.Tmpl { 172 tmpls[k] = fh 173 } 174 } 175 return tmpls 176 } 177 178 func (s *snapshot) ValidBuildConfiguration() bool { 179 return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.workspace.getActiveModFiles()) 180 } 181 182 // workspaceMode describes the way in which the snapshot's workspace should 183 // be loaded. 184 func (s *snapshot) workspaceMode() workspaceMode { 185 var mode workspaceMode 186 187 // If the view has an invalid configuration, don't build the workspace 188 // module. 189 validBuildConfiguration := s.ValidBuildConfiguration() 190 if !validBuildConfiguration { 191 return mode 192 } 193 // If the view is not in a module and contains no modules, but still has a 194 // valid workspace configuration, do not create the workspace module. 195 // It could be using GOPATH or a different build system entirely. 196 if len(s.workspace.getActiveModFiles()) == 0 && validBuildConfiguration { 197 return mode 198 } 199 mode |= moduleMode 200 options := s.view.Options() 201 // The -modfile flag is available for Go versions >= 1.14. 202 if options.TempModfile && s.view.workspaceInformation.goversion >= 14 { 203 mode |= tempModfile 204 } 205 return mode 206 } 207 208 // config returns the configuration used for the snapshot's interaction with 209 // the go/packages API. It uses the given working directory. 210 // 211 // TODO(rstambler): go/packages requires that we do not provide overlays for 212 // multiple modules in on config, so buildOverlay needs to filter overlays by 213 // module. 214 func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config { 215 s.view.optionsMu.Lock() 216 verboseOutput := s.view.options.VerboseOutput 217 s.view.optionsMu.Unlock() 218 219 cfg := &packages.Config{ 220 Context: ctx, 221 Dir: inv.WorkingDir, 222 Env: inv.Env, 223 BuildFlags: inv.BuildFlags, 224 Mode: packages.NeedName | 225 packages.NeedFiles | 226 packages.NeedCompiledGoFiles | 227 packages.NeedImports | 228 packages.NeedDeps | 229 packages.NeedTypesSizes | 230 packages.NeedModule, 231 Fset: s.FileSet(), 232 Overlay: s.buildOverlay(), 233 ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) { 234 panic("go/packages must not be used to parse files") 235 }, 236 Logf: func(format string, args ...interface{}) { 237 if verboseOutput { 238 event.Log(ctx, fmt.Sprintf(format, args...)) 239 } 240 }, 241 Tests: true, 242 } 243 packagesinternal.SetModFile(cfg, inv.ModFile) 244 packagesinternal.SetModFlag(cfg, inv.ModFlag) 245 // We want to type check cgo code if go/types supports it. 246 if typesinternal.SetUsesCgo(&types.Config{}) { 247 cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo) 248 } 249 packagesinternal.SetGoCmdRunner(cfg, s.view.session.gocmdRunner) 250 return cfg 251 } 252 253 func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) { 254 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 255 if err != nil { 256 return nil, err 257 } 258 defer cleanup() 259 260 return s.view.session.gocmdRunner.Run(ctx, *inv) 261 } 262 263 func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error { 264 _, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv) 265 if err != nil { 266 return err 267 } 268 defer cleanup() 269 return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr) 270 } 271 272 func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd string, run func(invoke func(...string) (*bytes.Buffer, error)) error) (bool, []byte, []byte, error) { 273 var flags source.InvocationFlags 274 if s.workspaceMode()&tempModfile != 0 { 275 flags = source.WriteTemporaryModFile 276 } else { 277 flags = source.Normal 278 } 279 if allowNetwork { 280 flags |= source.AllowNetwork 281 } 282 tmpURI, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{WorkingDir: wd}) 283 if err != nil { 284 return false, nil, nil, err 285 } 286 defer cleanup() 287 invoke := func(args ...string) (*bytes.Buffer, error) { 288 inv.Verb = args[0] 289 inv.Args = args[1:] 290 return s.view.session.gocmdRunner.Run(ctx, *inv) 291 } 292 if err := run(invoke); err != nil { 293 return false, nil, nil, err 294 } 295 if flags.Mode() != source.WriteTemporaryModFile { 296 return false, nil, nil, nil 297 } 298 var modBytes, sumBytes []byte 299 modBytes, err = ioutil.ReadFile(tmpURI.Filename()) 300 if err != nil && !os.IsNotExist(err) { 301 return false, nil, nil, err 302 } 303 sumBytes, err = ioutil.ReadFile(strings.TrimSuffix(tmpURI.Filename(), ".mod") + ".sum") 304 if err != nil && !os.IsNotExist(err) { 305 return false, nil, nil, err 306 } 307 return true, modBytes, sumBytes, nil 308 } 309 310 func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) { 311 s.view.optionsMu.Lock() 312 allowModfileModificationOption := s.view.options.AllowModfileModifications 313 allowNetworkOption := s.view.options.AllowImplicitNetworkAccess 314 inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.effectiveGo111Module) 315 inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...) 316 s.view.optionsMu.Unlock() 317 cleanup = func() {} // fallback 318 319 // All logic below is for module mode. 320 if s.workspaceMode()&moduleMode == 0 { 321 return "", inv, cleanup, nil 322 } 323 324 mode, allowNetwork := flags.Mode(), flags.AllowNetwork() 325 if !allowNetwork && !allowNetworkOption { 326 inv.Env = append(inv.Env, "GOPROXY=off") 327 } 328 329 // What follows is rather complicated logic for how to actually run the go 330 // command. A word of warning: this is the result of various incremental 331 // features added to gopls, and varying behavior of the Go command across Go 332 // versions. It can surely be cleaned up significantly, but tread carefully. 333 // 334 // Roughly speaking we need to resolve four things: 335 // - the working directory. 336 // - the -mod flag 337 // - the -modfile flag 338 // - the -workfile flag 339 // 340 // These are dependent on a number of factors: whether we need to run in a 341 // synthetic workspace, whether flags are supported at the current go 342 // version, and what we're actually trying to achieve (the 343 // source.InvocationFlags). 344 345 var modURI span.URI 346 // Select the module context to use. 347 // If we're type checking, we need to use the workspace context, meaning 348 // the main (workspace) module. Otherwise, we should use the module for 349 // the passed-in working dir. 350 if mode == source.LoadWorkspace { 351 switch s.workspace.moduleSource { 352 case legacyWorkspace: 353 for m := range s.workspace.getActiveModFiles() { // range to access the only element 354 modURI = m 355 } 356 case goWorkWorkspace: 357 if s.view.goversion >= 18 { 358 break 359 } 360 // Before go 1.18, the Go command did not natively support go.work files, 361 // so we 'fake' them with a workspace module. 362 fallthrough 363 case fileSystemWorkspace, goplsModWorkspace: 364 var tmpDir span.URI 365 var err error 366 tmpDir, err = s.getWorkspaceDir(ctx) 367 if err != nil { 368 return "", nil, cleanup, err 369 } 370 inv.WorkingDir = tmpDir.Filename() 371 modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod")) 372 } 373 } else { 374 modURI = s.GoModForFile(span.URIFromPath(inv.WorkingDir)) 375 } 376 377 var modContent []byte 378 if modURI != "" { 379 modFH, err := s.GetFile(ctx, modURI) 380 if err != nil { 381 return "", nil, cleanup, err 382 } 383 modContent, err = modFH.Read() 384 if err != nil { 385 return "", nil, cleanup, err 386 } 387 } 388 389 vendorEnabled, err := s.vendorEnabled(ctx, modURI, modContent) 390 if err != nil { 391 return "", nil, cleanup, err 392 } 393 394 mutableModFlag := "" 395 // If the mod flag isn't set, populate it based on the mode and workspace. 396 if inv.ModFlag == "" { 397 if s.view.goversion >= 16 { 398 mutableModFlag = "mod" 399 } 400 401 switch mode { 402 case source.LoadWorkspace, source.Normal: 403 if vendorEnabled { 404 inv.ModFlag = "vendor" 405 } else if !allowModfileModificationOption { 406 inv.ModFlag = "readonly" 407 } else { 408 inv.ModFlag = mutableModFlag 409 } 410 case source.WriteTemporaryModFile: 411 inv.ModFlag = mutableModFlag 412 } 413 } 414 415 // Only use a temp mod file if the modfile can actually be mutated. 416 needTempMod := inv.ModFlag == mutableModFlag 417 useTempMod := s.workspaceMode()&tempModfile != 0 418 if needTempMod && !useTempMod { 419 return "", nil, cleanup, source.ErrTmpModfileUnsupported 420 } 421 422 // We should use -workfile if: 423 // 1. We're not actively trying to mutate a modfile. 424 // 2. We have an active go.work file. 425 // 3. We're using at least Go 1.18. 426 useWorkFile := !needTempMod && s.workspace.moduleSource == goWorkWorkspace && s.view.goversion >= 18 427 if useWorkFile { 428 workURI := uriForSource(s.workspace.root, goWorkWorkspace) 429 workFH, err := s.GetFile(ctx, workURI) 430 if err != nil { 431 return "", nil, cleanup, err 432 } 433 // TODO(rfindley): we should use the last workfile that actually parsed, as 434 // tracked by the workspace. 435 tmpURI, cleanup, err = tempWorkFile(workFH) 436 if err != nil { 437 return "", nil, cleanup, err 438 } 439 inv.WorkFile = tmpURI.Filename() 440 } else if useTempMod { 441 if modURI == "" { 442 return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir) 443 } 444 modFH, err := s.GetFile(ctx, modURI) 445 if err != nil { 446 return "", nil, cleanup, err 447 } 448 // Use the go.sum if it happens to be available. 449 gosum := s.goSum(ctx, modURI) 450 tmpURI, cleanup, err = tempModFile(modFH, gosum) 451 if err != nil { 452 return "", nil, cleanup, err 453 } 454 inv.ModFile = tmpURI.Filename() 455 } 456 457 return tmpURI, inv, cleanup, nil 458 } 459 460 func (s *snapshot) buildOverlay() map[string][]byte { 461 s.mu.Lock() 462 defer s.mu.Unlock() 463 464 overlays := make(map[string][]byte) 465 for uri, fh := range s.files { 466 overlay, ok := fh.(*overlay) 467 if !ok { 468 continue 469 } 470 if overlay.saved { 471 continue 472 } 473 // TODO(rstambler): Make sure not to send overlays outside of the current view. 474 overlays[uri.Filename()] = overlay.text 475 } 476 return overlays 477 } 478 479 func hashUnsavedOverlays(files map[span.URI]source.VersionedFileHandle) string { 480 var unsaved []string 481 for uri, fh := range files { 482 if overlay, ok := fh.(*overlay); ok && !overlay.saved { 483 unsaved = append(unsaved, uri.Filename()) 484 } 485 } 486 sort.Strings(unsaved) 487 return hashContents([]byte(strings.Join(unsaved, ""))) 488 } 489 490 func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]source.Package, error) { 491 ctx = event.Label(ctx, tag.URI.Of(uri)) 492 493 phs, err := s.packageHandlesForFile(ctx, uri, mode, includeTestVariants) 494 if err != nil { 495 return nil, err 496 } 497 var pkgs []source.Package 498 for _, ph := range phs { 499 pkg, err := ph.check(ctx, s) 500 if err != nil { 501 return nil, err 502 } 503 pkgs = append(pkgs, pkg) 504 } 505 return pkgs, nil 506 } 507 508 func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) { 509 ctx = event.Label(ctx, tag.URI.Of(uri)) 510 511 phs, err := s.packageHandlesForFile(ctx, uri, mode, false) 512 if err != nil { 513 return nil, err 514 } 515 516 if len(phs) < 1 { 517 return nil, errors.Errorf("no packages") 518 } 519 520 ph := phs[0] 521 for _, handle := range phs[1:] { 522 switch pkgPolicy { 523 case source.WidestPackage: 524 if ph == nil || len(handle.CompiledGoFiles()) > len(ph.CompiledGoFiles()) { 525 ph = handle 526 } 527 case source.NarrowestPackage: 528 if ph == nil || len(handle.CompiledGoFiles()) < len(ph.CompiledGoFiles()) { 529 ph = handle 530 } 531 } 532 } 533 if ph == nil { 534 return nil, errors.Errorf("no packages in input") 535 } 536 537 return ph.check(ctx, s) 538 } 539 540 func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, includeTestVariants bool) ([]*packageHandle, error) { 541 // Check if we should reload metadata for the file. We don't invalidate IDs 542 // (though we should), so the IDs will be a better source of truth than the 543 // metadata. If there are no IDs for the file, then we should also reload. 544 fh, err := s.GetFile(ctx, uri) 545 if err != nil { 546 return nil, err 547 } 548 if kind := s.view.FileKind(fh); kind != source.Go { 549 return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind) 550 } 551 knownIDs, err := s.getOrLoadIDsForURI(ctx, uri) 552 if err != nil { 553 return nil, err 554 } 555 556 var phs []*packageHandle 557 for _, id := range knownIDs { 558 // Filter out any intermediate test variants. We typically aren't 559 // interested in these packages for file= style queries. 560 if m := s.getMetadata(id); m != nil && m.IsIntermediateTestVariant && !includeTestVariants { 561 continue 562 } 563 var parseModes []source.ParseMode 564 switch mode { 565 case source.TypecheckAll: 566 if s.workspaceParseMode(id) == source.ParseFull { 567 parseModes = []source.ParseMode{source.ParseFull} 568 } else { 569 parseModes = []source.ParseMode{source.ParseExported, source.ParseFull} 570 } 571 case source.TypecheckFull: 572 parseModes = []source.ParseMode{source.ParseFull} 573 case source.TypecheckWorkspace: 574 parseModes = []source.ParseMode{s.workspaceParseMode(id)} 575 } 576 577 for _, parseMode := range parseModes { 578 ph, err := s.buildPackageHandle(ctx, id, parseMode) 579 if err != nil { 580 return nil, err 581 } 582 phs = append(phs, ph) 583 } 584 } 585 return phs, nil 586 } 587 588 func (s *snapshot) getOrLoadIDsForURI(ctx context.Context, uri span.URI) ([]PackageID, error) { 589 knownIDs := s.getIDsForURI(uri) 590 reload := len(knownIDs) == 0 591 for _, id := range knownIDs { 592 // Reload package metadata if any of the metadata has missing 593 // dependencies, in case something has changed since the last time we 594 // reloaded it. 595 if s.noValidMetadataForID(id) { 596 reload = true 597 break 598 } 599 // TODO(golang/go#36918): Previously, we would reload any package with 600 // missing dependencies. This is expensive and results in too many 601 // calls to packages.Load. Determine what we should do instead. 602 } 603 if reload { 604 err := s.load(ctx, false, fileURI(uri)) 605 606 if !s.useInvalidMetadata() && err != nil { 607 return nil, err 608 } 609 // We've tried to reload and there are still no known IDs for the URI. 610 // Return the load error, if there was one. 611 knownIDs = s.getIDsForURI(uri) 612 if len(knownIDs) == 0 { 613 return nil, err 614 } 615 } 616 return knownIDs, nil 617 } 618 619 // Only use invalid metadata for Go versions >= 1.13. Go 1.12 and below has 620 // issues with overlays that will cause confusing error messages if we reuse 621 // old metadata. 622 func (s *snapshot) useInvalidMetadata() bool { 623 return s.view.goversion >= 13 && s.view.Options().ExperimentalUseInvalidMetadata 624 } 625 626 func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.Package, error) { 627 if err := s.awaitLoaded(ctx); err != nil { 628 return nil, err 629 } 630 ids := make(map[PackageID]struct{}) 631 s.transitiveReverseDependencies(PackageID(id), ids) 632 633 // Make sure to delete the original package ID from the map. 634 delete(ids, PackageID(id)) 635 636 var pkgs []source.Package 637 for id := range ids { 638 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id)) 639 if err != nil { 640 return nil, err 641 } 642 pkgs = append(pkgs, pkg) 643 } 644 return pkgs, nil 645 } 646 647 func (s *snapshot) checkedPackage(ctx context.Context, id PackageID, mode source.ParseMode) (*pkg, error) { 648 ph, err := s.buildPackageHandle(ctx, id, mode) 649 if err != nil { 650 return nil, err 651 } 652 return ph.check(ctx, s) 653 } 654 655 // transitiveReverseDependencies populates the ids map with package IDs 656 // belonging to the provided package and its transitive reverse dependencies. 657 func (s *snapshot) transitiveReverseDependencies(id PackageID, ids map[PackageID]struct{}) { 658 if _, ok := ids[id]; ok { 659 return 660 } 661 m := s.getMetadata(id) 662 // Only use invalid metadata if we support it. 663 if m == nil || !(m.Valid || s.useInvalidMetadata()) { 664 return 665 } 666 ids[id] = struct{}{} 667 importedBy := s.getImportedBy(id) 668 for _, parentID := range importedBy { 669 s.transitiveReverseDependencies(parentID, ids) 670 } 671 } 672 673 func (s *snapshot) getGoFile(key parseKey) *parseGoHandle { 674 s.mu.Lock() 675 defer s.mu.Unlock() 676 return s.goFiles[key] 677 } 678 679 func (s *snapshot) addGoFile(key parseKey, pgh *parseGoHandle) *parseGoHandle { 680 s.mu.Lock() 681 defer s.mu.Unlock() 682 if existing, ok := s.goFiles[key]; ok { 683 return existing 684 } 685 s.goFiles[key] = pgh 686 return pgh 687 } 688 689 func (s *snapshot) getParseModHandle(uri span.URI) *parseModHandle { 690 s.mu.Lock() 691 defer s.mu.Unlock() 692 return s.parseModHandles[uri] 693 } 694 695 func (s *snapshot) getParseWorkHandle(uri span.URI) *parseWorkHandle { 696 s.mu.Lock() 697 defer s.mu.Unlock() 698 return s.parseWorkHandles[uri] 699 } 700 701 func (s *snapshot) getModWhyHandle(uri span.URI) *modWhyHandle { 702 s.mu.Lock() 703 defer s.mu.Unlock() 704 return s.modWhyHandles[uri] 705 } 706 707 func (s *snapshot) getModTidyHandle(uri span.URI) *modTidyHandle { 708 s.mu.Lock() 709 defer s.mu.Unlock() 710 return s.modTidyHandles[uri] 711 } 712 713 func (s *snapshot) getImportedBy(id PackageID) []PackageID { 714 s.mu.Lock() 715 defer s.mu.Unlock() 716 return s.getImportedByLocked(id) 717 } 718 719 func (s *snapshot) getImportedByLocked(id PackageID) []PackageID { 720 // If we haven't rebuilt the import graph since creating the snapshot. 721 if len(s.importedBy) == 0 { 722 s.rebuildImportGraph() 723 } 724 return s.importedBy[id] 725 } 726 727 func (s *snapshot) clearAndRebuildImportGraph() { 728 s.mu.Lock() 729 defer s.mu.Unlock() 730 731 // Completely invalidate the original map. 732 s.importedBy = make(map[PackageID][]PackageID) 733 s.rebuildImportGraph() 734 } 735 736 func (s *snapshot) rebuildImportGraph() { 737 for id, m := range s.metadata { 738 for _, importID := range m.Deps { 739 s.importedBy[importID] = append(s.importedBy[importID], id) 740 } 741 } 742 } 743 744 func (s *snapshot) addPackageHandle(ph *packageHandle) *packageHandle { 745 s.mu.Lock() 746 defer s.mu.Unlock() 747 748 // If the package handle has already been cached, 749 // return the cached handle instead of overriding it. 750 if ph, ok := s.packages[ph.packageKey()]; ok { 751 return ph 752 } 753 s.packages[ph.packageKey()] = ph 754 return ph 755 } 756 757 func (s *snapshot) workspacePackageIDs() (ids []PackageID) { 758 s.mu.Lock() 759 defer s.mu.Unlock() 760 761 for id := range s.workspacePackages { 762 ids = append(ids, id) 763 } 764 return ids 765 } 766 767 func (s *snapshot) activePackageIDs() (ids []PackageID) { 768 if s.view.Options().MemoryMode == source.ModeNormal { 769 return s.workspacePackageIDs() 770 } 771 772 s.mu.Lock() 773 defer s.mu.Unlock() 774 775 seen := make(map[PackageID]bool) 776 for id := range s.workspacePackages { 777 if s.isActiveLocked(id, seen) { 778 ids = append(ids, id) 779 } 780 } 781 return ids 782 } 783 784 func (s *snapshot) isActiveLocked(id PackageID, seen map[PackageID]bool) (active bool) { 785 if seen == nil { 786 seen = make(map[PackageID]bool) 787 } 788 if seen, ok := seen[id]; ok { 789 return seen 790 } 791 defer func() { 792 seen[id] = active 793 }() 794 m, ok := s.metadata[id] 795 if !ok { 796 return false 797 } 798 for _, cgf := range m.CompiledGoFiles { 799 if s.isOpenLocked(cgf) { 800 return true 801 } 802 } 803 for _, dep := range m.Deps { 804 if s.isActiveLocked(dep, seen) { 805 return true 806 } 807 } 808 return false 809 } 810 811 func (s *snapshot) getWorkspacePkgPath(id PackageID) PackagePath { 812 s.mu.Lock() 813 defer s.mu.Unlock() 814 815 return s.workspacePackages[id] 816 } 817 818 const fileExtensions = "go,mod,sum,work" 819 820 func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]struct{} { 821 extensions := fileExtensions 822 for _, ext := range s.View().Options().TemplateExtensions { 823 extensions += "," + ext 824 } 825 // Work-around microsoft/vscode#100870 by making sure that we are, 826 // at least, watching the user's entire workspace. This will still be 827 // applied to every folder in the workspace. 828 patterns := map[string]struct{}{ 829 fmt.Sprintf("**/*.{%s}", extensions): {}, 830 } 831 dirs := s.workspace.dirs(ctx, s) 832 for _, dir := range dirs { 833 dirName := dir.Filename() 834 835 // If the directory is within the view's folder, we're already watching 836 // it with the pattern above. 837 if source.InDir(s.view.folder.Filename(), dirName) { 838 continue 839 } 840 // TODO(rstambler): If microsoft/vscode#3025 is resolved before 841 // microsoft/vscode#101042, we will need a work-around for Windows 842 // drive letter casing. 843 patterns[fmt.Sprintf("%s/**/*.{%s}", dirName, extensions)] = struct{}{} 844 } 845 846 // Some clients do not send notifications for changes to directories that 847 // contain Go code (golang/go#42348). To handle this, explicitly watch all 848 // of the directories in the workspace. We find them by adding the 849 // directories of every file in the snapshot's workspace directories. 850 var dirNames []string 851 for _, uri := range s.getKnownSubdirs(dirs) { 852 dirNames = append(dirNames, uri.Filename()) 853 } 854 sort.Strings(dirNames) 855 if len(dirNames) > 0 { 856 patterns[fmt.Sprintf("{%s}", strings.Join(dirNames, ","))] = struct{}{} 857 } 858 return patterns 859 } 860 861 // collectAllKnownSubdirs collects all of the subdirectories within the 862 // snapshot's workspace directories. None of the workspace directories are 863 // included. 864 func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) { 865 dirs := s.workspace.dirs(ctx, s) 866 867 s.mu.Lock() 868 defer s.mu.Unlock() 869 870 s.knownSubdirs = map[span.URI]struct{}{} 871 for uri := range s.files { 872 s.addKnownSubdirLocked(uri, dirs) 873 } 874 } 875 876 func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) []span.URI { 877 s.mu.Lock() 878 defer s.mu.Unlock() 879 880 // First, process any pending changes and update the set of known 881 // subdirectories. 882 for _, c := range s.unprocessedSubdirChanges { 883 if c.isUnchanged { 884 continue 885 } 886 if !c.exists { 887 s.removeKnownSubdirLocked(c.fileHandle.URI()) 888 } else { 889 s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs) 890 } 891 } 892 s.unprocessedSubdirChanges = nil 893 894 var result []span.URI 895 for uri := range s.knownSubdirs { 896 result = append(result, uri) 897 } 898 return result 899 } 900 901 func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) { 902 dir := filepath.Dir(uri.Filename()) 903 // First check if the directory is already known, because then we can 904 // return early. 905 if _, ok := s.knownSubdirs[span.URIFromPath(dir)]; ok { 906 return 907 } 908 var matched span.URI 909 for _, wsDir := range dirs { 910 if source.InDir(wsDir.Filename(), dir) { 911 matched = wsDir 912 break 913 } 914 } 915 // Don't watch any directory outside of the workspace directories. 916 if matched == "" { 917 return 918 } 919 for { 920 if dir == "" || dir == matched.Filename() { 921 break 922 } 923 uri := span.URIFromPath(dir) 924 if _, ok := s.knownSubdirs[uri]; ok { 925 break 926 } 927 s.knownSubdirs[uri] = struct{}{} 928 dir = filepath.Dir(dir) 929 } 930 } 931 932 func (s *snapshot) removeKnownSubdirLocked(uri span.URI) { 933 dir := filepath.Dir(uri.Filename()) 934 for dir != "" { 935 uri := span.URIFromPath(dir) 936 if _, ok := s.knownSubdirs[uri]; !ok { 937 break 938 } 939 if info, _ := os.Stat(dir); info == nil { 940 delete(s.knownSubdirs, uri) 941 } 942 dir = filepath.Dir(dir) 943 } 944 } 945 946 // knownFilesInDir returns the files known to the given snapshot that are in 947 // the given directory. It does not respect symlinks. 948 func (s *snapshot) knownFilesInDir(ctx context.Context, dir span.URI) []span.URI { 949 var files []span.URI 950 s.mu.Lock() 951 defer s.mu.Unlock() 952 953 for uri := range s.files { 954 if source.InDir(dir.Filename(), uri.Filename()) { 955 files = append(files, uri) 956 } 957 } 958 return files 959 } 960 961 func (s *snapshot) workspacePackageHandles(ctx context.Context) ([]*packageHandle, error) { 962 if err := s.awaitLoaded(ctx); err != nil { 963 return nil, err 964 } 965 var phs []*packageHandle 966 for _, pkgID := range s.workspacePackageIDs() { 967 ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID)) 968 if err != nil { 969 return nil, err 970 } 971 phs = append(phs, ph) 972 } 973 return phs, nil 974 } 975 976 func (s *snapshot) ActivePackages(ctx context.Context) ([]source.Package, error) { 977 phs, err := s.activePackageHandles(ctx) 978 if err != nil { 979 return nil, err 980 } 981 var pkgs []source.Package 982 for _, ph := range phs { 983 pkg, err := ph.check(ctx, s) 984 if err != nil { 985 return nil, err 986 } 987 pkgs = append(pkgs, pkg) 988 } 989 return pkgs, nil 990 } 991 992 func (s *snapshot) activePackageHandles(ctx context.Context) ([]*packageHandle, error) { 993 if err := s.awaitLoaded(ctx); err != nil { 994 return nil, err 995 } 996 var phs []*packageHandle 997 for _, pkgID := range s.activePackageIDs() { 998 ph, err := s.buildPackageHandle(ctx, pkgID, s.workspaceParseMode(pkgID)) 999 if err != nil { 1000 return nil, err 1001 } 1002 phs = append(phs, ph) 1003 } 1004 return phs, nil 1005 } 1006 1007 func (s *snapshot) Symbols(ctx context.Context) (map[span.URI][]source.Symbol, error) { 1008 result := make(map[span.URI][]source.Symbol) 1009 1010 // Keep going on errors, but log the first failure. Partial symbol results 1011 // are better than no symbol results. 1012 var firstErr error 1013 for uri, f := range s.files { 1014 sh := s.buildSymbolHandle(ctx, f) 1015 v, err := sh.handle.Get(ctx, s.generation, s) 1016 if err != nil { 1017 if firstErr == nil { 1018 firstErr = err 1019 } 1020 continue 1021 } 1022 data := v.(*symbolData) 1023 result[uri] = data.symbols 1024 } 1025 if firstErr != nil { 1026 event.Error(ctx, "getting snapshot symbols", firstErr) 1027 } 1028 return result, nil 1029 } 1030 1031 func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]source.Metadata, error) { 1032 knownIDs, err := s.getOrLoadIDsForURI(ctx, uri) 1033 if err != nil { 1034 return nil, err 1035 } 1036 var mds []source.Metadata 1037 for _, id := range knownIDs { 1038 md := s.getMetadata(id) 1039 mds = append(mds, md) 1040 } 1041 return mds, nil 1042 } 1043 1044 func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) { 1045 if err := s.awaitLoaded(ctx); err != nil { 1046 return nil, err 1047 } 1048 1049 // The WorkspaceSymbols implementation relies on this function returning 1050 // workspace packages first. 1051 ids := s.workspacePackageIDs() 1052 s.mu.Lock() 1053 for id := range s.metadata { 1054 if _, ok := s.workspacePackages[id]; ok { 1055 continue 1056 } 1057 ids = append(ids, id) 1058 } 1059 s.mu.Unlock() 1060 1061 var pkgs []source.Package 1062 for _, id := range ids { 1063 pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id)) 1064 if err != nil { 1065 return nil, err 1066 } 1067 pkgs = append(pkgs, pkg) 1068 } 1069 return pkgs, nil 1070 } 1071 1072 func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) { 1073 // Don't reload workspace package metadata. 1074 // This function is meant to only return currently cached information. 1075 s.AwaitInitialized(ctx) 1076 1077 s.mu.Lock() 1078 defer s.mu.Unlock() 1079 1080 results := map[string]source.Package{} 1081 for _, ph := range s.packages { 1082 cachedPkg, err := ph.cached(s.generation) 1083 if err != nil { 1084 continue 1085 } 1086 for importPath, newPkg := range cachedPkg.imports { 1087 if oldPkg, ok := results[string(importPath)]; ok { 1088 // Using the same trick as NarrowestPackage, prefer non-variants. 1089 if len(newPkg.compiledGoFiles) < len(oldPkg.(*pkg).compiledGoFiles) { 1090 results[string(importPath)] = newPkg 1091 } 1092 } else { 1093 results[string(importPath)] = newPkg 1094 } 1095 } 1096 } 1097 return results, nil 1098 } 1099 1100 func (s *snapshot) GoModForFile(uri span.URI) span.URI { 1101 return moduleForURI(s.workspace.activeModFiles, uri) 1102 } 1103 1104 func moduleForURI(modFiles map[span.URI]struct{}, uri span.URI) span.URI { 1105 var match span.URI 1106 for modURI := range modFiles { 1107 if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) { 1108 continue 1109 } 1110 if len(modURI) > len(match) { 1111 match = modURI 1112 } 1113 } 1114 return match 1115 } 1116 1117 func (s *snapshot) getPackage(id PackageID, mode source.ParseMode) *packageHandle { 1118 s.mu.Lock() 1119 defer s.mu.Unlock() 1120 1121 key := packageKey{ 1122 id: id, 1123 mode: mode, 1124 } 1125 return s.packages[key] 1126 } 1127 1128 func (s *snapshot) getSymbolHandle(uri span.URI) *symbolHandle { 1129 s.mu.Lock() 1130 defer s.mu.Unlock() 1131 1132 return s.symbols[uri] 1133 } 1134 1135 func (s *snapshot) addSymbolHandle(sh *symbolHandle) *symbolHandle { 1136 s.mu.Lock() 1137 defer s.mu.Unlock() 1138 1139 uri := sh.fh.URI() 1140 // If the package handle has already been cached, 1141 // return the cached handle instead of overriding it. 1142 if sh, ok := s.symbols[uri]; ok { 1143 return sh 1144 } 1145 s.symbols[uri] = sh 1146 return sh 1147 } 1148 1149 func (s *snapshot) getActionHandle(id PackageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle { 1150 s.mu.Lock() 1151 defer s.mu.Unlock() 1152 1153 key := actionKey{ 1154 pkg: packageKey{ 1155 id: id, 1156 mode: m, 1157 }, 1158 analyzer: a, 1159 } 1160 return s.actions[key] 1161 } 1162 1163 func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle { 1164 s.mu.Lock() 1165 defer s.mu.Unlock() 1166 1167 key := actionKey{ 1168 analyzer: ah.analyzer, 1169 pkg: packageKey{ 1170 id: ah.pkg.m.ID, 1171 mode: ah.pkg.mode, 1172 }, 1173 } 1174 if ah, ok := s.actions[key]; ok { 1175 return ah 1176 } 1177 s.actions[key] = ah 1178 return ah 1179 } 1180 1181 func (s *snapshot) getIDsForURI(uri span.URI) []PackageID { 1182 s.mu.Lock() 1183 defer s.mu.Unlock() 1184 1185 return s.ids[uri] 1186 } 1187 1188 func (s *snapshot) getMetadata(id PackageID) *KnownMetadata { 1189 s.mu.Lock() 1190 defer s.mu.Unlock() 1191 1192 return s.metadata[id] 1193 } 1194 1195 func (s *snapshot) shouldLoad(scope interface{}) bool { 1196 s.mu.Lock() 1197 defer s.mu.Unlock() 1198 1199 switch scope := scope.(type) { 1200 case PackagePath: 1201 var meta *KnownMetadata 1202 for _, m := range s.metadata { 1203 if m.PkgPath != scope { 1204 continue 1205 } 1206 meta = m 1207 } 1208 if meta == nil || meta.ShouldLoad { 1209 return true 1210 } 1211 return false 1212 case fileURI: 1213 uri := span.URI(scope) 1214 ids := s.ids[uri] 1215 if len(ids) == 0 { 1216 return true 1217 } 1218 for _, id := range ids { 1219 m, ok := s.metadata[id] 1220 if !ok || m.ShouldLoad { 1221 return true 1222 } 1223 } 1224 return false 1225 default: 1226 return true 1227 } 1228 } 1229 1230 func (s *snapshot) clearShouldLoad(scope interface{}) { 1231 s.mu.Lock() 1232 defer s.mu.Unlock() 1233 1234 switch scope := scope.(type) { 1235 case PackagePath: 1236 var meta *KnownMetadata 1237 for _, m := range s.metadata { 1238 if m.PkgPath == scope { 1239 meta = m 1240 } 1241 } 1242 if meta == nil { 1243 return 1244 } 1245 meta.ShouldLoad = false 1246 case fileURI: 1247 uri := span.URI(scope) 1248 ids := s.ids[uri] 1249 if len(ids) == 0 { 1250 return 1251 } 1252 for _, id := range ids { 1253 if m, ok := s.metadata[id]; ok { 1254 m.ShouldLoad = false 1255 } 1256 } 1257 } 1258 } 1259 1260 // noValidMetadataForURILocked reports whether there is any valid metadata for 1261 // the given URI. 1262 func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool { 1263 ids, ok := s.ids[uri] 1264 if !ok { 1265 return true 1266 } 1267 for _, id := range ids { 1268 if m, ok := s.metadata[id]; ok && m.Valid { 1269 return false 1270 } 1271 } 1272 return true 1273 } 1274 1275 // noValidMetadataForID reports whether there is no valid metadata for the 1276 // given ID. 1277 func (s *snapshot) noValidMetadataForID(id PackageID) bool { 1278 s.mu.Lock() 1279 defer s.mu.Unlock() 1280 return s.noValidMetadataForIDLocked(id) 1281 } 1282 1283 func (s *snapshot) noValidMetadataForIDLocked(id PackageID) bool { 1284 m := s.metadata[id] 1285 return m == nil || !m.Valid 1286 } 1287 1288 // updateIDForURIsLocked adds the given ID to the set of known IDs for the given URI. 1289 // Any existing invalid IDs are removed from the set of known IDs. IDs that are 1290 // not "command-line-arguments" are preferred, so if a new ID comes in for a 1291 // URI that previously only had "command-line-arguments", the new ID will 1292 // replace the "command-line-arguments" ID. 1293 func (s *snapshot) updateIDForURIsLocked(id PackageID, uris map[span.URI]struct{}) { 1294 for uri := range uris { 1295 // Collect the new set of IDs, preserving any valid existing IDs. 1296 newIDs := []PackageID{id} 1297 for _, existingID := range s.ids[uri] { 1298 // Don't set duplicates of the same ID. 1299 if existingID == id { 1300 continue 1301 } 1302 // If the package previously only had a command-line-arguments ID, 1303 // delete the command-line-arguments workspace package. 1304 if source.IsCommandLineArguments(string(existingID)) { 1305 delete(s.workspacePackages, existingID) 1306 continue 1307 } 1308 // If the metadata for an existing ID is invalid, and we are 1309 // setting metadata for a new, valid ID--don't preserve the old ID. 1310 if m, ok := s.metadata[existingID]; !ok || !m.Valid { 1311 continue 1312 } 1313 newIDs = append(newIDs, existingID) 1314 } 1315 sort.Slice(newIDs, func(i, j int) bool { 1316 return newIDs[i] < newIDs[j] 1317 }) 1318 s.ids[uri] = newIDs 1319 } 1320 } 1321 1322 func (s *snapshot) isWorkspacePackage(id PackageID) bool { 1323 s.mu.Lock() 1324 defer s.mu.Unlock() 1325 1326 _, ok := s.workspacePackages[id] 1327 return ok 1328 } 1329 1330 func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle { 1331 f := s.view.getFile(uri) 1332 1333 s.mu.Lock() 1334 defer s.mu.Unlock() 1335 1336 return s.files[f.URI()] 1337 } 1338 1339 // GetVersionedFile returns a File for the given URI. If the file is unknown it 1340 // is added to the managed set. 1341 // 1342 // GetVersionedFile succeeds even if the file does not exist. A non-nil error return 1343 // indicates some type of internal error, for example if ctx is cancelled. 1344 func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) { 1345 f := s.view.getFile(uri) 1346 1347 s.mu.Lock() 1348 defer s.mu.Unlock() 1349 return s.getFileLocked(ctx, f) 1350 } 1351 1352 // GetFile implements the fileSource interface by wrapping GetVersionedFile. 1353 func (s *snapshot) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 1354 return s.GetVersionedFile(ctx, uri) 1355 } 1356 1357 func (s *snapshot) getFileLocked(ctx context.Context, f *fileBase) (source.VersionedFileHandle, error) { 1358 if fh, ok := s.files[f.URI()]; ok { 1359 return fh, nil 1360 } 1361 1362 fh, err := s.view.session.cache.getFile(ctx, f.URI()) 1363 if err != nil { 1364 return nil, err 1365 } 1366 closed := &closedFile{fh} 1367 s.files[f.URI()] = closed 1368 return closed, nil 1369 } 1370 1371 func (s *snapshot) IsOpen(uri span.URI) bool { 1372 s.mu.Lock() 1373 defer s.mu.Unlock() 1374 return s.isOpenLocked(uri) 1375 1376 } 1377 1378 func (s *snapshot) openFiles() []source.VersionedFileHandle { 1379 s.mu.Lock() 1380 defer s.mu.Unlock() 1381 1382 var open []source.VersionedFileHandle 1383 for _, fh := range s.files { 1384 if s.isOpenLocked(fh.URI()) { 1385 open = append(open, fh) 1386 } 1387 } 1388 return open 1389 } 1390 1391 func (s *snapshot) isOpenLocked(uri span.URI) bool { 1392 _, open := s.files[uri].(*overlay) 1393 return open 1394 } 1395 1396 func (s *snapshot) awaitLoaded(ctx context.Context) error { 1397 loadErr := s.awaitLoadedAllErrors(ctx) 1398 1399 s.mu.Lock() 1400 defer s.mu.Unlock() 1401 1402 // If we still have absolutely no metadata, check if the view failed to 1403 // initialize and return any errors. 1404 if s.useInvalidMetadata() && len(s.metadata) > 0 { 1405 return nil 1406 } 1407 for _, m := range s.metadata { 1408 if m.Valid { 1409 return nil 1410 } 1411 } 1412 if loadErr != nil { 1413 return loadErr.MainError 1414 } 1415 return nil 1416 } 1417 1418 func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError { 1419 loadErr := s.awaitLoadedAllErrors(ctx) 1420 if loadErr != nil && errors.Is(loadErr.MainError, context.Canceled) { 1421 return nil 1422 } 1423 1424 // Even if packages didn't fail to load, we still may want to show 1425 // additional warnings. 1426 if loadErr == nil { 1427 wsPkgs, _ := s.ActivePackages(ctx) 1428 if msg := shouldShowAdHocPackagesWarning(s, wsPkgs); msg != "" { 1429 return &source.CriticalError{ 1430 MainError: errors.New(msg), 1431 } 1432 } 1433 // Even if workspace packages were returned, there still may be an error 1434 // with the user's workspace layout. Workspace packages that only have the 1435 // ID "command-line-arguments" are usually a symptom of a bad workspace 1436 // configuration. 1437 if containsCommandLineArguments(wsPkgs) { 1438 return s.workspaceLayoutError(ctx) 1439 } 1440 return nil 1441 } 1442 1443 if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") { 1444 return s.workspaceLayoutError(ctx) 1445 } 1446 return loadErr 1447 } 1448 1449 const adHocPackagesWarning = `You are outside of a module and outside of $GOPATH/src. 1450 If you are using modules, please open your editor to a directory in your module. 1451 If you believe this warning is incorrect, please file an issue: https://github.com/golang/go/issues/new.` 1452 1453 func shouldShowAdHocPackagesWarning(snapshot source.Snapshot, pkgs []source.Package) string { 1454 if snapshot.ValidBuildConfiguration() { 1455 return "" 1456 } 1457 for _, pkg := range pkgs { 1458 if len(pkg.MissingDependencies()) > 0 { 1459 return adHocPackagesWarning 1460 } 1461 } 1462 return "" 1463 } 1464 1465 func containsCommandLineArguments(pkgs []source.Package) bool { 1466 for _, pkg := range pkgs { 1467 if source.IsCommandLineArguments(pkg.ID()) { 1468 return true 1469 } 1470 } 1471 return false 1472 } 1473 1474 func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalError { 1475 // Do not return results until the snapshot's view has been initialized. 1476 s.AwaitInitialized(ctx) 1477 1478 // TODO(rstambler): Should we be more careful about returning the 1479 // initialization error? Is it possible for the initialization error to be 1480 // corrected without a successful reinitialization? 1481 s.mu.Lock() 1482 initializedErr := s.initializedErr 1483 s.mu.Unlock() 1484 if initializedErr != nil { 1485 return initializedErr 1486 } 1487 1488 if ctx.Err() != nil { 1489 return &source.CriticalError{MainError: ctx.Err()} 1490 } 1491 1492 if err := s.reloadWorkspace(ctx); err != nil { 1493 diags, _ := s.extractGoCommandErrors(ctx, err.Error()) 1494 return &source.CriticalError{ 1495 MainError: err, 1496 DiagList: diags, 1497 } 1498 } 1499 if err := s.reloadOrphanedFiles(ctx); err != nil { 1500 diags, _ := s.extractGoCommandErrors(ctx, err.Error()) 1501 return &source.CriticalError{ 1502 MainError: err, 1503 DiagList: diags, 1504 } 1505 } 1506 return nil 1507 } 1508 1509 func (s *snapshot) getInitializationError(ctx context.Context) *source.CriticalError { 1510 s.mu.Lock() 1511 defer s.mu.Unlock() 1512 1513 return s.initializedErr 1514 } 1515 1516 func (s *snapshot) AwaitInitialized(ctx context.Context) { 1517 select { 1518 case <-ctx.Done(): 1519 return 1520 case <-s.view.initialWorkspaceLoad: 1521 } 1522 // We typically prefer to run something as intensive as the IWL without 1523 // blocking. I'm not sure if there is a way to do that here. 1524 s.initialize(ctx, false) 1525 } 1526 1527 // reloadWorkspace reloads the metadata for all invalidated workspace packages. 1528 func (s *snapshot) reloadWorkspace(ctx context.Context) error { 1529 // See which of the workspace packages are missing metadata. 1530 s.mu.Lock() 1531 missingMetadata := len(s.workspacePackages) == 0 || len(s.metadata) == 0 1532 pkgPathSet := map[PackagePath]struct{}{} 1533 for id, pkgPath := range s.workspacePackages { 1534 if m, ok := s.metadata[id]; ok && m.Valid { 1535 continue 1536 } 1537 missingMetadata = true 1538 1539 // Don't try to reload "command-line-arguments" directly. 1540 if source.IsCommandLineArguments(string(pkgPath)) { 1541 continue 1542 } 1543 pkgPathSet[pkgPath] = struct{}{} 1544 } 1545 s.mu.Unlock() 1546 1547 // If the view's build configuration is invalid, we cannot reload by 1548 // package path. Just reload the directory instead. 1549 if missingMetadata && !s.ValidBuildConfiguration() { 1550 return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW")) 1551 } 1552 1553 if len(pkgPathSet) == 0 { 1554 return nil 1555 } 1556 1557 var pkgPaths []interface{} 1558 for pkgPath := range pkgPathSet { 1559 pkgPaths = append(pkgPaths, pkgPath) 1560 } 1561 return s.load(ctx, false, pkgPaths...) 1562 } 1563 1564 func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error { 1565 // When we load ./... or a package path directly, we may not get packages 1566 // that exist only in overlays. As a workaround, we search all of the files 1567 // available in the snapshot and reload their metadata individually using a 1568 // file= query if the metadata is unavailable. 1569 files := s.orphanedFiles() 1570 1571 // Files without a valid package declaration can't be loaded. Don't try. 1572 var scopes []interface{} 1573 for _, file := range files { 1574 pgf, err := s.ParseGo(ctx, file, source.ParseHeader) 1575 if err != nil { 1576 continue 1577 } 1578 if !pgf.File.Package.IsValid() { 1579 continue 1580 } 1581 scopes = append(scopes, fileURI(file.URI())) 1582 } 1583 1584 if len(scopes) == 0 { 1585 return nil 1586 } 1587 1588 // The regtests match this exact log message, keep them in sync. 1589 event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes)) 1590 err := s.load(ctx, false, scopes...) 1591 1592 // If we failed to load some files, i.e. they have no metadata, 1593 // mark the failures so we don't bother retrying until the file's 1594 // content changes. 1595 // 1596 // TODO(rstambler): This may be an overestimate if the load stopped 1597 // early for an unrelated errors. Add a fallback? 1598 // 1599 // Check for context cancellation so that we don't incorrectly mark files 1600 // as unloadable, but don't return before setting all workspace packages. 1601 if ctx.Err() == nil && err != nil { 1602 event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes)) 1603 s.mu.Lock() 1604 for _, scope := range scopes { 1605 uri := span.URI(scope.(fileURI)) 1606 if s.noValidMetadataForURILocked(uri) { 1607 s.unloadableFiles[uri] = struct{}{} 1608 } 1609 } 1610 s.mu.Unlock() 1611 } 1612 return nil 1613 } 1614 1615 func (s *snapshot) orphanedFiles() []source.VersionedFileHandle { 1616 s.mu.Lock() 1617 defer s.mu.Unlock() 1618 1619 var files []source.VersionedFileHandle 1620 for uri, fh := range s.files { 1621 // Don't try to reload metadata for go.mod files. 1622 if s.view.FileKind(fh) != source.Go { 1623 continue 1624 } 1625 // If the URI doesn't belong to this view, then it's not in a workspace 1626 // package and should not be reloaded directly. 1627 if !contains(s.view.session.viewsOf(uri), s.view) { 1628 continue 1629 } 1630 // If the file is not open and is in a vendor directory, don't treat it 1631 // like a workspace package. 1632 if _, ok := fh.(*overlay); !ok && inVendor(uri) { 1633 continue 1634 } 1635 // Don't reload metadata for files we've already deemed unloadable. 1636 if _, ok := s.unloadableFiles[uri]; ok { 1637 continue 1638 } 1639 if s.noValidMetadataForURILocked(uri) { 1640 files = append(files, fh) 1641 } 1642 } 1643 return files 1644 } 1645 1646 func contains(views []*View, view *View) bool { 1647 for _, v := range views { 1648 if v == view { 1649 return true 1650 } 1651 } 1652 return false 1653 } 1654 1655 func inVendor(uri span.URI) bool { 1656 if !strings.Contains(string(uri), "/vendor/") { 1657 return false 1658 } 1659 // Only packages in _subdirectories_ of /vendor/ are considered vendored 1660 // (/vendor/a/foo.go is vendored, /vendor/foo.go is not). 1661 split := strings.Split(string(uri), "/vendor/") 1662 if len(split) < 2 { 1663 return false 1664 } 1665 return strings.Contains(split[1], "/") 1666 } 1667 1668 func generationName(v *View, snapshotID uint64) string { 1669 return fmt.Sprintf("v%v/%v", v.id, snapshotID) 1670 } 1671 1672 // checkSnapshotLocked verifies that some invariants are preserved on the 1673 // snapshot. 1674 func checkSnapshotLocked(ctx context.Context, s *snapshot) { 1675 // Check that every go file for a workspace package is identified as 1676 // belonging to that workspace package. 1677 for wsID := range s.workspacePackages { 1678 if m, ok := s.metadata[wsID]; ok { 1679 for _, uri := range m.GoFiles { 1680 found := false 1681 for _, id := range s.ids[uri] { 1682 if id == wsID { 1683 found = true 1684 break 1685 } 1686 } 1687 if !found { 1688 log.Error.Logf(ctx, "workspace package %v not associated with %v", wsID, uri) 1689 } 1690 } 1691 } 1692 } 1693 } 1694 1695 // unappliedChanges is a file source that handles an uncloned snapshot. 1696 type unappliedChanges struct { 1697 originalSnapshot *snapshot 1698 changes map[span.URI]*fileChange 1699 } 1700 1701 func (ac *unappliedChanges) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 1702 if c, ok := ac.changes[uri]; ok { 1703 return c.fileHandle, nil 1704 } 1705 return ac.originalSnapshot.GetFile(ctx, uri) 1706 } 1707 1708 func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) *snapshot { 1709 var vendorChanged bool 1710 newWorkspace, workspaceChanged, workspaceReload := s.workspace.invalidate(ctx, changes, &unappliedChanges{ 1711 originalSnapshot: s, 1712 changes: changes, 1713 }) 1714 1715 s.mu.Lock() 1716 defer s.mu.Unlock() 1717 1718 checkSnapshotLocked(ctx, s) 1719 1720 newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1)) 1721 bgCtx, cancel := context.WithCancel(bgCtx) 1722 result := &snapshot{ 1723 id: s.id + 1, 1724 generation: newGen, 1725 view: s.view, 1726 backgroundCtx: bgCtx, 1727 cancel: cancel, 1728 builtin: s.builtin, 1729 initializeOnce: s.initializeOnce, 1730 initializedErr: s.initializedErr, 1731 ids: make(map[span.URI][]PackageID, len(s.ids)), 1732 importedBy: make(map[PackageID][]PackageID, len(s.importedBy)), 1733 metadata: make(map[PackageID]*KnownMetadata, len(s.metadata)), 1734 packages: make(map[packageKey]*packageHandle, len(s.packages)), 1735 actions: make(map[actionKey]*actionHandle, len(s.actions)), 1736 files: make(map[span.URI]source.VersionedFileHandle, len(s.files)), 1737 goFiles: make(map[parseKey]*parseGoHandle, len(s.goFiles)), 1738 symbols: make(map[span.URI]*symbolHandle, len(s.symbols)), 1739 workspacePackages: make(map[PackageID]PackagePath, len(s.workspacePackages)), 1740 unloadableFiles: make(map[span.URI]struct{}, len(s.unloadableFiles)), 1741 parseModHandles: make(map[span.URI]*parseModHandle, len(s.parseModHandles)), 1742 parseWorkHandles: make(map[span.URI]*parseWorkHandle, len(s.parseWorkHandles)), 1743 modTidyHandles: make(map[span.URI]*modTidyHandle, len(s.modTidyHandles)), 1744 modWhyHandles: make(map[span.URI]*modWhyHandle, len(s.modWhyHandles)), 1745 knownSubdirs: make(map[span.URI]struct{}, len(s.knownSubdirs)), 1746 workspace: newWorkspace, 1747 } 1748 1749 if !workspaceChanged && s.workspaceDirHandle != nil { 1750 result.workspaceDirHandle = s.workspaceDirHandle 1751 newGen.Inherit(s.workspaceDirHandle) 1752 } 1753 1754 // Copy all of the FileHandles. 1755 for k, v := range s.files { 1756 result.files[k] = v 1757 } 1758 for k, v := range s.symbols { 1759 if change, ok := changes[k]; ok { 1760 if change.exists { 1761 result.symbols[k] = result.buildSymbolHandle(ctx, change.fileHandle) 1762 } 1763 continue 1764 } 1765 newGen.Inherit(v.handle) 1766 result.symbols[k] = v 1767 } 1768 1769 // Copy the set of unloadable files. 1770 for k, v := range s.unloadableFiles { 1771 result.unloadableFiles[k] = v 1772 } 1773 // Copy all of the modHandles. 1774 for k, v := range s.parseModHandles { 1775 result.parseModHandles[k] = v 1776 } 1777 // Copy all of the parseWorkHandles. 1778 for k, v := range s.parseWorkHandles { 1779 result.parseWorkHandles[k] = v 1780 } 1781 1782 for k, v := range s.goFiles { 1783 if _, ok := changes[k.file.URI]; ok { 1784 continue 1785 } 1786 newGen.Inherit(v.handle) 1787 result.goFiles[k] = v 1788 } 1789 1790 // Copy all of the go.mod-related handles. They may be invalidated later, 1791 // so we inherit them at the end of the function. 1792 for k, v := range s.modTidyHandles { 1793 if _, ok := changes[k]; ok { 1794 continue 1795 } 1796 result.modTidyHandles[k] = v 1797 } 1798 for k, v := range s.modWhyHandles { 1799 if _, ok := changes[k]; ok { 1800 continue 1801 } 1802 result.modWhyHandles[k] = v 1803 } 1804 1805 // Add all of the known subdirectories, but don't update them for the 1806 // changed files. We need to rebuild the workspace module to know the 1807 // true set of known subdirectories, but we don't want to do that in clone. 1808 for k, v := range s.knownSubdirs { 1809 result.knownSubdirs[k] = v 1810 } 1811 for _, c := range changes { 1812 result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c) 1813 } 1814 1815 // directIDs keeps track of package IDs that have directly changed. 1816 // It maps id->invalidateMetadata. 1817 directIDs := map[PackageID]bool{} 1818 1819 // Invalidate all package metadata if the workspace module has changed. 1820 if workspaceReload { 1821 for k := range s.metadata { 1822 directIDs[k] = true 1823 } 1824 } 1825 1826 changedPkgNames := map[PackageID]struct{}{} 1827 anyImportDeleted := false 1828 for uri, change := range changes { 1829 // Maybe reinitialize the view if we see a change in the vendor 1830 // directory. 1831 if inVendor(uri) { 1832 vendorChanged = true 1833 } 1834 1835 // The original FileHandle for this URI is cached on the snapshot. 1836 originalFH := s.files[uri] 1837 1838 // Check if the file's package name or imports have changed, 1839 // and if so, invalidate this file's packages' metadata. 1840 var shouldInvalidateMetadata, pkgNameChanged, importDeleted bool 1841 if !isGoMod(uri) { 1842 shouldInvalidateMetadata, pkgNameChanged, importDeleted = s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle) 1843 } 1844 invalidateMetadata := forceReloadMetadata || workspaceReload || shouldInvalidateMetadata 1845 anyImportDeleted = anyImportDeleted || importDeleted 1846 1847 // Mark all of the package IDs containing the given file. 1848 // TODO: if the file has moved into a new package, we should invalidate that too. 1849 filePackageIDs := guessPackageIDsForURI(uri, s.ids) 1850 if pkgNameChanged { 1851 for _, id := range filePackageIDs { 1852 changedPkgNames[id] = struct{}{} 1853 } 1854 } 1855 for _, id := range filePackageIDs { 1856 directIDs[id] = directIDs[id] || invalidateMetadata 1857 } 1858 1859 // Invalidate the previous modTidyHandle if any of the files have been 1860 // saved or if any of the metadata has been invalidated. 1861 if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) { 1862 // TODO(rstambler): Only delete mod handles for which the 1863 // withoutURI is relevant. 1864 for k := range s.modTidyHandles { 1865 delete(result.modTidyHandles, k) 1866 } 1867 for k := range s.modWhyHandles { 1868 delete(result.modWhyHandles, k) 1869 } 1870 } 1871 delete(result.parseModHandles, uri) 1872 delete(result.parseWorkHandles, uri) 1873 // Handle the invalidated file; it may have new contents or not exist. 1874 if !change.exists { 1875 delete(result.files, uri) 1876 } else { 1877 result.files[uri] = change.fileHandle 1878 } 1879 1880 // Make sure to remove the changed file from the unloadable set. 1881 delete(result.unloadableFiles, uri) 1882 } 1883 1884 // Deleting an import can cause list errors due to import cycles to be 1885 // resolved. The best we can do without parsing the list error message is to 1886 // hope that list errors may have been resolved by a deleted import. 1887 // 1888 // We could do better by parsing the list error message. We already do this 1889 // to assign a better range to the list error, but for such critical 1890 // functionality as metadata, it's better to be conservative until it proves 1891 // impractical. 1892 // 1893 // We could also do better by looking at which imports were deleted and 1894 // trying to find cycles they are involved in. This fails when the file goes 1895 // from an unparseable state to a parseable state, as we don't have a 1896 // starting point to compare with. 1897 if anyImportDeleted { 1898 for id, metadata := range s.metadata { 1899 if len(metadata.Errors) > 0 { 1900 directIDs[id] = true 1901 } 1902 } 1903 } 1904 1905 // Invalidate reverse dependencies too. 1906 // TODO(heschi): figure out the locking model and use transitiveReverseDeps? 1907 // idsToInvalidate keeps track of transitive reverse dependencies. 1908 // If an ID is present in the map, invalidate its types. 1909 // If an ID's value is true, invalidate its metadata too. 1910 idsToInvalidate := map[PackageID]bool{} 1911 var addRevDeps func(PackageID, bool) 1912 addRevDeps = func(id PackageID, invalidateMetadata bool) { 1913 current, seen := idsToInvalidate[id] 1914 newInvalidateMetadata := current || invalidateMetadata 1915 1916 // If we've already seen this ID, and the value of invalidate 1917 // metadata has not changed, we can return early. 1918 if seen && current == newInvalidateMetadata { 1919 return 1920 } 1921 idsToInvalidate[id] = newInvalidateMetadata 1922 for _, rid := range s.getImportedByLocked(id) { 1923 addRevDeps(rid, invalidateMetadata) 1924 } 1925 } 1926 for id, invalidateMetadata := range directIDs { 1927 addRevDeps(id, invalidateMetadata) 1928 } 1929 1930 // Copy the package type information. 1931 for k, v := range s.packages { 1932 if _, ok := idsToInvalidate[k.id]; ok { 1933 continue 1934 } 1935 newGen.Inherit(v.handle) 1936 result.packages[k] = v 1937 } 1938 // Copy the package analysis information. 1939 for k, v := range s.actions { 1940 if _, ok := idsToInvalidate[k.pkg.id]; ok { 1941 continue 1942 } 1943 newGen.Inherit(v.handle) 1944 result.actions[k] = v 1945 } 1946 1947 // If the workspace mode has changed, we must delete all metadata, as it 1948 // is unusable and may produce confusing or incorrect diagnostics. 1949 // If a file has been deleted, we must delete metadata all packages 1950 // containing that file. 1951 workspaceModeChanged := s.workspaceMode() != result.workspaceMode() 1952 skipID := map[PackageID]bool{} 1953 for _, c := range changes { 1954 if c.exists { 1955 continue 1956 } 1957 // The file has been deleted. 1958 if ids, ok := s.ids[c.fileHandle.URI()]; ok { 1959 for _, id := range ids { 1960 skipID[id] = true 1961 } 1962 } 1963 } 1964 1965 // Collect all of the IDs that are reachable from the workspace packages. 1966 // Any unreachable IDs will have their metadata deleted outright. 1967 reachableID := map[PackageID]bool{} 1968 var addForwardDeps func(PackageID) 1969 addForwardDeps = func(id PackageID) { 1970 if reachableID[id] { 1971 return 1972 } 1973 reachableID[id] = true 1974 m, ok := s.metadata[id] 1975 if !ok { 1976 return 1977 } 1978 for _, depID := range m.Deps { 1979 addForwardDeps(depID) 1980 } 1981 } 1982 for id := range s.workspacePackages { 1983 addForwardDeps(id) 1984 } 1985 1986 // Copy the URI to package ID mappings, skipping only those URIs whose 1987 // metadata will be reloaded in future calls to load. 1988 deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged 1989 idsInSnapshot := map[PackageID]bool{} // track all known IDs 1990 for uri, ids := range s.ids { 1991 for _, id := range ids { 1992 invalidateMetadata := idsToInvalidate[id] 1993 if skipID[id] || (invalidateMetadata && deleteInvalidMetadata) { 1994 continue 1995 } 1996 // The ID is not reachable from any workspace package, so it should 1997 // be deleted. 1998 if !reachableID[id] { 1999 continue 2000 } 2001 idsInSnapshot[id] = true 2002 result.ids[uri] = append(result.ids[uri], id) 2003 } 2004 } 2005 2006 // Copy the package metadata. We only need to invalidate packages directly 2007 // containing the affected file, and only if it changed in a relevant way. 2008 for k, v := range s.metadata { 2009 if !idsInSnapshot[k] { 2010 // Delete metadata for IDs that are no longer reachable from files 2011 // in the snapshot. 2012 continue 2013 } 2014 invalidateMetadata := idsToInvalidate[k] 2015 // Mark invalidated metadata rather than deleting it outright. 2016 result.metadata[k] = &KnownMetadata{ 2017 Metadata: v.Metadata, 2018 Valid: v.Valid && !invalidateMetadata, 2019 ShouldLoad: v.ShouldLoad || invalidateMetadata, 2020 } 2021 } 2022 2023 // Copy the set of initially loaded packages. 2024 for id, pkgPath := range s.workspacePackages { 2025 // Packages with the id "command-line-arguments" are generated by the 2026 // go command when the user is outside of GOPATH and outside of a 2027 // module. Do not cache them as workspace packages for longer than 2028 // necessary. 2029 if source.IsCommandLineArguments(string(id)) { 2030 if invalidateMetadata, ok := idsToInvalidate[id]; invalidateMetadata && ok { 2031 continue 2032 } 2033 } 2034 2035 // If all the files we know about in a package have been deleted, 2036 // the package is gone and we should no longer try to load it. 2037 if m := s.metadata[id]; m != nil { 2038 hasFiles := false 2039 for _, uri := range s.metadata[id].GoFiles { 2040 // For internal tests, we need _test files, not just the normal 2041 // ones. External tests only have _test files, but we can check 2042 // them anyway. 2043 if m.ForTest != "" && !strings.HasSuffix(string(uri), "_test.go") { 2044 continue 2045 } 2046 if _, ok := result.files[uri]; ok { 2047 hasFiles = true 2048 break 2049 } 2050 } 2051 if !hasFiles { 2052 continue 2053 } 2054 } 2055 2056 // If the package name of a file in the package has changed, it's 2057 // possible that the package ID may no longer exist. Delete it from 2058 // the set of workspace packages, on the assumption that we will add it 2059 // back when the relevant files are reloaded. 2060 if _, ok := changedPkgNames[id]; ok { 2061 continue 2062 } 2063 2064 result.workspacePackages[id] = pkgPath 2065 } 2066 2067 // Inherit all of the go.mod-related handles. 2068 for _, v := range result.modTidyHandles { 2069 newGen.Inherit(v.handle) 2070 } 2071 for _, v := range result.modWhyHandles { 2072 newGen.Inherit(v.handle) 2073 } 2074 for _, v := range result.parseModHandles { 2075 newGen.Inherit(v.handle) 2076 } 2077 for _, v := range result.parseWorkHandles { 2078 newGen.Inherit(v.handle) 2079 } 2080 // Don't bother copying the importedBy graph, 2081 // as it changes each time we update metadata. 2082 2083 // If the snapshot's workspace mode has changed, the packages loaded using 2084 // the previous mode are no longer relevant, so clear them out. 2085 if workspaceModeChanged { 2086 result.workspacePackages = map[PackageID]PackagePath{} 2087 } 2088 2089 // The snapshot may need to be reinitialized. 2090 if workspaceReload || vendorChanged { 2091 if workspaceChanged || result.initializedErr != nil { 2092 result.initializeOnce = &sync.Once{} 2093 } 2094 } 2095 return result 2096 } 2097 2098 // guessPackageIDsForURI returns all packages related to uri. If we haven't 2099 // seen this URI before, we guess based on files in the same directory. This 2100 // is of course incorrect in build systems where packages are not organized by 2101 // directory. 2102 func guessPackageIDsForURI(uri span.URI, known map[span.URI][]PackageID) []PackageID { 2103 packages := known[uri] 2104 if len(packages) > 0 { 2105 // We've seen this file before. 2106 return packages 2107 } 2108 // This is a file we don't yet know about. Guess relevant packages by 2109 // considering files in the same directory. 2110 2111 // Cache of FileInfo to avoid unnecessary stats for multiple files in the 2112 // same directory. 2113 stats := make(map[string]struct { 2114 os.FileInfo 2115 error 2116 }) 2117 getInfo := func(dir string) (os.FileInfo, error) { 2118 if res, ok := stats[dir]; ok { 2119 return res.FileInfo, res.error 2120 } 2121 fi, err := os.Stat(dir) 2122 stats[dir] = struct { 2123 os.FileInfo 2124 error 2125 }{fi, err} 2126 return fi, err 2127 } 2128 dir := filepath.Dir(uri.Filename()) 2129 fi, err := getInfo(dir) 2130 if err != nil { 2131 return nil 2132 } 2133 2134 // Aggregate all possibly relevant package IDs. 2135 var found []PackageID 2136 for knownURI, ids := range known { 2137 knownDir := filepath.Dir(knownURI.Filename()) 2138 knownFI, err := getInfo(knownDir) 2139 if err != nil { 2140 continue 2141 } 2142 if os.SameFile(fi, knownFI) { 2143 found = append(found, ids...) 2144 } 2145 } 2146 return found 2147 } 2148 2149 // fileWasSaved reports whether the FileHandle passed in has been saved. It 2150 // accomplishes this by checking to see if the original and current FileHandles 2151 // are both overlays, and if the current FileHandle is saved while the original 2152 // FileHandle was not saved. 2153 func fileWasSaved(originalFH, currentFH source.FileHandle) bool { 2154 c, ok := currentFH.(*overlay) 2155 if !ok || c == nil { 2156 return true 2157 } 2158 o, ok := originalFH.(*overlay) 2159 if !ok || o == nil { 2160 return c.saved 2161 } 2162 return !o.saved && c.saved 2163 } 2164 2165 // shouldInvalidateMetadata reparses the full file's AST to determine 2166 // if the file requires a metadata reload. 2167 func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, newSnapshot *snapshot, originalFH, currentFH source.FileHandle) (invalidate, pkgNameChanged, importDeleted bool) { 2168 if originalFH == nil { 2169 return true, false, false 2170 } 2171 // If the file hasn't changed, there's no need to reload. 2172 if originalFH.FileIdentity() == currentFH.FileIdentity() { 2173 return false, false, false 2174 } 2175 // Get the original and current parsed files in order to check package name 2176 // and imports. Use the new snapshot to parse to avoid modifying the 2177 // current snapshot. 2178 original, originalErr := newSnapshot.ParseGo(ctx, originalFH, source.ParseFull) 2179 current, currentErr := newSnapshot.ParseGo(ctx, currentFH, source.ParseFull) 2180 if originalErr != nil || currentErr != nil { 2181 return (originalErr == nil) != (currentErr == nil), false, (currentErr != nil) // we don't know if an import was deleted 2182 } 2183 // Check if the package's metadata has changed. The cases handled are: 2184 // 1. A package's name has changed 2185 // 2. A file's imports have changed 2186 if original.File.Name.Name != current.File.Name.Name { 2187 invalidate = true 2188 pkgNameChanged = true 2189 } 2190 origImportSet := make(map[string]struct{}) 2191 for _, importSpec := range original.File.Imports { 2192 origImportSet[importSpec.Path.Value] = struct{}{} 2193 } 2194 curImportSet := make(map[string]struct{}) 2195 for _, importSpec := range current.File.Imports { 2196 curImportSet[importSpec.Path.Value] = struct{}{} 2197 } 2198 // If any of the current imports were not in the original imports. 2199 for path := range curImportSet { 2200 if _, ok := origImportSet[path]; ok { 2201 delete(origImportSet, path) 2202 continue 2203 } 2204 // If the import path is obviously not valid, we can skip reloading 2205 // metadata. For now, valid means properly quoted and without a 2206 // terminal slash. 2207 if isBadImportPath(path) { 2208 continue 2209 } 2210 invalidate = true 2211 } 2212 2213 for path := range origImportSet { 2214 if !isBadImportPath(path) { 2215 invalidate = true 2216 importDeleted = true 2217 } 2218 } 2219 2220 if !invalidate { 2221 invalidate = magicCommentsChanged(original.File, current.File) 2222 } 2223 return invalidate, pkgNameChanged, importDeleted 2224 } 2225 2226 func magicCommentsChanged(original *ast.File, current *ast.File) bool { 2227 oldComments := extractMagicComments(original) 2228 newComments := extractMagicComments(current) 2229 if len(oldComments) != len(newComments) { 2230 return true 2231 } 2232 for i := range oldComments { 2233 if oldComments[i] != newComments[i] { 2234 return true 2235 } 2236 } 2237 return false 2238 } 2239 2240 func isBadImportPath(path string) bool { 2241 path, err := strconv.Unquote(path) 2242 if err != nil { 2243 return true 2244 } 2245 if path == "" { 2246 return true 2247 } 2248 if path[len(path)-1] == '/' { 2249 return true 2250 } 2251 return false 2252 } 2253 2254 var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`) 2255 2256 // extractMagicComments finds magic comments that affect metadata in f. 2257 func extractMagicComments(f *ast.File) []string { 2258 var results []string 2259 for _, cg := range f.Comments { 2260 for _, c := range cg.List { 2261 if buildConstraintOrEmbedRe.MatchString(c.Text) { 2262 results = append(results, c.Text) 2263 } 2264 } 2265 } 2266 return results 2267 } 2268 2269 func (s *snapshot) BuiltinFile(ctx context.Context) (*source.ParsedGoFile, error) { 2270 s.AwaitInitialized(ctx) 2271 2272 s.mu.Lock() 2273 builtin := s.builtin 2274 s.mu.Unlock() 2275 2276 if builtin == "" { 2277 return nil, errors.Errorf("no builtin package for view %s", s.view.name) 2278 } 2279 2280 fh, err := s.GetFile(ctx, builtin) 2281 if err != nil { 2282 return nil, err 2283 } 2284 return s.ParseGo(ctx, fh, source.ParseFull) 2285 } 2286 2287 func (s *snapshot) IsBuiltin(ctx context.Context, uri span.URI) bool { 2288 s.mu.Lock() 2289 defer s.mu.Unlock() 2290 // We should always get the builtin URI in a canonical form, so use simple 2291 // string comparison here. span.CompareURI is too expensive. 2292 return uri == s.builtin 2293 } 2294 2295 func (s *snapshot) setBuiltin(path string) { 2296 s.mu.Lock() 2297 defer s.mu.Unlock() 2298 2299 s.builtin = span.URIFromPath(path) 2300 } 2301 2302 // BuildGoplsMod generates a go.mod file for all modules in the workspace. It 2303 // bypasses any existing gopls.mod. 2304 func (s *snapshot) BuildGoplsMod(ctx context.Context) (*modfile.File, error) { 2305 allModules, err := findModules(s.view.folder, pathExcludedByFilterFunc(s.view.rootURI.Filename(), s.view.gomodcache, s.View().Options()), 0) 2306 if err != nil { 2307 return nil, err 2308 } 2309 return buildWorkspaceModFile(ctx, allModules, s) 2310 } 2311 2312 // TODO(rfindley): move this to workspacemodule.go 2313 func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) { 2314 file := &modfile.File{} 2315 file.AddModuleStmt("gopls-workspace") 2316 // Track the highest Go version, to be set on the workspace module. 2317 // Fall back to 1.12 -- old versions insist on having some version. 2318 goVersion := "1.12" 2319 2320 paths := map[string]span.URI{} 2321 excludes := map[string][]string{} 2322 var sortedModURIs []span.URI 2323 for uri := range modFiles { 2324 sortedModURIs = append(sortedModURIs, uri) 2325 } 2326 sort.Slice(sortedModURIs, func(i, j int) bool { 2327 return sortedModURIs[i] < sortedModURIs[j] 2328 }) 2329 for _, modURI := range sortedModURIs { 2330 fh, err := fs.GetFile(ctx, modURI) 2331 if err != nil { 2332 return nil, err 2333 } 2334 content, err := fh.Read() 2335 if err != nil { 2336 return nil, err 2337 } 2338 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 2339 if err != nil { 2340 return nil, err 2341 } 2342 if file == nil || parsed.Module == nil { 2343 return nil, fmt.Errorf("no module declaration for %s", modURI) 2344 } 2345 // Prepend "v" to go versions to make them valid semver. 2346 if parsed.Go != nil && semver.Compare("v"+goVersion, "v"+parsed.Go.Version) < 0 { 2347 goVersion = parsed.Go.Version 2348 } 2349 path := parsed.Module.Mod.Path 2350 if _, ok := paths[path]; ok { 2351 return nil, fmt.Errorf("found module %q twice in the workspace", path) 2352 } 2353 paths[path] = modURI 2354 // If the module's path includes a major version, we expect it to have 2355 // a matching major version. 2356 _, majorVersion, _ := module.SplitPathVersion(path) 2357 if majorVersion == "" { 2358 majorVersion = "/v0" 2359 } 2360 majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions 2361 file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false) 2362 if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil { 2363 return nil, err 2364 } 2365 for _, exclude := range parsed.Exclude { 2366 excludes[exclude.Mod.Path] = append(excludes[exclude.Mod.Path], exclude.Mod.Version) 2367 } 2368 } 2369 if goVersion != "" { 2370 file.AddGoStmt(goVersion) 2371 } 2372 // Go back through all of the modules to handle any of their replace 2373 // statements. 2374 for _, modURI := range sortedModURIs { 2375 fh, err := fs.GetFile(ctx, modURI) 2376 if err != nil { 2377 return nil, err 2378 } 2379 content, err := fh.Read() 2380 if err != nil { 2381 return nil, err 2382 } 2383 parsed, err := modfile.Parse(fh.URI().Filename(), content, nil) 2384 if err != nil { 2385 return nil, err 2386 } 2387 // If any of the workspace modules have replace directives, they need 2388 // to be reflected in the workspace module. 2389 for _, rep := range parsed.Replace { 2390 // Don't replace any modules that are in our workspace--we should 2391 // always use the version in the workspace. 2392 if _, ok := paths[rep.Old.Path]; ok { 2393 continue 2394 } 2395 newPath := rep.New.Path 2396 newVersion := rep.New.Version 2397 // If a replace points to a module in the workspace, make sure we 2398 // direct it to version of the module in the workspace. 2399 if m, ok := paths[rep.New.Path]; ok { 2400 newPath = dirURI(m).Filename() 2401 newVersion = "" 2402 } else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) { 2403 // Make any relative paths absolute. 2404 newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path) 2405 } 2406 if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil { 2407 return nil, err 2408 } 2409 } 2410 } 2411 for path, versions := range excludes { 2412 for _, version := range versions { 2413 file.AddExclude(path, version) 2414 } 2415 } 2416 file.SortBlocks() 2417 return file, nil 2418 } 2419 2420 func buildWorkspaceSumFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) ([]byte, error) { 2421 allSums := map[module.Version][]string{} 2422 for modURI := range modFiles { 2423 // TODO(rfindley): factor out this pattern into a uripath package. 2424 sumURI := span.URIFromPath(filepath.Join(filepath.Dir(modURI.Filename()), "go.sum")) 2425 fh, err := fs.GetFile(ctx, sumURI) 2426 if err != nil { 2427 continue 2428 } 2429 data, err := fh.Read() 2430 if os.IsNotExist(err) { 2431 continue 2432 } 2433 if err != nil { 2434 return nil, errors.Errorf("reading go sum: %w", err) 2435 } 2436 if err := readGoSum(allSums, sumURI.Filename(), data); err != nil { 2437 return nil, err 2438 } 2439 } 2440 // This logic to write go.sum is copied (with minor modifications) from 2441 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=631;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0 2442 var mods []module.Version 2443 for m := range allSums { 2444 mods = append(mods, m) 2445 } 2446 module.Sort(mods) 2447 2448 var buf bytes.Buffer 2449 for _, m := range mods { 2450 list := allSums[m] 2451 sort.Strings(list) 2452 // Note (rfindley): here we add all sum lines without verification, because 2453 // the assumption is that if they come from a go.sum file, they are 2454 // trusted. 2455 for _, h := range list { 2456 fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) 2457 } 2458 } 2459 return buf.Bytes(), nil 2460 } 2461 2462 // readGoSum is copied (with minor modifications) from 2463 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/modfetch/fetch.go;l=398;drc=762eda346a9f4062feaa8a9fc0d17d72b11586f0 2464 func readGoSum(dst map[module.Version][]string, file string, data []byte) error { 2465 lineno := 0 2466 for len(data) > 0 { 2467 var line []byte 2468 lineno++ 2469 i := bytes.IndexByte(data, '\n') 2470 if i < 0 { 2471 line, data = data, nil 2472 } else { 2473 line, data = data[:i], data[i+1:] 2474 } 2475 f := strings.Fields(string(line)) 2476 if len(f) == 0 { 2477 // blank line; skip it 2478 continue 2479 } 2480 if len(f) != 3 { 2481 return fmt.Errorf("malformed go.sum:\n%s:%d: wrong number of fields %v", file, lineno, len(f)) 2482 } 2483 mod := module.Version{Path: f[0], Version: f[1]} 2484 dst[mod] = append(dst[mod], f[2]) 2485 } 2486 return nil 2487 }