// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package source import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" "strings" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/span" ) func GCOptimizationDetails(ctx context.Context, snapshot Snapshot, pkgDir span.URI) (map[VersionedFileIdentity][]*Diagnostic, error) { outDir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.details", os.Getpid())) if err := os.MkdirAll(outDir, 0700); err != nil { return nil, err } tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x") if err != nil { return nil, err } defer os.Remove(tmpFile.Name()) args := []string{fmt.Sprintf("-gcflags=-json=0,%s", outDir), fmt.Sprintf("-o=%s", tmpFile.Name()), pkgDir.Filename(), } err = snapshot.RunGoCommandDirect(ctx, "build", args) if err != nil { return nil, err } files, err := findJSONFiles(outDir) if err != nil { return nil, err } reports := make(map[VersionedFileIdentity][]*Diagnostic) opts := snapshot.View().Options() var parseError error for _, fn := range files { fname, v, err := parseDetailsFile(fn) if err != nil { // expect errors for all the files, save 1 parseError = err } if !strings.HasSuffix(fname, ".go") { continue // } uri := span.URIFromPath(fname) x := snapshot.FindFile(uri) if x == nil { continue } v = filterDiagnostics(v, &opts) reports[x.VersionedFileIdentity()] = v } return reports, parseError } func filterDiagnostics(v []*Diagnostic, o *Options) []*Diagnostic { var ans []*Diagnostic for _, x := range v { if x.Source != "go compiler" { continue } if o.Annotations["noInline"] && (strings.HasPrefix(x.Message, "canInline") || strings.HasPrefix(x.Message, "cannotInline") || strings.HasPrefix(x.Message, "inlineCall")) { continue } else if o.Annotations["noEscape"] && (strings.HasPrefix(x.Message, "escape") || x.Message == "leak") { continue } else if o.Annotations["noNilcheck"] && strings.HasPrefix(x.Message, "nilcheck") { continue } else if o.Annotations["noBounds"] && (strings.HasPrefix(x.Message, "isInBounds") || strings.HasPrefix(x.Message, "isSliceInBounds")) { continue } ans = append(ans, x) } return ans } func parseDetailsFile(fn string) (string, []*Diagnostic, error) { buf, err := ioutil.ReadFile(fn) if err != nil { return "", nil, err // This is an internal error. Likely ever file will fail. } var fname string var ans []*Diagnostic lines := bytes.Split(buf, []byte{'\n'}) for i, l := range lines { if len(l) == 0 { continue } if i == 0 { x := make(map[string]interface{}) if err := json.Unmarshal(l, &x); err != nil { return "", nil, fmt.Errorf("internal error (%v) parsing first line of json file %s", err, fn) } fname = x["file"].(string) continue } y := protocol.Diagnostic{} if err := json.Unmarshal(l, &y); err != nil { return "", nil, fmt.Errorf("internal error (%#v) parsing json file for %s", err, fname) } y.Range.Start.Line-- // change from 1-based to 0-based y.Range.Start.Character-- y.Range.End.Line-- y.Range.End.Character-- msg := y.Code.(string) if y.Message != "" { msg = fmt.Sprintf("%s(%s)", msg, y.Message) } x := Diagnostic{ Range: y.Range, Message: msg, Source: y.Source, Severity: y.Severity, } for _, ri := range y.RelatedInformation { x.Related = append(x.Related, RelatedInformation{ URI: ri.Location.URI.SpanURI(), Range: ri.Location.Range, Message: ri.Message, }) } ans = append(ans, &x) } return fname, ans, nil } func findJSONFiles(dir string) ([]string, error) { ans := []string{} f := func(path string, fi os.FileInfo, err error) error { if fi.IsDir() { return nil } if strings.HasSuffix(path, ".json") { ans = append(ans, path) } return nil } err := filepath.Walk(dir, f) return ans, err }