github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/source/gc_annotations.go (about)

     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.
     4  
     5  package source
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"github.com/april1989/origin-go-tools/internal/lsp/protocol"
    18  	"github.com/april1989/origin-go-tools/internal/span"
    19  )
    20  
    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()))
    23  	if err := os.MkdirAll(outDir, 0700); err != nil {
    24  		return nil, err
    25  	}
    26  	tmpFile, err := ioutil.TempFile(os.TempDir(), "gopls-x")
    27  	if err != nil {
    28  		return nil, err
    29  	}
    30  	defer os.Remove(tmpFile.Name())
    31  	args := []string{fmt.Sprintf("-gcflags=-json=0,%s", outDir),
    32  		fmt.Sprintf("-o=%s", tmpFile.Name()),
    33  		pkgDir.Filename(),
    34  	}
    35  	err = snapshot.RunGoCommandDirect(ctx, "build", args)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	files, err := findJSONFiles(outDir)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	reports := make(map[VersionedFileIdentity][]*Diagnostic)
    44  	opts := snapshot.View().Options()
    45  	var parseError error
    46  	for _, fn := range files {
    47  		fname, v, err := parseDetailsFile(fn)
    48  		if err != nil {
    49  			// expect errors for all the files, save 1
    50  			parseError = err
    51  		}
    52  		if !strings.HasSuffix(fname, ".go") {
    53  			continue // <autogenerated>
    54  		}
    55  		uri := span.URIFromPath(fname)
    56  		x := snapshot.FindFile(uri)
    57  		if x == nil {
    58  			continue
    59  		}
    60  		v = filterDiagnostics(v, &opts)
    61  		reports[x.VersionedFileIdentity()] = v
    62  	}
    63  	return reports, parseError
    64  }
    65  
    66  func filterDiagnostics(v []*Diagnostic, o *Options) []*Diagnostic {
    67  	var ans []*Diagnostic
    68  	for _, x := range v {
    69  		if x.Source != "go compiler" {
    70  			continue
    71  		}
    72  		if o.Annotations["noInline"] &&
    73  			(strings.HasPrefix(x.Message, "canInline") ||
    74  				strings.HasPrefix(x.Message, "cannotInline") ||
    75  				strings.HasPrefix(x.Message, "inlineCall")) {
    76  			continue
    77  		} else if o.Annotations["noEscape"] &&
    78  			(strings.HasPrefix(x.Message, "escape") || x.Message == "leak") {
    79  			continue
    80  		} else if o.Annotations["noNilcheck"] && strings.HasPrefix(x.Message, "nilcheck") {
    81  			continue
    82  		} else if o.Annotations["noBounds"] &&
    83  			(strings.HasPrefix(x.Message, "isInBounds") ||
    84  				strings.HasPrefix(x.Message, "isSliceInBounds")) {
    85  			continue
    86  		}
    87  		ans = append(ans, x)
    88  	}
    89  	return ans
    90  }
    91  
    92  func parseDetailsFile(fn string) (string, []*Diagnostic, error) {
    93  	buf, err := ioutil.ReadFile(fn)
    94  	if err != nil {
    95  		return "", nil, err // This is an internal error. Likely ever file will fail.
    96  	}
    97  	var fname string
    98  	var ans []*Diagnostic
    99  	lines := bytes.Split(buf, []byte{'\n'})
   100  	for i, l := range lines {
   101  		if len(l) == 0 {
   102  			continue
   103  		}
   104  		if i == 0 {
   105  			x := make(map[string]interface{})
   106  			if err := json.Unmarshal(l, &x); err != nil {
   107  				return "", nil, fmt.Errorf("internal error (%v) parsing first line of json file %s",
   108  					err, fn)
   109  			}
   110  			fname = x["file"].(string)
   111  			continue
   112  		}
   113  		y := protocol.Diagnostic{}
   114  		if err := json.Unmarshal(l, &y); err != nil {
   115  			return "", nil, fmt.Errorf("internal error (%#v) parsing json file for %s", err, fname)
   116  		}
   117  		y.Range.Start.Line-- // change from 1-based to 0-based
   118  		y.Range.Start.Character--
   119  		y.Range.End.Line--
   120  		y.Range.End.Character--
   121  		msg := y.Code.(string)
   122  		if y.Message != "" {
   123  			msg = fmt.Sprintf("%s(%s)", msg, y.Message)
   124  		}
   125  		x := Diagnostic{
   126  			Range:    y.Range,
   127  			Message:  msg,
   128  			Source:   y.Source,
   129  			Severity: y.Severity,
   130  		}
   131  		for _, ri := range y.RelatedInformation {
   132  			x.Related = append(x.Related, RelatedInformation{
   133  				URI:     ri.Location.URI.SpanURI(),
   134  				Range:   ri.Location.Range,
   135  				Message: ri.Message,
   136  			})
   137  		}
   138  		ans = append(ans, &x)
   139  	}
   140  	return fname, ans, nil
   141  }
   142  
   143  func findJSONFiles(dir string) ([]string, error) {
   144  	ans := []string{}
   145  	f := func(path string, fi os.FileInfo, err error) error {
   146  		if fi.IsDir() {
   147  			return nil
   148  		}
   149  		if strings.HasSuffix(path, ".json") {
   150  			ans = append(ans, path)
   151  		}
   152  		return nil
   153  	}
   154  	err := filepath.Walk(dir, f)
   155  	return ans, err
   156  }