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