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 func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[VersionedFileIdentity][]*Diagnostic, error) {
23 outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
25 if err := os.MkdirAll(outDir, 0700); err != nil {
28 tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x")
32 defer os.Remove(tmpFile.Name())
34 outDirURI := span.URIFromPath(outDir)
35 // GC details doesn't handle Windows URIs in the form of "file:///C:/...",
36 // so rewrite them to "file://C:/...". See golang/go#41614.
37 if !strings.HasPrefix(outDir, "/") {
38 outDirURI = span.URI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
40 inv := &gocommand.Invocation{
43 fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
44 fmt.Sprintf("-o=%s", tmpFile.Name()),
47 WorkingDir: pkgDir.Filename(),
49 _, err = snapshot.RunGoCommandDirect(ctx, Normal, inv)
53 files, err := findJSONFiles(outDir)
57 reports := make(map[VersionedFileIdentity][]*Diagnostic)
58 opts := snapshot.View().Options()
60 for _, fn := range files {
61 uri, diagnostics, err := parseDetailsFile(fn, opts)
63 // expect errors for all the files, save 1
66 fh := snapshot.FindFile(uri)
70 if pkgDir.Filename() != filepath.Dir(fh.URI().Filename()) {
71 // https://github.com/golang/go/issues/42198
72 // sometimes the detail diagnostics generated for files
73 // outside the package can never be taken back.
76 reports[fh.VersionedFileIdentity()] = diagnostics
78 return reports, parseError
81 func parseDetailsFile(filename string, options *Options) (span.URI, []*Diagnostic, error) {
82 buf, err := ioutil.ReadFile(filename)
89 diagnostics []*Diagnostic
91 type metadata struct {
92 File string `json:"file,omitempty"`
94 for dec := json.NewDecoder(bytes.NewReader(buf)); dec.More(); {
95 // The first element always contains metadata.
99 if err := dec.Decode(m); err != nil {
102 if !strings.HasSuffix(m.File, ".go") {
103 continue // <autogenerated>
105 uri = span.URIFromPath(m.File)
108 d := new(protocol.Diagnostic)
109 if err := dec.Decode(d); err != nil {
112 msg := d.Code.(string)
114 msg = fmt.Sprintf("%s(%s)", msg, d.Message)
116 if skipDiagnostic(msg, d.Source, options) {
119 var related []RelatedInformation
120 for _, ri := range d.RelatedInformation {
121 related = append(related, RelatedInformation{
122 URI: ri.Location.URI.SpanURI(),
123 Range: zeroIndexedRange(ri.Location.Range),
127 diagnostic := &Diagnostic{
128 Range: zeroIndexedRange(d.Range),
130 Severity: d.Severity,
135 diagnostics = append(diagnostics, diagnostic)
138 return uri, diagnostics, nil
141 // skipDiagnostic reports whether a given diagnostic should be shown to the end
142 // user, given the current options.
143 func skipDiagnostic(msg, source string, o *Options) bool {
144 if source != "go compiler" {
148 case o.Annotations["noInline"]:
149 return strings.HasPrefix(msg, "canInline") ||
150 strings.HasPrefix(msg, "cannotInline") ||
151 strings.HasPrefix(msg, "inlineCall")
152 case o.Annotations["noEscape"]:
153 return strings.HasPrefix(msg, "escape") || msg == "leak"
154 case o.Annotations["noNilcheck"]:
155 return strings.HasPrefix(msg, "nilcheck")
156 case o.Annotations["noBounds"]:
157 return strings.HasPrefix(msg, "isInBounds") ||
158 strings.HasPrefix(msg, "isSliceInBounds")
163 // The range produced by the compiler is 1-indexed, so subtract range by 1.
164 func zeroIndexedRange(rng protocol.Range) protocol.Range {
165 return protocol.Range{
166 Start: protocol.Position{
167 Line: rng.Start.Line - 1,
168 Character: rng.Start.Character - 1,
170 End: protocol.Position{
171 Line: rng.End.Line - 1,
172 Character: rng.End.Character - 1,
177 func findJSONFiles(dir string) ([]string, error) {
179 f := func(path string, fi os.FileInfo, _ error) error {
183 if strings.HasSuffix(path, ".json") {
184 ans = append(ans, path)
188 err := filepath.Walk(dir, f)