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/lsp/protocol"
18 "golang.org/x/tools/internal/span"
21 func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[VersionedFileIdentity][]*Diagnostic, error) {
22 outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid()))
24 if err := os.MkdirAll(outDir, 0700); err != nil {
27 tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x")
31 defer os.Remove(tmpFile.Name())
33 outDirURI := span.URIFromPath(outDir)
34 // GC details doesn't handle Windows URIs in the form of "file:///C:/...",
35 // so rewrite them to "file://C:/...". See golang/go#41614.
36 if !strings.HasPrefix(outDir, "/") {
37 outDirURI = span.URI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
40 fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
41 fmt.Sprintf("-o=%s", tmpFile.Name()),
44 err = snapshot.RunGoCommandDirect(ctx, pkgDir.Filename(), "build", args)
48 files, err := findJSONFiles(outDir)
52 reports := make(map[VersionedFileIdentity][]*Diagnostic)
53 opts := snapshot.View().Options()
55 for _, fn := range files {
56 uri, diagnostics, err := parseDetailsFile(fn, opts)
58 // expect errors for all the files, save 1
61 fh := snapshot.FindFile(uri)
65 reports[fh.VersionedFileIdentity()] = diagnostics
67 return reports, parseError
70 func parseDetailsFile(filename string, options *Options) (span.URI, []*Diagnostic, error) {
71 buf, err := ioutil.ReadFile(filename)
78 diagnostics []*Diagnostic
80 type metadata struct {
81 File string `json:"file,omitempty"`
83 for dec := json.NewDecoder(bytes.NewReader(buf)); dec.More(); {
84 // The first element always contains metadata.
88 if err := dec.Decode(m); err != nil {
91 if !strings.HasSuffix(m.File, ".go") {
92 continue // <autogenerated>
94 uri = span.URIFromPath(m.File)
97 d := new(protocol.Diagnostic)
98 if err := dec.Decode(d); err != nil {
101 msg := d.Code.(string)
103 msg = fmt.Sprintf("%s(%s)", msg, d.Message)
105 if skipDiagnostic(msg, d.Source, options) {
108 var related []RelatedInformation
109 for _, ri := range d.RelatedInformation {
110 related = append(related, RelatedInformation{
111 URI: ri.Location.URI.SpanURI(),
112 Range: zeroIndexedRange(ri.Location.Range),
116 diagnostic := &Diagnostic{
117 Range: zeroIndexedRange(d.Range),
119 Severity: d.Severity,
124 diagnostics = append(diagnostics, diagnostic)
127 return uri, diagnostics, nil
130 // skipDiagnostic reports whether a given diagnostic should be shown to the end
131 // user, given the current options.
132 func skipDiagnostic(msg, source string, o *Options) bool {
133 if source != "go compiler" {
137 case o.Annotations["noInline"]:
138 return strings.HasPrefix(msg, "canInline") ||
139 strings.HasPrefix(msg, "cannotInline") ||
140 strings.HasPrefix(msg, "inlineCall")
141 case o.Annotations["noEscape"]:
142 return strings.HasPrefix(msg, "escape") || msg == "leak"
143 case o.Annotations["noNilcheck"]:
144 return strings.HasPrefix(msg, "nilcheck")
145 case o.Annotations["noBounds"]:
146 return strings.HasPrefix(msg, "isInBounds") ||
147 strings.HasPrefix(msg, "isSliceInBounds")
152 // The range produced by the compiler is 1-indexed, so subtract range by 1.
153 func zeroIndexedRange(rng protocol.Range) protocol.Range {
154 return protocol.Range{
155 Start: protocol.Position{
156 Line: rng.Start.Line - 1,
157 Character: rng.Start.Character - 1,
159 End: protocol.Position{
160 Line: rng.End.Line - 1,
161 Character: rng.End.Character - 1,
166 func findJSONFiles(dir string) ([]string, error) {
168 f := func(path string, fi os.FileInfo, _ error) error {
172 if strings.HasSuffix(path, ".json") {
173 ans = append(ans, path)
177 err := filepath.Walk(dir, f)