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 5package cache 6 7import ( 8 "context" 9 "go/ast" 10 "go/types" 11 "sort" 12 "sync" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/packages" 16 "golang.org/x/tools/internal/lsp/source" 17) 18 19// pkg contains the type information needed by the source package. 20type pkg struct { 21 // ID and package path have their own types to avoid being used interchangeably. 22 id packageID 23 pkgPath packagePath 24 25 files []*astFile 26 errors []packages.Error 27 imports map[packagePath]*pkg 28 types *types.Package 29 typesInfo *types.Info 30 typesSizes types.Sizes 31 32 // The analysis cache holds analysis information for all the packages in a view. 33 // Each graph node (action) is one unit of analysis. 34 // Edges express package-to-package (vertical) dependencies, 35 // and analysis-to-analysis (horizontal) dependencies. 36 mu sync.Mutex 37 analyses map[*analysis.Analyzer]*analysisEntry 38 39 diagMu sync.Mutex 40 diagnostics []source.Diagnostic 41} 42 43// packageID is a type that abstracts a package ID. 44type packageID string 45 46// packagePath is a type that abstracts a package path. 47type packagePath string 48 49type analysisEntry struct { 50 done chan struct{} 51 succeeded bool 52 *source.Action 53} 54 55func (pkg *pkg) GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*source.Action, error) { 56 if ctx.Err() != nil { 57 return nil, ctx.Err() 58 } 59 60 pkg.mu.Lock() 61 e, ok := pkg.analyses[a] 62 if ok { 63 // cache hit 64 pkg.mu.Unlock() 65 66 // wait for entry to become ready or the context to be cancelled 67 select { 68 case <-e.done: 69 // If the goroutine we are waiting on was cancelled, we should retry. 70 // If errors other than cancelation/timeout become possible, it may 71 // no longer be appropriate to always retry here. 72 if !e.succeeded { 73 return pkg.GetActionGraph(ctx, a) 74 } 75 case <-ctx.Done(): 76 return nil, ctx.Err() 77 } 78 } else { 79 // cache miss 80 e = &analysisEntry{ 81 done: make(chan struct{}), 82 Action: &source.Action{ 83 Analyzer: a, 84 Pkg: pkg, 85 }, 86 } 87 pkg.analyses[a] = e 88 pkg.mu.Unlock() 89 90 defer func() { 91 // If we got an error, clear out our defunct cache entry. We don't cache 92 // errors since they could depend on our dependencies, which can change. 93 // Currently the only possible error is context.Canceled, though, which 94 // should also not be cached. 95 if !e.succeeded { 96 pkg.mu.Lock() 97 delete(pkg.analyses, a) 98 pkg.mu.Unlock() 99 } 100 101 // Always close done so waiters don't get stuck. 102 close(e.done) 103 }() 104 105 // This goroutine becomes responsible for populating 106 // the entry and broadcasting its readiness. 107 108 // Add a dependency on each required analyzers. 109 for _, req := range a.Requires { 110 act, err := pkg.GetActionGraph(ctx, req) 111 if err != nil { 112 return nil, err 113 } 114 e.Deps = append(e.Deps, act) 115 } 116 117 // An analysis that consumes/produces facts 118 // must run on the package's dependencies too. 119 if len(a.FactTypes) > 0 { 120 importPaths := make([]string, 0, len(pkg.imports)) 121 for importPath := range pkg.imports { 122 importPaths = append(importPaths, string(importPath)) 123 } 124 sort.Strings(importPaths) // for determinism 125 for _, importPath := range importPaths { 126 dep := pkg.GetImport(importPath) 127 if dep == nil { 128 continue 129 } 130 act, err := dep.GetActionGraph(ctx, a) 131 if err != nil { 132 return nil, err 133 } 134 e.Deps = append(e.Deps, act) 135 } 136 } 137 e.succeeded = true 138 } 139 return e.Action, nil 140} 141 142func (pkg *pkg) ID() string { 143 return string(pkg.id) 144} 145 146func (pkg *pkg) PkgPath() string { 147 return string(pkg.pkgPath) 148} 149 150func (pkg *pkg) GetFilenames() []string { 151 filenames := make([]string, 0, len(pkg.files)) 152 for _, f := range pkg.files { 153 filenames = append(filenames, f.uri.Filename()) 154 } 155 return filenames 156} 157 158func (pkg *pkg) GetSyntax() []*ast.File { 159 var syntax []*ast.File 160 for _, f := range pkg.files { 161 if f.file != nil { 162 syntax = append(syntax, f.file) 163 } 164 } 165 return syntax 166} 167 168func (pkg *pkg) GetErrors() []packages.Error { 169 return pkg.errors 170} 171 172func (pkg *pkg) GetTypes() *types.Package { 173 return pkg.types 174} 175 176func (pkg *pkg) GetTypesInfo() *types.Info { 177 return pkg.typesInfo 178} 179 180func (pkg *pkg) GetTypesSizes() types.Sizes { 181 return pkg.typesSizes 182} 183 184func (pkg *pkg) IsIllTyped() bool { 185 return pkg.types == nil && pkg.typesInfo == nil 186} 187 188func (pkg *pkg) GetImport(pkgPath string) source.Package { 189 if imp := pkg.imports[packagePath(pkgPath)]; imp != nil { 190 return imp 191 } 192 // Don't return a nil pointer because that still satisfies the interface. 193 return nil 194} 195 196func (pkg *pkg) SetDiagnostics(diags []source.Diagnostic) { 197 pkg.diagMu.Lock() 198 defer pkg.diagMu.Unlock() 199 pkg.diagnostics = diags 200} 201 202func (pkg *pkg) GetDiagnostics() []source.Diagnostic { 203 pkg.diagMu.Lock() 204 defer pkg.diagMu.Unlock() 205 return pkg.diagnostics 206} 207