github.com/jd-ly/tools@v0.5.7/internal/lsp/cache/check.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/types" 13 "path" 14 "path/filepath" 15 "sort" 16 "strings" 17 "sync" 18 19 "golang.org/x/mod/module" 20 "github.com/jd-ly/tools/go/packages" 21 "github.com/jd-ly/tools/internal/event" 22 "github.com/jd-ly/tools/internal/lsp/debug/tag" 23 "github.com/jd-ly/tools/internal/lsp/source" 24 "github.com/jd-ly/tools/internal/memoize" 25 "github.com/jd-ly/tools/internal/span" 26 "github.com/jd-ly/tools/internal/typesinternal" 27 errors "golang.org/x/xerrors" 28 ) 29 30 type packageHandleKey string 31 32 type packageHandle struct { 33 handle *memoize.Handle 34 35 goFiles, compiledGoFiles []*parseGoHandle 36 37 // mode is the mode the files were parsed in. 38 mode source.ParseMode 39 40 // m is the metadata associated with the package. 41 m *metadata 42 43 // key is the hashed key for the package. 44 key packageHandleKey 45 } 46 47 func (ph *packageHandle) packageKey() packageKey { 48 return packageKey{ 49 id: ph.m.id, 50 mode: ph.mode, 51 } 52 } 53 54 // packageData contains the data produced by type-checking a package. 55 type packageData struct { 56 pkg *pkg 57 err error 58 } 59 60 // buildPackageHandle returns a packageHandle for a given package and mode. 61 func (s *snapshot) buildPackageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, error) { 62 if ph := s.getPackage(id, mode); ph != nil { 63 return ph, nil 64 } 65 66 // Build the packageHandle for this ID and its dependencies. 67 ph, deps, err := s.buildKey(ctx, id, mode) 68 if err != nil { 69 return nil, err 70 } 71 72 // Do not close over the packageHandle or the snapshot in the Bind function. 73 // This creates a cycle, which causes the finalizers to never run on the handles. 74 // The possible cycles are: 75 // 76 // packageHandle.h.function -> packageHandle 77 // packageHandle.h.function -> snapshot -> packageHandle 78 // 79 80 m := ph.m 81 key := ph.key 82 83 h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} { 84 snapshot := arg.(*snapshot) 85 86 // Begin loading the direct dependencies, in parallel. 87 var wg sync.WaitGroup 88 for _, dep := range deps { 89 wg.Add(1) 90 go func(dep *packageHandle) { 91 dep.check(ctx, snapshot) 92 wg.Done() 93 }(dep) 94 } 95 96 data := &packageData{} 97 data.pkg, data.err = typeCheck(ctx, snapshot, m, mode, deps) 98 // Make sure that the workers above have finished before we return, 99 // especially in case of cancellation. 100 wg.Wait() 101 102 return data 103 }, nil) 104 ph.handle = h 105 106 // Cache the handle in the snapshot. If a package handle has already 107 // been cached, addPackage will return the cached value. This is fine, 108 // since the original package handle above will have no references and be 109 // garbage collected. 110 ph = s.addPackageHandle(ph) 111 112 return ph, nil 113 } 114 115 // buildKey computes the key for a given packageHandle. 116 func (s *snapshot) buildKey(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, map[packagePath]*packageHandle, error) { 117 m := s.getMetadata(id) 118 if m == nil { 119 return nil, nil, errors.Errorf("no metadata for %s", id) 120 } 121 goFiles, err := s.parseGoHandles(ctx, m.goFiles, mode) 122 if err != nil { 123 return nil, nil, err 124 } 125 compiledGoFiles, err := s.parseGoHandles(ctx, m.compiledGoFiles, mode) 126 if err != nil { 127 return nil, nil, err 128 } 129 ph := &packageHandle{ 130 m: m, 131 goFiles: goFiles, 132 compiledGoFiles: compiledGoFiles, 133 mode: mode, 134 } 135 // Make sure all of the depList are sorted. 136 depList := append([]packageID{}, m.deps...) 137 sort.Slice(depList, func(i, j int) bool { 138 return depList[i] < depList[j] 139 }) 140 141 deps := make(map[packagePath]*packageHandle) 142 143 // Begin computing the key by getting the depKeys for all dependencies. 144 var depKeys []packageHandleKey 145 for _, depID := range depList { 146 depHandle, err := s.buildPackageHandle(ctx, depID, s.workspaceParseMode(depID)) 147 if err != nil { 148 event.Error(ctx, fmt.Sprintf("%s: no dep handle for %s", id, depID), err, tag.Snapshot.Of(s.id)) 149 if ctx.Err() != nil { 150 return nil, nil, ctx.Err() 151 } 152 // One bad dependency should not prevent us from checking the entire package. 153 // Add a special key to mark a bad dependency. 154 depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", id))) 155 continue 156 } 157 deps[depHandle.m.pkgPath] = depHandle 158 depKeys = append(depKeys, depHandle.key) 159 } 160 experimentalKey := s.View().Options().ExperimentalPackageCacheKey 161 ph.key = checkPackageKey(ctx, ph.m.id, compiledGoFiles, m.config, depKeys, mode, experimentalKey) 162 return ph, deps, nil 163 } 164 165 func (s *snapshot) workspaceParseMode(id packageID) source.ParseMode { 166 if _, ws := s.isWorkspacePackage(id); ws { 167 return source.ParseFull 168 } else { 169 return source.ParseExported 170 } 171 } 172 173 func checkPackageKey(ctx context.Context, id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode, experimentalKey bool) packageHandleKey { 174 b := bytes.NewBuffer(nil) 175 b.WriteString(string(id)) 176 if !experimentalKey { 177 // cfg was used to produce the other hashed inputs (package ID, parsed Go 178 // files, and deps). It should not otherwise affect the inputs to the type 179 // checker, so this experiment omits it. This should increase cache hits on 180 // the daemon as cfg contains the environment and working directory. 181 b.WriteString(hashConfig(cfg)) 182 } 183 b.WriteByte(byte(mode)) 184 for _, dep := range deps { 185 b.WriteString(string(dep)) 186 } 187 for _, cgf := range pghs { 188 b.WriteString(cgf.file.FileIdentity().String()) 189 } 190 return packageHandleKey(hashContents(b.Bytes())) 191 } 192 193 // hashEnv returns a hash of the snapshot's configuration. 194 func hashEnv(s *snapshot) string { 195 s.view.optionsMu.Lock() 196 env := s.view.options.EnvSlice() 197 s.view.optionsMu.Unlock() 198 199 b := &bytes.Buffer{} 200 for _, e := range env { 201 b.WriteString(e) 202 } 203 return hashContents(b.Bytes()) 204 } 205 206 // hashConfig returns the hash for the *packages.Config. 207 func hashConfig(config *packages.Config) string { 208 b := bytes.NewBuffer(nil) 209 210 // Dir, Mode, Env, BuildFlags are the parts of the config that can change. 211 b.WriteString(config.Dir) 212 b.WriteString(string(rune(config.Mode))) 213 214 for _, e := range config.Env { 215 b.WriteString(e) 216 } 217 for _, f := range config.BuildFlags { 218 b.WriteString(f) 219 } 220 return hashContents(b.Bytes()) 221 } 222 223 func (ph *packageHandle) Check(ctx context.Context, s source.Snapshot) (source.Package, error) { 224 return ph.check(ctx, s.(*snapshot)) 225 } 226 227 func (ph *packageHandle) check(ctx context.Context, s *snapshot) (*pkg, error) { 228 v, err := ph.handle.Get(ctx, s.generation, s) 229 if err != nil { 230 return nil, err 231 } 232 data := v.(*packageData) 233 return data.pkg, data.err 234 } 235 236 func (ph *packageHandle) CompiledGoFiles() []span.URI { 237 return ph.m.compiledGoFiles 238 } 239 240 func (ph *packageHandle) ID() string { 241 return string(ph.m.id) 242 } 243 244 func (ph *packageHandle) cached(g *memoize.Generation) (*pkg, error) { 245 v := ph.handle.Cached(g) 246 if v == nil { 247 return nil, errors.Errorf("no cached type information for %s", ph.m.pkgPath) 248 } 249 data := v.(*packageData) 250 return data.pkg, data.err 251 } 252 253 func (s *snapshot) parseGoHandles(ctx context.Context, files []span.URI, mode source.ParseMode) ([]*parseGoHandle, error) { 254 pghs := make([]*parseGoHandle, 0, len(files)) 255 for _, uri := range files { 256 fh, err := s.GetFile(ctx, uri) 257 if err != nil { 258 return nil, err 259 } 260 pghs = append(pghs, s.parseGoHandle(ctx, fh, mode)) 261 } 262 return pghs, nil 263 } 264 265 func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source.ParseMode, deps map[packagePath]*packageHandle) (*pkg, error) { 266 ctx, done := event.Start(ctx, "cache.importer.typeCheck", tag.Package.Of(string(m.id))) 267 defer done() 268 269 var rawErrors []error 270 for _, err := range m.errors { 271 rawErrors = append(rawErrors, err) 272 } 273 274 fset := snapshot.view.session.cache.fset 275 pkg := &pkg{ 276 m: m, 277 mode: mode, 278 goFiles: make([]*source.ParsedGoFile, len(m.goFiles)), 279 compiledGoFiles: make([]*source.ParsedGoFile, len(m.compiledGoFiles)), 280 imports: make(map[packagePath]*pkg), 281 typesSizes: m.typesSizes, 282 typesInfo: &types.Info{ 283 Types: make(map[ast.Expr]types.TypeAndValue), 284 Defs: make(map[*ast.Ident]types.Object), 285 Uses: make(map[*ast.Ident]types.Object), 286 Implicits: make(map[ast.Node]types.Object), 287 Selections: make(map[*ast.SelectorExpr]*types.Selection), 288 Scopes: make(map[ast.Node]*types.Scope), 289 }, 290 } 291 // If this is a replaced module in the workspace, the version is 292 // meaningless, and we don't want clients to access it. 293 if m.module != nil { 294 version := m.module.Version 295 if source.IsWorkspaceModuleVersion(version) { 296 version = "" 297 } 298 pkg.version = &module.Version{ 299 Path: m.module.Path, 300 Version: version, 301 } 302 } 303 var ( 304 files = make([]*ast.File, len(m.compiledGoFiles)) 305 parseErrors = make([]error, len(m.compiledGoFiles)) 306 actualErrors = make([]error, len(m.compiledGoFiles)) 307 wg sync.WaitGroup 308 309 mu sync.Mutex 310 skipTypeErrors bool 311 ) 312 for i, cgf := range m.compiledGoFiles { 313 wg.Add(1) 314 go func(i int, cgf span.URI) { 315 defer wg.Done() 316 fh, err := snapshot.GetFile(ctx, cgf) 317 if err != nil { 318 actualErrors[i] = err 319 return 320 } 321 pgh := snapshot.parseGoHandle(ctx, fh, mode) 322 pgf, fixed, err := snapshot.parseGo(ctx, pgh) 323 if err != nil { 324 actualErrors[i] = err 325 return 326 } 327 pkg.compiledGoFiles[i] = pgf 328 files[i], parseErrors[i], actualErrors[i] = pgf.File, pgf.ParseErr, err 329 330 mu.Lock() 331 skipTypeErrors = skipTypeErrors || fixed 332 mu.Unlock() 333 }(i, cgf) 334 } 335 for i, gf := range m.goFiles { 336 wg.Add(1) 337 // We need to parse the non-compiled go files, but we don't care about their errors. 338 go func(i int, gf span.URI) { 339 defer wg.Done() 340 fh, err := snapshot.GetFile(ctx, gf) 341 if err != nil { 342 return 343 } 344 pgf, _ := snapshot.ParseGo(ctx, fh, mode) 345 pkg.goFiles[i] = pgf 346 }(i, gf) 347 } 348 wg.Wait() 349 for _, err := range actualErrors { 350 if err != nil { 351 return nil, err 352 } 353 } 354 355 for _, e := range parseErrors { 356 if e != nil { 357 rawErrors = append(rawErrors, e) 358 } 359 } 360 361 var i int 362 for _, f := range files { 363 if f != nil { 364 files[i] = f 365 i++ 366 } 367 } 368 files = files[:i] 369 370 // Use the default type information for the unsafe package. 371 if pkg.m.pkgPath == "unsafe" { 372 pkg.types = types.Unsafe 373 // Don't type check Unsafe: it's unnecessary, and doing so exposes a data 374 // race to Unsafe.completed. 375 return pkg, nil 376 } else if len(files) == 0 { // not the unsafe package, no parsed files 377 // Try to attach errors messages to the file as much as possible. 378 var found bool 379 for _, e := range rawErrors { 380 srcErr, err := sourceError(ctx, snapshot, pkg, e) 381 if err != nil { 382 continue 383 } 384 found = true 385 pkg.errors = append(pkg.errors, srcErr) 386 } 387 if found { 388 return pkg, nil 389 } 390 return nil, errors.Errorf("no parsed files for package %s, expected: %v, list errors: %v", pkg.m.pkgPath, pkg.compiledGoFiles, rawErrors) 391 } else { 392 pkg.types = types.NewPackage(string(m.pkgPath), string(m.name)) 393 } 394 395 cfg := &types.Config{ 396 Error: func(e error) { 397 // If we have fixed parse errors in any of the files, 398 // we should hide type errors, as they may be completely nonsensical. 399 if skipTypeErrors { 400 return 401 } 402 rawErrors = append(rawErrors, e) 403 }, 404 Importer: importerFunc(func(pkgPath string) (*types.Package, error) { 405 // If the context was cancelled, we should abort. 406 if ctx.Err() != nil { 407 return nil, ctx.Err() 408 } 409 dep := resolveImportPath(pkgPath, pkg, deps) 410 if dep == nil { 411 return nil, snapshot.missingPkgError(pkgPath) 412 } 413 if !isValidImport(m.pkgPath, dep.m.pkgPath) { 414 return nil, errors.Errorf("invalid use of internal package %s", pkgPath) 415 } 416 depPkg, err := dep.check(ctx, snapshot) 417 if err != nil { 418 return nil, err 419 } 420 pkg.imports[depPkg.m.pkgPath] = depPkg 421 return depPkg.types, nil 422 }), 423 } 424 // We want to type check cgo code if go/types supports it. 425 // We passed typecheckCgo to go/packages when we Loaded. 426 typesinternal.SetUsesCgo(cfg) 427 428 check := types.NewChecker(cfg, fset, pkg.types, pkg.typesInfo) 429 430 // Type checking errors are handled via the config, so ignore them here. 431 _ = check.Files(files) 432 // If the context was cancelled, we may have returned a ton of transient 433 // errors to the type checker. Swallow them. 434 if ctx.Err() != nil { 435 return nil, ctx.Err() 436 } 437 438 // We don't care about a package's errors unless we have parsed it in full. 439 if mode == source.ParseFull { 440 expandErrors(rawErrors) 441 for _, e := range rawErrors { 442 srcErr, err := sourceError(ctx, snapshot, pkg, e) 443 if err != nil { 444 event.Error(ctx, "unable to compute error positions", err, tag.Package.Of(pkg.ID())) 445 continue 446 } 447 pkg.errors = append(pkg.errors, srcErr) 448 if err, ok := e.(extendedError); ok { 449 pkg.typeErrors = append(pkg.typeErrors, err.primary) 450 } 451 } 452 } 453 454 return pkg, nil 455 } 456 457 // missingPkgError returns an error message for a missing package that varies 458 // based on the user's workspace mode. 459 func (s *snapshot) missingPkgError(pkgPath string) error { 460 if s.workspaceMode()&moduleMode != 0 { 461 return fmt.Errorf("no required module provides package %q", pkgPath) 462 } 463 gorootSrcPkg := filepath.FromSlash(filepath.Join(s.view.goroot, "src", pkgPath)) 464 465 var b strings.Builder 466 b.WriteString(fmt.Sprintf("cannot find package %q in any of \n\t%s (from $GOROOT)", pkgPath, gorootSrcPkg)) 467 468 for _, gopath := range strings.Split(s.view.gopath, ":") { 469 gopathSrcPkg := filepath.FromSlash(filepath.Join(gopath, "src", pkgPath)) 470 b.WriteString(fmt.Sprintf("\n\t%s (from $GOPATH)", gopathSrcPkg)) 471 } 472 return errors.New(b.String()) 473 } 474 475 type extendedError struct { 476 primary types.Error 477 secondaries []types.Error 478 } 479 480 func (e extendedError) Error() string { 481 return e.primary.Error() 482 } 483 484 // expandErrors duplicates "secondary" errors by mapping them to their main 485 // error. Some errors returned by the type checker are followed by secondary 486 // errors which give more information about the error. These are errors in 487 // their own right, and they are marked by starting with \t. For instance, when 488 // there is a multiply-defined function, the secondary error points back to the 489 // definition first noticed. 490 // 491 // This code associates the secondary error with its primary error, which can 492 // then be used as RelatedInformation when the error becomes a diagnostic. 493 func expandErrors(errs []error) []error { 494 for i := 0; i < len(errs); { 495 e, ok := errs[i].(types.Error) 496 if !ok { 497 i++ 498 continue 499 } 500 enew := extendedError{ 501 primary: e, 502 } 503 j := i + 1 504 for ; j < len(errs); j++ { 505 spl, ok := errs[j].(types.Error) 506 if !ok || len(spl.Msg) == 0 || spl.Msg[0] != '\t' { 507 break 508 } 509 enew.secondaries = append(enew.secondaries, spl) 510 } 511 errs[i] = enew 512 i = j 513 } 514 return errs 515 } 516 517 // resolveImportPath resolves an import path in pkg to a package from deps. 518 // It should produce the same results as resolveImportPath: 519 // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/load/pkg.go;drc=641918ee09cb44d282a30ee8b66f99a0b63eaef9;l=990. 520 func resolveImportPath(importPath string, pkg *pkg, deps map[packagePath]*packageHandle) *packageHandle { 521 if dep := deps[packagePath(importPath)]; dep != nil { 522 return dep 523 } 524 // We may be in GOPATH mode, in which case we need to check vendor dirs. 525 searchDir := path.Dir(pkg.PkgPath()) 526 for { 527 vdir := packagePath(path.Join(searchDir, "vendor", importPath)) 528 if vdep := deps[vdir]; vdep != nil { 529 return vdep 530 } 531 532 // Search until Dir doesn't take us anywhere new, e.g. "." or "/". 533 next := path.Dir(searchDir) 534 if searchDir == next { 535 break 536 } 537 searchDir = next 538 } 539 540 // Vendor didn't work. Let's try minimal module compatibility mode. 541 // In MMC, the packagePath is the canonical (.../vN/...) path, which 542 // is hard to calculate. But the go command has already resolved the ID 543 // to the non-versioned path, and we can take advantage of that. 544 for _, dep := range deps { 545 if dep.ID() == importPath { 546 return dep 547 } 548 } 549 return nil 550 } 551 552 func isValidImport(pkgPath, importPkgPath packagePath) bool { 553 i := strings.LastIndex(string(importPkgPath), "/internal/") 554 if i == -1 { 555 return true 556 } 557 if pkgPath == "command-line-arguments" { 558 return true 559 } 560 return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i])) 561 } 562 563 // An importFunc is an implementation of the single-method 564 // types.Importer interface based on a function value. 565 type importerFunc func(path string) (*types.Package, error) 566 567 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }