1 // Copyright 2020 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.
17 "golang.org/x/tools/internal/gocommand"
18 "golang.org/x/tools/internal/lsp/protocol"
19 "golang.org/x/tools/internal/span"
22 type Annotation string
25 // Nil controls nil checks.
26 Nil Annotation = "nil"
28 // Escape controls diagnostics about escape choices.
29 Escape Annotation = "escape"
31 // Inline controls diagnostics about inlining choices.
32 Inline Annotation = "inline"
34 // Bounds controls bounds checking diagnostics.
35 Bounds Annotation = "bounds"
38 func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[VersionedFileIdentity][]*Diagnostic, error) {
39 outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
41 if err := os.MkdirAll(outDir, 0700); err != nil {
44 tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x")
48 defer os.Remove(tmpFile.Name())
50 outDirURI := span.URIFromPath(outDir)
51 // GC details doesn't handle Windows URIs in the form of "file:///C:/...",
52 // so rewrite them to "file://C:/...". See golang/go#41614.
53 if !strings.HasPrefix(outDir, "/") {
54 outDirURI = span.URI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
56 inv := &gocommand.Invocation{
59 fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
60 fmt.Sprintf("-o=%s", tmpFile.Name()),
63 WorkingDir: pkgDir.Filename(),
65 _, err = snapshot.RunGoCommandDirect(ctx, Normal, inv)
69 files, err := findJSONFiles(outDir)
73 reports := make(map[VersionedFileIdentity][]*Diagnostic)
74 opts := snapshot.View().Options()
76 for _, fn := range files {
77 uri, diagnostics, err := parseDetailsFile(fn, opts)
79 // expect errors for all the files, save 1
82 fh := snapshot.FindFile(uri)
86 if pkgDir.Filename() != filepath.Dir(fh.URI().Filename()) {
87 // https://github.com/golang/go/issues/42198
88 // sometimes the detail diagnostics generated for files
89 // outside the package can never be taken back.
92 reports[fh.VersionedFileIdentity()] = diagnostics
94 return reports, parseError
97 func parseDetailsFile(filename string, options *Options) (span.URI, []*Diagnostic, error) {
98 buf, err := ioutil.ReadFile(filename)
105 diagnostics []*Diagnostic
107 type metadata struct {
108 File string `json:"file,omitempty"`
110 for dec := json.NewDecoder(bytes.NewReader(buf)); dec.More(); {
111 // The first element always contains metadata.
115 if err := dec.Decode(m); err != nil {
118 if !strings.HasSuffix(m.File, ".go") {
119 continue // <autogenerated>
121 uri = span.URIFromPath(m.File)
124 d := new(protocol.Diagnostic)
125 if err := dec.Decode(d); err != nil {
128 msg := d.Code.(string)
130 msg = fmt.Sprintf("%s(%s)", msg, d.Message)
132 if !showDiagnostic(msg, d.Source, options) {
135 var related []RelatedInformation
136 for _, ri := range d.RelatedInformation {
137 related = append(related, RelatedInformation{
138 URI: ri.Location.URI.SpanURI(),
139 Range: zeroIndexedRange(ri.Location.Range),
143 diagnostic := &Diagnostic{
144 Range: zeroIndexedRange(d.Range),
146 Severity: d.Severity,
151 diagnostics = append(diagnostics, diagnostic)
154 return uri, diagnostics, nil
157 // showDiagnostic reports whether a given diagnostic should be shown to the end
158 // user, given the current options.
159 func showDiagnostic(msg, source string, o *Options) bool {
160 if source != "go compiler" {
163 if o.Annotations == nil {
167 case strings.HasPrefix(msg, "canInline") ||
168 strings.HasPrefix(msg, "cannotInline") ||
169 strings.HasPrefix(msg, "inlineCall"):
170 return o.Annotations[Inline]
171 case strings.HasPrefix(msg, "escape") || msg == "leak":
172 return o.Annotations[Escape]
173 case strings.HasPrefix(msg, "nilcheck"):
174 return o.Annotations[Nil]
175 case strings.HasPrefix(msg, "isInBounds") ||
176 strings.HasPrefix(msg, "isSliceInBounds"):
177 return o.Annotations[Bounds]
182 // The range produced by the compiler is 1-indexed, so subtract range by 1.
183 func zeroIndexedRange(rng protocol.Range) protocol.Range {
184 return protocol.Range{
185 Start: protocol.Position{
186 Line: rng.Start.Line - 1,
187 Character: rng.Start.Character - 1,
189 End: protocol.Position{
190 Line: rng.End.Line - 1,
191 Character: rng.End.Character - 1,
196 func findJSONFiles(dir string) ([]string, error) {
198 f := func(path string, fi os.FileInfo, _ error) error {
202 if strings.HasSuffix(path, ".json") {
203 ans = append(ans, path)
207 err := filepath.Walk(dir, f)