// Copyright 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package cache import ( "context" "go/ast" "go/types" "sort" "sync" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" "golang.org/x/tools/internal/lsp/source" ) // pkg contains the type information needed by the source package. type pkg struct { // ID and package path have their own types to avoid being used interchangeably. id packageID pkgPath packagePath files []*astFile errors []packages.Error imports map[packagePath]*pkg types *types.Package typesInfo *types.Info typesSizes types.Sizes // The analysis cache holds analysis information for all the packages in a view. // Each graph node (action) is one unit of analysis. // Edges express package-to-package (vertical) dependencies, // and analysis-to-analysis (horizontal) dependencies. mu sync.Mutex analyses map[*analysis.Analyzer]*analysisEntry diagMu sync.Mutex diagnostics []source.Diagnostic } // packageID is a type that abstracts a package ID. type packageID string // packagePath is a type that abstracts a package path. type packagePath string type analysisEntry struct { done chan struct{} succeeded bool *source.Action } func (pkg *pkg) GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*source.Action, error) { if ctx.Err() != nil { return nil, ctx.Err() } pkg.mu.Lock() e, ok := pkg.analyses[a] if ok { // cache hit pkg.mu.Unlock() // wait for entry to become ready or the context to be cancelled select { case <-e.done: // If the goroutine we are waiting on was cancelled, we should retry. // If errors other than cancelation/timeout become possible, it may // no longer be appropriate to always retry here. if !e.succeeded { return pkg.GetActionGraph(ctx, a) } case <-ctx.Done(): return nil, ctx.Err() } } else { // cache miss e = &analysisEntry{ done: make(chan struct{}), Action: &source.Action{ Analyzer: a, Pkg: pkg, }, } pkg.analyses[a] = e pkg.mu.Unlock() defer func() { // If we got an error, clear out our defunct cache entry. We don't cache // errors since they could depend on our dependencies, which can change. // Currently the only possible error is context.Canceled, though, which // should also not be cached. if !e.succeeded { pkg.mu.Lock() delete(pkg.analyses, a) pkg.mu.Unlock() } // Always close done so waiters don't get stuck. close(e.done) }() // This goroutine becomes responsible for populating // the entry and broadcasting its readiness. // Add a dependency on each required analyzers. for _, req := range a.Requires { act, err := pkg.GetActionGraph(ctx, req) if err != nil { return nil, err } e.Deps = append(e.Deps, act) } // An analysis that consumes/produces facts // must run on the package's dependencies too. if len(a.FactTypes) > 0 { importPaths := make([]string, 0, len(pkg.imports)) for importPath := range pkg.imports { importPaths = append(importPaths, string(importPath)) } sort.Strings(importPaths) // for determinism for _, importPath := range importPaths { dep := pkg.GetImport(importPath) if dep == nil { continue } act, err := dep.GetActionGraph(ctx, a) if err != nil { return nil, err } e.Deps = append(e.Deps, act) } } e.succeeded = true } return e.Action, nil } func (pkg *pkg) ID() string { return string(pkg.id) } func (pkg *pkg) PkgPath() string { return string(pkg.pkgPath) } func (pkg *pkg) GetFilenames() []string { filenames := make([]string, 0, len(pkg.files)) for _, f := range pkg.files { filenames = append(filenames, f.uri.Filename()) } return filenames } func (pkg *pkg) GetSyntax() []*ast.File { var syntax []*ast.File for _, f := range pkg.files { if f.file != nil { syntax = append(syntax, f.file) } } return syntax } func (pkg *pkg) GetErrors() []packages.Error { return pkg.errors } func (pkg *pkg) GetTypes() *types.Package { return pkg.types } func (pkg *pkg) GetTypesInfo() *types.Info { return pkg.typesInfo } func (pkg *pkg) GetTypesSizes() types.Sizes { return pkg.typesSizes } func (pkg *pkg) IsIllTyped() bool { return pkg.types == nil && pkg.typesInfo == nil } func (pkg *pkg) GetImport(pkgPath string) source.Package { if imp := pkg.imports[packagePath(pkgPath)]; imp != nil { return imp } // Don't return a nil pointer because that still satisfies the interface. return nil } func (pkg *pkg) SetDiagnostics(diags []source.Diagnostic) { pkg.diagMu.Lock() defer pkg.diagMu.Unlock() pkg.diagnostics = diags } func (pkg *pkg) GetDiagnostics() []source.Diagnostic { pkg.diagMu.Lock() defer pkg.diagMu.Unlock() return pkg.diagnostics }