golang.org/x/tools/gopls@v0.15.3/internal/cache/view.go (about) 1 // Copyright 2018 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 is the core of gopls: it is concerned with state 6 // management, dependency analysis, and invalidation; and it holds the 7 // machinery of type checking and modular static analysis. Its 8 // principal types are [Session], [Folder], [View], [Snapshot], 9 // [Cache], and [Package]. 10 package cache 11 12 import ( 13 "bytes" 14 "context" 15 "encoding/json" 16 "errors" 17 "fmt" 18 "os" 19 "os/exec" 20 "path" 21 "path/filepath" 22 "regexp" 23 "sort" 24 "strings" 25 "sync" 26 "time" 27 28 "golang.org/x/tools/gopls/internal/cache/metadata" 29 "golang.org/x/tools/gopls/internal/file" 30 "golang.org/x/tools/gopls/internal/protocol" 31 "golang.org/x/tools/gopls/internal/settings" 32 "golang.org/x/tools/gopls/internal/util/maps" 33 "golang.org/x/tools/gopls/internal/util/pathutil" 34 "golang.org/x/tools/gopls/internal/util/slices" 35 "golang.org/x/tools/gopls/internal/vulncheck" 36 "golang.org/x/tools/internal/event" 37 "golang.org/x/tools/internal/gocommand" 38 "golang.org/x/tools/internal/imports" 39 "golang.org/x/tools/internal/xcontext" 40 ) 41 42 // A Folder represents an LSP workspace folder, together with its per-folder 43 // options and environment variables that affect build configuration. 44 // 45 // Folders (Name and Dir) are specified by the 'initialize' and subsequent 46 // 'didChangeWorkspaceFolders' requests; their options come from 47 // didChangeConfiguration. 48 // 49 // Folders must not be mutated, as they may be shared across multiple views. 50 type Folder struct { 51 Dir protocol.DocumentURI 52 Name string // decorative name for UI; not necessarily unique 53 Options *settings.Options 54 Env GoEnv 55 } 56 57 // GoEnv holds the environment variables and data from the Go command that is 58 // required for operating on a workspace folder. 59 type GoEnv struct { 60 // Go environment variables. These correspond directly with the Go env var of 61 // the same name. 62 GOOS string 63 GOARCH string 64 GOCACHE string 65 GOMODCACHE string 66 GOPATH string 67 GOPRIVATE string 68 GOFLAGS string 69 GO111MODULE string 70 71 // Go version output. 72 GoVersion int // The X in Go 1.X 73 GoVersionOutput string // complete go version output 74 75 // OS environment variables (notably not go env). 76 GOWORK string 77 GOPACKAGESDRIVER string 78 } 79 80 // View represents a single build for a workspace. 81 // 82 // A View is a logical build (the viewDefinition) along with a state of that 83 // build (the Snapshot). 84 type View struct { 85 id string // a unique string to identify this View in (e.g.) serialized Commands 86 87 *viewDefinition // build configuration 88 89 gocmdRunner *gocommand.Runner // limits go command concurrency 90 91 // baseCtx is the context handed to NewView. This is the parent of all 92 // background contexts created for this view. 93 baseCtx context.Context 94 95 importsState *importsState 96 97 // parseCache holds an LRU cache of recently parsed files. 98 parseCache *parseCache 99 100 // fs is the file source used to populate this view. 101 fs *overlayFS 102 103 // ignoreFilter is used for fast checking of ignored files. 104 ignoreFilter *ignoreFilter 105 106 // cancelInitialWorkspaceLoad can be used to terminate the view's first 107 // attempt at initialization. 108 cancelInitialWorkspaceLoad context.CancelFunc 109 110 snapshotMu sync.Mutex 111 snapshot *Snapshot // latest snapshot; nil after shutdown has been called 112 113 // initialWorkspaceLoad is closed when the first workspace initialization has 114 // completed. If we failed to load, we only retry if the go.mod file changes, 115 // to avoid too many go/packages calls. 116 initialWorkspaceLoad chan struct{} 117 118 // initializationSema is used limit concurrent initialization of snapshots in 119 // the view. We use a channel instead of a mutex to avoid blocking when a 120 // context is canceled. 121 // 122 // This field (along with snapshot.initialized) guards against duplicate 123 // initialization of snapshots. Do not change it without adjusting snapshot 124 // accordingly. 125 initializationSema chan struct{} 126 127 // Document filters are constructed once, in View.filterFunc. 128 filterFuncOnce sync.Once 129 _filterFunc func(protocol.DocumentURI) bool // only accessed by View.filterFunc 130 } 131 132 // definition implements the viewDefiner interface. 133 func (v *View) definition() *viewDefinition { return v.viewDefinition } 134 135 // A viewDefinition is a logical build, i.e. configuration (Folder) along with 136 // a build directory and possibly an environment overlay (e.g. GOWORK=off or 137 // GOOS, GOARCH=...) to affect the build. 138 // 139 // This type is immutable, and compared to see if the View needs to be 140 // reconstructed. 141 // 142 // Note: whenever modifying this type, also modify the equivalence relation 143 // implemented by viewDefinitionsEqual. 144 // 145 // TODO(golang/go#57979): viewDefinition should be sufficient for running 146 // go/packages. Enforce this in the API. 147 type viewDefinition struct { 148 folder *Folder // pointer comparison is OK, as any new Folder creates a new def 149 150 typ ViewType 151 root protocol.DocumentURI // root directory; where to run the Go command 152 gomod protocol.DocumentURI // the nearest go.mod file, or "" 153 gowork protocol.DocumentURI // the nearest go.work file, or "" 154 155 // workspaceModFiles holds the set of mod files active in this snapshot. 156 // 157 // For a go.work workspace, this is the set of workspace modfiles. For a 158 // go.mod workspace, this contains the go.mod file defining the workspace 159 // root, as well as any locally replaced modules (if 160 // "includeReplaceInWorkspace" is set). 161 // 162 // TODO(rfindley): should we just run `go list -m` to compute this set? 163 workspaceModFiles map[protocol.DocumentURI]struct{} 164 workspaceModFilesErr error // error encountered computing workspaceModFiles 165 166 // envOverlay holds additional environment to apply to this viewDefinition. 167 envOverlay map[string]string 168 } 169 170 // definition implements the viewDefiner interface. 171 func (d *viewDefinition) definition() *viewDefinition { return d } 172 173 // Type returns the ViewType type, which determines how go/packages are loaded 174 // for this View. 175 func (d *viewDefinition) Type() ViewType { return d.typ } 176 177 // Root returns the view root, which determines where packages are loaded from. 178 func (d *viewDefinition) Root() protocol.DocumentURI { return d.root } 179 180 // GoMod returns the nearest go.mod file for this view's root, or "". 181 func (d *viewDefinition) GoMod() protocol.DocumentURI { return d.gomod } 182 183 // GoWork returns the nearest go.work file for this view's root, or "". 184 func (d *viewDefinition) GoWork() protocol.DocumentURI { return d.gowork } 185 186 // EnvOverlay returns a new sorted slice of environment variables (in the form 187 // "k=v") for this view definition's env overlay. 188 func (d *viewDefinition) EnvOverlay() []string { 189 var env []string 190 for k, v := range d.envOverlay { 191 env = append(env, fmt.Sprintf("%s=%s", k, v)) 192 } 193 sort.Strings(env) 194 return env 195 } 196 197 // GOOS returns the effective GOOS value for this view definition, accounting 198 // for its env overlay. 199 func (d *viewDefinition) GOOS() string { 200 if goos, ok := d.envOverlay["GOOS"]; ok { 201 return goos 202 } 203 return d.folder.Env.GOOS 204 } 205 206 // GOOS returns the effective GOARCH value for this view definition, accounting 207 // for its env overlay. 208 func (d *viewDefinition) GOARCH() string { 209 if goarch, ok := d.envOverlay["GOARCH"]; ok { 210 return goarch 211 } 212 return d.folder.Env.GOARCH 213 } 214 215 // adjustedGO111MODULE is the value of GO111MODULE to use for loading packages. 216 // It is adjusted to default to "auto" rather than "on", since if we are in 217 // GOPATH and have no module, we may as well allow a GOPATH view to work. 218 func (d viewDefinition) adjustedGO111MODULE() string { 219 if d.folder.Env.GO111MODULE != "" { 220 return d.folder.Env.GO111MODULE 221 } 222 return "auto" 223 } 224 225 // ModFiles are the go.mod files enclosed in the snapshot's view and known 226 // to the snapshot. 227 func (d viewDefinition) ModFiles() []protocol.DocumentURI { 228 var uris []protocol.DocumentURI 229 for modURI := range d.workspaceModFiles { 230 uris = append(uris, modURI) 231 } 232 return uris 233 } 234 235 // viewDefinitionsEqual reports whether x and y are equivalent. 236 func viewDefinitionsEqual(x, y *viewDefinition) bool { 237 if (x.workspaceModFilesErr == nil) != (y.workspaceModFilesErr == nil) { 238 return false 239 } 240 if x.workspaceModFilesErr != nil { 241 if x.workspaceModFilesErr.Error() != y.workspaceModFilesErr.Error() { 242 return false 243 } 244 } else if !maps.SameKeys(x.workspaceModFiles, y.workspaceModFiles) { 245 return false 246 } 247 if len(x.envOverlay) != len(y.envOverlay) { 248 return false 249 } 250 for i, xv := range x.envOverlay { 251 if xv != y.envOverlay[i] { 252 return false 253 } 254 } 255 return x.folder == y.folder && 256 x.typ == y.typ && 257 x.root == y.root && 258 x.gomod == y.gomod && 259 x.gowork == y.gowork 260 } 261 262 // A ViewType describes how we load package information for a view. 263 // 264 // This is used for constructing the go/packages.Load query, and for 265 // interpreting missing packages, imports, or errors. 266 // 267 // See the documentation for individual ViewType values for details. 268 type ViewType int 269 270 const ( 271 // GoPackagesDriverView is a view with a non-empty GOPACKAGESDRIVER 272 // environment variable. 273 // 274 // Load: ./... from the workspace folder. 275 GoPackagesDriverView ViewType = iota 276 277 // GOPATHView is a view in GOPATH mode. 278 // 279 // I.e. in GOPATH, with GO111MODULE=off, or GO111MODULE=auto with no 280 // go.mod file. 281 // 282 // Load: ./... from the workspace folder. 283 GOPATHView 284 285 // GoModView is a view in module mode with a single Go module. 286 // 287 // Load: <modulePath>/... from the module root. 288 GoModView 289 290 // GoWorkView is a view in module mode with a go.work file. 291 // 292 // Load: <modulePath>/... from the workspace folder, for each module. 293 GoWorkView 294 295 // An AdHocView is a collection of files in a given directory, not in GOPATH 296 // or a module. 297 // 298 // Load: . from the workspace folder. 299 AdHocView 300 ) 301 302 func (t ViewType) String() string { 303 switch t { 304 case GoPackagesDriverView: 305 return "GoPackagesDriverView" 306 case GOPATHView: 307 return "GOPATHView" 308 case GoModView: 309 return "GoModView" 310 case GoWorkView: 311 return "GoWorkView" 312 case AdHocView: 313 return "AdHocView" 314 default: 315 return "Unknown" 316 } 317 } 318 319 // moduleMode reports whether the view uses Go modules. 320 func (w viewDefinition) moduleMode() bool { 321 switch w.typ { 322 case GoModView, GoWorkView: 323 return true 324 default: 325 return false 326 } 327 } 328 329 func (v *View) ID() string { return v.id } 330 331 // tempModFile creates a temporary go.mod file based on the contents 332 // of the given go.mod file. On success, it is the caller's 333 // responsibility to call the cleanup function when the file is no 334 // longer needed. 335 func tempModFile(modURI protocol.DocumentURI, gomod, gosum []byte) (tmpURI protocol.DocumentURI, cleanup func(), err error) { 336 filenameHash := file.HashOf([]byte(modURI.Path())) 337 tmpMod, err := os.CreateTemp("", fmt.Sprintf("go.%s.*.mod", filenameHash)) 338 if err != nil { 339 return "", nil, err 340 } 341 defer tmpMod.Close() 342 343 tmpURI = protocol.URIFromPath(tmpMod.Name()) 344 tmpSumName := sumFilename(tmpURI) 345 346 if _, err := tmpMod.Write(gomod); err != nil { 347 return "", nil, err 348 } 349 350 // We use a distinct name here to avoid subtlety around the fact 351 // that both 'return' and 'defer' update the "cleanup" variable. 352 doCleanup := func() { 353 _ = os.Remove(tmpSumName) 354 _ = os.Remove(tmpURI.Path()) 355 } 356 357 // Be careful to clean up if we return an error from this function. 358 defer func() { 359 if err != nil { 360 doCleanup() 361 cleanup = nil 362 } 363 }() 364 365 // Create an analogous go.sum, if one exists. 366 if gosum != nil { 367 if err := os.WriteFile(tmpSumName, gosum, 0655); err != nil { 368 return "", nil, err 369 } 370 } 371 372 return tmpURI, doCleanup, nil 373 } 374 375 // Folder returns the folder at the base of this view. 376 func (v *View) Folder() *Folder { 377 return v.folder 378 } 379 380 // UpdateFolders updates the set of views for the new folders. 381 // 382 // Calling this causes each view to be reinitialized. 383 func (s *Session) UpdateFolders(ctx context.Context, newFolders []*Folder) error { 384 s.viewMu.Lock() 385 defer s.viewMu.Unlock() 386 387 overlays := s.Overlays() 388 var openFiles []protocol.DocumentURI 389 for _, o := range overlays { 390 openFiles = append(openFiles, o.URI()) 391 } 392 393 defs, err := selectViewDefs(ctx, s, newFolders, openFiles) 394 if err != nil { 395 return err 396 } 397 var newViews []*View 398 for _, def := range defs { 399 v, _, release := s.createView(ctx, def) 400 release() 401 newViews = append(newViews, v) 402 } 403 for _, v := range s.views { 404 v.shutdown() 405 } 406 s.views = newViews 407 return nil 408 } 409 410 // viewEnv returns a string describing the environment of a newly created view. 411 // 412 // It must not be called concurrently with any other view methods. 413 // TODO(rfindley): rethink this function, or inline sole call. 414 func viewEnv(v *View) string { 415 var buf bytes.Buffer 416 fmt.Fprintf(&buf, `go info for %v 417 (view type %v) 418 (root dir %s) 419 (go version %s) 420 (build flags: %v) 421 (go env: %+v) 422 (env overlay: %v) 423 `, 424 v.folder.Dir.Path(), 425 v.typ, 426 v.root.Path(), 427 strings.TrimRight(v.folder.Env.GoVersionOutput, "\n"), 428 v.folder.Options.BuildFlags, 429 v.folder.Env, 430 v.envOverlay, 431 ) 432 433 return buf.String() 434 } 435 436 // RunProcessEnvFunc runs fn with the process env for this snapshot's view. 437 // Note: the process env contains cached module and filesystem state. 438 func (s *Snapshot) RunProcessEnvFunc(ctx context.Context, fn func(context.Context, *imports.Options) error) error { 439 return s.view.importsState.runProcessEnvFunc(ctx, s, fn) 440 } 441 442 // separated out from its sole use in locateTemplateFiles for testability 443 func fileHasExtension(path string, suffixes []string) bool { 444 ext := filepath.Ext(path) 445 if ext != "" && ext[0] == '.' { 446 ext = ext[1:] 447 } 448 for _, s := range suffixes { 449 if s != "" && ext == s { 450 return true 451 } 452 } 453 return false 454 } 455 456 // locateTemplateFiles ensures that the snapshot has mapped template files 457 // within the workspace folder. 458 func (s *Snapshot) locateTemplateFiles(ctx context.Context) { 459 suffixes := s.Options().TemplateExtensions 460 if len(suffixes) == 0 { 461 return 462 } 463 464 searched := 0 465 filterFunc := s.view.filterFunc() 466 err := filepath.WalkDir(s.view.folder.Dir.Path(), func(path string, entry os.DirEntry, err error) error { 467 if err != nil { 468 return err 469 } 470 if entry.IsDir() { 471 return nil 472 } 473 if fileLimit > 0 && searched > fileLimit { 474 return errExhausted 475 } 476 searched++ 477 if !fileHasExtension(path, suffixes) { 478 return nil 479 } 480 uri := protocol.URIFromPath(path) 481 if filterFunc(uri) { 482 return nil 483 } 484 // Get the file in order to include it in the snapshot. 485 // TODO(golang/go#57558): it is fundamentally broken to track files in this 486 // way; we may lose them if configuration or layout changes cause a view to 487 // be recreated. 488 // 489 // Furthermore, this operation must ignore errors, including context 490 // cancellation, or risk leaving the snapshot in an undefined state. 491 s.ReadFile(ctx, uri) 492 return nil 493 }) 494 if err != nil { 495 event.Error(ctx, "searching for template files failed", err) 496 } 497 } 498 499 // filterFunc returns a func that reports whether uri is filtered by the currently configured 500 // directoryFilters. 501 func (v *View) filterFunc() func(protocol.DocumentURI) bool { 502 v.filterFuncOnce.Do(func() { 503 folderDir := v.folder.Dir.Path() 504 gomodcache := v.folder.Env.GOMODCACHE 505 var filters []string 506 filters = append(filters, v.folder.Options.DirectoryFilters...) 507 if pref := strings.TrimPrefix(gomodcache, folderDir); pref != gomodcache { 508 modcacheFilter := "-" + strings.TrimPrefix(filepath.ToSlash(pref), "/") 509 filters = append(filters, modcacheFilter) 510 } 511 filterer := NewFilterer(filters) 512 v._filterFunc = func(uri protocol.DocumentURI) bool { 513 // Only filter relative to the configured root directory. 514 if pathutil.InDir(folderDir, uri.Path()) { 515 return relPathExcludedByFilter(strings.TrimPrefix(uri.Path(), folderDir), filterer) 516 } 517 return false 518 } 519 }) 520 return v._filterFunc 521 } 522 523 // shutdown releases resources associated with the view. 524 func (v *View) shutdown() { 525 // Cancel the initial workspace load if it is still running. 526 v.cancelInitialWorkspaceLoad() 527 528 v.snapshotMu.Lock() 529 if v.snapshot != nil { 530 v.snapshot.cancel() 531 v.snapshot.decref() 532 v.snapshot = nil 533 } 534 v.snapshotMu.Unlock() 535 } 536 537 // IgnoredFile reports if a file would be ignored by a `go list` of the whole 538 // workspace. 539 // 540 // While go list ./... skips directories starting with '.', '_', or 'testdata', 541 // gopls may still load them via file queries. Explicitly filter them out. 542 func (s *Snapshot) IgnoredFile(uri protocol.DocumentURI) bool { 543 // Fast path: if uri doesn't contain '.', '_', or 'testdata', it is not 544 // possible that it is ignored. 545 { 546 uriStr := string(uri) 547 if !strings.Contains(uriStr, ".") && !strings.Contains(uriStr, "_") && !strings.Contains(uriStr, "testdata") { 548 return false 549 } 550 } 551 552 return s.view.ignoreFilter.ignored(uri.Path()) 553 } 554 555 // An ignoreFilter implements go list's exclusion rules via its 'ignored' method. 556 type ignoreFilter struct { 557 prefixes []string // root dirs, ending in filepath.Separator 558 } 559 560 // newIgnoreFilter returns a new ignoreFilter implementing exclusion rules 561 // relative to the provided directories. 562 func newIgnoreFilter(dirs []string) *ignoreFilter { 563 f := new(ignoreFilter) 564 for _, d := range dirs { 565 f.prefixes = append(f.prefixes, filepath.Clean(d)+string(filepath.Separator)) 566 } 567 return f 568 } 569 570 func (f *ignoreFilter) ignored(filename string) bool { 571 for _, prefix := range f.prefixes { 572 if suffix := strings.TrimPrefix(filename, prefix); suffix != filename { 573 if checkIgnored(suffix) { 574 return true 575 } 576 } 577 } 578 return false 579 } 580 581 // checkIgnored implements go list's exclusion rules. 582 // Quoting “go help list”: 583 // 584 // Directory and file names that begin with "." or "_" are ignored 585 // by the go tool, as are directories named "testdata". 586 func checkIgnored(suffix string) bool { 587 // Note: this could be further optimized by writing a HasSegment helper, a 588 // segment-boundary respecting variant of strings.Contains. 589 for _, component := range strings.Split(suffix, string(filepath.Separator)) { 590 if len(component) == 0 { 591 continue 592 } 593 if component[0] == '.' || component[0] == '_' || component == "testdata" { 594 return true 595 } 596 } 597 return false 598 } 599 600 // Snapshot returns the current snapshot for the view, and a 601 // release function that must be called when the Snapshot is 602 // no longer needed. 603 // 604 // The resulting error is non-nil if and only if the view is shut down, in 605 // which case the resulting release function will also be nil. 606 func (v *View) Snapshot() (*Snapshot, func(), error) { 607 v.snapshotMu.Lock() 608 defer v.snapshotMu.Unlock() 609 if v.snapshot == nil { 610 return nil, nil, errors.New("view is shutdown") 611 } 612 return v.snapshot, v.snapshot.Acquire(), nil 613 } 614 615 // initialize loads the metadata (and currently, file contents, due to 616 // golang/go#57558) for the main package query of the View, which depends on 617 // the view type (see ViewType). If s.initialized is already true, initialize 618 // is a no op. 619 // 620 // The first attempt--which populates the first snapshot for a new view--must 621 // be allowed to run to completion without being cancelled. 622 // 623 // Subsequent attempts are triggered by conditions where gopls can't enumerate 624 // specific packages that require reloading, such as a change to a go.mod file. 625 // These attempts may be cancelled, and then retried by a later call. 626 // 627 // Postcondition: if ctx was not cancelled, s.initialized is true, s.initialErr 628 // holds the error resulting from initialization, if any, and s.metadata holds 629 // the resulting metadata graph. 630 func (s *Snapshot) initialize(ctx context.Context, firstAttempt bool) { 631 // Acquire initializationSema, which is 632 // (in effect) a mutex with a timeout. 633 select { 634 case <-ctx.Done(): 635 return 636 case s.view.initializationSema <- struct{}{}: 637 } 638 639 defer func() { 640 <-s.view.initializationSema 641 }() 642 643 s.mu.Lock() 644 initialized := s.initialized 645 s.mu.Unlock() 646 647 if initialized { 648 return 649 } 650 651 defer func() { 652 if firstAttempt { 653 close(s.view.initialWorkspaceLoad) 654 } 655 }() 656 657 // TODO(rFindley): we should only locate template files on the first attempt, 658 // or guard it via a different mechanism. 659 s.locateTemplateFiles(ctx) 660 661 // Collect module paths to load by parsing go.mod files. If a module fails to 662 // parse, capture the parsing failure as a critical diagnostic. 663 var scopes []loadScope // scopes to load 664 var modDiagnostics []*Diagnostic // diagnostics for broken go.mod files 665 addError := func(uri protocol.DocumentURI, err error) { 666 modDiagnostics = append(modDiagnostics, &Diagnostic{ 667 URI: uri, 668 Severity: protocol.SeverityError, 669 Source: ListError, 670 Message: err.Error(), 671 }) 672 } 673 674 if len(s.view.workspaceModFiles) > 0 { 675 for modURI := range s.view.workspaceModFiles { 676 // Verify that the modfile is valid before trying to load it. 677 // 678 // TODO(rfindley): now that we no longer need to parse the modfile in 679 // order to load scope, we could move these diagnostics to a more general 680 // location where we diagnose problems with modfiles or the workspace. 681 // 682 // Be careful not to add context cancellation errors as critical module 683 // errors. 684 fh, err := s.ReadFile(ctx, modURI) 685 if err != nil { 686 if ctx.Err() != nil { 687 return 688 } 689 addError(modURI, err) 690 continue 691 } 692 parsed, err := s.ParseMod(ctx, fh) 693 if err != nil { 694 if ctx.Err() != nil { 695 return 696 } 697 addError(modURI, err) 698 continue 699 } 700 if parsed.File == nil || parsed.File.Module == nil { 701 addError(modURI, fmt.Errorf("no module path for %s", modURI)) 702 continue 703 } 704 moduleDir := filepath.Dir(modURI.Path()) 705 // Previously, we loaded <modulepath>/... for each module path, but that 706 // is actually incorrect when the pattern may match packages in more than 707 // one module. See golang/go#59458 for more details. 708 scopes = append(scopes, moduleLoadScope{dir: moduleDir, modulePath: parsed.File.Module.Mod.Path}) 709 } 710 } else { 711 scopes = append(scopes, viewLoadScope{}) 712 } 713 714 // If we're loading anything, ensure we also load builtin, 715 // since it provides fake definitions (and documentation) 716 // for types like int that are used everywhere. 717 if len(scopes) > 0 { 718 scopes = append(scopes, packageLoadScope("builtin")) 719 } 720 loadErr := s.load(ctx, true, scopes...) 721 722 // A failure is retryable if it may have been due to context cancellation, 723 // and this is not the initial workspace load (firstAttempt==true). 724 // 725 // The IWL runs on a detached context with a long (~10m) timeout, so 726 // if the context was canceled we consider loading to have failed 727 // permanently. 728 if loadErr != nil && ctx.Err() != nil && !firstAttempt { 729 return 730 } 731 732 var initialErr *InitializationError 733 switch { 734 case loadErr != nil && ctx.Err() != nil: 735 event.Error(ctx, fmt.Sprintf("initial workspace load: %v", loadErr), loadErr) 736 initialErr = &InitializationError{ 737 MainError: loadErr, 738 } 739 case loadErr != nil: 740 event.Error(ctx, "initial workspace load failed", loadErr) 741 extractedDiags := s.extractGoCommandErrors(ctx, loadErr) 742 initialErr = &InitializationError{ 743 MainError: loadErr, 744 Diagnostics: maps.Group(extractedDiags, byURI), 745 } 746 case s.view.workspaceModFilesErr != nil: 747 initialErr = &InitializationError{ 748 MainError: s.view.workspaceModFilesErr, 749 } 750 case len(modDiagnostics) > 0: 751 initialErr = &InitializationError{ 752 MainError: fmt.Errorf(modDiagnostics[0].Message), 753 } 754 } 755 756 s.mu.Lock() 757 defer s.mu.Unlock() 758 759 s.initialized = true 760 s.initialErr = initialErr 761 } 762 763 // A StateChange describes external state changes that may affect a snapshot. 764 // 765 // By far the most common of these is a change to file state, but a query of 766 // module upgrade information or vulnerabilities also affects gopls' behavior. 767 type StateChange struct { 768 Modifications []file.Modification // if set, the raw modifications originating this change 769 Files map[protocol.DocumentURI]file.Handle 770 ModuleUpgrades map[protocol.DocumentURI]map[string]string 771 Vulns map[protocol.DocumentURI]*vulncheck.Result 772 GCDetails map[metadata.PackageID]bool // package -> whether or not we want details 773 } 774 775 // InvalidateView processes the provided state change, invalidating any derived 776 // results that depend on the changed state. 777 // 778 // The resulting snapshot is non-nil, representing the outcome of the state 779 // change. The second result is a function that must be called to release the 780 // snapshot when the snapshot is no longer needed. 781 // 782 // An error is returned if the given view is no longer active in the session. 783 func (s *Session) InvalidateView(ctx context.Context, view *View, changed StateChange) (*Snapshot, func(), error) { 784 s.viewMu.Lock() 785 defer s.viewMu.Unlock() 786 787 if !slices.Contains(s.views, view) { 788 return nil, nil, fmt.Errorf("view is no longer active") 789 } 790 snapshot, release, _ := s.invalidateViewLocked(ctx, view, changed) 791 return snapshot, release, nil 792 } 793 794 // invalidateViewLocked invalidates the content of the given view. 795 // (See [Session.InvalidateView]). 796 // 797 // The resulting bool reports whether the View needs to be re-diagnosed. 798 // (See [Snapshot.clone]). 799 // 800 // s.viewMu must be held while calling this method. 801 func (s *Session) invalidateViewLocked(ctx context.Context, v *View, changed StateChange) (*Snapshot, func(), bool) { 802 // Detach the context so that content invalidation cannot be canceled. 803 ctx = xcontext.Detach(ctx) 804 805 // This should be the only time we hold the view's snapshot lock for any period of time. 806 v.snapshotMu.Lock() 807 defer v.snapshotMu.Unlock() 808 809 prevSnapshot := v.snapshot 810 811 if prevSnapshot == nil { 812 panic("invalidateContent called after shutdown") 813 } 814 815 // Cancel all still-running previous requests, since they would be 816 // operating on stale data. 817 prevSnapshot.cancel() 818 819 // Do not clone a snapshot until its view has finished initializing. 820 // 821 // TODO(rfindley): shouldn't we do this before canceling? 822 prevSnapshot.AwaitInitialized(ctx) 823 824 var needsDiagnosis bool 825 s.snapshotWG.Add(1) 826 v.snapshot, needsDiagnosis = prevSnapshot.clone(ctx, v.baseCtx, changed, s.snapshotWG.Done) 827 828 // Remove the initial reference created when prevSnapshot was created. 829 prevSnapshot.decref() 830 831 // Return a second lease to the caller. 832 return v.snapshot, v.snapshot.Acquire(), needsDiagnosis 833 } 834 835 // defineView computes the view definition for the provided workspace folder 836 // and URI. 837 // 838 // If forURI is non-empty, this view should be the best view including forURI. 839 // Otherwise, it is the default view for the folder. 840 // 841 // defineView only returns an error in the event of context cancellation. 842 // 843 // Note: keep this function in sync with bestView. 844 // 845 // TODO(rfindley): we should be able to remove the error return, as 846 // findModules is going away, and all other I/O is memoized. 847 // 848 // TODO(rfindley): pass in a narrower interface for the file.Source 849 // (e.g. fileExists func(DocumentURI) bool) to make clear that this 850 // process depends only on directory information, not file contents. 851 func defineView(ctx context.Context, fs file.Source, folder *Folder, forFile file.Handle) (*viewDefinition, error) { 852 if err := checkPathValid(folder.Dir.Path()); err != nil { 853 return nil, fmt.Errorf("invalid workspace folder path: %w; check that the spelling of the configured workspace folder path agrees with the spelling reported by the operating system", err) 854 } 855 dir := folder.Dir.Path() 856 if forFile != nil { 857 dir = filepath.Dir(forFile.URI().Path()) 858 } 859 860 def := new(viewDefinition) 861 def.folder = folder 862 863 if forFile != nil && fileKind(forFile) == file.Go { 864 // If the file has GOOS/GOARCH build constraints that 865 // don't match the folder's environment (which comes from 866 // 'go env' in the folder, plus user options), 867 // add those constraints to the viewDefinition's environment. 868 869 // Content trimming is nontrivial, so do this outside of the loop below. 870 // Keep this in sync with bestView. 871 path := forFile.URI().Path() 872 if content, err := forFile.Content(); err == nil { 873 // Note the err == nil condition above: by convention a non-existent file 874 // does not have any constraints. See the related note in bestView: this 875 // choice of behavior shouldn't actually matter. In this case, we should 876 // only call defineView with Overlays, which always have content. 877 content = trimContentForPortMatch(content) 878 viewPort := port{def.folder.Env.GOOS, def.folder.Env.GOARCH} 879 if !viewPort.matches(path, content) { 880 for _, p := range preferredPorts { 881 if p.matches(path, content) { 882 if def.envOverlay == nil { 883 def.envOverlay = make(map[string]string) 884 } 885 def.envOverlay["GOOS"] = p.GOOS 886 def.envOverlay["GOARCH"] = p.GOARCH 887 break 888 } 889 } 890 } 891 } 892 } 893 894 var err error 895 dirURI := protocol.URIFromPath(dir) 896 goworkFromEnv := false 897 if folder.Env.GOWORK != "off" && folder.Env.GOWORK != "" { 898 goworkFromEnv = true 899 def.gowork = protocol.URIFromPath(folder.Env.GOWORK) 900 } else { 901 def.gowork, err = findRootPattern(ctx, dirURI, "go.work", fs) 902 if err != nil { 903 return nil, err 904 } 905 } 906 907 // When deriving the best view for a given file, we only want to search 908 // up the directory hierarchy for modfiles. 909 def.gomod, err = findRootPattern(ctx, dirURI, "go.mod", fs) 910 if err != nil { 911 return nil, err 912 } 913 914 // Determine how we load and where to load package information for this view 915 // 916 // Specifically, set 917 // - def.typ 918 // - def.root 919 // - def.workspaceModFiles, and 920 // - def.envOverlay. 921 922 // If GOPACKAGESDRIVER is set it takes precedence. 923 { 924 // The value of GOPACKAGESDRIVER is not returned through the go command. 925 gopackagesdriver := os.Getenv("GOPACKAGESDRIVER") 926 // A user may also have a gopackagesdriver binary on their machine, which 927 // works the same way as setting GOPACKAGESDRIVER. 928 // 929 // TODO(rfindley): remove this call to LookPath. We should not support this 930 // undocumented method of setting GOPACKAGESDRIVER. 931 tool, err := exec.LookPath("gopackagesdriver") 932 if gopackagesdriver != "off" && (gopackagesdriver != "" || (err == nil && tool != "")) { 933 def.typ = GoPackagesDriverView 934 def.root = dirURI 935 return def, nil 936 } 937 } 938 939 // From go.dev/ref/mod, module mode is active if GO111MODULE=on, or 940 // GO111MODULE=auto or "" and we are inside a module or have a GOWORK value. 941 // But gopls is less strict, allowing GOPATH mode if GO111MODULE="", and 942 // AdHoc views if no module is found. 943 944 // gomodWorkspace is a helper to compute the correct set of workspace 945 // modfiles for a go.mod file, based on folder options. 946 gomodWorkspace := func() map[protocol.DocumentURI]unit { 947 modFiles := map[protocol.DocumentURI]struct{}{def.gomod: {}} 948 if folder.Options.IncludeReplaceInWorkspace { 949 includingReplace, err := goModModules(ctx, def.gomod, fs) 950 if err == nil { 951 modFiles = includingReplace 952 } else { 953 // If the go.mod file fails to parse, we don't know anything about 954 // replace directives, so fall back to a view of just the root module. 955 } 956 } 957 return modFiles 958 } 959 960 // Prefer a go.work file if it is available and contains the module relevant 961 // to forURI. 962 if def.adjustedGO111MODULE() != "off" && folder.Env.GOWORK != "off" && def.gowork != "" { 963 def.typ = GoWorkView 964 if goworkFromEnv { 965 // The go.work file could be anywhere, which can lead to confusing error 966 // messages. 967 def.root = dirURI 968 } else { 969 // The go.work file could be anywhere, which can lead to confusing error 970 def.root = def.gowork.Dir() 971 } 972 def.workspaceModFiles, def.workspaceModFilesErr = goWorkModules(ctx, def.gowork, fs) 973 974 // If forURI is in a module but that module is not 975 // included in the go.work file, use a go.mod view with GOWORK=off. 976 if forFile != nil && def.workspaceModFilesErr == nil && def.gomod != "" { 977 if _, ok := def.workspaceModFiles[def.gomod]; !ok { 978 def.typ = GoModView 979 def.root = def.gomod.Dir() 980 def.workspaceModFiles = gomodWorkspace() 981 if def.envOverlay == nil { 982 def.envOverlay = make(map[string]string) 983 } 984 def.envOverlay["GOWORK"] = "off" 985 } 986 } 987 return def, nil 988 } 989 990 // Otherwise, use the active module, if in module mode. 991 // 992 // Note, we could override GO111MODULE here via envOverlay if we wanted to 993 // support the case where someone opens a module with GO111MODULE=off. But 994 // that is probably not worth worrying about (at this point, folks probably 995 // shouldn't be setting GO111MODULE). 996 if def.adjustedGO111MODULE() != "off" && def.gomod != "" { 997 def.typ = GoModView 998 def.root = def.gomod.Dir() 999 def.workspaceModFiles = gomodWorkspace() 1000 return def, nil 1001 } 1002 1003 // Check if the workspace is within any GOPATH directory. 1004 inGOPATH := false 1005 for _, gp := range filepath.SplitList(folder.Env.GOPATH) { 1006 if pathutil.InDir(filepath.Join(gp, "src"), dir) { 1007 inGOPATH = true 1008 break 1009 } 1010 } 1011 if def.adjustedGO111MODULE() != "on" && inGOPATH { 1012 def.typ = GOPATHView 1013 def.root = dirURI 1014 return def, nil 1015 } 1016 1017 // We're not in a workspace, module, or GOPATH, so have no better choice than 1018 // an ad-hoc view. 1019 def.typ = AdHocView 1020 def.root = dirURI 1021 return def, nil 1022 } 1023 1024 // FetchGoEnv queries the environment and Go command to collect environment 1025 // variables necessary for the workspace folder. 1026 func FetchGoEnv(ctx context.Context, folder protocol.DocumentURI, opts *settings.Options) (*GoEnv, error) { 1027 dir := folder.Path() 1028 // All of the go commands invoked here should be fast. No need to share a 1029 // runner with other operations. 1030 runner := new(gocommand.Runner) 1031 inv := gocommand.Invocation{ 1032 WorkingDir: dir, 1033 Env: opts.EnvSlice(), 1034 } 1035 1036 var ( 1037 env = new(GoEnv) 1038 err error 1039 ) 1040 envvars := map[string]*string{ 1041 "GOOS": &env.GOOS, 1042 "GOARCH": &env.GOARCH, 1043 "GOCACHE": &env.GOCACHE, 1044 "GOPATH": &env.GOPATH, 1045 "GOPRIVATE": &env.GOPRIVATE, 1046 "GOMODCACHE": &env.GOMODCACHE, 1047 "GOFLAGS": &env.GOFLAGS, 1048 "GO111MODULE": &env.GO111MODULE, 1049 } 1050 if err := loadGoEnv(ctx, dir, opts.EnvSlice(), runner, envvars); err != nil { 1051 return nil, err 1052 } 1053 1054 env.GoVersion, err = gocommand.GoVersion(ctx, inv, runner) 1055 if err != nil { 1056 return nil, err 1057 } 1058 env.GoVersionOutput, err = gocommand.GoVersionOutput(ctx, inv, runner) 1059 if err != nil { 1060 return nil, err 1061 } 1062 1063 // The value of GOPACKAGESDRIVER is not returned through the go command. 1064 if driver, ok := opts.Env["GOPACKAGESDRIVER"]; ok { 1065 env.GOPACKAGESDRIVER = driver 1066 } else { 1067 env.GOPACKAGESDRIVER = os.Getenv("GOPACKAGESDRIVER") 1068 // A user may also have a gopackagesdriver binary on their machine, which 1069 // works the same way as setting GOPACKAGESDRIVER. 1070 // 1071 // TODO(rfindley): remove this call to LookPath. We should not support this 1072 // undocumented method of setting GOPACKAGESDRIVER. 1073 if env.GOPACKAGESDRIVER == "" { 1074 tool, err := exec.LookPath("gopackagesdriver") 1075 if err == nil && tool != "" { 1076 env.GOPACKAGESDRIVER = tool 1077 } 1078 } 1079 } 1080 1081 // While GOWORK is available through the Go command, we want to differentiate 1082 // between an explicit GOWORK value and one which is implicit from the file 1083 // system. The former doesn't change unless the environment changes. 1084 if gowork, ok := opts.Env["GOWORK"]; ok { 1085 env.GOWORK = gowork 1086 } else { 1087 env.GOWORK = os.Getenv("GOWORK") 1088 } 1089 return env, nil 1090 } 1091 1092 // loadGoEnv loads `go env` values into the provided map, keyed by Go variable 1093 // name. 1094 func loadGoEnv(ctx context.Context, dir string, configEnv []string, runner *gocommand.Runner, vars map[string]*string) error { 1095 // We can save ~200 ms by requesting only the variables we care about. 1096 args := []string{"-json"} 1097 for k := range vars { 1098 args = append(args, k) 1099 } 1100 1101 inv := gocommand.Invocation{ 1102 Verb: "env", 1103 Args: args, 1104 Env: configEnv, 1105 WorkingDir: dir, 1106 } 1107 stdout, err := runner.Run(ctx, inv) 1108 if err != nil { 1109 return err 1110 } 1111 envMap := make(map[string]string) 1112 if err := json.Unmarshal(stdout.Bytes(), &envMap); err != nil { 1113 return fmt.Errorf("internal error unmarshaling JSON from 'go env': %w", err) 1114 } 1115 for key, ptr := range vars { 1116 *ptr = envMap[key] 1117 } 1118 1119 return nil 1120 } 1121 1122 // findRootPattern looks for files with the given basename in dir or any parent 1123 // directory of dir, using the provided FileSource. It returns the first match, 1124 // starting from dir and search parents. 1125 // 1126 // The resulting string is either the file path of a matching file with the 1127 // given basename, or "" if none was found. 1128 // 1129 // findRootPattern only returns an error in the case of context cancellation. 1130 func findRootPattern(ctx context.Context, dirURI protocol.DocumentURI, basename string, fs file.Source) (protocol.DocumentURI, error) { 1131 dir := dirURI.Path() 1132 for dir != "" { 1133 target := filepath.Join(dir, basename) 1134 uri := protocol.URIFromPath(target) 1135 fh, err := fs.ReadFile(ctx, uri) 1136 if err != nil { 1137 return "", err // context cancelled 1138 } 1139 if fileExists(fh) { 1140 return uri, nil 1141 } 1142 // Trailing separators must be trimmed, otherwise filepath.Split is a noop. 1143 next, _ := filepath.Split(strings.TrimRight(dir, string(filepath.Separator))) 1144 if next == dir { 1145 break 1146 } 1147 dir = next 1148 } 1149 return "", nil 1150 } 1151 1152 // checkPathValid performs an OS-specific path validity check. The 1153 // implementation varies for filesystems that are case-insensitive 1154 // (e.g. macOS, Windows), and for those that disallow certain file 1155 // names (e.g. path segments ending with a period on Windows, or 1156 // reserved names such as "com"; see 1157 // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file). 1158 var checkPathValid = defaultCheckPathValid 1159 1160 // CheckPathValid checks whether a directory is suitable as a workspace folder. 1161 func CheckPathValid(dir string) error { return checkPathValid(dir) } 1162 1163 func defaultCheckPathValid(path string) error { 1164 return nil 1165 } 1166 1167 // IsGoPrivatePath reports whether target is a private import path, as identified 1168 // by the GOPRIVATE environment variable. 1169 func (s *Snapshot) IsGoPrivatePath(target string) bool { 1170 return globsMatchPath(s.view.folder.Env.GOPRIVATE, target) 1171 } 1172 1173 // ModuleUpgrades returns known module upgrades for the dependencies of 1174 // modfile. 1175 func (s *Snapshot) ModuleUpgrades(modfile protocol.DocumentURI) map[string]string { 1176 s.mu.Lock() 1177 defer s.mu.Unlock() 1178 upgrades := map[string]string{} 1179 orig, _ := s.moduleUpgrades.Get(modfile) 1180 for mod, ver := range orig { 1181 upgrades[mod] = ver 1182 } 1183 return upgrades 1184 } 1185 1186 // MaxGovulncheckResultsAge defines the maximum vulnerability age considered 1187 // valid by gopls. 1188 // 1189 // Mutable for testing. 1190 var MaxGovulncheckResultAge = 1 * time.Hour 1191 1192 // Vulnerabilities returns known vulnerabilities for the given modfile. 1193 // 1194 // Results more than an hour old are excluded. 1195 // 1196 // TODO(suzmue): replace command.Vuln with a different type, maybe 1197 // https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck/govulnchecklib#Summary? 1198 // 1199 // TODO(rfindley): move to snapshot.go 1200 func (s *Snapshot) Vulnerabilities(modfiles ...protocol.DocumentURI) map[protocol.DocumentURI]*vulncheck.Result { 1201 m := make(map[protocol.DocumentURI]*vulncheck.Result) 1202 now := time.Now() 1203 1204 s.mu.Lock() 1205 defer s.mu.Unlock() 1206 1207 if len(modfiles) == 0 { // empty means all modfiles 1208 modfiles = s.vulns.Keys() 1209 } 1210 for _, modfile := range modfiles { 1211 vuln, _ := s.vulns.Get(modfile) 1212 if vuln != nil && now.Sub(vuln.AsOf) > MaxGovulncheckResultAge { 1213 vuln = nil 1214 } 1215 m[modfile] = vuln 1216 } 1217 return m 1218 } 1219 1220 // GoVersion returns the effective release Go version (the X in go1.X) for this 1221 // view. 1222 func (v *View) GoVersion() int { 1223 return v.folder.Env.GoVersion 1224 } 1225 1226 // GoVersionString returns the effective Go version string for this view. 1227 // 1228 // Unlike [GoVersion], this encodes the minor version and commit hash information. 1229 func (v *View) GoVersionString() string { 1230 return gocommand.ParseGoVersionOutput(v.folder.Env.GoVersionOutput) 1231 } 1232 1233 // GoVersionString is temporarily available from the snapshot. 1234 // 1235 // TODO(rfindley): refactor so that this method is not necessary. 1236 func (s *Snapshot) GoVersionString() string { 1237 return s.view.GoVersionString() 1238 } 1239 1240 // Copied from 1241 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a 1242 func globsMatchPath(globs, target string) bool { 1243 for globs != "" { 1244 // Extract next non-empty glob in comma-separated list. 1245 var glob string 1246 if i := strings.Index(globs, ","); i >= 0 { 1247 glob, globs = globs[:i], globs[i+1:] 1248 } else { 1249 glob, globs = globs, "" 1250 } 1251 if glob == "" { 1252 continue 1253 } 1254 1255 // A glob with N+1 path elements (N slashes) needs to be matched 1256 // against the first N+1 path elements of target, 1257 // which end just before the N+1'th slash. 1258 n := strings.Count(glob, "/") 1259 prefix := target 1260 // Walk target, counting slashes, truncating at the N+1'th slash. 1261 for i := 0; i < len(target); i++ { 1262 if target[i] == '/' { 1263 if n == 0 { 1264 prefix = target[:i] 1265 break 1266 } 1267 n-- 1268 } 1269 } 1270 if n > 0 { 1271 // Not enough prefix elements. 1272 continue 1273 } 1274 matched, _ := path.Match(glob, prefix) 1275 if matched { 1276 return true 1277 } 1278 } 1279 return false 1280 } 1281 1282 var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) 1283 1284 // TODO(rfindley): clean up the redundancy of allFilesExcluded, 1285 // pathExcludedByFilterFunc, pathExcludedByFilter, view.filterFunc... 1286 func allFilesExcluded(files []string, filterFunc func(protocol.DocumentURI) bool) bool { 1287 for _, f := range files { 1288 uri := protocol.URIFromPath(f) 1289 if !filterFunc(uri) { 1290 return false 1291 } 1292 } 1293 return true 1294 } 1295 1296 func relPathExcludedByFilter(path string, filterer *Filterer) bool { 1297 path = strings.TrimPrefix(filepath.ToSlash(path), "/") 1298 return filterer.Disallow(path) 1299 }