Compare commits
3 Commits
master
...
release-br
Author | SHA1 | Date |
---|---|---|
|
aa82965741 | |
|
86c5873b48 | |
|
49d818b077 |
|
@ -1,231 +0,0 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// authtest is a diagnostic tool for implementations of the GOAUTH protocol
|
||||
// described in https://golang.org/issue/26232.
|
||||
//
|
||||
// It accepts a single URL as an argument, and executes the GOAUTH protocol to
|
||||
// fetch and display the headers for that URL.
|
||||
//
|
||||
// CAUTION: authtest logs the GOAUTH responses, which may include user
|
||||
// credentials, to stderr. Do not post its output unless you are certain that
|
||||
// all of the credentials involved are fake!
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var v = flag.Bool("v", false, "if true, log GOAUTH responses to stderr")
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) != 1 {
|
||||
log.Fatalf("usage: [GOAUTH=CMD...] %s URL", filepath.Base(os.Args[0]))
|
||||
}
|
||||
|
||||
resp := try(args[0], nil)
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
resp = try(args[0], resp)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func try(url string, prev *http.Response) *http.Response {
|
||||
req := new(http.Request)
|
||||
if prev != nil {
|
||||
*req = *prev.Request
|
||||
} else {
|
||||
var err error
|
||||
req, err = http.NewRequest("HEAD", os.Args[1], nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
goauth:
|
||||
for _, argList := range strings.Split(os.Getenv("GOAUTH"), ";") {
|
||||
// TODO(golang.org/issue/26849): If we escape quoted strings in GOFLAGS, use
|
||||
// the same quoting here.
|
||||
args := strings.Split(argList, " ")
|
||||
if len(args) == 0 || args[0] == "" {
|
||||
log.Fatalf("invalid or empty command in GOAUTH")
|
||||
}
|
||||
|
||||
creds, err := getCreds(args, prev)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, c := range creds {
|
||||
if c.Apply(req) {
|
||||
fmt.Fprintf(os.Stderr, "# request to %s\n", req.URL)
|
||||
fmt.Fprintf(os.Stderr, "%s %s %s\n", req.Method, req.URL, req.Proto)
|
||||
req.Header.Write(os.Stderr)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
break goauth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode < 400 || resp.StatusCode > 500 {
|
||||
log.Fatalf("unexpected status: %v", resp.Status)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "# response from %s\n", resp.Request.URL)
|
||||
formatHead(os.Stderr, resp)
|
||||
return resp
|
||||
}
|
||||
|
||||
func formatHead(out io.Writer, resp *http.Response) {
|
||||
fmt.Fprintf(out, "%s %s\n", resp.Proto, resp.Status)
|
||||
if err := resp.Header.Write(out); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintln(out)
|
||||
}
|
||||
|
||||
type Cred struct {
|
||||
URLPrefixes []*url.URL
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
func (c Cred) Apply(req *http.Request) bool {
|
||||
if req.URL == nil {
|
||||
return false
|
||||
}
|
||||
ok := false
|
||||
for _, prefix := range c.URLPrefixes {
|
||||
if prefix.Host == req.URL.Host &&
|
||||
(req.URL.Path == prefix.Path ||
|
||||
(strings.HasPrefix(req.URL.Path, prefix.Path) &&
|
||||
(strings.HasSuffix(prefix.Path, "/") ||
|
||||
req.URL.Path[len(prefix.Path)] == '/'))) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for k, vs := range c.Header {
|
||||
req.Header.Del(k)
|
||||
for _, v := range vs {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c Cred) String() string {
|
||||
var buf strings.Builder
|
||||
for _, u := range c.URLPrefixes {
|
||||
fmt.Fprintln(&buf, u)
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
c.Header.Write(&buf)
|
||||
buf.WriteString("\n")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func getCreds(args []string, resp *http.Response) ([]Cred, error) {
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if resp != nil {
|
||||
u := *resp.Request.URL
|
||||
u.RawQuery = ""
|
||||
cmd.Args = append(cmd.Args, u.String())
|
||||
}
|
||||
|
||||
var head strings.Builder
|
||||
if resp != nil {
|
||||
formatHead(&head, resp)
|
||||
}
|
||||
cmd.Stdin = strings.NewReader(head.String())
|
||||
|
||||
fmt.Fprintf(os.Stderr, "# %s\n", strings.Join(cmd.Args, " "))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %v", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
os.Stderr.Write(out)
|
||||
os.Stderr.WriteString("\n")
|
||||
|
||||
var creds []Cred
|
||||
r := textproto.NewReader(bufio.NewReader(bytes.NewReader(out)))
|
||||
line := 0
|
||||
readLoop:
|
||||
for {
|
||||
var prefixes []*url.URL
|
||||
for {
|
||||
prefix, err := r.ReadLine()
|
||||
if err == io.EOF {
|
||||
if len(prefixes) > 0 {
|
||||
return nil, fmt.Errorf("line %d: %v", line, io.ErrUnexpectedEOF)
|
||||
}
|
||||
break readLoop
|
||||
}
|
||||
line++
|
||||
|
||||
if prefix == "" {
|
||||
if len(prefixes) == 0 {
|
||||
return nil, fmt.Errorf("line %d: unexpected newline", line)
|
||||
}
|
||||
break
|
||||
}
|
||||
u, err := url.Parse(prefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("line %d: malformed URL: %v", line, err)
|
||||
}
|
||||
if u.Scheme != "https" {
|
||||
return nil, fmt.Errorf("line %d: non-HTTPS URL %q", line, prefix)
|
||||
}
|
||||
if len(u.RawQuery) > 0 {
|
||||
return nil, fmt.Errorf("line %d: unexpected query string in URL %q", line, prefix)
|
||||
}
|
||||
if len(u.Fragment) > 0 {
|
||||
return nil, fmt.Errorf("line %d: unexpected fragment in URL %q", line, prefix)
|
||||
}
|
||||
prefixes = append(prefixes, u)
|
||||
}
|
||||
|
||||
header, err := r.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("headers at line %d: %v", line, err)
|
||||
}
|
||||
if len(header) > 0 {
|
||||
creds = append(creds, Cred{
|
||||
URLPrefixes: prefixes,
|
||||
Header: http.Header(header),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// cookieauth uses a “Netscape cookie file” to implement the GOAUTH protocol
|
||||
// described in https://golang.org/issue/26232.
|
||||
// It expects the location of the file as the first command-line argument.
|
||||
//
|
||||
// Example GOAUTH usage:
|
||||
// export GOAUTH="cookieauth $(git config --get http.cookieFile)"
|
||||
//
|
||||
// See http://www.cookiecentral.com/faq/#3.5 for a description of the Netscape
|
||||
// cookie file format.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s COOKIEFILE [URL]\n", os.Args[0])
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
log.SetPrefix("cookieauth: ")
|
||||
|
||||
f, err := os.Open(os.Args[1])
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read cookie file: %v\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var (
|
||||
targetURL *url.URL
|
||||
targetURLs = map[string]*url.URL{}
|
||||
)
|
||||
if len(os.Args) == 3 {
|
||||
targetURL, err = url.ParseRequestURI(os.Args[2])
|
||||
if err != nil {
|
||||
log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[2])
|
||||
}
|
||||
targetURLs[targetURL.String()] = targetURL
|
||||
} else if len(os.Args) > 3 {
|
||||
// Extra arguments were passed: maybe the protocol was expanded?
|
||||
// We don't know how to interpret the request, so ignore it.
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := parseCookieFile(f.Name(), f)
|
||||
if err != nil {
|
||||
log.Fatalf("error reading cookie file: %v\n", f.Name())
|
||||
}
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize cookie jar: %v\n", err)
|
||||
}
|
||||
|
||||
for _, e := range entries {
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: e.Host,
|
||||
Path: e.Cookie.Path,
|
||||
}
|
||||
|
||||
if targetURL == nil {
|
||||
targetURLs[u.String()] = u
|
||||
}
|
||||
|
||||
jar.SetCookies(u, []*http.Cookie{&e.Cookie})
|
||||
}
|
||||
|
||||
for _, u := range targetURLs {
|
||||
req := &http.Request{URL: u, Header: make(http.Header)}
|
||||
for _, c := range jar.Cookies(req.URL) {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
fmt.Printf("%s\n\n", u)
|
||||
req.Header.Write(os.Stdout)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Host string
|
||||
Cookie http.Cookie
|
||||
}
|
||||
|
||||
// parseCookieFile parses a Netscape cookie file as described in
|
||||
// http://www.cookiecentral.com/faq/#3.5.
|
||||
func parseCookieFile(name string, r io.Reader) ([]*Entry, error) {
|
||||
var entries []*Entry
|
||||
s := bufio.NewScanner(r)
|
||||
line := 0
|
||||
for s.Scan() {
|
||||
line++
|
||||
text := strings.TrimSpace(s.Text())
|
||||
if len(text) < 2 || (text[0] == '#' && unicode.IsSpace(rune(text[1]))) {
|
||||
continue
|
||||
}
|
||||
|
||||
e, err := parseCookieLine(text)
|
||||
if err != nil {
|
||||
log.Printf("%s:%d: %v\n", name, line, err)
|
||||
continue
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
return entries, s.Err()
|
||||
}
|
||||
|
||||
func parseCookieLine(line string) (*Entry, error) {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 7 {
|
||||
return nil, fmt.Errorf("found %d columns; want 7", len(f))
|
||||
}
|
||||
|
||||
e := new(Entry)
|
||||
c := &e.Cookie
|
||||
|
||||
if domain := f[0]; strings.HasPrefix(domain, "#HttpOnly_") {
|
||||
c.HttpOnly = true
|
||||
e.Host = strings.TrimPrefix(domain[10:], ".")
|
||||
} else {
|
||||
e.Host = strings.TrimPrefix(domain, ".")
|
||||
}
|
||||
|
||||
isDomain, err := strconv.ParseBool(f[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("non-boolean domain flag: %v", err)
|
||||
}
|
||||
if isDomain {
|
||||
c.Domain = e.Host
|
||||
}
|
||||
|
||||
c.Path = f[2]
|
||||
|
||||
c.Secure, err = strconv.ParseBool(f[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("non-boolean secure flag: %v", err)
|
||||
}
|
||||
|
||||
expiration, err := strconv.ParseInt(f[4], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed expiration: %v", err)
|
||||
}
|
||||
c.Expires = time.Unix(expiration, 0)
|
||||
|
||||
c.Name = f[5]
|
||||
c.Value = f[6]
|
||||
|
||||
return e, nil
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// gitauth uses 'git credential' to implement the GOAUTH protocol described in
|
||||
// https://golang.org/issue/26232. It expects an absolute path to the working
|
||||
// directory for the 'git' command as the first command-line argument.
|
||||
//
|
||||
// Example GOAUTH usage:
|
||||
// export GOAUTH="gitauth $HOME"
|
||||
//
|
||||
// See https://git-scm.com/docs/gitcredentials or run 'man gitcredentials' for
|
||||
// information on how to configure 'git credential'.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 || !filepath.IsAbs(os.Args[1]) {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s WORKDIR [URL]", os.Args[0])
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
log.SetPrefix("gitauth: ")
|
||||
|
||||
if len(os.Args) != 3 {
|
||||
// No explicit URL was passed on the command line, but 'git credential'
|
||||
// provides no way to enumerate existing credentials.
|
||||
// Wait for a request for a specific URL.
|
||||
return
|
||||
}
|
||||
|
||||
u, err := url.ParseRequestURI(os.Args[2])
|
||||
if err != nil {
|
||||
log.Fatalf("invalid request URI (%v): %q\n", err, os.Args[1])
|
||||
}
|
||||
|
||||
var (
|
||||
prefix *url.URL
|
||||
lastHeader http.Header
|
||||
lastStatus = http.StatusUnauthorized
|
||||
)
|
||||
for lastStatus == http.StatusUnauthorized {
|
||||
cmd := exec.Command("git", "credential", "fill")
|
||||
|
||||
// We don't want to execute a 'git' command in an arbitrary directory, since
|
||||
// that opens up a number of config-injection attacks (for example,
|
||||
// https://golang.org/issue/29230). Instead, we have the user configure a
|
||||
// directory explicitly on the command line.
|
||||
cmd.Dir = os.Args[1]
|
||||
|
||||
cmd.Stdin = strings.NewReader(fmt.Sprintf("url=%s\n", u))
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Fatalf("'git credential fill' failed: %v\n", err)
|
||||
}
|
||||
|
||||
prefix = new(url.URL)
|
||||
var username, password string
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
frags := strings.SplitN(line, "=", 2)
|
||||
if len(frags) != 2 {
|
||||
continue // Ignore unrecognized response lines.
|
||||
}
|
||||
switch strings.TrimSpace(frags[0]) {
|
||||
case "protocol":
|
||||
prefix.Scheme = frags[1]
|
||||
case "host":
|
||||
prefix.Host = frags[1]
|
||||
case "path":
|
||||
prefix.Path = frags[1]
|
||||
case "username":
|
||||
username = frags[1]
|
||||
case "password":
|
||||
password = frags[1]
|
||||
case "url":
|
||||
// Write to a local variable instead of updating prefix directly:
|
||||
// if the url field is malformed, we don't want to invalidate
|
||||
// information parsed from the protocol, host, and path fields.
|
||||
u, err := url.ParseRequestURI(frags[1])
|
||||
if err == nil {
|
||||
prefix = u
|
||||
} else {
|
||||
log.Printf("malformed URL from 'git credential fill' (%v): %q\n", err, frags[1])
|
||||
// Proceed anyway: we might be able to parse the prefix from other fields of the response.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Double-check that the URL Git gave us is a prefix of the one we requested.
|
||||
if !strings.HasPrefix(u.String(), prefix.String()) {
|
||||
log.Fatalf("requested a credential for %q, but 'git credential fill' provided one for %q\n", u, prefix)
|
||||
}
|
||||
|
||||
// Send a HEAD request to try to detect whether the credential is valid.
|
||||
// If the user just typed in a correct password and has caching enabled,
|
||||
// we don't want to nag them for it again the next time they run a 'go' command.
|
||||
req, err := http.NewRequest("HEAD", u.String(), nil)
|
||||
if err != nil {
|
||||
log.Fatalf("internal error constructing HTTP HEAD request: %v\n", err)
|
||||
}
|
||||
req.SetBasicAuth(username, password)
|
||||
lastHeader = req.Header
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("HTTPS HEAD request failed to connect: %v\n", err)
|
||||
// Couldn't verify the credential, but we have no evidence that it is invalid either.
|
||||
// Proceed, but don't update git's credential cache.
|
||||
break
|
||||
}
|
||||
lastStatus = resp.StatusCode
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("%s: %v %s\n", u, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized {
|
||||
// We learned something about the credential: it either worked or it was invalid.
|
||||
// Approve or reject the credential (on a best-effort basis)
|
||||
// so that the git credential helper can update its cache as appropriate.
|
||||
action := "approve"
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
action = "reject"
|
||||
}
|
||||
cmd = exec.Command("git", "credential", action)
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stdin = bytes.NewReader(out)
|
||||
_ = cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the credential in the format expected by the 'go' command.
|
||||
fmt.Printf("%s\n\n", prefix)
|
||||
lastHeader.Write(os.Stdout)
|
||||
fmt.Println()
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
// netrcauth uses a .netrc file (or _netrc file on Windows) to implement the
|
||||
// GOAUTH protocol described in https://golang.org/issue/26232.
|
||||
// It expects the location of the file as the first command-line argument.
|
||||
//
|
||||
// Example GOAUTH usage:
|
||||
// export GOAUTH="netrcauth $HOME/.netrc"
|
||||
//
|
||||
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
|
||||
// or run 'man 5 netrc' for a description of the .netrc file format.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s NETRCFILE [URL]", os.Args[0])
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
log.SetPrefix("netrcauth: ")
|
||||
|
||||
if len(os.Args) != 2 {
|
||||
// An explicit URL was passed on the command line, but netrcauth does not
|
||||
// have any URL-specific output: it dumps the entire .netrc file at the
|
||||
// first call.
|
||||
return
|
||||
}
|
||||
|
||||
path := os.Args[1]
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
log.Fatalf("failed to read %s: %v\n", path, err)
|
||||
}
|
||||
|
||||
u := &url.URL{Scheme: "https"}
|
||||
lines := parseNetrc(string(data))
|
||||
for _, l := range lines {
|
||||
u.Host = l.machine
|
||||
fmt.Printf("%s\n\n", u)
|
||||
|
||||
req := &http.Request{Header: make(http.Header)}
|
||||
req.SetBasicAuth(l.login, l.password)
|
||||
req.Header.Write(os.Stdout)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// The following functions were extracted from src/cmd/go/internal/web2/web.go
|
||||
// as of https://golang.org/cl/161698.
|
||||
|
||||
type netrcLine struct {
|
||||
machine string
|
||||
login string
|
||||
password string
|
||||
}
|
||||
|
||||
func parseNetrc(data string) []netrcLine {
|
||||
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
|
||||
// for documentation on the .netrc format.
|
||||
var nrc []netrcLine
|
||||
var l netrcLine
|
||||
inMacro := false
|
||||
for _, line := range strings.Split(data, "\n") {
|
||||
if inMacro {
|
||||
if line == "" {
|
||||
inMacro = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
f := strings.Fields(line)
|
||||
i := 0
|
||||
for ; i < len(f)-1; i += 2 {
|
||||
// Reset at each "machine" token.
|
||||
// “The auto-login process searches the .netrc file for a machine token
|
||||
// that matches […]. Once a match is made, the subsequent .netrc tokens
|
||||
// are processed, stopping when the end of file is reached or another
|
||||
// machine or a default token is encountered.”
|
||||
switch f[i] {
|
||||
case "machine":
|
||||
l = netrcLine{machine: f[i+1]}
|
||||
case "default":
|
||||
break
|
||||
case "login":
|
||||
l.login = f[i+1]
|
||||
case "password":
|
||||
l.password = f[i+1]
|
||||
case "macdef":
|
||||
// “A macro is defined with the specified name; its contents begin with
|
||||
// the next .netrc line and continue until a null line (consecutive
|
||||
// new-line characters) is encountered.”
|
||||
inMacro = true
|
||||
}
|
||||
if l.machine != "" && l.login != "" && l.password != "" {
|
||||
nrc = append(nrc, l)
|
||||
l = netrcLine{}
|
||||
}
|
||||
}
|
||||
|
||||
if i < len(f) && f[i] == "default" {
|
||||
// “There can be only one default token, and it must be after all machine tokens.”
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nrc
|
||||
}
|
|
@ -22,21 +22,12 @@
|
|||
// -compileflags 'list'
|
||||
// Pass the space-separated list of flags to the compilation.
|
||||
//
|
||||
// -link exe
|
||||
// Use exe as the path to the cmd/link binary.
|
||||
//
|
||||
// -linkflags 'list'
|
||||
// Pass the space-separated list of flags to the linker.
|
||||
//
|
||||
// -count n
|
||||
// Run each benchmark n times (default 1).
|
||||
//
|
||||
// -cpuprofile file
|
||||
// Write a CPU profile of the compiler to file.
|
||||
//
|
||||
// -go path
|
||||
// Path to "go" command (default "go").
|
||||
//
|
||||
// -memprofile file
|
||||
// Write a memory profile of the compiler to file.
|
||||
//
|
||||
|
@ -46,15 +37,12 @@
|
|||
// -obj
|
||||
// Report object file statistics.
|
||||
//
|
||||
// -pkg pkg
|
||||
// -pkg
|
||||
// Benchmark compiling a single package.
|
||||
//
|
||||
// -run regexp
|
||||
// Only run benchmarks with names matching regexp.
|
||||
//
|
||||
// -short
|
||||
// Skip long-running benchmarks.
|
||||
//
|
||||
// Although -cpuprofile and -memprofile are intended to write a
|
||||
// combined profile for all the executed benchmarks to file,
|
||||
// today they write only the profile for the last benchmark executed.
|
||||
|
@ -96,7 +84,6 @@ import (
|
|||
var (
|
||||
goroot string
|
||||
compiler string
|
||||
linker string
|
||||
runRE *regexp.Regexp
|
||||
is6g bool
|
||||
)
|
||||
|
@ -107,8 +94,6 @@ var (
|
|||
flagObj = flag.Bool("obj", false, "report object file stats")
|
||||
flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary")
|
||||
flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile")
|
||||
flagLinker = flag.String("link", "", "use `exe` as the cmd/link binary")
|
||||
flagLinkerFlags = flag.String("linkflags", "", "additional `flags` to pass to link")
|
||||
flagRun = flag.String("run", "", "run benchmarks matching `regexp`")
|
||||
flagCount = flag.Int("count", 1, "run benchmarks `n` times")
|
||||
flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`")
|
||||
|
@ -118,31 +103,24 @@ var (
|
|||
flagShort = flag.Bool("short", false, "skip long-running benchmarks")
|
||||
)
|
||||
|
||||
type test struct {
|
||||
var tests = []struct {
|
||||
name string
|
||||
r runner
|
||||
}
|
||||
|
||||
type runner interface {
|
||||
long() bool
|
||||
run(name string, count int) error
|
||||
}
|
||||
|
||||
var tests = []test{
|
||||
{"BenchmarkTemplate", compile{"html/template"}},
|
||||
{"BenchmarkUnicode", compile{"unicode"}},
|
||||
{"BenchmarkGoTypes", compile{"go/types"}},
|
||||
{"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}},
|
||||
{"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}},
|
||||
{"BenchmarkFlate", compile{"compress/flate"}},
|
||||
{"BenchmarkGoParser", compile{"go/parser"}},
|
||||
{"BenchmarkReflect", compile{"reflect"}},
|
||||
{"BenchmarkTar", compile{"archive/tar"}},
|
||||
{"BenchmarkXML", compile{"encoding/xml"}},
|
||||
{"BenchmarkLinkCompiler", link{"cmd/compile"}},
|
||||
{"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}},
|
||||
{"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}},
|
||||
{"BenchmarkCmdGoSize", size{"cmd/go", true}},
|
||||
dir string
|
||||
long bool
|
||||
}{
|
||||
{"BenchmarkTemplate", "html/template", false},
|
||||
{"BenchmarkUnicode", "unicode", false},
|
||||
{"BenchmarkGoTypes", "go/types", false},
|
||||
{"BenchmarkCompiler", "cmd/compile/internal/gc", false},
|
||||
{"BenchmarkSSA", "cmd/compile/internal/ssa", false},
|
||||
{"BenchmarkFlate", "compress/flate", false},
|
||||
{"BenchmarkGoParser", "go/parser", false},
|
||||
{"BenchmarkReflect", "reflect", false},
|
||||
{"BenchmarkTar", "archive/tar", false},
|
||||
{"BenchmarkXML", "encoding/xml", false},
|
||||
{"BenchmarkStdCmd", "", true},
|
||||
{"BenchmarkHelloSize", "", false},
|
||||
{"BenchmarkCmdGoSize", "", true},
|
||||
}
|
||||
|
||||
func usage() {
|
||||
|
@ -170,23 +148,16 @@ func main() {
|
|||
|
||||
compiler = *flagCompiler
|
||||
if compiler == "" {
|
||||
var foundTool string
|
||||
foundTool, compiler = toolPath("compile", "6g")
|
||||
if foundTool == "6g" {
|
||||
out, err := exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
|
||||
if err != nil {
|
||||
out, err = exec.Command(*flagGoCmd, "tool", "-n", "6g").CombinedOutput()
|
||||
is6g = true
|
||||
if err != nil {
|
||||
out, err = exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
|
||||
log.Fatalf("go tool -n compiler: %v\n%s", err, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linker = *flagLinker
|
||||
if linker == "" && !is6g { // TODO: Support 6l
|
||||
_, linker = toolPath("link")
|
||||
}
|
||||
|
||||
if is6g {
|
||||
*flagMemprofilerate = -1
|
||||
*flagAlloc = false
|
||||
*flagCpuprofile = ""
|
||||
*flagMemprofile = ""
|
||||
compiler = strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
if *flagRun != "" {
|
||||
|
@ -197,117 +168,67 @@ func main() {
|
|||
runRE = r
|
||||
}
|
||||
|
||||
if *flagPackage != "" {
|
||||
tests = []test{
|
||||
{"BenchmarkPkg", compile{*flagPackage}},
|
||||
{"BenchmarkPkgLink", link{*flagPackage}},
|
||||
}
|
||||
runRE = nil
|
||||
}
|
||||
|
||||
for i := 0; i < *flagCount; i++ {
|
||||
if *flagPackage != "" {
|
||||
runBuild("BenchmarkPkg", *flagPackage, i)
|
||||
continue
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if tt.r.long() && *flagShort {
|
||||
if tt.long && *flagShort {
|
||||
continue
|
||||
}
|
||||
if runRE == nil || runRE.MatchString(tt.name) {
|
||||
if err := tt.r.run(tt.name, i); err != nil {
|
||||
log.Printf("%s: %v", tt.name, err)
|
||||
}
|
||||
runBuild(tt.name, tt.dir, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toolPath(names ...string) (found, path string) {
|
||||
var out1 []byte
|
||||
var err1 error
|
||||
for i, name := range names {
|
||||
out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput()
|
||||
if err == nil {
|
||||
return name, strings.TrimSpace(string(out))
|
||||
}
|
||||
if i == 0 {
|
||||
out1, err1 = out, err
|
||||
}
|
||||
}
|
||||
log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
type Pkg struct {
|
||||
Dir string
|
||||
GoFiles []string
|
||||
}
|
||||
|
||||
func goList(dir string) (*Pkg, error) {
|
||||
var pkg Pkg
|
||||
out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("go list -json %s: %v\n", dir, err)
|
||||
}
|
||||
if err := json.Unmarshal(out, &pkg); err != nil {
|
||||
return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err)
|
||||
}
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
func runCmd(name string, cmd *exec.Cmd) error {
|
||||
func runCmd(name string, cmd *exec.Cmd) {
|
||||
start := time.Now()
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v\n%s", err, out)
|
||||
log.Printf("%v: %v\n%s", name, err, out)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
|
||||
return nil
|
||||
}
|
||||
|
||||
type goBuild struct{ pkgs []string }
|
||||
|
||||
func (goBuild) long() bool { return true }
|
||||
|
||||
func (r goBuild) run(name string, count int) error {
|
||||
func runStdCmd() {
|
||||
args := []string{"build", "-a"}
|
||||
if *flagCompilerFlags != "" {
|
||||
args = append(args, "-gcflags", *flagCompilerFlags)
|
||||
}
|
||||
args = append(args, r.pkgs...)
|
||||
args = append(args, "std", "cmd")
|
||||
cmd := exec.Command(*flagGoCmd, args...)
|
||||
cmd.Dir = filepath.Join(goroot, "src")
|
||||
return runCmd(name, cmd)
|
||||
runCmd("BenchmarkStdCmd", cmd)
|
||||
}
|
||||
|
||||
type size struct {
|
||||
// path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
|
||||
path string
|
||||
isLong bool
|
||||
}
|
||||
|
||||
func (r size) long() bool { return r.isLong }
|
||||
|
||||
func (r size) run(name string, count int) error {
|
||||
if strings.HasPrefix(r.path, "$GOROOT/") {
|
||||
r.path = goroot + "/" + r.path[len("$GOROOT/"):]
|
||||
}
|
||||
|
||||
cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path)
|
||||
// path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
|
||||
func runSize(name, path string) {
|
||||
cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", path)
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
defer os.Remove("_compilebenchout_")
|
||||
info, err := os.Stat("_compilebenchout_")
|
||||
if err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("size: %v\n%s", err, out)
|
||||
log.Printf("size: %v\n%s", err, out)
|
||||
return
|
||||
}
|
||||
lines := strings.Split(string(out), "\n")
|
||||
if len(lines) < 2 {
|
||||
return fmt.Errorf("not enough output from size: %s", out)
|
||||
log.Printf("not enough output from size: %s", out)
|
||||
return
|
||||
}
|
||||
f := strings.Fields(lines[1])
|
||||
if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
|
||||
|
@ -315,31 +236,127 @@ func (r size) run(name string, count int) error {
|
|||
} else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
|
||||
fmt.Printf("%s 1 %s text-bytes %s data-bytes %s bss-bytes %v exe-bytes\n", name, f[0], f[1], f[2], info.Size())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type compile struct{ dir string }
|
||||
func runBuild(name, dir string, count int) {
|
||||
switch name {
|
||||
case "BenchmarkStdCmd":
|
||||
runStdCmd()
|
||||
return
|
||||
case "BenchmarkCmdGoSize":
|
||||
runSize("BenchmarkCmdGoSize", "cmd/go")
|
||||
return
|
||||
case "BenchmarkHelloSize":
|
||||
runSize("BenchmarkHelloSize", filepath.Join(goroot, "test/helloworld.go"))
|
||||
return
|
||||
}
|
||||
|
||||
func (compile) long() bool { return false }
|
||||
|
||||
func (c compile) run(name string, count int) error {
|
||||
// Make sure dependencies needed by go tool compile are installed to GOROOT/pkg.
|
||||
out, err := exec.Command(*flagGoCmd, "build", "-i", c.dir).CombinedOutput()
|
||||
out, err := exec.Command(*flagGoCmd, "build", "-i", dir).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("go build -i %s: %v\n%s", c.dir, err, out)
|
||||
log.Printf("go build -i %s: %v\n%s", dir, err, out)
|
||||
return
|
||||
}
|
||||
|
||||
// Find dir and source file list.
|
||||
pkg, err := goList(c.dir)
|
||||
var pkg struct {
|
||||
Dir string
|
||||
GoFiles []string
|
||||
}
|
||||
out, err = exec.Command(*flagGoCmd, "list", "-json", dir).Output()
|
||||
if err != nil {
|
||||
return err
|
||||
log.Printf("go list -json %s: %v\n", dir, err)
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(out, &pkg); err != nil {
|
||||
log.Printf("go list -json %s: unmarshal: %v", dir, err)
|
||||
return
|
||||
}
|
||||
|
||||
args := []string{"-o", "_compilebench_.o"}
|
||||
if is6g {
|
||||
*flagMemprofilerate = -1
|
||||
*flagAlloc = false
|
||||
*flagCpuprofile = ""
|
||||
*flagMemprofile = ""
|
||||
}
|
||||
if *flagMemprofilerate >= 0 {
|
||||
args = append(args, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
|
||||
}
|
||||
args = append(args, strings.Fields(*flagCompilerFlags)...)
|
||||
if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
|
||||
if *flagAlloc || *flagMemprofile != "" {
|
||||
args = append(args, "-memprofile", "_compilebench_.memprof")
|
||||
}
|
||||
if *flagCpuprofile != "" {
|
||||
args = append(args, "-cpuprofile", "_compilebench_.cpuprof")
|
||||
}
|
||||
}
|
||||
args = append(args, pkg.GoFiles...)
|
||||
if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil {
|
||||
return err
|
||||
cmd := exec.Command(compiler, args...)
|
||||
cmd.Dir = pkg.Dir
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
start := time.Now()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Printf("%v: %v", name, err)
|
||||
return
|
||||
}
|
||||
end := time.Now()
|
||||
|
||||
var allocs, allocbytes int64
|
||||
if *flagAlloc || *flagMemprofile != "" {
|
||||
out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.memprof")
|
||||
if err != nil {
|
||||
log.Print("cannot find memory profile after compilation")
|
||||
}
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 4 || f[0] != "#" || f[2] != "=" {
|
||||
continue
|
||||
}
|
||||
val, err := strconv.ParseInt(f[3], 0, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
switch f[1] {
|
||||
case "TotalAlloc":
|
||||
allocbytes = val
|
||||
case "Mallocs":
|
||||
allocs = val
|
||||
}
|
||||
}
|
||||
|
||||
if *flagMemprofile != "" {
|
||||
if err := ioutil.WriteFile(*flagMemprofile, out, 0666); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
os.Remove(pkg.Dir + "/_compilebench_.memprof")
|
||||
}
|
||||
|
||||
if *flagCpuprofile != "" {
|
||||
out, err := ioutil.ReadFile(pkg.Dir + "/_compilebench_.cpuprof")
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
outpath := *flagCpuprofile
|
||||
if *flagCount != 1 {
|
||||
outpath = fmt.Sprintf("%s_%d", outpath, count)
|
||||
}
|
||||
if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
os.Remove(pkg.Dir + "/_compilebench_.cpuprof")
|
||||
}
|
||||
|
||||
wallns := end.Sub(start).Nanoseconds()
|
||||
userns := cmd.ProcessState.UserTime().Nanoseconds()
|
||||
|
||||
fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns)
|
||||
if *flagAlloc {
|
||||
fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
|
||||
}
|
||||
|
||||
opath := pkg.Dir + "/_compilebench_.o"
|
||||
|
@ -358,147 +375,4 @@ func (c compile) run(name string, count int) error {
|
|||
fmt.Println()
|
||||
|
||||
os.Remove(opath)
|
||||
return nil
|
||||
}
|
||||
|
||||
type link struct{ dir string }
|
||||
|
||||
func (link) long() bool { return false }
|
||||
|
||||
func (r link) run(name string, count int) error {
|
||||
if linker == "" {
|
||||
// No linker. Skip the test.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build dependencies.
|
||||
out, err := exec.Command(*flagGoCmd, "build", "-i", "-o", "/dev/null", r.dir).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("go build -i %s: %v\n%s", r.dir, err, out)
|
||||
}
|
||||
|
||||
// Build the main package.
|
||||
pkg, err := goList(r.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := []string{"-o", "_compilebench_.o"}
|
||||
args = append(args, pkg.GoFiles...)
|
||||
cmd := exec.Command(compiler, args...)
|
||||
cmd.Dir = pkg.Dir
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("compiling: %v", err)
|
||||
}
|
||||
defer os.Remove(pkg.Dir + "/_compilebench_.o")
|
||||
|
||||
// Link the main package.
|
||||
args = []string{"-o", "_compilebench_.exe"}
|
||||
args = append(args, strings.Fields(*flagLinkerFlags)...)
|
||||
args = append(args, "_compilebench_.o")
|
||||
if err := runBuildCmd(name, count, pkg.Dir, linker, args); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
defer os.Remove(pkg.Dir + "/_compilebench_.exe")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// runBuildCmd runs "tool args..." in dir, measures standard build
|
||||
// tool metrics, and prints a benchmark line. The caller may print
|
||||
// additional metrics and then must print a newline.
|
||||
//
|
||||
// This assumes tool accepts standard build tool flags like
|
||||
// -memprofilerate, -memprofile, and -cpuprofile.
|
||||
func runBuildCmd(name string, count int, dir, tool string, args []string) error {
|
||||
var preArgs []string
|
||||
if *flagMemprofilerate >= 0 {
|
||||
preArgs = append(preArgs, "-memprofilerate", fmt.Sprint(*flagMemprofilerate))
|
||||
}
|
||||
if *flagAlloc || *flagCpuprofile != "" || *flagMemprofile != "" {
|
||||
if *flagAlloc || *flagMemprofile != "" {
|
||||
preArgs = append(preArgs, "-memprofile", "_compilebench_.memprof")
|
||||
}
|
||||
if *flagCpuprofile != "" {
|
||||
preArgs = append(preArgs, "-cpuprofile", "_compilebench_.cpuprof")
|
||||
}
|
||||
}
|
||||
cmd := exec.Command(tool, append(preArgs, args...)...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
start := time.Now()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end := time.Now()
|
||||
|
||||
haveAllocs := false
|
||||
var allocs, allocbytes int64
|
||||
if *flagAlloc || *flagMemprofile != "" {
|
||||
out, err := ioutil.ReadFile(dir + "/_compilebench_.memprof")
|
||||
if err != nil {
|
||||
log.Print("cannot find memory profile after compilation")
|
||||
}
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 4 || f[0] != "#" || f[2] != "=" {
|
||||
continue
|
||||
}
|
||||
val, err := strconv.ParseInt(f[3], 0, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
haveAllocs = true
|
||||
switch f[1] {
|
||||
case "TotalAlloc":
|
||||
allocbytes = val
|
||||
case "Mallocs":
|
||||
allocs = val
|
||||
}
|
||||
}
|
||||
if !haveAllocs {
|
||||
log.Println("missing stats in memprof (golang.org/issue/18641)")
|
||||
}
|
||||
|
||||
if *flagMemprofile != "" {
|
||||
outpath := *flagMemprofile
|
||||
if *flagCount != 1 {
|
||||
outpath = fmt.Sprintf("%s_%d", outpath, count)
|
||||
}
|
||||
if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
os.Remove(dir + "/_compilebench_.memprof")
|
||||
}
|
||||
|
||||
if *flagCpuprofile != "" {
|
||||
out, err := ioutil.ReadFile(dir + "/_compilebench_.cpuprof")
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
outpath := *flagCpuprofile
|
||||
if *flagCount != 1 {
|
||||
outpath = fmt.Sprintf("%s_%d", outpath, count)
|
||||
}
|
||||
if err := ioutil.WriteFile(outpath, out, 0666); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
os.Remove(dir + "/_compilebench_.cpuprof")
|
||||
}
|
||||
|
||||
wallns := end.Sub(start).Nanoseconds()
|
||||
userns := cmd.ProcessState.UserTime().Nanoseconds()
|
||||
|
||||
fmt.Printf("%s 1 %d ns/op %d user-ns/op", name, wallns, userns)
|
||||
if haveAllocs {
|
||||
fmt.Printf(" %d B/op %d allocs/op", allocbytes, allocs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,83 +1,18 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
/*
|
||||
The digraph command performs queries over unlabelled directed graphs
|
||||
represented in text form. It is intended to integrate nicely with
|
||||
typical UNIX command pipelines.
|
||||
|
||||
Usage:
|
||||
|
||||
your-application | digraph [command]
|
||||
|
||||
The support commands are:
|
||||
|
||||
nodes
|
||||
the set of all nodes
|
||||
degree
|
||||
the in-degree and out-degree of each node
|
||||
preds <node> ...
|
||||
the set of immediate predecessors of the specified nodes
|
||||
succs <node> ...
|
||||
the set of immediate successors of the specified nodes
|
||||
forward <node> ...
|
||||
the set of nodes transitively reachable from the specified nodes
|
||||
reverse <node> ...
|
||||
the set of nodes that transitively reach the specified nodes
|
||||
somepath <node> <node>
|
||||
the list of nodes on some arbitrary path from the first node to the second
|
||||
allpaths <node> <node>
|
||||
the set of nodes on all paths from the first node to the second
|
||||
sccs
|
||||
all strongly connected components (one per line)
|
||||
scc <node>
|
||||
the set of nodes nodes strongly connected to the specified one
|
||||
|
||||
Input format:
|
||||
|
||||
Each line contains zero or more words. Words are separated by unquoted
|
||||
whitespace; words may contain Go-style double-quoted portions, allowing spaces
|
||||
and other characters to be expressed.
|
||||
|
||||
Each word declares a node, and if there are more than one, an edge from the
|
||||
first to each subsequent one. The graph is provided on the standard input.
|
||||
|
||||
For instance, the following (acyclic) graph specifies a partial order among the
|
||||
subtasks of getting dressed:
|
||||
|
||||
$ cat clothes.txt
|
||||
socks shoes
|
||||
"boxer shorts" pants
|
||||
pants belt shoes
|
||||
shirt tie sweater
|
||||
sweater jacket
|
||||
hat
|
||||
|
||||
The line "shirt tie sweater" indicates the two edges shirt -> tie and
|
||||
shirt -> sweater, not shirt -> tie -> sweater.
|
||||
|
||||
Example usage:
|
||||
|
||||
Using digraph with existing Go tools:
|
||||
|
||||
$ go mod graph | digraph nodes # Operate on the Go module graph.
|
||||
$ go list -m all | digraph nodes # Operate on the Go package graph.
|
||||
|
||||
Show the transitive closure of imports of the digraph tool itself:
|
||||
$ go list -f '{{.ImportPath}} {{join .Imports " "}}' ... | digraph forward golang.org/x/tools/cmd/digraph
|
||||
|
||||
Show which clothes (see above) must be donned before a jacket:
|
||||
$ digraph reverse jacket
|
||||
|
||||
*/
|
||||
package main // import "golang.org/x/tools/cmd/digraph"
|
||||
|
||||
// The digraph command performs queries over unlabelled directed graphs
|
||||
// represented in text form. It is intended to integrate nicely with
|
||||
// typical UNIX command pipelines.
|
||||
//
|
||||
// Since directed graphs (import graphs, reference graphs, call graphs,
|
||||
// etc) often arise during software tool development and debugging, this
|
||||
// command is included in the go.tools repository.
|
||||
//
|
||||
// TODO(adonovan):
|
||||
// - support input files other than stdin
|
||||
// - support alternative formats (AT&T GraphViz, CSV, etc),
|
||||
// a comment syntax, etc.
|
||||
// - allow queries to nest, like Blaze query language.
|
||||
//
|
||||
package main // import "golang.org/x/tools/cmd/digraph"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
@ -93,41 +28,74 @@ import (
|
|||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, `Usage: your-application | digraph [command]
|
||||
const Usage = `digraph: queries over directed graphs in text form.
|
||||
|
||||
The support commands are:
|
||||
nodes
|
||||
the set of all nodes
|
||||
degree
|
||||
the in-degree and out-degree of each node
|
||||
preds <node> ...
|
||||
the set of immediate predecessors of the specified nodes
|
||||
succs <node> ...
|
||||
the set of immediate successors of the specified nodes
|
||||
forward <node> ...
|
||||
the set of nodes transitively reachable from the specified nodes
|
||||
reverse <node> ...
|
||||
the set of nodes that transitively reach the specified nodes
|
||||
somepath <node> <node>
|
||||
the list of nodes on some arbitrary path from the first node to the second
|
||||
allpaths <node> <node>
|
||||
the set of nodes on all paths from the first node to the second
|
||||
sccs
|
||||
all strongly connected components (one per line)
|
||||
scc <node>
|
||||
the set of nodes nodes strongly connected to the specified one
|
||||
`)
|
||||
os.Exit(2)
|
||||
}
|
||||
Graph format:
|
||||
|
||||
Each line contains zero or more words. Words are separated by
|
||||
unquoted whitespace; words may contain Go-style double-quoted portions,
|
||||
allowing spaces and other characters to be expressed.
|
||||
|
||||
Each field declares a node, and if there are more than one,
|
||||
an edge from the first to each subsequent one.
|
||||
The graph is provided on the standard input.
|
||||
|
||||
For instance, the following (acyclic) graph specifies a partial order
|
||||
among the subtasks of getting dressed:
|
||||
|
||||
% cat clothes.txt
|
||||
socks shoes
|
||||
"boxer shorts" pants
|
||||
pants belt shoes
|
||||
shirt tie sweater
|
||||
sweater jacket
|
||||
hat
|
||||
|
||||
The line "shirt tie sweater" indicates the two edges shirt -> tie and
|
||||
shirt -> sweater, not shirt -> tie -> sweater.
|
||||
|
||||
Supported queries:
|
||||
|
||||
nodes
|
||||
the set of all nodes
|
||||
degree
|
||||
the in-degree and out-degree of each node.
|
||||
preds <label> ...
|
||||
the set of immediate predecessors of the specified nodes
|
||||
succs <label> ...
|
||||
the set of immediate successors of the specified nodes
|
||||
forward <label> ...
|
||||
the set of nodes transitively reachable from the specified nodes
|
||||
reverse <label> ...
|
||||
the set of nodes that transitively reach the specified nodes
|
||||
somepath <label> <label>
|
||||
the list of nodes on some arbitrary path from the first node to the second
|
||||
allpaths <label> <label>
|
||||
the set of nodes on all paths from the first node to the second
|
||||
sccs
|
||||
all strongly connected components (one per line)
|
||||
scc <label>
|
||||
the set of nodes nodes strongly connected to the specified one
|
||||
|
||||
Example usage:
|
||||
|
||||
Show the transitive closure of imports of the digraph tool itself:
|
||||
% go list -f '{{.ImportPath}}{{.Imports}}' ... | tr '[]' ' ' |
|
||||
digraph forward golang.org/x/tools/cmd/digraph
|
||||
|
||||
Show which clothes (see above) must be donned before a jacket:
|
||||
% digraph reverse jacket <clothes.txt
|
||||
|
||||
`
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Usage = func() { fmt.Fprintln(os.Stderr, Usage) }
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
usage()
|
||||
fmt.Fprintln(os.Stderr, Usage)
|
||||
return
|
||||
}
|
||||
|
||||
if err := digraph(args[0], args[1:]); err != nil {
|
||||
|
@ -262,47 +230,6 @@ func (g graph) sccs() []nodeset {
|
|||
return sccs
|
||||
}
|
||||
|
||||
func (g graph) allpaths(from, to string) error {
|
||||
// Mark all nodes to "to".
|
||||
seen := make(nodeset) // value of seen[x] indicates whether x is on some path to "to"
|
||||
var visit func(node string) bool
|
||||
visit = func(node string) bool {
|
||||
reachesTo, ok := seen[node]
|
||||
if !ok {
|
||||
reachesTo = node == to
|
||||
seen[node] = reachesTo
|
||||
for e := range g[node] {
|
||||
if visit(e) {
|
||||
reachesTo = true
|
||||
}
|
||||
}
|
||||
if reachesTo && node != to {
|
||||
seen[node] = true
|
||||
}
|
||||
}
|
||||
return reachesTo
|
||||
}
|
||||
visit(from)
|
||||
|
||||
// For each marked node, collect its marked successors.
|
||||
var edges []string
|
||||
for n := range seen {
|
||||
for succ := range g[n] {
|
||||
if seen[succ] {
|
||||
edges = append(edges, n+" "+succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort (so that this method is deterministic) and print edges.
|
||||
sort.Strings(edges)
|
||||
for _, e := range edges {
|
||||
fmt.Fprintln(stdout, e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse(rd io.Reader) (graph, error) {
|
||||
g := make(graph)
|
||||
|
||||
|
@ -325,7 +252,6 @@ func parse(rd io.Reader) (graph, error) {
|
|||
return g, nil
|
||||
}
|
||||
|
||||
// Overridable for testing purposes.
|
||||
var stdin io.Reader = os.Stdin
|
||||
var stdout io.Writer = os.Stdout
|
||||
|
||||
|
@ -440,7 +366,33 @@ func digraph(cmd string, args []string) error {
|
|||
if g[to] == nil {
|
||||
return fmt.Errorf("no such 'to' node %q", to)
|
||||
}
|
||||
g.allpaths(from, to)
|
||||
|
||||
seen := make(nodeset) // value of seen[x] indicates whether x is on some path to 'to'
|
||||
var visit func(label string) bool
|
||||
visit = func(label string) bool {
|
||||
reachesTo, ok := seen[label]
|
||||
if !ok {
|
||||
reachesTo = label == to
|
||||
|
||||
seen[label] = reachesTo
|
||||
for e := range g[label] {
|
||||
if visit(e) {
|
||||
reachesTo = true
|
||||
}
|
||||
}
|
||||
seen[label] = reachesTo
|
||||
}
|
||||
return reachesTo
|
||||
}
|
||||
if !visit(from) {
|
||||
return fmt.Errorf("no path from %q to %q", from, to)
|
||||
}
|
||||
for label, reachesTo := range seen {
|
||||
if !reachesTo {
|
||||
delete(seen, label)
|
||||
}
|
||||
}
|
||||
seen.sort().println("\n")
|
||||
|
||||
case "sccs":
|
||||
if len(args) != 0 {
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
// Copyright 2019 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 main
|
||||
|
||||
import (
|
||||
|
@ -29,34 +26,35 @@ d c
|
|||
`
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
input string
|
||||
cmd string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
|
||||
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
|
||||
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
|
||||
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
|
||||
{"scss", g2, "sccs", nil, "a\nb\nc d\n"},
|
||||
{"scc", g2, "scc", []string{"d"}, "c\nd\n"},
|
||||
{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
|
||||
{"preds", g2, "preds", []string{"c"}, "a\nd\n"},
|
||||
{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
stdin = strings.NewReader(test.input)
|
||||
stdout = new(bytes.Buffer)
|
||||
if err := digraph(test.cmd, test.args); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
{g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
|
||||
{g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
|
||||
{g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
|
||||
{g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
|
||||
|
||||
got := stdout.(fmt.Stringer).String()
|
||||
if got != test.want {
|
||||
t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want)
|
||||
}
|
||||
})
|
||||
{g2, "allpaths", []string{"a", "d"}, "a\nb\nc\nd\n"},
|
||||
|
||||
{g2, "sccs", nil, "a\nb\nc d\n"},
|
||||
{g2, "scc", []string{"d"}, "c\nd\n"},
|
||||
{g2, "succs", []string{"a"}, "b\nc\n"},
|
||||
{g2, "preds", []string{"c"}, "a\nd\n"},
|
||||
{g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
|
||||
} {
|
||||
stdin = strings.NewReader(test.input)
|
||||
stdout = new(bytes.Buffer)
|
||||
if err := digraph(test.cmd, test.args); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
got := stdout.(fmt.Stringer).String()
|
||||
if got != test.want {
|
||||
t.Errorf("digraph(%s, %s) = %q, want %q", test.cmd, test.args, got, test.want)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan):
|
||||
|
@ -64,110 +62,6 @@ d c
|
|||
// - test errors
|
||||
}
|
||||
|
||||
func TestAllpaths(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
in string
|
||||
to string // from is always "A"
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Basic",
|
||||
in: "A B\nB C",
|
||||
to: "B",
|
||||
want: "A B\n",
|
||||
},
|
||||
{
|
||||
name: "Long",
|
||||
in: "A B\nB C\n",
|
||||
to: "C",
|
||||
want: "A B\nB C\n",
|
||||
},
|
||||
{
|
||||
name: "Cycle Basic",
|
||||
in: "A B\nB A",
|
||||
to: "B",
|
||||
want: "A B\nB A\n",
|
||||
},
|
||||
{
|
||||
name: "Cycle Path Out",
|
||||
// A <-> B -> C -> D
|
||||
in: "A B\nB A\nB C\nC D",
|
||||
to: "C",
|
||||
want: "A B\nB A\nB C\n",
|
||||
},
|
||||
{
|
||||
name: "Cycle Path Out Further Out",
|
||||
// A -> B <-> C -> D -> E
|
||||
in: "A B\nB C\nC D\nC B\nD E",
|
||||
to: "D",
|
||||
want: "A B\nB C\nC B\nC D\n",
|
||||
},
|
||||
{
|
||||
name: "Two Paths Basic",
|
||||
// /-> C --\
|
||||
// A -> B -- -> E -> F
|
||||
// \-> D --/
|
||||
in: "A B\nB C\nC E\nB D\nD E\nE F",
|
||||
to: "E",
|
||||
want: "A B\nB C\nB D\nC E\nD E\n",
|
||||
},
|
||||
{
|
||||
name: "Two Paths With One Immediately From Start",
|
||||
// /-> B -+ -> D
|
||||
// A -- |
|
||||
// \-> C <+
|
||||
in: "A B\nA C\nB C\nB D",
|
||||
to: "C",
|
||||
want: "A B\nA C\nB C\n",
|
||||
},
|
||||
{
|
||||
name: "Two Paths Further Up",
|
||||
// /-> B --\
|
||||
// A -- -> D -> E -> F
|
||||
// \-> C --/
|
||||
in: "A B\nA C\nB D\nC D\nD E\nE F",
|
||||
to: "E",
|
||||
want: "A B\nA C\nB D\nC D\nD E\n",
|
||||
},
|
||||
{
|
||||
// We should include A - C - D even though it's further up the
|
||||
// second path than D (which would already be in the graph by
|
||||
// the time we get around to integrating the second path).
|
||||
name: "Two Splits",
|
||||
// /-> B --\ /-> E --\
|
||||
// A -- -> D -- -> G -> H
|
||||
// \-> C --/ \-> F --/
|
||||
in: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\nG H",
|
||||
to: "G",
|
||||
want: "A B\nA C\nB D\nC D\nD E\nD F\nE G\nF G\n",
|
||||
},
|
||||
{
|
||||
// D - E should not be duplicated.
|
||||
name: "Two Paths - Two Splits With Gap",
|
||||
// /-> B --\ /-> F --\
|
||||
// A -- -> D -> E -- -> H -> I
|
||||
// \-> C --/ \-> G --/
|
||||
in: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\nH I",
|
||||
to: "H",
|
||||
want: "A B\nA C\nB D\nC D\nD E\nE F\nE G\nF H\nG H\n",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
stdin = strings.NewReader(test.in)
|
||||
stdout = new(bytes.Buffer)
|
||||
if err := digraph("allpaths", []string{"A", test.to}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := stdout.(fmt.Stringer).String()
|
||||
if got != test.want {
|
||||
t.Errorf("digraph(allpaths, A, %s) = got %q, want %q", test.to, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
line string
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
index.split.*
|
||||
godoc.index
|
||||
godoc.zip
|
|
@ -0,0 +1,67 @@
|
|||
# Builder
|
||||
#########
|
||||
|
||||
FROM golang:1.11 AS build
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
zip # required for generate-index.bash
|
||||
|
||||
# Check out the desired version of Go, both to build the godoc binary and serve
|
||||
# as the goroot for content serving.
|
||||
ARG GO_REF
|
||||
RUN test -n "$GO_REF" # GO_REF is required.
|
||||
RUN git clone --single-branch --depth=1 -b $GO_REF https://go.googlesource.com/go /goroot
|
||||
RUN cd /goroot/src && ./make.bash
|
||||
|
||||
ENV GOROOT /goroot
|
||||
ENV PATH=/goroot/bin:$PATH
|
||||
|
||||
RUN go version
|
||||
|
||||
RUN go get -v -d \
|
||||
golang.org/x/net/context \
|
||||
google.golang.org/appengine \
|
||||
cloud.google.com/go/datastore \
|
||||
golang.org/x/build \
|
||||
github.com/gomodule/redigo/redis
|
||||
|
||||
COPY . /go/src/golang.org/x/tools
|
||||
|
||||
WORKDIR /go/src/golang.org/x/tools/cmd/godoc
|
||||
RUN GODOC_DOCSET=/goroot ./generate-index.bash
|
||||
|
||||
RUN go build -o /godoc -tags=golangorg golang.org/x/tools/cmd/godoc
|
||||
|
||||
# Clean up goroot for the final image.
|
||||
RUN cd /goroot && git clean -xdf
|
||||
|
||||
# Add build metadata.
|
||||
RUN cd /goroot && echo "go repo HEAD: $(git rev-parse HEAD)" >> /goroot/buildinfo
|
||||
RUN echo "requested go ref: ${GO_REF}" >> /goroot/buildinfo
|
||||
ARG TOOLS_HEAD
|
||||
RUN echo "x/tools HEAD: ${TOOLS_HEAD}" >> /goroot/buildinfo
|
||||
ARG TOOLS_CLEAN
|
||||
RUN echo "x/tools clean: ${TOOLS_CLEAN}" >> /goroot/buildinfo
|
||||
ARG DOCKER_TAG
|
||||
RUN echo "image: ${DOCKER_TAG}" >> /goroot/buildinfo
|
||||
ARG BUILD_ENV
|
||||
RUN echo "build env: ${BUILD_ENV}" >> /goroot/buildinfo
|
||||
|
||||
RUN rm -rf /goroot/.git
|
||||
|
||||
# Final image
|
||||
#############
|
||||
|
||||
FROM gcr.io/distroless/base
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /godoc /app/
|
||||
COPY --from=build /go/src/golang.org/x/tools/cmd/godoc/hg-git-mapping.bin /app/
|
||||
|
||||
COPY --from=build /goroot /goroot
|
||||
ENV GOROOT /goroot
|
||||
|
||||
COPY --from=build /go/src/golang.org/x/tools/cmd/godoc/index.split.* /app/
|
||||
ENV GODOC_INDEX_GLOB index.split.*
|
||||
|
||||
CMD ["/app/godoc"]
|
|
@ -0,0 +1,80 @@
|
|||
# Copyright 2018 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.
|
||||
|
||||
.PHONY: usage
|
||||
|
||||
GO_REF ?= release-branch.go1.11
|
||||
TOOLS_HEAD := $(shell git rev-parse HEAD)
|
||||
TOOLS_CLEAN := $(shell (git status --porcelain | grep -q .) && echo dirty || echo clean)
|
||||
ifeq ($(TOOLS_CLEAN),clean)
|
||||
DOCKER_VERSION ?= $(TOOLS_HEAD)
|
||||
else
|
||||
DOCKER_VERSION ?= $(TOOLS_HEAD)-dirty
|
||||
endif
|
||||
GCP_PROJECT := golang-org
|
||||
DOCKER_TAG := gcr.io/$(GCP_PROJECT)/godoc:$(DOCKER_VERSION)
|
||||
|
||||
usage:
|
||||
@echo "See Makefile and README.godoc-app"
|
||||
@exit 1
|
||||
|
||||
cloud-build: Dockerfile.prod
|
||||
gcloud builds submit \
|
||||
--project=$(GCP_PROJECT) \
|
||||
--config=cloudbuild.yaml \
|
||||
--substitutions=_GO_REF=$(GO_REF),_TOOLS_HEAD=$(TOOLS_HEAD),_TOOLS_CLEAN=$(TOOLS_CLEAN),_DOCKER_TAG=$(DOCKER_TAG) \
|
||||
../.. # source code
|
||||
|
||||
docker-build: Dockerfile.prod
|
||||
# NOTE(cbro): move up in directory to include entire tools repo.
|
||||
# NOTE(cbro): any changes made to this command must also be made in cloudbuild.yaml.
|
||||
cd ../..; docker build \
|
||||
-f=cmd/godoc/Dockerfile.prod \
|
||||
--build-arg=GO_REF=$(GO_REF) \
|
||||
--build-arg=TOOLS_HEAD=$(TOOLS_HEAD) \
|
||||
--build-arg=TOOLS_CLEAN=$(TOOLS_CLEAN) \
|
||||
--build-arg=DOCKER_TAG=$(DOCKER_TAG) \
|
||||
--build-arg=BUILD_ENV=local \
|
||||
--tag=$(DOCKER_TAG) \
|
||||
.
|
||||
|
||||
docker-push: docker-build
|
||||
docker push $(DOCKER_TAG)
|
||||
|
||||
deploy:
|
||||
gcloud -q app deploy app.prod.yaml \
|
||||
--project=$(GCP_PROJECT) \
|
||||
--no-promote \
|
||||
--image-url=$(DOCKER_TAG)
|
||||
|
||||
get-latest-url:
|
||||
@gcloud app versions list \
|
||||
--service=default \
|
||||
--project=$(GCP_PROJECT) \
|
||||
--sort-by=~version.createTime \
|
||||
--format='value(version.versionUrl)' \
|
||||
--limit 1 | cut -f1 # NOTE(cbro): gcloud prints out createTime as the second field.
|
||||
|
||||
get-latest-id:
|
||||
@gcloud app versions list \
|
||||
--service=default \
|
||||
--project=$(GCP_PROJECT) \
|
||||
--sort-by=~version.createTime \
|
||||
--format='value(version.id)' \
|
||||
--limit 1 | cut -f1 # NOTE(cbro): gcloud prints out createTime as the second field.
|
||||
|
||||
regtest:
|
||||
go test -v \
|
||||
-regtest.host=$(shell make get-latest-url) \
|
||||
-run=Live
|
||||
|
||||
publish: regtest
|
||||
gcloud -q app services set-traffic default \
|
||||
--splits=$(shell make get-latest-id)=1 \
|
||||
--project=$(GCP_PROJECT)
|
||||
|
||||
@echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
@echo Stop and/or delete old versions:
|
||||
@echo "https://console.cloud.google.com/appengine/versions?project=$(GCP_PROJECT)&serviceId=default&versionssize=50"
|
||||
@echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
@ -0,0 +1,94 @@
|
|||
godoc on Google App Engine
|
||||
==========================
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
* Google Cloud SDK
|
||||
https://cloud.google.com/sdk/
|
||||
|
||||
* Redis
|
||||
|
||||
* Go sources under $GOROOT
|
||||
|
||||
* Godoc sources inside $GOPATH
|
||||
(go get -d golang.org/x/tools/cmd/godoc)
|
||||
|
||||
|
||||
Running locally, in production mode
|
||||
-----------------------------------
|
||||
|
||||
Build the app:
|
||||
|
||||
go build -tags golangorg
|
||||
|
||||
Run the app:
|
||||
|
||||
./godoc
|
||||
|
||||
godoc should come up at http://localhost:8080
|
||||
|
||||
Use the PORT environment variable to change the port:
|
||||
|
||||
PORT=8081 ./godoc
|
||||
|
||||
Running locally, in production mode, using Docker
|
||||
-------------------------------------------------
|
||||
|
||||
Build the app's Docker container:
|
||||
|
||||
make docker-build
|
||||
|
||||
Make sure redis is running on port 6379:
|
||||
|
||||
$ echo PING | nc localhost 6379
|
||||
+PONG
|
||||
^C
|
||||
|
||||
Run the datastore emulator:
|
||||
|
||||
gcloud beta emulators datastore start --project golang-org
|
||||
|
||||
In another terminal window, run the container:
|
||||
|
||||
$(gcloud beta emulators datastore env-init)
|
||||
|
||||
docker run --rm \
|
||||
--net host \
|
||||
--env GODOC_REDIS_ADDR=localhost:6379 \
|
||||
--env DATASTORE_EMULATOR_HOST=$DATASTORE_EMULATOR_HOST \
|
||||
--env DATASTORE_PROJECT_ID=$DATASTORE_PROJECT_ID \
|
||||
gcr.io/golang-org/godoc
|
||||
|
||||
godoc should come up at http://localhost:8080
|
||||
|
||||
|
||||
Deploying to golang.org
|
||||
-----------------------
|
||||
|
||||
Make sure you're signed in to gcloud:
|
||||
|
||||
gcloud auth login
|
||||
|
||||
Build the image, push it to gcr.io, and deploy to Flex:
|
||||
|
||||
make cloud-build deploy
|
||||
|
||||
Point the load balancer to the newly deployed version:
|
||||
(This also runs regression tests)
|
||||
|
||||
make publish
|
||||
|
||||
Stop and/or delete down any very old versions. (Stopped versions can be re-started.)
|
||||
Keep at least one older verson to roll back to, just in case.
|
||||
You can also migrate traffic to the new version via this UI.
|
||||
|
||||
https://console.cloud.google.com/appengine/versions?project=golang-org&serviceId=default&versionssize=50
|
||||
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Ensure the Cloud SDK is on your PATH and you have the app-engine-go component
|
||||
installed (gcloud components install app-engine-go) and your components are
|
||||
up-to-date (gcloud components update)
|
|
@ -0,0 +1,13 @@
|
|||
runtime: go
|
||||
api_version: go1
|
||||
instance_class: F4_1G
|
||||
|
||||
handlers:
|
||||
- url: /s
|
||||
script: _go_app
|
||||
login: admin
|
||||
- url: /dl/init
|
||||
script: _go_app
|
||||
login: admin
|
||||
- url: /.*
|
||||
script: _go_app
|
|
@ -0,0 +1,16 @@
|
|||
runtime: custom
|
||||
env: flex
|
||||
|
||||
env_variables:
|
||||
GODOC_PROD: true
|
||||
GODOC_ENFORCE_HOSTS: true
|
||||
GODOC_REDIS_ADDR: 10.0.0.4:6379 # instance "gophercache"
|
||||
GODOC_ANALYTICS: UA-11222381-2
|
||||
DATASTORE_PROJECT_ID: golang-org
|
||||
|
||||
network:
|
||||
name: golang
|
||||
|
||||
resources:
|
||||
cpu: 4
|
||||
memory_gb: 7.50
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright 2011 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.
|
||||
|
||||
// +build golangorg
|
||||
|
||||
package main
|
||||
|
||||
// This file replaces main.go when running godoc under app-engine.
|
||||
// See README.godoc-app for details.
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/godoc"
|
||||
"golang.org/x/tools/godoc/dl"
|
||||
"golang.org/x/tools/godoc/proxy"
|
||||
"golang.org/x/tools/godoc/redirect"
|
||||
"golang.org/x/tools/godoc/short"
|
||||
"golang.org/x/tools/godoc/static"
|
||||
"golang.org/x/tools/godoc/vfs"
|
||||
"golang.org/x/tools/godoc/vfs/gatefs"
|
||||
"golang.org/x/tools/godoc/vfs/mapfs"
|
||||
"golang.org/x/tools/godoc/vfs/zipfs"
|
||||
|
||||
"cloud.google.com/go/datastore"
|
||||
"golang.org/x/tools/internal/memcache"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile | log.LstdFlags)
|
||||
|
||||
var (
|
||||
// .zip filename
|
||||
zipFilename = os.Getenv("GODOC_ZIP")
|
||||
|
||||
// goroot directory in .zip file
|
||||
zipGoroot = os.Getenv("GODOC_ZIP_PREFIX")
|
||||
|
||||
// glob pattern describing search index files
|
||||
// (if empty, the index is built at run-time)
|
||||
indexFilenames = os.Getenv("GODOC_INDEX_GLOB")
|
||||
)
|
||||
|
||||
playEnabled = true
|
||||
|
||||
log.Println("initializing godoc ...")
|
||||
log.Printf(".zip file = %s", zipFilename)
|
||||
log.Printf(".zip GOROOT = %s", zipGoroot)
|
||||
log.Printf("index files = %s", indexFilenames)
|
||||
|
||||
if zipFilename != "" {
|
||||
goroot := path.Join("/", zipGoroot) // fsHttp paths are relative to '/'
|
||||
// read .zip file and set up file systems
|
||||
rc, err := zip.OpenReader(zipFilename)
|
||||
if err != nil {
|
||||
log.Fatalf("%s: %s\n", zipFilename, err)
|
||||
}
|
||||
// rc is never closed (app running forever)
|
||||
fs.Bind("/", zipfs.New(rc, zipFilename), goroot, vfs.BindReplace)
|
||||
} else {
|
||||
rootfs := gatefs.New(vfs.OS(runtime.GOROOT()), make(chan bool, 20))
|
||||
fs.Bind("/", rootfs, "/", vfs.BindReplace)
|
||||
}
|
||||
|
||||
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
|
||||
|
||||
corpus := godoc.NewCorpus(fs)
|
||||
corpus.Verbose = false
|
||||
corpus.MaxResults = 10000 // matches flag default in main.go
|
||||
corpus.IndexEnabled = true
|
||||
corpus.IndexFiles = indexFilenames
|
||||
if err := corpus.Init(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
corpus.IndexDirectory = indexDirectoryDefault
|
||||
corpus.InitVersionInfo()
|
||||
if indexFilenames != "" {
|
||||
corpus.RunIndexer()
|
||||
} else {
|
||||
go corpus.RunIndexer()
|
||||
}
|
||||
|
||||
pres = godoc.NewPresentation(corpus)
|
||||
pres.TabWidth = 8
|
||||
pres.ShowPlayground = true
|
||||
pres.DeclLinks = true
|
||||
pres.NotesRx = regexp.MustCompile("BUG")
|
||||
pres.GoogleAnalytics = os.Getenv("GODOC_ANALYTICS")
|
||||
|
||||
readTemplates(pres)
|
||||
|
||||
datastoreClient, memcacheClient := getClients()
|
||||
|
||||
// NOTE(cbro): registerHandlers registers itself against DefaultServeMux.
|
||||
// The mux returned has host enforcement, so it's important to register
|
||||
// against this mux and not DefaultServeMux.
|
||||
mux := registerHandlers(pres)
|
||||
dl.RegisterHandlers(mux, datastoreClient, memcacheClient)
|
||||
short.RegisterHandlers(mux, datastoreClient, memcacheClient)
|
||||
|
||||
// Register /compile and /share handlers against the default serve mux
|
||||
// so that other app modules can make plain HTTP requests to those
|
||||
// hosts. (For reasons, HTTPS communication between modules is broken.)
|
||||
proxy.RegisterHandlers(http.DefaultServeMux)
|
||||
|
||||
http.HandleFunc("/_ah/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "ok")
|
||||
})
|
||||
|
||||
http.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "User-agent: *\nDisallow: /search\n")
|
||||
})
|
||||
|
||||
if err := redirect.LoadChangeMap("hg-git-mapping.bin"); err != nil {
|
||||
log.Fatalf("LoadChangeMap: %v", err)
|
||||
}
|
||||
|
||||
log.Println("godoc initialization complete")
|
||||
|
||||
// TODO(cbro): add instrumentation via opencensus.
|
||||
port := "8080"
|
||||
if p := os.Getenv("PORT"); p != "" { // PORT is set by GAE flex.
|
||||
port = p
|
||||
}
|
||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||
}
|
||||
|
||||
func getClients() (*datastore.Client, *memcache.Client) {
|
||||
ctx := context.Background()
|
||||
|
||||
datastoreClient, err := datastore.NewClient(ctx, "")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "missing project") {
|
||||
log.Fatalf("Missing datastore project. Set the DATASTORE_PROJECT_ID env variable. Use `gcloud beta emulators datastore` to start a local datastore.")
|
||||
}
|
||||
log.Fatalf("datastore.NewClient: %v.", err)
|
||||
}
|
||||
|
||||
redisAddr := os.Getenv("GODOC_REDIS_ADDR")
|
||||
if redisAddr == "" {
|
||||
log.Fatalf("Missing redis server for godoc in production mode. set GODOC_REDIS_ADDR environment variable.")
|
||||
}
|
||||
memcacheClient := memcache.New(redisAddr)
|
||||
return datastoreClient, memcacheClient
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2016 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.
|
||||
|
||||
// +build autocert
|
||||
|
||||
// This file adds automatic TLS certificate support (using
|
||||
// golang.org/x/crypto/acme/autocert), conditional on the use of the
|
||||
// autocert build tag. It sets the serveAutoCertHook func variable
|
||||
// non-nil. It is used by main.go.
|
||||
//
|
||||
// TODO: make this the default? We're in the Go 1.8 freeze now, so
|
||||
// this is too invasive to be default, but we want it for
|
||||
// https://beta.golang.org/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var (
|
||||
autoCertDirFlag = flag.String("autocert_cache_dir", "/var/cache/autocert", "Directory to cache TLS certs")
|
||||
autoCertHostFlag = flag.String("autocert_hostname", "", "optional hostname to require in autocert SNI requests")
|
||||
)
|
||||
|
||||
func init() {
|
||||
runHTTPS = runHTTPSAutocert
|
||||
certInit = certInitAutocert
|
||||
wrapHTTPMux = wrapHTTPMuxAutocert
|
||||
}
|
||||
|
||||
var autocertManager *autocert.Manager
|
||||
|
||||
func certInitAutocert() {
|
||||
autocertManager = &autocert.Manager{
|
||||
Cache: autocert.DirCache(*autoCertDirFlag),
|
||||
Prompt: autocert.AcceptTOS,
|
||||
}
|
||||
if *autoCertHostFlag != "" {
|
||||
autocertManager.HostPolicy = autocert.HostWhitelist(*autoCertHostFlag)
|
||||
}
|
||||
}
|
||||
|
||||
func runHTTPSAutocert(h http.Handler) error {
|
||||
srv := &http.Server{
|
||||
Handler: h,
|
||||
TLSConfig: &tls.Config{
|
||||
GetCertificate: autocertManager.GetCertificate,
|
||||
},
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
http2.ConfigureServer(srv, &http2.Server{})
|
||||
ln, err := net.Listen("tcp", ":443")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.Serve(tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig))
|
||||
}
|
||||
|
||||
func wrapHTTPMuxAutocert(h http.Handler) http.Handler {
|
||||
return autocertManager.HTTPHandler(h)
|
||||
}
|
||||
|
||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away.
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
# Copyright 2018 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.
|
||||
|
||||
# NOTE(cbro): any changes to the docker command must also be
|
||||
# made in docker-build in the Makefile.
|
||||
#
|
||||
# Variable substitutions must have a preceding underscore. See:
|
||||
# https://cloud.google.com/cloud-build/docs/configuring-builds/substitute-variable-values#using_user-defined_substitutions
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: [
|
||||
'build',
|
||||
'-f=cmd/godoc/Dockerfile.prod',
|
||||
'--build-arg=GO_REF=${_GO_REF}',
|
||||
'--build-arg=TOOLS_HEAD=${_TOOLS_HEAD}',
|
||||
'--build-arg=TOOLS_CLEAN=${_TOOLS_CLEAN}',
|
||||
'--build-arg=DOCKER_TAG=${_DOCKER_TAG}',
|
||||
'--build-arg=BUILD_ENV=cloudbuild',
|
||||
'--tag=${_DOCKER_TAG}',
|
||||
'.',
|
||||
]
|
||||
images: ['${_DOCKER_TAG}']
|
||||
options:
|
||||
machineType: 'N1_HIGHCPU_8' # building the godoc index takes a lot of memory.
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !golangorg
|
||||
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
|
|
@ -89,8 +89,7 @@ The presentation mode of web pages served by godoc can be controlled with the
|
|||
|
||||
all show documentation for all declarations, not just the exported ones
|
||||
methods show all embedded methods, not just those of unexported anonymous fields
|
||||
src show the original source code rather than the extracted documentation
|
||||
flat present flat (not indented) directory listings using full paths
|
||||
src show the original source code rather then the extracted documentation
|
||||
|
||||
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation
|
||||
for all (not just the exported) declarations of package big.
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2011 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.
|
||||
|
||||
# This script creates a .zip file representing the $GOROOT file system
|
||||
# and computes the corresponding search index files.
|
||||
#
|
||||
# These are used in production (see app.prod.yaml)
|
||||
|
||||
set -e -u -x
|
||||
|
||||
ZIPFILE=godoc.zip
|
||||
INDEXFILE=godoc.index
|
||||
SPLITFILES=index.split.
|
||||
|
||||
error() {
|
||||
echo "error: $1"
|
||||
exit 2
|
||||
}
|
||||
|
||||
install() {
|
||||
go install
|
||||
}
|
||||
|
||||
getArgs() {
|
||||
if [ ! -v GODOC_DOCSET ]; then
|
||||
GODOC_DOCSET="$(go env GOROOT)"
|
||||
echo "GODOC_DOCSET not set explicitly, using GOROOT instead"
|
||||
fi
|
||||
|
||||
# safety checks
|
||||
if [ ! -d "$GODOC_DOCSET" ]; then
|
||||
error "$GODOC_DOCSET is not a directory"
|
||||
fi
|
||||
|
||||
# reporting
|
||||
echo "GODOC_DOCSET = $GODOC_DOCSET"
|
||||
}
|
||||
|
||||
makeZipfile() {
|
||||
echo "*** make $ZIPFILE"
|
||||
rm -f $ZIPFILE goroot
|
||||
ln -s "$GODOC_DOCSET" goroot
|
||||
zip -q -r $ZIPFILE goroot/* # glob to ignore dotfiles (like .git)
|
||||
rm goroot
|
||||
}
|
||||
|
||||
makeIndexfile() {
|
||||
echo "*** make $INDEXFILE"
|
||||
godoc=$(go env GOPATH)/bin/godoc
|
||||
# NOTE: run godoc without GOPATH set. Otherwise third-party packages will end up in the index.
|
||||
GOPATH= $godoc -write_index -goroot goroot -index_files=$INDEXFILE -zip=$ZIPFILE
|
||||
}
|
||||
|
||||
splitIndexfile() {
|
||||
echo "*** split $INDEXFILE"
|
||||
rm -f $SPLITFILES*
|
||||
split -b8m $INDEXFILE $SPLITFILES
|
||||
}
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
install
|
||||
getArgs "$@"
|
||||
makeZipfile
|
||||
makeIndexfile
|
||||
splitIndexfile
|
||||
rm $INDEXFILE
|
||||
|
||||
echo "*** setup complete"
|
|
@ -32,10 +32,6 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
|
|||
if runtime.GOARCH == "arm" {
|
||||
t.Skip("skipping test on arm platforms; too slow")
|
||||
}
|
||||
if runtime.GOOS == "android" {
|
||||
t.Skipf("the dependencies are not available on android")
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "godoc-regtest-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"text/template"
|
||||
|
||||
"golang.org/x/tools/godoc"
|
||||
"golang.org/x/tools/godoc/golangorgenv"
|
||||
"golang.org/x/tools/godoc/env"
|
||||
"golang.org/x/tools/godoc/redirect"
|
||||
"golang.org/x/tools/godoc/vfs"
|
||||
)
|
||||
|
@ -40,7 +40,7 @@ type hostEnforcerHandler struct {
|
|||
}
|
||||
|
||||
func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !golangorgenv.EnforceHosts() {
|
||||
if !env.EnforceHosts() {
|
||||
h.h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -15,6 +15,8 @@
|
|||
// http://godoc/pkg/compress/zlib)
|
||||
//
|
||||
|
||||
// +build !golangorg
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !golangorg
|
||||
|
||||
package main
|
||||
|
||||
// This package registers "/compile" and "/share" handlers
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
// Regression tests to run against a production instance of godoc.
|
||||
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var host = flag.String("regtest.host", "", "host to run regression test against")
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
*host = strings.TrimSuffix(*host, "/")
|
||||
}
|
||||
|
||||
func TestLiveServer(t *testing.T) {
|
||||
if *host == "" {
|
||||
t.Skip("regtest.host flag missing.")
|
||||
}
|
||||
substringTests := []struct {
|
||||
Message string
|
||||
Path string
|
||||
Substring string
|
||||
Regexp string
|
||||
NoAnalytics bool // expect the response to not contain GA.
|
||||
PostBody string
|
||||
StatusCode int // if 0, expect 2xx status code.
|
||||
}{
|
||||
{
|
||||
Path: "/doc/faq",
|
||||
Substring: "What is the purpose of the project",
|
||||
},
|
||||
{
|
||||
Path: "/pkg/",
|
||||
Substring: "Package tar",
|
||||
},
|
||||
{
|
||||
Path: "/pkg/os/",
|
||||
Substring: "func Open",
|
||||
},
|
||||
{
|
||||
Path: "/pkg/net/http/",
|
||||
Substring: `title="Added in Go 1.11"`,
|
||||
Message: "version information not present - failed InitVersionInfo?",
|
||||
},
|
||||
{
|
||||
Path: "/robots.txt",
|
||||
Substring: "Disallow: /search",
|
||||
Message: "robots not present - not deployed from Dockerfile?",
|
||||
NoAnalytics: true,
|
||||
},
|
||||
{
|
||||
Path: "/change/75944e2e3a63",
|
||||
Substring: "bdb10cf",
|
||||
Message: "no change redirect - hg to git mapping not registered?",
|
||||
NoAnalytics: true,
|
||||
StatusCode: 302,
|
||||
},
|
||||
{
|
||||
Path: "/dl/",
|
||||
Substring: "go1.11.windows-amd64.msi",
|
||||
Message: "missing data on dl page - misconfiguration of datastore?",
|
||||
},
|
||||
{
|
||||
Path: "/dl/?mode=json",
|
||||
Substring: ".windows-amd64.msi",
|
||||
NoAnalytics: true,
|
||||
},
|
||||
{
|
||||
Message: "broken shortlinks - misconfiguration of datastore or memcache?",
|
||||
Path: "/s/go2design",
|
||||
Regexp: "proposal.*Found",
|
||||
NoAnalytics: true,
|
||||
StatusCode: 302,
|
||||
},
|
||||
{
|
||||
Message: "incorrect search result - broken index?",
|
||||
Path: "/search?q=IsDir",
|
||||
Substring: "src/os/types.go",
|
||||
},
|
||||
{
|
||||
Path: "/compile",
|
||||
PostBody: "body=" + url.QueryEscape("package main; func main() { print(6*7); }"),
|
||||
Regexp: `^{"compile_errors":"","output":"42"}$`,
|
||||
NoAnalytics: true,
|
||||
},
|
||||
{
|
||||
Path: "/compile",
|
||||
PostBody: "body=" + url.QueryEscape("//empty"),
|
||||
Substring: "expected 'package', found 'EOF'",
|
||||
NoAnalytics: true,
|
||||
},
|
||||
{
|
||||
Path: "/compile",
|
||||
PostBody: "version=2&body=package+main%3Bimport+(%22fmt%22%3B%22time%22)%3Bfunc+main()%7Bfmt.Print(%22A%22)%3Btime.Sleep(time.Second)%3Bfmt.Print(%22B%22)%7D",
|
||||
Regexp: `^{"Errors":"","Events":\[{"Message":"A","Kind":"stdout","Delay":0},{"Message":"B","Kind":"stdout","Delay":1000000000}\]}$`,
|
||||
NoAnalytics: true,
|
||||
},
|
||||
{
|
||||
Path: "/share",
|
||||
PostBody: "package main",
|
||||
Substring: "", // just check it is a 2xx.
|
||||
NoAnalytics: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range substringTests {
|
||||
t.Run(tc.Path, func(t *testing.T) {
|
||||
method := "GET"
|
||||
var reqBody io.Reader
|
||||
if tc.PostBody != "" {
|
||||
method = "POST"
|
||||
reqBody = strings.NewReader(tc.PostBody)
|
||||
}
|
||||
req, err := http.NewRequest(method, *host+tc.Path, reqBody)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest: %v", err)
|
||||
}
|
||||
if reqBody != nil {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
resp, err := http.DefaultTransport.RoundTrip(req)
|
||||
if err != nil {
|
||||
t.Fatalf("RoundTrip: %v", err)
|
||||
}
|
||||
if tc.StatusCode == 0 {
|
||||
if resp.StatusCode > 299 {
|
||||
t.Errorf("Non-OK status code: %v", resp.StatusCode)
|
||||
}
|
||||
} else if tc.StatusCode != resp.StatusCode {
|
||||
t.Errorf("StatusCode; got %v, want %v", resp.StatusCode, tc.StatusCode)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadAll: %v", err)
|
||||
}
|
||||
|
||||
const googleAnalyticsID = "UA-11222381-2" // golang.org analytics ID
|
||||
if !tc.NoAnalytics && !bytes.Contains(body, []byte(googleAnalyticsID)) {
|
||||
t.Errorf("want response to contain analytics tracking ID")
|
||||
}
|
||||
|
||||
if tc.Substring != "" {
|
||||
tc.Regexp = regexp.QuoteMeta(tc.Substring)
|
||||
}
|
||||
re := regexp.MustCompile(tc.Regexp)
|
||||
|
||||
if !re.Match(body) {
|
||||
t.Log("------ actual output -------")
|
||||
t.Log(string(body))
|
||||
t.Log("----------------------------")
|
||||
if tc.Message != "" {
|
||||
t.Log(tc.Message)
|
||||
}
|
||||
t.Fatalf("wanted response to match %s", tc.Regexp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2013 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.
|
||||
|
||||
// This file contains the handlers that serve go-import redirects for Go
|
||||
// sub-repositories. It specifies the mapping from import paths like
|
||||
// "golang.org/x/tools" to the actual repository locations.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const xPrefix = "/x/"
|
||||
|
||||
type xRepo struct {
|
||||
URL, VCS string
|
||||
}
|
||||
|
||||
var xMap = map[string]xRepo{
|
||||
"codereview": {"https://code.google.com/p/go.codereview", "hg"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
|
||||
"arch": {"https://go.googlesource.com/arch", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"benchmarks": {"https://go.googlesource.com/benchmarks", "git"},
|
||||
"blog": {"https://go.googlesource.com/blog", "git"},
|
||||
"build": {"https://go.googlesource.com/build", "git"},
|
||||
"crypto": {"https://go.googlesource.com/crypto", "git"},
|
||||
"debug": {"https://go.googlesource.com/debug", "git"},
|
||||
"exp": {"https://go.googlesource.com/exp", "git"},
|
||||
"image": {"https://go.googlesource.com/image", "git"},
|
||||
"lint": {"https://go.googlesource.com/lint", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"mobile": {"https://go.googlesource.com/mobile", "git"},
|
||||
"net": {"https://go.googlesource.com/net", "git"},
|
||||
"oauth2": {"https://go.googlesource.com/oauth2", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"perf": {"https://go.googlesource.com/perf", "git"},
|
||||
"playground": {"https://go.googlesource.com/playground", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"review": {"https://go.googlesource.com/review", "git"},
|
||||
"sync": {"https://go.googlesource.com/sync", "git"},
|
||||
"sys": {"https://go.googlesource.com/sys", "git"},
|
||||
"talks": {"https://go.googlesource.com/talks", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"term": {"https://go.googlesource.com/term", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"text": {"https://go.googlesource.com/text", "git"},
|
||||
"time": {"https://go.googlesource.com/time", "git"},
|
||||
"tools": {"https://go.googlesource.com/tools", "git"},
|
||||
"tour": {"https://go.googlesource.com/tour", "git"},
|
||||
"vgo": {"https://go.googlesource.com/vgo", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"website": {"https://go.googlesource.com/website", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
"xerrors": {"https://go.googlesource.com/xerrors", "git"}, // Not included at https://golang.org/pkg/#subrepo.
|
||||
}
|
||||
|
||||
func init() {
|
||||
http.HandleFunc(xPrefix, xHandler)
|
||||
}
|
||||
|
||||
func xHandler(w http.ResponseWriter, r *http.Request) {
|
||||
head, tail := strings.TrimPrefix(r.URL.Path, xPrefix), ""
|
||||
if i := strings.Index(head, "/"); i != -1 {
|
||||
head, tail = head[:i], head[i:]
|
||||
}
|
||||
if head == "" {
|
||||
http.Redirect(w, r, "https://godoc.org/-/subrepo", http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
repo, ok := xMap[head]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
data := struct {
|
||||
Prefix, Head, Tail string
|
||||
Repo xRepo
|
||||
}{xPrefix, head, tail, repo}
|
||||
if err := xTemplate.Execute(w, data); err != nil {
|
||||
log.Println("xHandler:", err)
|
||||
}
|
||||
}
|
||||
|
||||
var xTemplate = template.Must(template.New("x").Parse(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta name="go-import" content="golang.org{{.Prefix}}{{.Head}} {{.Repo.VCS}} {{.Repo.URL}}">
|
||||
<meta name="go-source" content="golang.org{{.Prefix}}{{.Head}} https://github.com/golang/{{.Head}}/ https://github.com/golang/{{.Head}}/tree/master{/dir} https://github.com/golang/{{.Head}}/blob/master{/dir}/{file}#L{line}">
|
||||
<meta http-equiv="refresh" content="0; url=https://godoc.org/golang.org{{.Prefix}}{{.Head}}{{.Tail}}">
|
||||
</head>
|
||||
<body>
|
||||
Nothing to see here; <a href="https://godoc.org/golang.org{{.Prefix}}{{.Head}}{{.Tail}}">move along</a>.
|
||||
</body>
|
||||
</html>
|
||||
`))
|
|
@ -10,7 +10,6 @@ import (
|
|||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/scanner"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -22,16 +21,15 @@ import (
|
|||
"runtime/pprof"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/imports"
|
||||
"golang.org/x/tools/imports"
|
||||
)
|
||||
|
||||
var (
|
||||
// main operation modes
|
||||
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
|
||||
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
||||
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
|
||||
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
|
||||
|
||||
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
|
||||
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
||||
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
|
||||
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
|
||||
verbose bool // verbose logging
|
||||
|
||||
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
|
||||
|
@ -43,19 +41,13 @@ var (
|
|||
TabIndent: true,
|
||||
Comments: true,
|
||||
Fragment: true,
|
||||
// This environment, and its caches, will be reused for the whole run.
|
||||
Env: &imports.ProcessEnv{
|
||||
GOPATH: build.Default.GOPATH,
|
||||
GOROOT: build.Default.GOROOT,
|
||||
},
|
||||
}
|
||||
exitCode = 0
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
|
||||
flag.StringVar(&options.Env.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list")
|
||||
flag.BoolVar(&options.FormatOnly, "format-only", false, "if true, don't fix imports and only format. In this mode, goimports is effectively gofmt, with the addition that imports are grouped into sections.")
|
||||
flag.StringVar(&imports.LocalPrefix, "local", "", "put imports beginning with this string after 3rd-party packages; comma-separated list")
|
||||
}
|
||||
|
||||
func report(err error) {
|
||||
|
@ -258,7 +250,7 @@ func gofmtMain() {
|
|||
|
||||
if verbose {
|
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||
options.Env.Debug = true
|
||||
imports.Debug = true
|
||||
}
|
||||
if options.TabWidth < 0 {
|
||||
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// WARNING: golsp has been renamed to gopls (see cmd/gopls/main.go).
|
||||
// This file will be deleted soon.
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// The golsp command is an LSP server for Go.
|
||||
// The Language Server Protocol allows any text editor
|
||||
// to be extended with IDE-like features;
|
||||
// see https://langserver.org/ for details.
|
||||
package main // import "golang.org/x/tools/cmd/golsp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/cmd"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tool.Main(context.Background(), &cmd.Application{}, os.Args[1:])
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// The forward command writes and reads to a gopls server on a network socket.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/cmd"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tool.Main(context.Background(), &app{&cmd.Server{}}, os.Args[1:])
|
||||
}
|
||||
|
||||
type app struct {
|
||||
*cmd.Server
|
||||
}
|
||||
|
||||
func (*app) Name() string { return "forward" }
|
||||
func (*app) Usage() string { return "[-port=<value>]" }
|
||||
func (*app) ShortHelp() string { return "An intermediary between an editor and gopls." }
|
||||
func (*app) DetailedHelp(*flag.FlagSet) {}
|
||||
|
||||
func (a *app) Run(ctx context.Context, args ...string) error {
|
||||
if a.Server.Port == 0 {
|
||||
a.ShortHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf(":%v", a.Server.Port))
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
_, err := io.Copy(conn, os.Stdin)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}(conn)
|
||||
|
||||
go func(conn net.Conn) {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}(conn)
|
||||
|
||||
for {
|
||||
}
|
||||
}
|
|
@ -10,11 +10,6 @@ import vscode = require('vscode');
|
|||
import path = require('path');
|
||||
|
||||
export function activate(ctx: vscode.ExtensionContext): void {
|
||||
// The handleDiagnostics middleware writes to the diagnostics log, in order
|
||||
// to confirm the order in which the extension received diagnostics.
|
||||
let r = Math.floor(Math.random() * 100000000);
|
||||
let diagnosticsLog = fs.openSync('/tmp/diagnostics' + r + '.log', 'w');
|
||||
|
||||
let document = vscode.window.activeTextEditor.document;
|
||||
let config = vscode.workspace.getConfiguration('gopls', document.uri);
|
||||
let goplsCommand: string = config['command'];
|
||||
|
@ -29,20 +24,6 @@ export function activate(ctx: vscode.ExtensionContext): void {
|
|||
(uri.scheme ? uri : uri.with({scheme: 'file'})).toString(),
|
||||
protocol2Code: (uri: string) => vscode.Uri.parse(uri),
|
||||
},
|
||||
middleware: {
|
||||
handleDiagnostics: (uri: vscode.Uri, diagnostics: vscode.Diagnostic[], next: lsp.HandleDiagnosticsSignature) => {
|
||||
let diagString = "-------------------------------------------\n";
|
||||
diagString += uri.toString(); + ": " + diagnostics.length + "\n";
|
||||
if (diagnostics.length > 0) {
|
||||
diagString += "\n";
|
||||
for (const diag of diagnostics) {
|
||||
diagString += diag.message + "\n";
|
||||
}
|
||||
}
|
||||
fs.writeSync(diagnosticsLog, diagString);
|
||||
return next(uri, diagnostics);
|
||||
}
|
||||
},
|
||||
revealOutputChannelOn: lsp.RevealOutputChannelOn.Never,
|
||||
};
|
||||
const c = new lsp.LanguageClient('gopls', serverOptions, clientOptions);
|
||||
|
|
|
@ -13,11 +13,9 @@ import (
|
|||
"os"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/cmd"
|
||||
"golang.org/x/tools/internal/lsp/debug"
|
||||
"golang.org/x/tools/internal/tool"
|
||||
)
|
||||
|
||||
func main() {
|
||||
debug.Version += "-cmd.gopls"
|
||||
tool.Main(context.Background(), cmd.New("gopls-legacy", "", nil), os.Args[1:])
|
||||
tool.Main(context.Background(), &cmd.Application{}, os.Args[1:])
|
||||
}
|
||||
|
|
|
@ -312,9 +312,6 @@ func g() { fmt.Println(test.Foo(3)) }
|
|||
// buildGorename builds the gorename executable.
|
||||
// It returns its path, and a cleanup function.
|
||||
func buildGorename(t *testing.T) (tmp, bin string, cleanup func()) {
|
||||
if runtime.GOOS == "android" {
|
||||
t.Skipf("the dependencies are not available on android")
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "gorename-regtest-")
|
||||
if err != nil {
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"go/build"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -203,7 +202,7 @@ func guessImportPath(filename string, buildContext *build.Context) (srcdir, impo
|
|||
if d >= 0 && d < minD {
|
||||
minD = d
|
||||
srcdir = gopathDir
|
||||
importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
|
||||
importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator))
|
||||
}
|
||||
}
|
||||
if srcdir == "" {
|
||||
|
|
|
@ -26,35 +26,12 @@ function toggleNotesWindow() {
|
|||
initNotes();
|
||||
};
|
||||
|
||||
// Create an unique key for the local storage so we don't mix the
|
||||
// destSlide of different presentations. For golang.org/issue/24688.
|
||||
function destSlideKey() {
|
||||
var key = '';
|
||||
if (notesWindow) {
|
||||
var slides = notesWindow.document.getElementById('presenter-slides');
|
||||
key = slides.src.split('#')[0];
|
||||
} else {
|
||||
key = window.location.href.split('#')[0];
|
||||
}
|
||||
return 'destSlide:' + key;
|
||||
}
|
||||
|
||||
function initNotes() {
|
||||
notesWindow = window.open('', '', 'width=1000,height=700');
|
||||
var w = notesWindow;
|
||||
var slidesUrl = window.location.href;
|
||||
|
||||
// Hack to apply css. Requires existing html on notesWindow.
|
||||
w.document.write("<div style='display:none;'></div>");
|
||||
|
||||
w.document.title = window.document.title;
|
||||
|
||||
var slides = w.document.createElement('iframe');
|
||||
slides.id = 'presenter-slides';
|
||||
slides.src = slidesUrl;
|
||||
w.document.body.appendChild(slides);
|
||||
|
||||
var curSlide = parseInt(localStorage.getItem(destSlideKey()), 10);
|
||||
var curSlide = parseInt(localStorage.getItem('destSlide'), 10);
|
||||
var formattedNotes = '';
|
||||
var section = sections[curSlide - 1];
|
||||
// curSlide is 0 when initialized from the first page of slides.
|
||||
|
@ -65,6 +42,15 @@ function initNotes() {
|
|||
formattedNotes = formatNotes(titleNotes);
|
||||
}
|
||||
|
||||
// Hack to apply css. Requires existing html on notesWindow.
|
||||
w.document.write("<div style='display:none;'></div>");
|
||||
|
||||
w.document.title = window.document.title;
|
||||
|
||||
var slides = w.document.createElement('iframe');
|
||||
slides.id = 'presenter-slides';
|
||||
slides.src = slidesUrl;
|
||||
w.document.body.appendChild(slides);
|
||||
// setTimeout needed for Firefox
|
||||
setTimeout(function() {
|
||||
slides.focus();
|
||||
|
@ -107,7 +93,7 @@ function updateNotes() {
|
|||
// When triggered from parent window, notesWindow is null
|
||||
// The storage event listener on notesWindow will update notes
|
||||
if (!notesWindow) return;
|
||||
var destSlide = parseInt(localStorage.getItem(destSlideKey()), 10);
|
||||
var destSlide = parseInt(localStorage.getItem('destSlide'), 10);
|
||||
var section = sections[destSlide - 1];
|
||||
var el = notesWindow.document.getElementById('presenter-notes');
|
||||
|
||||
|
@ -117,7 +103,7 @@ function updateNotes() {
|
|||
el.innerHTML = formatNotes(section.Notes);
|
||||
} else if (destSlide == 0) {
|
||||
el.innerHTML = formatNotes(titleNotes);
|
||||
} else {
|
||||
} else {
|
||||
el.innerHTML = '';
|
||||
}
|
||||
};
|
||||
|
@ -129,47 +115,47 @@ function updateNotes() {
|
|||
var playgroundHandlers = {onRun: [], onKill: [], onClose: []};
|
||||
|
||||
function updatePlay(e) {
|
||||
var i = localStorage.getItem('play-index');
|
||||
var i = localStorage.getItem('play-index');
|
||||
|
||||
switch (e.key) {
|
||||
case 'play-index':
|
||||
return;
|
||||
case 'play-action':
|
||||
// Sync 'run', 'kill', 'close' actions
|
||||
var action = localStorage.getItem('play-action');
|
||||
playgroundHandlers[action][i](e);
|
||||
return;
|
||||
case 'play-code':
|
||||
// Sync code editing
|
||||
var play = document.querySelectorAll('div.playground')[i];
|
||||
play.innerHTML = localStorage.getItem('play-code');
|
||||
return;
|
||||
case 'output-style':
|
||||
// Sync resizing of playground output
|
||||
var out = document.querySelectorAll('.output')[i];
|
||||
out.style = localStorage.getItem('output-style');
|
||||
return;
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'play-index':
|
||||
return;
|
||||
case 'play-action':
|
||||
// Sync 'run', 'kill', 'close' actions
|
||||
var action = localStorage.getItem('play-action');
|
||||
playgroundHandlers[action][i](e);
|
||||
return;
|
||||
case 'play-code':
|
||||
// Sync code editing
|
||||
var play = document.querySelectorAll('div.playground')[i];
|
||||
play.innerHTML = localStorage.getItem('play-code');
|
||||
return;
|
||||
case 'output-style':
|
||||
// Sync resizing of playground output
|
||||
var out = document.querySelectorAll('.output')[i];
|
||||
out.style = localStorage.getItem('output-style');
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Reset 'run', 'kill', 'close' storage items when synced
|
||||
// so that successive actions can be synced correctly
|
||||
function updatePlayStorage(action, index, e) {
|
||||
localStorage.setItem('play-index', index);
|
||||
localStorage.setItem('play-index', index);
|
||||
|
||||
if (localStorage.getItem('play-action') === action) {
|
||||
// We're the receiving window, and the message has been received
|
||||
localStorage.removeItem('play-action');
|
||||
} else {
|
||||
// We're the triggering window, send the message
|
||||
localStorage.setItem('play-action', action);
|
||||
}
|
||||
if (localStorage.getItem('play-action') === action) {
|
||||
// We're the receiving window, and the message has been received
|
||||
localStorage.removeItem('play-action');
|
||||
} else {
|
||||
// We're the triggering window, send the message
|
||||
localStorage.setItem('play-action', action);
|
||||
}
|
||||
|
||||
if (action === 'onRun') {
|
||||
if (localStorage.getItem('play-shiftKey') === 'true') {
|
||||
localStorage.removeItem('play-shiftKey');
|
||||
} else if (e.shiftKey) {
|
||||
localStorage.setItem('play-shiftKey', e.shiftKey);
|
||||
}
|
||||
}
|
||||
if (action === 'onRun') {
|
||||
if (localStorage.getItem('play-shiftKey') === 'true') {
|
||||
localStorage.removeItem('play-shiftKey');
|
||||
} else if (e.shiftKey) {
|
||||
localStorage.setItem('play-shiftKey', e.shiftKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -212,7 +212,7 @@ function prevSlide() {
|
|||
updateSlides();
|
||||
}
|
||||
|
||||
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
|
||||
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
|
||||
};
|
||||
|
||||
function nextSlide() {
|
||||
|
@ -223,7 +223,7 @@ function nextSlide() {
|
|||
updateSlides();
|
||||
}
|
||||
|
||||
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
|
||||
if (notesEnabled) localStorage.setItem('destSlide', curSlide);
|
||||
};
|
||||
|
||||
/* Slide events */
|
||||
|
@ -602,7 +602,7 @@ function setupNotesSync() {
|
|||
|
||||
setupPlayCodeSync();
|
||||
setupPlayResizeSync();
|
||||
localStorage.setItem(destSlideKey(), curSlide);
|
||||
localStorage.setItem('destSlide', curSlide);
|
||||
window.addEventListener('storage', updateOtherWindow, false);
|
||||
}
|
||||
|
||||
|
@ -613,7 +613,7 @@ function updateOtherWindow(e) {
|
|||
var isRemoveStorageEvent = !e.newValue;
|
||||
if (isRemoveStorageEvent) return;
|
||||
|
||||
var destSlide = localStorage.getItem(destSlideKey());
|
||||
var destSlide = localStorage.getItem('destSlide');
|
||||
while (destSlide > curSlide) {
|
||||
nextSlide();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
/*
|
||||
|
||||
Splitdwarf uncompresses and copies the DWARF segment of a Mach-O
|
||||
executable into the "dSYM" file expected by lldb and ports of gdb
|
||||
on OSX.
|
||||
|
||||
Usage: splitdwarf osxMachoFile [ osxDsymFile ]
|
||||
|
||||
Unless a dSYM file name is provided on the command line,
|
||||
splitdwarf will place it where the OSX tools expect it, in
|
||||
"<osxMachoFile>.dSYM/Contents/Resources/DWARF/<osxMachoFile>",
|
||||
creating directories as necessary.
|
||||
|
||||
*/
|
||||
package main // import "golang.org/x/tools/cmd/splitdwarf"
|
|
@ -4,21 +4,7 @@
|
|||
|
||||
// +build !js,!nacl,!plan9,!solaris,!windows
|
||||
|
||||
/*
|
||||
|
||||
Splitdwarf uncompresses and copies the DWARF segment of a Mach-O
|
||||
executable into the "dSYM" file expected by lldb and ports of gdb
|
||||
on OSX.
|
||||
|
||||
Usage: splitdwarf osxMachoFile [ osxDsymFile ]
|
||||
|
||||
Unless a dSYM file name is provided on the command line,
|
||||
splitdwarf will place it where the OSX tools expect it, in
|
||||
"<osxMachoFile>.dSYM/Contents/Resources/DWARF/<osxMachoFile>",
|
||||
creating directories as necessary.
|
||||
|
||||
*/
|
||||
package main // import "golang.org/x/tools/cmd/splitdwarf"
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
|
|
@ -45,8 +45,8 @@ func TestEndToEnd(t *testing.T) {
|
|||
t.Errorf("%s is not a Go file", name)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(name, "tag_") || strings.HasPrefix(name, "vary_") {
|
||||
// This file is used for tag processing in TestTags or TestConstValueChange, below.
|
||||
if strings.HasPrefix(name, "tag_") {
|
||||
// This file is used for tag processing in TestTags, below.
|
||||
continue
|
||||
}
|
||||
if name == "cgo.go" && !build.Default.CgoEnabled {
|
||||
|
@ -68,10 +68,12 @@ func TestTags(t *testing.T) {
|
|||
output = filepath.Join(dir, "const_string.go")
|
||||
)
|
||||
for _, file := range []string{"tag_main.go", "tag_tag.go"} {
|
||||
|
||||
err := copy(filepath.Join(dir, file), filepath.Join("testdata", file))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
// Run stringer in the directory that contains the package files.
|
||||
// We cannot run stringer in the current directory for the following reasons:
|
||||
|
@ -106,48 +108,6 @@ func TestTags(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestConstValueChange verifies that if a constant value changes and
|
||||
// the stringer code is not regenerated, we'll get a compiler error.
|
||||
func TestConstValueChange(t *testing.T) {
|
||||
dir, stringer := buildStringer(t)
|
||||
defer os.RemoveAll(dir)
|
||||
source := filepath.Join(dir, "day.go")
|
||||
err := copy(source, filepath.Join("testdata", "day.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stringSource := filepath.Join(dir, "day_string.go")
|
||||
// Run stringer in the directory that contains the package files.
|
||||
err = runInDir(dir, stringer, "-type", "Day", "-output", stringSource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Run the binary in the temporary directory as a sanity check.
|
||||
err = run("go", "run", stringSource, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Overwrite the source file with a version that has changed constants.
|
||||
err = copy(source, filepath.Join("testdata", "vary_day.go"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Unfortunately different compilers may give different error messages,
|
||||
// so there's no easy way to verify that the build failed specifically
|
||||
// because the constants changed rather than because the vary_day.go
|
||||
// file is invalid.
|
||||
//
|
||||
// Instead we'll just rely on manual inspection of the polluted test
|
||||
// output. An alternative might be to check that the error output
|
||||
// matches a set of possible error strings emitted by known
|
||||
// Go compilers.
|
||||
fmt.Fprintf(os.Stderr, "Note: the following messages should indicate an out-of-bounds compiler error\n")
|
||||
err = run("go", "build", stringSource, source)
|
||||
if err == nil {
|
||||
t.Fatal("unexpected compiler success")
|
||||
}
|
||||
}
|
||||
|
||||
// buildStringer creates a temporary directory and installs stringer there.
|
||||
func buildStringer(t *testing.T) (dir string, stringer string) {
|
||||
t.Helper()
|
||||
|
@ -215,6 +175,5 @@ func runInDir(dir, name string, arg ...string) error {
|
|||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = append(os.Environ(), "GO111MODULE=auto")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
|
|
@ -52,19 +52,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const day_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Monday-0]
|
||||
_ = x[Tuesday-1]
|
||||
_ = x[Wednesday-2]
|
||||
_ = x[Thursday-3]
|
||||
_ = x[Friday-4]
|
||||
_ = x[Saturday-5]
|
||||
_ = x[Sunday-6]
|
||||
}
|
||||
|
||||
const day_out = `
|
||||
const _Day_name = "MondayTuesdayWednesdayThursdayFridaySaturdaySunday"
|
||||
|
||||
var _Day_index = [...]uint8{0, 6, 13, 22, 30, 36, 44, 50}
|
||||
|
@ -89,15 +77,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const offset_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[One-1]
|
||||
_ = x[Two-2]
|
||||
_ = x[Three-3]
|
||||
}
|
||||
|
||||
const offset_out = `
|
||||
const _Number_name = "OneTwoThree"
|
||||
|
||||
var _Number_index = [...]uint8{0, 3, 6, 11}
|
||||
|
@ -125,20 +105,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const gap_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Two-2]
|
||||
_ = x[Three-3]
|
||||
_ = x[Five-5]
|
||||
_ = x[Six-6]
|
||||
_ = x[Seven-7]
|
||||
_ = x[Eight-8]
|
||||
_ = x[Nine-9]
|
||||
_ = x[Eleven-11]
|
||||
}
|
||||
|
||||
const gap_out = `
|
||||
const (
|
||||
_Gap_name_0 = "TwoThree"
|
||||
_Gap_name_1 = "FiveSixSevenEightNine"
|
||||
|
@ -177,17 +144,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const num_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[m_2 - -2]
|
||||
_ = x[m_1 - -1]
|
||||
_ = x[m0-0]
|
||||
_ = x[m1-1]
|
||||
_ = x[m2-2]
|
||||
}
|
||||
|
||||
const num_out = `
|
||||
const _Num_name = "m_2m_1m0m1m2"
|
||||
|
||||
var _Num_index = [...]uint8{0, 3, 6, 8, 10, 12}
|
||||
|
@ -215,17 +172,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const unum_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[m_2-253]
|
||||
_ = x[m_1-254]
|
||||
_ = x[m0-0]
|
||||
_ = x[m1-1]
|
||||
_ = x[m2-2]
|
||||
}
|
||||
|
||||
const unum_out = `
|
||||
const (
|
||||
_Unum_name_0 = "m0m1m2"
|
||||
_Unum_name_1 = "m_2m_1"
|
||||
|
@ -270,26 +217,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const prime_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[p2-2]
|
||||
_ = x[p3-3]
|
||||
_ = x[p5-5]
|
||||
_ = x[p7-7]
|
||||
_ = x[p77-7]
|
||||
_ = x[p11-11]
|
||||
_ = x[p13-13]
|
||||
_ = x[p17-17]
|
||||
_ = x[p19-19]
|
||||
_ = x[p23-23]
|
||||
_ = x[p29-29]
|
||||
_ = x[p37-31]
|
||||
_ = x[p41-41]
|
||||
_ = x[p43-43]
|
||||
}
|
||||
|
||||
const prime_out = `
|
||||
const _Prime_name = "p2p3p5p7p11p13p17p19p23p29p37p41p43"
|
||||
|
||||
var _Prime_map = map[Prime]string{
|
||||
|
@ -328,19 +256,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const prefix_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TypeInt-0]
|
||||
_ = x[TypeString-1]
|
||||
_ = x[TypeFloat-2]
|
||||
_ = x[TypeRune-3]
|
||||
_ = x[TypeByte-4]
|
||||
_ = x[TypeStruct-5]
|
||||
_ = x[TypeSlice-6]
|
||||
}
|
||||
|
||||
const prefix_out = `
|
||||
const _Type_name = "IntStringFloatRuneByteStructSlice"
|
||||
|
||||
var _Type_index = [...]uint8{0, 3, 9, 14, 18, 22, 28, 33}
|
||||
|
@ -370,21 +286,7 @@ const (
|
|||
)
|
||||
`
|
||||
|
||||
const tokens_out = `func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[And-0]
|
||||
_ = x[Or-1]
|
||||
_ = x[Add-2]
|
||||
_ = x[Sub-3]
|
||||
_ = x[Ident-4]
|
||||
_ = x[Period-5]
|
||||
_ = x[SingleBefore-6]
|
||||
_ = x[BeforeAndInline-7]
|
||||
_ = x[InlineGeneral-8]
|
||||
}
|
||||
|
||||
const tokens_out = `
|
||||
const _Token_name = "&|+-Ident.SingleBeforeinlineinline general"
|
||||
|
||||
var _Token_index = [...]uint8{0, 1, 2, 3, 4, 9, 10, 22, 28, 42}
|
||||
|
@ -426,7 +328,7 @@ func TestGolden(t *testing.T) {
|
|||
g.generate(tokens[1])
|
||||
got := string(g.format())
|
||||
if got != test.output {
|
||||
t.Errorf("%s: got(%d)\n====\n%q====\nexpected(%d)\n====%q", test.name, len(got), got, len(test.output), test.output)
|
||||
t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -258,15 +258,6 @@ func (g *Generator) generate(typeName string) {
|
|||
if len(values) == 0 {
|
||||
log.Fatalf("no values defined for type %s", typeName)
|
||||
}
|
||||
// Generate code that will fail if the constants change value.
|
||||
g.Printf("func _() {\n")
|
||||
g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n")
|
||||
g.Printf("\t// Re-run the stringer command to generate them again.\n")
|
||||
g.Printf("\tvar x [1]struct{}\n")
|
||||
for _, v := range values {
|
||||
g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str)
|
||||
}
|
||||
g.Printf("}\n")
|
||||
runs := splitIntoRuns(values)
|
||||
// The decision of which pattern to use depends on the number of
|
||||
// runs in the numbers. If there's only one, it's easy. For more than
|
||||
|
@ -337,8 +328,7 @@ func (g *Generator) format() []byte {
|
|||
|
||||
// Value represents a declared constant.
|
||||
type Value struct {
|
||||
originalName string // The name of the constant.
|
||||
name string // The name with trimmed prefix.
|
||||
name string // The name of the constant.
|
||||
// The value is stored as a bit pattern alone. The boolean tells us
|
||||
// whether to interpret it as an int64 or a uint64; the only place
|
||||
// this matters is when sorting.
|
||||
|
@ -446,16 +436,15 @@ func (f *File) genDecl(node ast.Node) bool {
|
|||
u64 = uint64(i64)
|
||||
}
|
||||
v := Value{
|
||||
originalName: name.Name,
|
||||
value: u64,
|
||||
signed: info&types.IsUnsigned == 0,
|
||||
str: value.String(),
|
||||
name: name.Name,
|
||||
value: u64,
|
||||
signed: info&types.IsUnsigned == 0,
|
||||
str: value.String(),
|
||||
}
|
||||
if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
|
||||
v.name = strings.TrimSpace(c.Text())
|
||||
} else {
|
||||
v.name = strings.TrimPrefix(v.originalName, f.trimPrefix)
|
||||
}
|
||||
v.name = strings.TrimPrefix(v.name, f.trimPrefix)
|
||||
f.values = append(f.values, v)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
// This file is the same as day.go except the constants have different values.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Day int
|
||||
|
||||
const (
|
||||
Sunday Day = iota
|
||||
Monday
|
||||
Tuesday
|
||||
Wednesday
|
||||
Thursday
|
||||
Friday
|
||||
Saturday
|
||||
)
|
||||
|
||||
func main() {
|
||||
ck(Monday, "Monday")
|
||||
ck(Tuesday, "Tuesday")
|
||||
ck(Wednesday, "Wednesday")
|
||||
ck(Thursday, "Thursday")
|
||||
ck(Friday, "Friday")
|
||||
ck(Saturday, "Saturday")
|
||||
ck(Sunday, "Sunday")
|
||||
ck(-127, "Day(-127)")
|
||||
ck(127, "Day(127)")
|
||||
}
|
||||
|
||||
func ck(day Day, str string) {
|
||||
if fmt.Sprint(day) != str {
|
||||
panic("day.go: " + str)
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ Outer:
|
|||
for n, test := range splitTests {
|
||||
values := make([]Value, len(test.input))
|
||||
for i, v := range test.input {
|
||||
values[i] = Value{"", "", v, test.signed, fmt.Sprint(v)}
|
||||
values[i] = Value{"", v, test.signed, fmt.Sprint(v)}
|
||||
}
|
||||
runs := splitIntoRuns(values)
|
||||
if len(runs) != len(test.output) {
|
||||
|
|
8
go.mod
8
go.mod
|
@ -1,8 +0,0 @@
|
|||
module golang.org/x/tools
|
||||
|
||||
go 1.11
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
)
|
7
go.sum
7
go.sum
|
@ -1,7 +0,0 @@
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
@ -128,32 +128,10 @@ type Pass struct {
|
|||
// See comments for ExportObjectFact.
|
||||
ExportPackageFact func(fact Fact)
|
||||
|
||||
// AllPackageFacts returns a new slice containing all package facts in unspecified order.
|
||||
// WARNING: This is an experimental API and may change in the future.
|
||||
AllPackageFacts func() []PackageFact
|
||||
|
||||
// AllObjectFacts returns a new slice containing all object facts in unspecified order.
|
||||
// WARNING: This is an experimental API and may change in the future.
|
||||
AllObjectFacts func() []ObjectFact
|
||||
|
||||
/* Further fields may be added in future. */
|
||||
// For example, suggested or applied refactorings.
|
||||
}
|
||||
|
||||
// PackageFact is a package together with an associated fact.
|
||||
// WARNING: This is an experimental API and may change in the future.
|
||||
type PackageFact struct {
|
||||
Package *types.Package
|
||||
Fact Fact
|
||||
}
|
||||
|
||||
// ObjectFact is an object together with an associated fact.
|
||||
// WARNING: This is an experimental API and may change in the future.
|
||||
type ObjectFact struct {
|
||||
Object types.Object
|
||||
Fact Fact
|
||||
}
|
||||
|
||||
// Reportf is a helper function that reports a Diagnostic using the
|
||||
// specified position and formatted error message.
|
||||
func (pass *Pass) Reportf(pos token.Pos, format string, args ...interface{}) {
|
||||
|
@ -161,15 +139,6 @@ func (pass *Pass) Reportf(pos token.Pos, format string, args ...interface{}) {
|
|||
pass.Report(Diagnostic{Pos: pos, Message: msg})
|
||||
}
|
||||
|
||||
// reportNodef is a helper function that reports a Diagnostic using the
|
||||
// range denoted by the AST node.
|
||||
//
|
||||
// WARNING: This is an experimental API and may change in the future.
|
||||
func (pass *Pass) reportNodef(node ast.Node, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
pass.Report(Diagnostic{Pos: node.Pos(), End: node.End(), Message: msg})
|
||||
}
|
||||
|
||||
func (pass *Pass) String() string {
|
||||
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
|
||||
}
|
||||
|
@ -211,3 +180,14 @@ func (pass *Pass) String() string {
|
|||
type Fact interface {
|
||||
AFact() // dummy method to avoid type errors
|
||||
}
|
||||
|
||||
// A Diagnostic is a message associated with a source location.
|
||||
//
|
||||
// An Analyzer may return a variety of diagnostics; the optional Category,
|
||||
// which should be a constant, may be used to classify them.
|
||||
// It is primarily intended to make it easy to look up documentation.
|
||||
type Diagnostic struct {
|
||||
Pos token.Pos
|
||||
Category string // optional
|
||||
Message string
|
||||
}
|
||||
|
|
|
@ -257,7 +257,6 @@ func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis
|
|||
|
||||
// Check the diagnostics match expectations.
|
||||
for _, f := range diagnostics {
|
||||
// TODO(matloob): Support ranges in analysistest.
|
||||
posn := pass.Fset.Position(f.Pos)
|
||||
checkMessage(posn, "diagnostic", "", f.Message)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
func init() {
|
||||
// This test currently requires GOPATH mode.
|
||||
// Explicitly disabling module mode should suffice, but
|
||||
// Explicitly disabling module mode should suffix, but
|
||||
// we'll also turn off GOPROXY just for good measure.
|
||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -73,18 +73,12 @@ func println(...interface{}) { println() } // want println:"found" "call of prin
|
|||
`a/b.go:5: in 'want' comment: unexpected ":"`,
|
||||
`a/b.go:6: in 'want' comment: got String after foo, want ':'`,
|
||||
`a/b.go:7: in 'want' comment: got EOF, want regular expression`,
|
||||
`a/b.go:8: in 'want' comment: invalid char escape`,
|
||||
`a/b.go:8: in 'want' comment: illegal char escape`,
|
||||
`a/b.go:11:9: diagnostic "call of println(...)" does not match pattern "wrong expectation text"`,
|
||||
`a/b.go:14:9: unexpected diagnostic: call of println(...)`,
|
||||
`a/b.go:11: no diagnostic was reported matching "wrong expectation text"`,
|
||||
`a/b.go:17: no diagnostic was reported matching "unsatisfied expectation"`,
|
||||
}
|
||||
// Go 1.13's scanner error messages uses the word invalid where Go 1.12 used illegal. Convert them
|
||||
// to keep tests compatible with both.
|
||||
// TODO(matloob): Remove this once Go 1.13 is released.
|
||||
for i := range got {
|
||||
got[i] = strings.Replace(got[i], "illegal", "invalid", -1)
|
||||
} //
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got:\n%s\nwant:\n%s",
|
||||
strings.Join(got, "\n"),
|
||||
|
|
|
@ -26,11 +26,11 @@ import (
|
|||
"golang.org/x/tools/go/analysis/passes/cgocall"
|
||||
"golang.org/x/tools/go/analysis/passes/composite"
|
||||
"golang.org/x/tools/go/analysis/passes/copylock"
|
||||
"golang.org/x/tools/go/analysis/passes/errorsas"
|
||||
"golang.org/x/tools/go/analysis/passes/httpresponse"
|
||||
"golang.org/x/tools/go/analysis/passes/loopclosure"
|
||||
"golang.org/x/tools/go/analysis/passes/lostcancel"
|
||||
"golang.org/x/tools/go/analysis/passes/nilfunc"
|
||||
"golang.org/x/tools/go/analysis/passes/nilness"
|
||||
"golang.org/x/tools/go/analysis/passes/printf"
|
||||
"golang.org/x/tools/go/analysis/passes/shift"
|
||||
"golang.org/x/tools/go/analysis/passes/stdmethods"
|
||||
|
@ -58,7 +58,6 @@ func main() {
|
|||
cgocall.Analyzer,
|
||||
composite.Analyzer,
|
||||
copylock.Analyzer,
|
||||
errorsas.Analyzer,
|
||||
httpresponse.Analyzer,
|
||||
loopclosure.Analyzer,
|
||||
lostcancel.Analyzer,
|
||||
|
@ -78,6 +77,6 @@ func main() {
|
|||
// pkgfact.Analyzer,
|
||||
|
||||
// uses SSA:
|
||||
// nilness.Analyzer,
|
||||
nilness.Analyzer,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
// +build !experimental
|
||||
|
||||
package analysis
|
||||
|
||||
import "go/token"
|
||||
|
||||
// A Diagnostic is a message associated with a source location or range.
|
||||
//
|
||||
// An Analyzer may return a variety of diagnostics; the optional Category,
|
||||
// which should be a constant, may be used to classify them.
|
||||
// It is primarily intended to make it easy to look up documentation.
|
||||
//
|
||||
// If End is provided, the diagnostic is specified to apply to the range between
|
||||
// Pos and End.
|
||||
type Diagnostic struct {
|
||||
Pos token.Pos
|
||||
End token.Pos // optional
|
||||
Category string // optional
|
||||
Message string
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// +build experimental
|
||||
|
||||
package analysis
|
||||
|
||||
import "go/token"
|
||||
|
||||
// A Diagnostic is a message associated with a source location or range.
|
||||
//
|
||||
// An Analyzer may return a variety of diagnostics; the optional Category,
|
||||
// which should be a constant, may be used to classify them.
|
||||
// It is primarily intended to make it easy to look up documentation.
|
||||
//
|
||||
// If End is provided, the diagnostic is specified to apply to the range between
|
||||
// Pos and End.
|
||||
type Diagnostic struct {
|
||||
Pos token.Pos
|
||||
End token.Pos // optional
|
||||
Category string // optional
|
||||
Message string
|
||||
|
||||
// TODO(matloob): Should multiple SuggestedFixes be allowed for a diagnostic?
|
||||
SuggestedFixes []SuggestedFix // optional
|
||||
}
|
||||
|
||||
// A SuggestedFix is a code change associated with a Diagnostic that a user can choose
|
||||
// to apply to their code. Usually the SuggestedFix is meant to fix the issue flagged
|
||||
// by the diagnostic.
|
||||
type SuggestedFix struct {
|
||||
// A description for this suggested fix to be shown to a user deciding
|
||||
// whether to accept it.
|
||||
Message string
|
||||
TextEdits []TextEdit
|
||||
}
|
||||
|
||||
// A TextEdit represents the replacement of the code between Pos and End with the new text.
|
||||
type TextEdit struct {
|
||||
// For a pure insertion, End can either be set to Pos or token.NoPos.
|
||||
Pos token.Pos
|
||||
End token.Pos
|
||||
NewText []byte
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
# Suggested Fixes in the Analysis Framework
|
||||
|
||||
## The Purpose of Suggested Fixes
|
||||
|
||||
The analysis framework is planned to add a facility to output
|
||||
suggested fixes. Suggested fixes in the analysis framework
|
||||
are meant to address two common use cases. The first is the
|
||||
natural use case of allowing the user to quickly fix errors or issues
|
||||
pointed out by analyzers through their editor or analysis tool.
|
||||
An editor, when showing a diagnostic for an issue, can propose
|
||||
code to fix that issue. Users can accept the proposal and have
|
||||
the editor apply the fix for them. The second case is to allow
|
||||
for defining refactorings. An analyzer meant to perform a
|
||||
refactoring can produce suggested fixes equivalent to the diff
|
||||
of the refactoring. Then, an analysis driver meant to apply
|
||||
refactorings can automatically apply all the diffs that
|
||||
are produced by the analysis as suggested fixes.
|
||||
|
||||
## Proposed Suggested Fix API
|
||||
|
||||
Suggested fixes will be defined using the following structs:
|
||||
|
||||
```go
|
||||
// A SuggestedFix is a code change associated with a Diagnostic that a user can choose
|
||||
// to apply to their code. Usually the SuggestedFix is meant to fix the issue flagged
|
||||
// by the diagnostic.
|
||||
type SuggestedFix struct {
|
||||
// A description for this suggested fix to be shown to a user deciding
|
||||
// whether to accept it.
|
||||
Message string
|
||||
TextEdits []TextEdit
|
||||
}
|
||||
|
||||
// A TextEdit represents the replacement of the code between Pos and End with the new text.
|
||||
type TextEdit struct {
|
||||
// For a pure insertion, End can either be set to Pos or token.NoPos.
|
||||
Pos token.Pos
|
||||
End token.Pos
|
||||
NewText []byte
|
||||
}
|
||||
```
|
||||
|
||||
A suggested fix needs a message field so it can specify what it will do.
|
||||
Some analyses may not have clear cut fixes, and a suggested fix may need
|
||||
to provide additional information to help users specify whether they
|
||||
should be added.
|
||||
|
||||
Suggested fixes are allowed to make multiple
|
||||
edits in a file, because some logical changes may affect otherwise
|
||||
unrelated parts of the AST.
|
||||
|
||||
A TextEdit specifies a Pos and End: these will usually be the Pos
|
||||
and End of an AST node that will be replaced.
|
||||
|
||||
Finally, the replacements themselves are represented as []bytes.
|
||||
|
||||
|
||||
Suggested fixes themselves will be added as a field in the
|
||||
Diagnostic struct:
|
||||
|
||||
```go
|
||||
|
||||
type Diagnostic struct {
|
||||
...
|
||||
SuggestedFixes []SuggestedFix // this is an optional field
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Alternatives
|
||||
|
||||
# Performing transformations directly on the AST
|
||||
|
||||
TODO(matloob): expand on this.
|
||||
|
||||
Even though it may be more convienient
|
||||
for authors of refactorings to perform transformations directly on
|
||||
the AST, allowing mutations on the AST would mean that a copy of the AST
|
||||
would need to be made every time a transformation was produced, to avoid
|
||||
having transformations interfere with each other.
|
|
@ -8,7 +8,6 @@ package analysisflags
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
@ -33,14 +32,6 @@ var (
|
|||
// including (in multi mode) a flag named after the analyzer,
|
||||
// parses the flags, then filters and returns the list of
|
||||
// analyzers enabled by flags.
|
||||
//
|
||||
// The result is intended to be passed to unitchecker.Run or checker.Run.
|
||||
// Use in unitchecker.Run will gob.Register all fact types for the returned
|
||||
// graph of analyzers but of course not the ones only reachable from
|
||||
// dropped analyzers. To avoid inconsistency about which gob types are
|
||||
// registered from run to run, Parse itself gob.Registers all the facts
|
||||
// only reachable from dropped analyzers.
|
||||
// This is not a particularly elegant API, but this is an internal package.
|
||||
func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
|
||||
// Connect each analysis flag to the command line as -analysis.flag.
|
||||
enabled := make(map[*analysis.Analyzer]*triState)
|
||||
|
@ -97,8 +88,6 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
everything := expand(analyzers)
|
||||
|
||||
// If any -NAME flag is true, run only those analyzers. Otherwise,
|
||||
// if any -NAME flag is false, run all but those analyzers.
|
||||
if multi {
|
||||
|
@ -130,35 +119,9 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
|
|||
}
|
||||
}
|
||||
|
||||
// Register fact types of skipped analyzers
|
||||
// in case we encounter them in imported files.
|
||||
kept := expand(analyzers)
|
||||
for a := range everything {
|
||||
if !kept[a] {
|
||||
for _, f := range a.FactTypes {
|
||||
gob.Register(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return analyzers
|
||||
}
|
||||
|
||||
func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
|
||||
seen := make(map[*analysis.Analyzer]bool)
|
||||
var visitAll func([]*analysis.Analyzer)
|
||||
visitAll = func(analyzers []*analysis.Analyzer) {
|
||||
for _, a := range analyzers {
|
||||
if !seen[a] {
|
||||
seen[a] = true
|
||||
visitAll(a.Requires)
|
||||
}
|
||||
}
|
||||
}
|
||||
visitAll(analyzers)
|
||||
return seen
|
||||
}
|
||||
|
||||
func printFlags() {
|
||||
type jsonFlag struct {
|
||||
Name string
|
||||
|
@ -189,13 +152,12 @@ func printFlags() {
|
|||
// addVersionFlag registers a -V flag that, if set,
|
||||
// prints the executable version and exits 0.
|
||||
//
|
||||
// If the -V flag already exists — for example, because it was already
|
||||
// registered by a call to cmd/internal/objabi.AddVersionFlag — then
|
||||
// addVersionFlag does nothing.
|
||||
func addVersionFlag() {
|
||||
if flag.Lookup("V") == nil {
|
||||
flag.Var(versionFlag{}, "V", "print version and exit")
|
||||
}
|
||||
// It is a variable not a function to permit easy
|
||||
// overriding in the copy vendored in $GOROOT/src/cmd/vet:
|
||||
//
|
||||
// func init() { addVersionFlag = objabi.AddVersionFlag }
|
||||
var addVersionFlag = func() {
|
||||
flag.Var(versionFlag{}, "V", "print version and exit")
|
||||
}
|
||||
|
||||
// versionFlag minimally complies with the -V protocol required by "go vet".
|
||||
|
@ -323,14 +285,9 @@ func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
|
|||
|
||||
// -c=N: show offending line plus N lines of context.
|
||||
if Context >= 0 {
|
||||
posn := fset.Position(diag.Pos)
|
||||
end := fset.Position(diag.End)
|
||||
if !end.IsValid() {
|
||||
end = posn
|
||||
}
|
||||
data, _ := ioutil.ReadFile(posn.Filename)
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for i := posn.Line - Context; i <= end.Line+Context; i++ {
|
||||
for i := posn.Line - Context; i <= posn.Line+Context; i++ {
|
||||
if 1 <= i && i <= len(lines) {
|
||||
fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
|
||||
}
|
||||
|
@ -358,8 +315,6 @@ func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.
|
|||
Message string `json:"message"`
|
||||
}
|
||||
var diagnostics []jsonDiagnostic
|
||||
// TODO(matloob): Should the JSON diagnostics contain ranges?
|
||||
// If so, how should they be formatted?
|
||||
for _, f := range diags {
|
||||
diagnostics = append(diagnostics, jsonDiagnostic{
|
||||
Category: f.Category,
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
@ -48,7 +47,6 @@ func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
|
|||
fs.Var(f.Value, f.Name, f.Usage)
|
||||
}
|
||||
})
|
||||
fs.SetOutput(os.Stdout)
|
||||
fs.PrintDefaults()
|
||||
|
||||
fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname)
|
||||
|
@ -77,7 +75,6 @@ outer:
|
|||
}
|
||||
fs.Var(f.Value, a.Name+"."+f.Name, f.Usage)
|
||||
})
|
||||
fs.SetOutput(os.Stdout)
|
||||
fs.PrintDefaults()
|
||||
|
||||
if len(paras) > 1 {
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
// Copyright 2018 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 checker defines the implementation of the checker commands.
|
||||
// The same code drives the multi-analysis driver, the single-analysis
|
||||
// driver that is conventionally provided for convenience along with
|
||||
|
@ -295,8 +291,7 @@ func printDiagnostics(roots []*action) (exitcode int) {
|
|||
// avoid double-reporting in source files that belong to
|
||||
// multiple packages, such as foo and foo.test.
|
||||
type key struct {
|
||||
pos token.Position
|
||||
end token.Position
|
||||
token.Position
|
||||
*analysis.Analyzer
|
||||
message string
|
||||
}
|
||||
|
@ -314,8 +309,7 @@ func printDiagnostics(roots []*action) (exitcode int) {
|
|||
// as most users don't care.
|
||||
|
||||
posn := act.pkg.Fset.Position(diag.Pos)
|
||||
end := act.pkg.Fset.Position(diag.End)
|
||||
k := key{posn, end, act.a, diag.Message}
|
||||
k := key{posn, act.a, diag.Message}
|
||||
if seen[k] {
|
||||
continue // duplicate
|
||||
}
|
||||
|
@ -505,8 +499,6 @@ func (act *action) execOnce() {
|
|||
ExportObjectFact: act.exportObjectFact,
|
||||
ImportPackageFact: act.importPackageFact,
|
||||
ExportPackageFact: act.exportPackageFact,
|
||||
AllObjectFacts: act.allObjectFacts,
|
||||
AllPackageFacts: act.allPackageFacts,
|
||||
}
|
||||
act.pass = pass
|
||||
|
||||
|
@ -549,11 +541,11 @@ func inheritFacts(act, dep *action) {
|
|||
// Optionally serialize/deserialize fact
|
||||
// to verify that it works across address spaces.
|
||||
if serialize {
|
||||
encodedFact, err := codeFact(fact)
|
||||
var err error
|
||||
fact, err = codeFact(fact)
|
||||
if err != nil {
|
||||
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
|
||||
}
|
||||
fact = encodedFact
|
||||
}
|
||||
|
||||
if false {
|
||||
|
@ -571,11 +563,11 @@ func inheritFacts(act, dep *action) {
|
|||
// to verify that it works across address spaces
|
||||
// and is deterministic.
|
||||
if serialize {
|
||||
encodedFact, err := codeFact(fact)
|
||||
var err error
|
||||
fact, err = codeFact(fact)
|
||||
if err != nil {
|
||||
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
|
||||
}
|
||||
fact = encodedFact
|
||||
}
|
||||
|
||||
if false {
|
||||
|
@ -670,15 +662,6 @@ func (act *action) exportObjectFact(obj types.Object, fact analysis.Fact) {
|
|||
}
|
||||
}
|
||||
|
||||
// allObjectFacts implements Pass.AllObjectFacts.
|
||||
func (act *action) allObjectFacts() []analysis.ObjectFact {
|
||||
facts := make([]analysis.ObjectFact, 0, len(act.objectFacts))
|
||||
for k := range act.objectFacts {
|
||||
facts = append(facts, analysis.ObjectFact{k.obj, act.objectFacts[k]})
|
||||
}
|
||||
return facts
|
||||
}
|
||||
|
||||
// importPackageFact implements Pass.ImportPackageFact.
|
||||
// Given a non-nil pointer ptr of type *T, where *T satisfies Fact,
|
||||
// fact copies the fact value to *ptr.
|
||||
|
@ -716,13 +699,4 @@ func factType(fact analysis.Fact) reflect.Type {
|
|||
return t
|
||||
}
|
||||
|
||||
// allObjectFacts implements Pass.AllObjectFacts.
|
||||
func (act *action) allPackageFacts() []analysis.PackageFact {
|
||||
facts := make([]analysis.PackageFact, 0, len(act.packageFacts))
|
||||
for k := range act.packageFacts {
|
||||
facts = append(facts, analysis.PackageFact{k.pkg, act.packageFacts[k]})
|
||||
}
|
||||
return facts
|
||||
}
|
||||
|
||||
func dbg(b byte) bool { return strings.IndexByte(Debug, b) >= 0 }
|
||||
|
|
|
@ -99,16 +99,6 @@ func (s *Set) ExportObjectFact(obj types.Object, fact analysis.Fact) {
|
|||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Set) AllObjectFacts() []analysis.ObjectFact {
|
||||
var facts []analysis.ObjectFact
|
||||
for k, v := range s.m {
|
||||
if k.obj != nil {
|
||||
facts = append(facts, analysis.ObjectFact{k.obj, v})
|
||||
}
|
||||
}
|
||||
return facts
|
||||
}
|
||||
|
||||
// ImportPackageFact implements analysis.Pass.ImportPackageFact.
|
||||
func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool {
|
||||
if pkg == nil {
|
||||
|
@ -132,16 +122,6 @@ func (s *Set) ExportPackageFact(fact analysis.Fact) {
|
|||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Set) AllPackageFacts() []analysis.PackageFact {
|
||||
var facts []analysis.PackageFact
|
||||
for k, v := range s.m {
|
||||
if k.obj == nil {
|
||||
facts = append(facts, analysis.PackageFact{k.pkg, v})
|
||||
}
|
||||
}
|
||||
return facts
|
||||
}
|
||||
|
||||
// gobFact is the Gob declaration of a serialized fact.
|
||||
type gobFact struct {
|
||||
PkgPath string // path of package
|
||||
|
|
|
@ -130,7 +130,7 @@ var (
|
|||
asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
|
||||
asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
|
||||
asmDATA = re(`\b(DATA|GLOBL)\b`)
|
||||
asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
|
||||
asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
|
||||
asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
|
||||
asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
|
||||
asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
|
||||
|
@ -184,7 +184,6 @@ Files:
|
|||
fnName string
|
||||
localSize, argSize int
|
||||
wroteSP bool
|
||||
noframe bool
|
||||
haveRetArg bool
|
||||
retLine []int
|
||||
)
|
||||
|
@ -232,11 +231,6 @@ Files:
|
|||
}
|
||||
}
|
||||
|
||||
// Ignore comments and commented-out code.
|
||||
if i := strings.Index(line, "//"); i >= 0 {
|
||||
line = line[:i]
|
||||
}
|
||||
|
||||
if m := asmTEXT.FindStringSubmatch(line); m != nil {
|
||||
flushRet()
|
||||
if arch == "" {
|
||||
|
@ -260,7 +254,7 @@ Files:
|
|||
// identifiers to represent the directory separator.
|
||||
pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
|
||||
if pkgPath != pass.Pkg.Path() {
|
||||
// log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
|
||||
log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
|
||||
fn = nil
|
||||
fnName = ""
|
||||
continue
|
||||
|
@ -281,8 +275,7 @@ Files:
|
|||
localSize += archDef.intSize
|
||||
}
|
||||
argSize, _ = strconv.Atoi(m[5])
|
||||
noframe = strings.Contains(flag, "NOFRAME")
|
||||
if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
|
||||
if fn == nil && !strings.Contains(fnName, "<>") {
|
||||
badf("function %s missing Go declaration", fnName)
|
||||
}
|
||||
wroteSP = false
|
||||
|
@ -312,18 +305,13 @@ Files:
|
|||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) {
|
||||
if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) {
|
||||
wroteSP = true
|
||||
continue
|
||||
}
|
||||
|
||||
if arch == "wasm" && strings.Contains(line, "CallImport") {
|
||||
// CallImport is a call out to magic that can write the result.
|
||||
haveRetArg = true
|
||||
}
|
||||
|
||||
for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
|
||||
if m[3] != archDef.stack || wroteSP || noframe {
|
||||
if m[3] != archDef.stack || wroteSP {
|
||||
continue
|
||||
}
|
||||
off := 0
|
||||
|
@ -383,7 +371,7 @@ Files:
|
|||
}
|
||||
continue
|
||||
}
|
||||
asmCheckVar(badf, fn, line, m[0], off, v, archDef)
|
||||
asmCheckVar(badf, fn, line, m[0], off, v)
|
||||
}
|
||||
}
|
||||
flushRet()
|
||||
|
@ -601,7 +589,7 @@ func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc {
|
|||
}
|
||||
|
||||
// asmCheckVar checks a single variable reference.
|
||||
func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) {
|
||||
func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) {
|
||||
m := asmOpcode.FindStringSubmatch(line)
|
||||
if m == nil {
|
||||
if !strings.HasPrefix(strings.TrimSpace(line), "//") {
|
||||
|
@ -610,8 +598,6 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri
|
|||
return
|
||||
}
|
||||
|
||||
addr := strings.HasPrefix(expr, "$")
|
||||
|
||||
// Determine operand sizes from instruction.
|
||||
// Typically the suffix suffices, but there are exceptions.
|
||||
var src, dst, kind asmKind
|
||||
|
@ -631,13 +617,10 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri
|
|||
// They just take the address of it.
|
||||
case "386.LEAL":
|
||||
dst = 4
|
||||
addr = true
|
||||
case "amd64.LEAQ":
|
||||
dst = 8
|
||||
addr = true
|
||||
case "amd64p32.LEAL":
|
||||
dst = 4
|
||||
addr = true
|
||||
default:
|
||||
switch fn.arch.name {
|
||||
case "386", "amd64":
|
||||
|
@ -742,11 +725,6 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri
|
|||
vs = v.inner[0].size
|
||||
vt = v.inner[0].typ
|
||||
}
|
||||
if addr {
|
||||
vk = asmKind(archDef.ptrSize)
|
||||
vs = archDef.ptrSize
|
||||
vt = "address"
|
||||
}
|
||||
|
||||
if off != v.off {
|
||||
var inner bytes.Buffer
|
||||
|
|
|
@ -5,33 +5,13 @@
|
|||
package asmdecl_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/go/analysis/passes/asmdecl"
|
||||
)
|
||||
|
||||
var goosarches = []string{
|
||||
"linux/amd64", // asm1.s, asm4.s
|
||||
"linux/386", // asm2.s
|
||||
"linux/arm", // asm3.s
|
||||
"linux/mips64", // asm5.s
|
||||
"linux/s390x", // asm6.s
|
||||
"linux/ppc64", // asm7.s
|
||||
"linux/mips", // asm8.s,
|
||||
"js/wasm", // asm9.s
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
for _, goosarch := range goosarches {
|
||||
t.Run(goosarch, func(t *testing.T) {
|
||||
i := strings.Index(goosarch, "/")
|
||||
os.Setenv("GOOS", goosarch[:i])
|
||||
os.Setenv("GOARCH", goosarch[i+1:])
|
||||
analysistest.Run(t, testdata, asmdecl.Analyzer, "a")
|
||||
})
|
||||
}
|
||||
analysistest.Run(t, testdata, asmdecl.Analyzer, "a")
|
||||
}
|
||||
|
|
|
@ -4,11 +4,6 @@
|
|||
|
||||
// +build amd64
|
||||
|
||||
// Commented-out code should be ignored.
|
||||
//
|
||||
// TEXT ·unknown(SB),0,$0
|
||||
// RET
|
||||
|
||||
TEXT ·arg1(SB),0,$0-2
|
||||
MOVB x+0(FP), AX
|
||||
// MOVB x+0(FP), AX // commented out instructions used to panic
|
||||
|
@ -157,7 +152,6 @@ TEXT ·argptr(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-40`
|
|||
TEXT ·argstring(SB),0,$32 // want `wrong argument size 0; expected \$\.\.\.-32`
|
||||
MOVW x+0(FP), AX // want `invalid MOVW of x\+0\(FP\); string base is 8-byte value`
|
||||
MOVL x+0(FP), AX // want `invalid MOVL of x\+0\(FP\); string base is 8-byte value`
|
||||
LEAQ x+0(FP), AX // ok
|
||||
MOVQ x+0(FP), AX
|
||||
MOVW x_base+0(FP), AX // want `invalid MOVW of x_base\+0\(FP\); string base is 8-byte value`
|
||||
MOVL x_base+0(FP), AX // want `invalid MOVL of x_base\+0\(FP\); string base is 8-byte value`
|
||||
|
|
|
@ -186,6 +186,6 @@ TEXT ·noframe1(SB),0,$0-4
|
|||
TEXT ·noframe2(SB),NOFRAME,$0-4
|
||||
MOVW 0(R13), AX // Okay; caller's saved LR
|
||||
MOVW x+4(R13), AX // Okay; x argument
|
||||
MOVW 8(R13), AX // Okay - NOFRAME is assumed special
|
||||
MOVW 12(R13), AX // Okay - NOFRAME is assumed special
|
||||
MOVW 8(R13), AX // want `use of 8\(R13\) points beyond argument frame`
|
||||
MOVW 12(R13), AX // want `use of 12\(R13\) points beyond argument frame`
|
||||
RET
|
||||
|
|
|
@ -47,7 +47,6 @@ TEXT ·arg8(SB),7,$0-2 // want `wrong argument size 2; expected \$\.\.\.-16`
|
|||
MOVH x+0(FP), R1 // want `invalid MOVH of x\+0\(FP\); int64 is 8-byte value`
|
||||
MOVH y+8(FP), R1 // want `invalid MOVH of y\+8\(FP\); uint64 is 8-byte value`
|
||||
MOVW x+0(FP), R1 // want `invalid MOVW of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)`
|
||||
MOVW $x+0(FP), R1 // ok
|
||||
MOVW x_lo+0(FP), R1
|
||||
MOVW x_hi+4(FP), R1
|
||||
MOVW y+8(FP), R1 // want `invalid MOVW of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)`
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// +build wasm
|
||||
|
||||
TEXT ·returnint(SB),NOSPLIT|NOFRAME,$0-8
|
||||
MOVD 24(SP), R1 // ok to access beyond stack frame with NOFRAME
|
||||
CallImport // interpreted as writing result
|
||||
RET
|
||||
|
||||
TEXT ·returnbyte(SB),$0-9
|
||||
RET // want `RET without writing`
|
|
@ -30,13 +30,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
nodeFilter := []ast.Node{
|
||||
(*ast.BinaryExpr)(nil),
|
||||
}
|
||||
seen := make(map[*ast.BinaryExpr]bool)
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
e := n.(*ast.BinaryExpr)
|
||||
if seen[e] {
|
||||
// Already processed as a subexpression of an earlier node.
|
||||
return
|
||||
}
|
||||
|
||||
var op boolOp
|
||||
switch e.Op {
|
||||
|
@ -48,7 +43,10 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
return
|
||||
}
|
||||
|
||||
comm := op.commutativeSets(pass.TypesInfo, e, seen)
|
||||
// TODO(adonovan): this reports n(n-1)/2 errors for an
|
||||
// expression e||...||e of depth n. Fix.
|
||||
// See https://golang.org/issue/28086.
|
||||
comm := op.commutativeSets(pass.TypesInfo, e)
|
||||
for _, exprs := range comm {
|
||||
op.checkRedundant(pass, exprs)
|
||||
op.checkSuspect(pass, exprs)
|
||||
|
@ -72,9 +70,8 @@ var (
|
|||
// expressions in e that are connected by op.
|
||||
// For example, given 'a || b || f() || c || d' with the or op,
|
||||
// commutativeSets returns {{b, a}, {d, c}}.
|
||||
// commutativeSets adds any expanded BinaryExprs to seen.
|
||||
func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr {
|
||||
exprs := op.split(e, seen)
|
||||
func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr) [][]ast.Expr {
|
||||
exprs := op.split(e)
|
||||
|
||||
// Partition the slice of expressions into commutative sets.
|
||||
i := 0
|
||||
|
@ -191,13 +188,11 @@ func hasSideEffects(info *types.Info, e ast.Expr) bool {
|
|||
// split returns a slice of all subexpressions in e that are connected by op.
|
||||
// For example, given 'a || (b || c) || d' with the or op,
|
||||
// split returns []{d, c, b, a}.
|
||||
// seen[e] is already true; any newly processed exprs are added to seen.
|
||||
func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
|
||||
func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) {
|
||||
for {
|
||||
e = unparen(e)
|
||||
if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
|
||||
seen[b] = true
|
||||
exprs = append(exprs, op.split(b.Y, seen)...)
|
||||
exprs = append(exprs, op.split(b.Y)...)
|
||||
e = b.X
|
||||
} else {
|
||||
exprs = append(exprs, e)
|
||||
|
|
|
@ -46,17 +46,22 @@ func RatherStupidConditions() {
|
|||
_ = i+1 == 1 || i+1 == 1 // want `redundant or: i\+1 == 1 \|\| i\+1 == 1`
|
||||
_ = i == 1 || j+1 == i || i == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
|
||||
_ = i == 1 || i == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
// The various r.* patterns are intended to match duplicate
|
||||
// diagnostics reported for the same underlying problem.
|
||||
// See https://golang.org/issue/28086.
|
||||
// TODO(adonovan): fix the checker.
|
||||
|
||||
_ = i == 1 || i == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1` `r.*`
|
||||
_ = i == 1 || f() == 1 || i == 1 // OK f may alter i as a side effect
|
||||
_ = f() == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
|
||||
// Test partition edge cases
|
||||
_ = f() == 1 || i == 1 || i == 1 || j == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
_ = f() == 1 || i == 1 || i == 1 || j == 1 // want `redundant or: i == 1 \|\| i == 1` `r.*`
|
||||
_ = f() == 1 || j == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
_ = i == 1 || f() == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
_ = i == 1 || i == 1 || f() == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
_ = i == 1 || i == 1 || j == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
_ = j == 1 || i == 1 || i == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1`
|
||||
_ = i == 1 || i == 1 || f() == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1` `r.*` `r.*`
|
||||
_ = i == 1 || i == 1 || j == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1` `r.*` `r.*`
|
||||
_ = j == 1 || i == 1 || i == 1 || f() == 1 // want `redundant or: i == 1 \|\| i == 1` `r.*`
|
||||
_ = i == 1 || f() == 1 || f() == 1 || i == 1
|
||||
|
||||
_ = i == 1 || (i == 1 || i == 2) // want `redundant or: i == 1 \|\| i == 1`
|
||||
|
@ -71,9 +76,9 @@ func RatherStupidConditions() {
|
|||
_ = j == 0 ||
|
||||
i == 1 ||
|
||||
f() == 1 ||
|
||||
j == 0 || // want `redundant or: j == 0 \|\| j == 0`
|
||||
i == 1 || // want `redundant or: i == 1 \|\| i == 1`
|
||||
i == 1 || // want `redundant or: i == 1 \|\| i == 1`
|
||||
j == 0 || // want `redundant or: j == 0 \|\| j == 0` `r.*`
|
||||
i == 1 || // want `redundant or: i == 1 \|\| i == 1` `r.*` `r.*` `r.*`
|
||||
i == 1 || // want `redundant or: i == 1 \|\| i == 1` `r.*` `r.*`
|
||||
i == 1 ||
|
||||
j == 0 ||
|
||||
k == 0
|
||||
|
@ -89,7 +94,7 @@ func RatherStupidConditions() {
|
|||
_ = 0 != <-c && 0 != <-c // OK subsequent receives may yield different values
|
||||
_ = f() != 0 && f() != 0 // OK f might have side effects
|
||||
_ = f != nil && f != nil // want `redundant and: f != nil && f != nil`
|
||||
_ = i != 1 && i != 1 && f() != 1 // want `redundant and: i != 1 && i != 1`
|
||||
_ = i != 1 && i != 1 && f() != 1 // want `redundant and: i != 1 && i != 1` `r.*`
|
||||
_ = i != 1 && f() != 1 && i != 1 // OK f may alter i as a side effect
|
||||
_ = f() != 1 && i != 1 && i != 1 // want `redundant and: i != 1 && i != 1`
|
||||
}
|
||||
|
|
|
@ -119,9 +119,3 @@ var badNamedPointerSliceLiteral = []*unicode.CaseRange{
|
|||
{1, 2, delta}, // want "unkeyed fields"
|
||||
&unicode.CaseRange{1, 2, delta}, // want "unkeyed fields"
|
||||
}
|
||||
|
||||
// unicode.Range16 is whitelisted, so there'll be no vet error
|
||||
var range16 = unicode.Range16{0xfdd0, 0xfdef, 1}
|
||||
|
||||
// unicode.Range32 is whitelisted, so there'll be no vet error
|
||||
var range32 = unicode.Range32{0x1fffe, 0x1ffff, 1}
|
||||
|
|
|
@ -24,7 +24,6 @@ var unkeyedLiteral = map[string]bool{
|
|||
"image.Uniform": true,
|
||||
|
||||
"unicode.Range16": true,
|
||||
"unicode.Range32": true,
|
||||
|
||||
// These three structs are used in generated test main files,
|
||||
// but the generator can be trusted.
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
// Copyright 2019 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 deepequalerrors defines an Analyzer that checks for the use
|
||||
// of reflect.DeepEqual with error values.
|
||||
package deepequalerrors
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
const Doc = `check for calls of reflect.DeepEqual on error values
|
||||
|
||||
The deepequalerrors checker looks for calls of the form:
|
||||
|
||||
reflect.DeepEqual(err1, err2)
|
||||
|
||||
where err1 and err2 are errors. Using reflect.DeepEqual to compare
|
||||
errors is discouraged.`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "deepequalerrors",
|
||||
Doc: Doc,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.CallExpr)(nil),
|
||||
}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
call := n.(*ast.CallExpr)
|
||||
fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if fn.FullName() == "reflect.DeepEqual" && hasError(pass, call.Args[0]) && hasError(pass, call.Args[1]) {
|
||||
pass.Reportf(call.Pos(), "avoid using reflect.DeepEqual with errors")
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
|
||||
|
||||
// hasError reports whether the type of e contains the type error.
|
||||
// See containsError, below, for the meaning of "contains".
|
||||
func hasError(pass *analysis.Pass, e ast.Expr) bool {
|
||||
tv, ok := pass.TypesInfo.Types[e]
|
||||
if !ok { // no type info, assume good
|
||||
return false
|
||||
}
|
||||
return containsError(tv.Type)
|
||||
}
|
||||
|
||||
// Report whether any type that typ could store and that could be compared is the
|
||||
// error type. This includes typ itself, as well as the types of struct field, slice
|
||||
// and array elements, map keys and elements, and pointers. It does not include
|
||||
// channel types (incomparable), arg and result types of a Signature (not stored), or
|
||||
// methods of a named or interface type (not stored).
|
||||
func containsError(typ types.Type) bool {
|
||||
// Track types being processed, to avoid infinite recursion.
|
||||
// Using types as keys here is OK because we are checking for the identical pointer, not
|
||||
// type identity. See analysis/passes/printf/types.go.
|
||||
inProgress := make(map[types.Type]bool)
|
||||
|
||||
var check func(t types.Type) bool
|
||||
check = func(t types.Type) bool {
|
||||
if t == errorType {
|
||||
return true
|
||||
}
|
||||
if inProgress[t] {
|
||||
return false
|
||||
}
|
||||
inProgress[t] = true
|
||||
switch t := t.(type) {
|
||||
case *types.Pointer:
|
||||
return check(t.Elem())
|
||||
case *types.Slice:
|
||||
return check(t.Elem())
|
||||
case *types.Array:
|
||||
return check(t.Elem())
|
||||
case *types.Map:
|
||||
return check(t.Key()) || check(t.Elem())
|
||||
case *types.Struct:
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if check(t.Field(i).Type()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *types.Named:
|
||||
return check(t.Underlying())
|
||||
|
||||
// We list the remaining valid type kinds for completeness.
|
||||
case *types.Basic:
|
||||
case *types.Chan: // channels store values, but they are not comparable
|
||||
case *types.Signature:
|
||||
case *types.Tuple: // tuples are only part of signatures
|
||||
case *types.Interface:
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return check(typ)
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright 2019 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 deepequalerrors_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/go/analysis/passes/deepequalerrors"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, deepequalerrors.Analyzer, "a")
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// This file contains tests for the deepequalerrors checker.
|
||||
|
||||
package a
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type myError int
|
||||
|
||||
func (myError) Error() string { return "" }
|
||||
|
||||
func bad() error { return nil }
|
||||
|
||||
type s1 struct {
|
||||
s2 *s2
|
||||
i int
|
||||
}
|
||||
|
||||
type myError2 error
|
||||
|
||||
type s2 struct {
|
||||
s1 *s1
|
||||
errs []*myError2
|
||||
}
|
||||
|
||||
func hasError() {
|
||||
var e error
|
||||
var m myError2
|
||||
reflect.DeepEqual(bad(), e) // want `avoid using reflect.DeepEqual with errors`
|
||||
reflect.DeepEqual(io.EOF, io.EOF) // want `avoid using reflect.DeepEqual with errors`
|
||||
reflect.DeepEqual(e, &e) // want `avoid using reflect.DeepEqual with errors`
|
||||
reflect.DeepEqual(e, m) // want `avoid using reflect.DeepEqual with errors`
|
||||
reflect.DeepEqual(e, s1{}) // want `avoid using reflect.DeepEqual with errors`
|
||||
reflect.DeepEqual(e, [1]error{}) // want `avoid using reflect.DeepEqual with errors`
|
||||
reflect.DeepEqual(e, map[error]int{}) // want `avoid using reflect.DeepEqual with errors`
|
||||
reflect.DeepEqual(e, map[int]error{}) // want `avoid using reflect.DeepEqual with errors`
|
||||
// We catch the next not because *os.PathError implements error, but because it contains
|
||||
// a field Err of type error.
|
||||
reflect.DeepEqual(&os.PathError{}, io.EOF) // want `avoid using reflect.DeepEqual with errors`
|
||||
|
||||
}
|
||||
|
||||
func notHasError() {
|
||||
reflect.ValueOf(4) // not reflect.DeepEqual
|
||||
reflect.DeepEqual(3, 4) // not errors
|
||||
reflect.DeepEqual(5, io.EOF) // only one error
|
||||
reflect.DeepEqual(myError(1), io.EOF) // not types that implement error
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright 2018 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.
|
||||
|
||||
// The errorsas package defines an Analyzer that checks that the second arugment to
|
||||
// errors.As is a pointer to a type implementing error.
|
||||
package errorsas
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
const doc = `report passing non-pointer or non-error values to errors.As
|
||||
|
||||
The errorsas analysis reports calls to errors.As where the type
|
||||
of the second argument is not a pointer to a type implementing error.`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "errorsas",
|
||||
Doc: doc,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: run,
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
switch pass.Pkg.Path() {
|
||||
case "errors", "errors_test":
|
||||
// These packages know how to use their own APIs.
|
||||
// Sometimes they are testing what happens to incorrect programs.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.CallExpr)(nil),
|
||||
}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
call := n.(*ast.CallExpr)
|
||||
fn := typeutil.StaticCallee(pass.TypesInfo, call)
|
||||
if fn == nil {
|
||||
return // not a static call
|
||||
}
|
||||
if len(call.Args) < 2 {
|
||||
return // not enough arguments, e.g. called with return values of another function
|
||||
}
|
||||
if fn.FullName() == "errors.As" && !pointerToInterfaceOrError(pass, call.Args[1]) {
|
||||
pass.Reportf(call.Pos(), "second argument to errors.As must be a pointer to an interface or a type implementing error")
|
||||
}
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
|
||||
|
||||
// pointerToInterfaceOrError reports whether the type of e is a pointer to an interface or a type implementing error,
|
||||
// or is the empty interface.
|
||||
func pointerToInterfaceOrError(pass *analysis.Pass, e ast.Expr) bool {
|
||||
t := pass.TypesInfo.Types[e].Type
|
||||
if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 {
|
||||
return true
|
||||
}
|
||||
pt, ok := t.Underlying().(*types.Pointer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = pt.Elem().Underlying().(*types.Interface)
|
||||
return ok || types.Implements(pt.Elem(), errorType)
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// +build go1.13
|
||||
|
||||
package errorsas_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/go/analysis/passes/errorsas"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, errorsas.Analyzer, "a")
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
// This file contains tests for the errorsas checker.
|
||||
|
||||
package a
|
||||
|
||||
import "errors"
|
||||
|
||||
type myError int
|
||||
|
||||
func (myError) Error() string { return "" }
|
||||
|
||||
func perr() *error { return nil }
|
||||
|
||||
type iface interface {
|
||||
m()
|
||||
}
|
||||
|
||||
func two() (error, interface{}) { return nil, nil }
|
||||
|
||||
func _() {
|
||||
var (
|
||||
e error
|
||||
m myError
|
||||
i int
|
||||
f iface
|
||||
ei interface{}
|
||||
)
|
||||
errors.As(nil, &e) // *error
|
||||
errors.As(nil, &m) // *T where T implemements error
|
||||
errors.As(nil, &f) // *interface
|
||||
errors.As(nil, perr()) // *error, via a call
|
||||
errors.As(nil, ei) // empty interface
|
||||
|
||||
errors.As(nil, nil) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
|
||||
errors.As(nil, e) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
|
||||
errors.As(nil, m) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
|
||||
errors.As(nil, f) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
|
||||
errors.As(nil, &i) // want `second argument to errors.As must be a pointer to an interface or a type implementing error`
|
||||
errors.As(two())
|
||||
}
|
|
@ -45,8 +45,6 @@ var contextPackage = "context"
|
|||
// control-flow path from the call to a return statement and that path
|
||||
// does not "use" the cancel function. Any reference to the variable
|
||||
// counts as a use, even within a nested function literal.
|
||||
// If the variable's scope is larger than the function
|
||||
// containing the assignment, we assume that other uses exist.
|
||||
//
|
||||
// checkLostCancel analyzes a single named or literal function.
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
|
@ -68,15 +66,6 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
}
|
||||
|
||||
func runFunc(pass *analysis.Pass, node ast.Node) {
|
||||
// Find scope of function node
|
||||
var funcScope *types.Scope
|
||||
switch v := node.(type) {
|
||||
case *ast.FuncLit:
|
||||
funcScope = pass.TypesInfo.Scopes[v.Type]
|
||||
case *ast.FuncDecl:
|
||||
funcScope = pass.TypesInfo.Scopes[v.Type]
|
||||
}
|
||||
|
||||
// Maps each cancel variable to its defining ValueSpec/AssignStmt.
|
||||
cancelvars := make(map[*types.Var]ast.Node)
|
||||
|
||||
|
@ -125,11 +114,7 @@ func runFunc(pass *analysis.Pass, node ast.Node) {
|
|||
"the cancel function returned by context.%s should be called, not discarded, to avoid a context leak",
|
||||
n.(*ast.SelectorExpr).Sel.Name)
|
||||
} else if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok {
|
||||
// If the cancel variable is defined outside function scope,
|
||||
// do not analyze it.
|
||||
if funcScope.Contains(v.Pos()) {
|
||||
cancelvars[v] = stmt
|
||||
}
|
||||
cancelvars[v] = stmt
|
||||
} else if v, ok := pass.TypesInfo.Defs[id].(*types.Var); ok {
|
||||
cancelvars[v] = stmt
|
||||
}
|
||||
|
|
|
@ -171,22 +171,3 @@ var _ = func() (ctx context.Context, cancel func()) {
|
|||
ctx, cancel = context.WithCancel(bg)
|
||||
return
|
||||
}
|
||||
|
||||
// Test for Go issue 31856.
|
||||
func _() {
|
||||
var cancel func()
|
||||
|
||||
func() {
|
||||
_, cancel = context.WithCancel(bg)
|
||||
}()
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
var cancel1 func()
|
||||
|
||||
// Same as above, but for package-level cancel variable.
|
||||
func _() {
|
||||
// We assume that other uses of cancel1 exist.
|
||||
_, cancel1 = context.WithCancel(bg)
|
||||
}
|
||||
|
|
|
@ -739,7 +739,6 @@ var printVerbs = []printVerb{
|
|||
{'T', "-", anyType},
|
||||
{'U', "-#", argRune | argInt},
|
||||
{'v', allFlags, anyType},
|
||||
{'w', noFlag, anyType},
|
||||
{'x', sharpNumFlag, argRune | argInt | argString | argPointer},
|
||||
{'X', sharpNumFlag, argRune | argInt | argString | argPointer},
|
||||
}
|
||||
|
@ -856,28 +855,20 @@ func recursiveStringer(pass *analysis.Pass, e ast.Expr) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
sig := stringMethod.Type().(*types.Signature)
|
||||
if !isStringer(sig) {
|
||||
// Is it the receiver r, or &r?
|
||||
recv := stringMethod.Type().(*types.Signature).Recv()
|
||||
if recv == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Is it the receiver r, or &r?
|
||||
if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND {
|
||||
e = u.X // strip off & from &r
|
||||
}
|
||||
if id, ok := e.(*ast.Ident); ok {
|
||||
return pass.TypesInfo.Uses[id] == sig.Recv()
|
||||
return pass.TypesInfo.Uses[id] == recv
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isStringer reports whether the method signature matches the String() definition in fmt.Stringer.
|
||||
func isStringer(sig *types.Signature) bool {
|
||||
return sig.Params().Len() == 0 &&
|
||||
sig.Results().Len() == 1 &&
|
||||
sig.Results().At(0).Type() == types.Typ[types.String]
|
||||
}
|
||||
|
||||
// isFunctionValue reports whether the expression is a function as opposed to a function call.
|
||||
// It is almost always a mistake to print a function value.
|
||||
func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
|
||||
|
|
|
@ -63,7 +63,6 @@ func PrintfTests() {
|
|||
var imap map[int]int
|
||||
var fslice []float64
|
||||
var c complex64
|
||||
var err error
|
||||
// Some good format/argtypes
|
||||
fmt.Printf("")
|
||||
fmt.Printf("%b %b %b", 3, i, x)
|
||||
|
@ -97,7 +96,6 @@ func PrintfTests() {
|
|||
fmt.Printf("%T", notstringerv)
|
||||
fmt.Printf("%q", stringerarrayv)
|
||||
fmt.Printf("%v", stringerarrayv)
|
||||
fmt.Printf("%w", err)
|
||||
fmt.Printf("%s", stringerarrayv)
|
||||
fmt.Printf("%v", notstringerarrayv)
|
||||
fmt.Printf("%T", notstringerarrayv)
|
||||
|
@ -516,20 +514,6 @@ func (p *recursivePtrStringer) String() string {
|
|||
return fmt.Sprintln(p) // want "Sprintln arg p causes recursive call to String method"
|
||||
}
|
||||
|
||||
// implements a String() method but with non-matching return types
|
||||
type nonStringerWrongReturn int
|
||||
|
||||
func (s nonStringerWrongReturn) String() (string, error) {
|
||||
return "", fmt.Errorf("%v", s)
|
||||
}
|
||||
|
||||
// implements a String() method but with non-matching arguments
|
||||
type nonStringerWrongArgs int
|
||||
|
||||
func (s nonStringerWrongArgs) String(i int) string {
|
||||
return fmt.Sprintf("%d%v", i, s)
|
||||
}
|
||||
|
||||
type cons struct {
|
||||
car int
|
||||
cdr *cons
|
||||
|
|
|
@ -8,6 +8,7 @@ package stdmethods
|
|||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
|
@ -116,13 +117,6 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident) {
|
|||
args := sign.Params()
|
||||
results := sign.Results()
|
||||
|
||||
// Special case: WriteTo with more than one argument,
|
||||
// not trying at all to implement io.WriterTo,
|
||||
// comes up often enough to skip.
|
||||
if id.Name == "WriteTo" && args.Len() > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Do the =s (if any) all match?
|
||||
if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") {
|
||||
return
|
||||
|
@ -169,7 +163,7 @@ func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, pref
|
|||
if i >= actual.Len() {
|
||||
return false
|
||||
}
|
||||
if !matchParamType(x, actual.At(i).Type()) {
|
||||
if !matchParamType(pass.Fset, pass.Pkg, x, actual.At(i).Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -180,8 +174,13 @@ func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, pref
|
|||
}
|
||||
|
||||
// Does this one type match?
|
||||
func matchParamType(expect string, actual types.Type) bool {
|
||||
func matchParamType(fset *token.FileSet, pkg *types.Package, expect string, actual types.Type) bool {
|
||||
expect = strings.TrimPrefix(expect, "=")
|
||||
// Strip package name if we're in that package.
|
||||
if n := len(pkg.Name()); len(expect) > n && expect[:n] == pkg.Name() && expect[n] == '.' {
|
||||
expect = expect[n+1:]
|
||||
}
|
||||
|
||||
// Overkill but easy.
|
||||
return typeString(actual) == expect
|
||||
}
|
||||
|
|
|
@ -15,7 +15,3 @@ func Test(t *testing.T) {
|
|||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, stdmethods.Analyzer, "a")
|
||||
}
|
||||
|
||||
func TestAnalyzeEncodingXML(t *testing.T) {
|
||||
analysistest.Run(t, "", stdmethods.Analyzer, "encoding/xml")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package a
|
|||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type T int
|
||||
|
@ -29,10 +28,6 @@ func (U) UnmarshalXML(*xml.Decoder, xml.StartElement) error { // no error: signa
|
|||
return nil
|
||||
}
|
||||
|
||||
func (U) WriteTo(w io.Writer) {} // want `method WriteTo\(w io.Writer\) should have signature WriteTo\(io.Writer\) \(int64, error\)`
|
||||
|
||||
func (T) WriteTo(w io.Writer, more, args int) {} // ok - clearly not io.WriterTo
|
||||
|
||||
type I interface {
|
||||
ReadByte() byte // want `should have signature ReadByte\(\) \(byte, error\)`
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
styp := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
|
||||
var seen namesSeen
|
||||
var seen map[[2]string]token.Pos
|
||||
for i := 0; i < styp.NumFields(); i++ {
|
||||
field := styp.Field(i)
|
||||
tag := styp.Tag(i)
|
||||
|
@ -51,47 +51,13 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// namesSeen keeps track of encoding tags by their key, name, and nested level
|
||||
// from the initial struct. The level is taken into account because equal
|
||||
// encoding key names only conflict when at the same level; otherwise, the lower
|
||||
// level shadows the higher level.
|
||||
type namesSeen map[uniqueName]token.Pos
|
||||
|
||||
type uniqueName struct {
|
||||
key string // "xml" or "json"
|
||||
name string // the encoding name
|
||||
level int // anonymous struct nesting level
|
||||
}
|
||||
|
||||
func (s *namesSeen) Get(key, name string, level int) (token.Pos, bool) {
|
||||
if *s == nil {
|
||||
*s = make(map[uniqueName]token.Pos)
|
||||
}
|
||||
pos, ok := (*s)[uniqueName{key, name, level}]
|
||||
return pos, ok
|
||||
}
|
||||
|
||||
func (s *namesSeen) Set(key, name string, level int, pos token.Pos) {
|
||||
if *s == nil {
|
||||
*s = make(map[uniqueName]token.Pos)
|
||||
}
|
||||
(*s)[uniqueName{key, name, level}] = pos
|
||||
}
|
||||
|
||||
var checkTagDups = []string{"json", "xml"}
|
||||
var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
|
||||
|
||||
// checkCanonicalFieldTag checks a single struct field tag.
|
||||
func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *namesSeen) {
|
||||
switch pass.Pkg.Path() {
|
||||
case "encoding/json", "encoding/xml":
|
||||
// These packages know how to use their own APIs.
|
||||
// Sometimes they are testing what happens to incorrect programs.
|
||||
return
|
||||
}
|
||||
|
||||
func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *map[[2]string]token.Pos) {
|
||||
for _, key := range checkTagDups {
|
||||
checkTagDuplicates(pass, tag, key, field, field, seen, 1)
|
||||
checkTagDuplicates(pass, tag, key, field, field, seen)
|
||||
}
|
||||
|
||||
if err := validateStructTag(tag); err != nil {
|
||||
|
@ -122,29 +88,33 @@ func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, s
|
|||
// checkTagDuplicates checks a single struct field tag to see if any tags are
|
||||
// duplicated. nearest is the field that's closest to the field being checked,
|
||||
// while still being part of the top-level struct type.
|
||||
func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *types.Var, seen *namesSeen, level int) {
|
||||
func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *types.Var, seen *map[[2]string]token.Pos) {
|
||||
val := reflect.StructTag(tag).Get(key)
|
||||
if val == "-" {
|
||||
// Ignored, even if the field is anonymous.
|
||||
return
|
||||
}
|
||||
if val == "" || val[0] == ',' {
|
||||
if !field.Anonymous() {
|
||||
// Ignored if the field isn't anonymous.
|
||||
if field.Anonymous() {
|
||||
// Disable this check enhancement in Go 1.12.1; some
|
||||
// false positives were spotted in the initial 1.12
|
||||
// release. See https://golang.org/issues/30465.
|
||||
return
|
||||
}
|
||||
typ, ok := field.Type().Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
field := typ.Field(i)
|
||||
if !field.Exported() {
|
||||
continue
|
||||
|
||||
typ, ok := field.Type().Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
field := typ.Field(i)
|
||||
if !field.Exported() {
|
||||
continue
|
||||
}
|
||||
tag := typ.Tag(i)
|
||||
checkTagDuplicates(pass, tag, key, nearest, field, seen)
|
||||
}
|
||||
tag := typ.Tag(i)
|
||||
checkTagDuplicates(pass, tag, key, nearest, field, seen, level+1)
|
||||
}
|
||||
// Ignored if the field isn't anonymous.
|
||||
return
|
||||
}
|
||||
if key == "xml" && field.Name() == "XMLName" {
|
||||
|
@ -167,7 +137,10 @@ func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *ty
|
|||
}
|
||||
val = val[:i]
|
||||
}
|
||||
if pos, ok := seen.Get(key, val, level); ok {
|
||||
if *seen == nil {
|
||||
*seen = map[[2]string]token.Pos{}
|
||||
}
|
||||
if pos, ok := (*seen)[[2]string{key, val}]; ok {
|
||||
alsoPos := pass.Fset.Position(pos)
|
||||
alsoPos.Column = 0
|
||||
|
||||
|
@ -186,7 +159,7 @@ func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *ty
|
|||
|
||||
pass.Reportf(nearest.Pos(), "struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos)
|
||||
} else {
|
||||
seen.Set(key, val, level, field.Pos())
|
||||
(*seen)[[2]string{key, val}] = field.Pos()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,27 +75,29 @@ type DuplicateJSONFields struct {
|
|||
}
|
||||
AnonymousJSON `json:"a"` // want "struct field AnonymousJSON repeats json tag .a. also at a.go:64"
|
||||
|
||||
AnonymousJSONField
|
||||
|
||||
XML int `xml:"a"`
|
||||
DuplicateXML int `xml:"a"` // want "struct field DuplicateXML repeats xml tag .a. also at a.go:78"
|
||||
DuplicateXML int `xml:"a"` // want "struct field DuplicateXML repeats xml tag .a. also at a.go:80"
|
||||
IgnoredXML int `xml:"-"`
|
||||
OtherIgnoredXML int `xml:"-"`
|
||||
OmitXML int `xml:",omitempty"`
|
||||
OtherOmitXML int `xml:",omitempty"`
|
||||
DuplicateOmitXML int `xml:"a,omitempty"` // want "struct field DuplicateOmitXML repeats xml tag .a. also at a.go:78"
|
||||
DuplicateOmitXML int `xml:"a,omitempty"` // want "struct field DuplicateOmitXML repeats xml tag .a. also at a.go:80"
|
||||
NonXML int `foo:"a"`
|
||||
DuplicateNonXML int `foo:"a"`
|
||||
Embedded2 struct {
|
||||
DuplicateXML int `xml:"a"` // OK because it's not in the same struct type
|
||||
}
|
||||
AnonymousXML `xml:"a"` // want "struct field AnonymousXML repeats xml tag .a. also at a.go:78"
|
||||
AnonymousXML `xml:"a"` // want "struct field AnonymousXML repeats xml tag .a. also at a.go:80"
|
||||
Attribute struct {
|
||||
XMLName xml.Name `xml:"b"`
|
||||
NoDup int `xml:"b"` // OK because XMLName above affects enclosing struct.
|
||||
Attr int `xml:"b,attr"` // OK because <b b="0"><b>0</b></b> is valid.
|
||||
DupAttr int `xml:"b,attr"` // want "struct field DupAttr repeats xml attribute tag .b. also at a.go:94"
|
||||
DupOmitAttr int `xml:"b,omitempty,attr"` // want "struct field DupOmitAttr repeats xml attribute tag .b. also at a.go:94"
|
||||
DupAttr int `xml:"b,attr"` // want "struct field DupAttr repeats xml attribute tag .b. also at a.go:96"
|
||||
DupOmitAttr int `xml:"b,omitempty,attr"` // want "struct field DupOmitAttr repeats xml attribute tag .b. also at a.go:96"
|
||||
|
||||
AnonymousXML `xml:"b,attr"` // want "struct field AnonymousXML repeats xml attribute tag .b. also at a.go:94"
|
||||
AnonymousXML `xml:"b,attr"` // want "struct field AnonymousXML repeats xml attribute tag .b. also at a.go:96"
|
||||
}
|
||||
|
||||
AnonymousJSONField2 `json:"not_anon"` // ok; fields aren't embedded in JSON
|
||||
|
@ -122,17 +124,10 @@ type UnexpectedSpacetest struct {
|
|||
Q int `foo:" doesn't care "`
|
||||
}
|
||||
|
||||
// Nested fiels can be shadowed by fields further up. For example,
|
||||
// ShadowingAnonJSON replaces the json:"a" field in AnonymousJSONField.
|
||||
// However, if the two conflicting fields appear at the same level like in
|
||||
// DuplicateWithAnotherPackage, we should error.
|
||||
|
||||
type ShadowingJsonFieldName struct {
|
||||
AnonymousJSONField
|
||||
ShadowingAnonJSON int `json:"a"`
|
||||
}
|
||||
|
||||
type DuplicateWithAnotherPackage struct {
|
||||
b.AnonymousJSONField
|
||||
AnonymousJSONField2 // want "struct field DuplicateAnonJSON repeats json tag .a. also at b.b.go:8"
|
||||
|
||||
// The "also at" position is in a different package and directory. Use
|
||||
// "b.b" instead of "b/b" to match the relative path on Windows easily.
|
||||
DuplicateJSON int `json:"a"`
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package b
|
||||
|
||||
type Foo struct {
|
||||
}
|
||||
|
||||
func (f *Foo) F() {
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package b_x_test
|
||||
|
||||
import (
|
||||
"a"
|
||||
"b"
|
||||
)
|
||||
|
||||
func ExampleFoo_F() {
|
||||
var x b.Foo
|
||||
x.F()
|
||||
a.Foo()
|
||||
}
|
||||
|
||||
func ExampleFoo_G() { // want "ExampleFoo_G refers to unknown field or method: Foo.G"
|
||||
|
||||
}
|
||||
|
||||
func ExampleBar_F() { // want "ExampleBar_F refers to unknown identifier: Bar"
|
||||
|
||||
}
|
|
@ -20,10 +20,7 @@ const Doc = `check for common mistaken usages of tests and examples
|
|||
|
||||
The tests checker walks Test, Benchmark and Example functions checking
|
||||
malformed names, wrong signatures and examples documenting non-existent
|
||||
identifiers.
|
||||
|
||||
Please see the documentation for package testing in golang.org/pkg/testing
|
||||
for the conventions that are enforced for Tests, Benchmarks, and Examples.`
|
||||
identifiers.`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "tests",
|
||||
|
@ -87,25 +84,23 @@ func isTestParam(typ ast.Expr, wantType string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func lookup(pkg *types.Package, name string) []types.Object {
|
||||
func lookup(pkg *types.Package, name string) types.Object {
|
||||
if o := pkg.Scope().Lookup(name); o != nil {
|
||||
return []types.Object{o}
|
||||
return o
|
||||
}
|
||||
|
||||
var ret []types.Object
|
||||
// Search through the imports to see if any of them define name.
|
||||
// It's hard to tell in general which package is being tested, so
|
||||
// for the purposes of the analysis, allow the object to appear
|
||||
// in any of the imports. This guarantees there are no false positives
|
||||
// because the example needs to use the object so it must be defined
|
||||
// in the package or one if its imports. On the other hand, false
|
||||
// negatives are possible, but should be rare.
|
||||
for _, imp := range pkg.Imports() {
|
||||
if obj := imp.Scope().Lookup(name); obj != nil {
|
||||
ret = append(ret, obj)
|
||||
// If this package is ".../foo_test" and it imports a package
|
||||
// ".../foo", try looking in the latter package.
|
||||
// This heuristic should work even on build systems that do not
|
||||
// record any special link between the packages.
|
||||
if basePath := strings.TrimSuffix(pkg.Path(), "_test"); basePath != pkg.Path() {
|
||||
for _, imp := range pkg.Imports() {
|
||||
if imp.Path() == basePath {
|
||||
return imp.Scope().Lookup(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
|
||||
|
@ -126,9 +121,9 @@ func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
|
|||
exName = strings.TrimPrefix(fnName, "Example")
|
||||
elems = strings.SplitN(exName, "_", 3)
|
||||
ident = elems[0]
|
||||
objs = lookup(pass.Pkg, ident)
|
||||
obj = lookup(pass.Pkg, ident)
|
||||
)
|
||||
if ident != "" && len(objs) == 0 {
|
||||
if ident != "" && obj == nil {
|
||||
// Check ExampleFoo and ExampleBadFoo.
|
||||
pass.Reportf(fn.Pos(), "%s refers to unknown identifier: %s", fnName, ident)
|
||||
// Abort since obj is absent and no subsequent checks can be performed.
|
||||
|
@ -150,15 +145,7 @@ func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) {
|
|||
mmbr := elems[1]
|
||||
if !isExampleSuffix(mmbr) {
|
||||
// Check ExampleFoo_Method and ExampleFoo_BadMethod.
|
||||
found := false
|
||||
// Check if Foo.Method exists in this package or its imports.
|
||||
for _, obj := range objs {
|
||||
if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj != nil {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil {
|
||||
pass.Reportf(fn.Pos(), "%s refers to unknown field or method: %s.%s", fnName, ident, mmbr)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,7 @@ func Test(t *testing.T) {
|
|||
testdata := analysistest.TestData()
|
||||
|
||||
analysistest.Run(t, testdata, tests.Analyzer,
|
||||
"a", // loads "a", "a [a.test]", and "a.test"
|
||||
"b_x_test", // loads "b" and "b_x_test"
|
||||
"a", // loads "a", "a [a.test]", and "a.test"
|
||||
"divergent",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,13 +29,6 @@ var Analyzer = &analysis.Analyzer{
|
|||
}
|
||||
|
||||
func run(pass *analysis.Pass) (interface{}, error) {
|
||||
switch pass.Pkg.Path() {
|
||||
case "encoding/gob", "encoding/json", "encoding/xml":
|
||||
// These packages know how to use their own APIs.
|
||||
// Sometimes they are testing what happens to incorrect programs.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
nodeFilter := []ast.Node{
|
||||
|
|
|
@ -52,11 +52,11 @@ func Main(a *analysis.Analyzer) {
|
|||
flag.Usage = func() {
|
||||
paras := strings.Split(a.Doc, "\n\n")
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n\n", a.Name, paras[0])
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [-flag] [package]\n\n", a.Name)
|
||||
fmt.Printf("Usage: %s [-flag] [package]\n\n", a.Name)
|
||||
if len(paras) > 1 {
|
||||
fmt.Fprintln(os.Stderr, strings.Join(paras[1:], "\n\n"))
|
||||
fmt.Println(strings.Join(paras[1:], "\n\n"))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\nFlags:")
|
||||
fmt.Println("\nFlags:")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package a
|
||||
|
||||
func _() {
|
||||
MyFunc123()
|
||||
}
|
||||
|
||||
func MyFunc123() {}
|
|
@ -0,0 +1,10 @@
|
|||
package b
|
||||
|
||||
import "a"
|
||||
|
||||
func _() {
|
||||
a.MyFunc123()
|
||||
MyFunc123()
|
||||
}
|
||||
|
||||
func MyFunc123() {}
|
|
@ -334,10 +334,8 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
|
|||
Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
|
||||
ImportObjectFact: facts.ImportObjectFact,
|
||||
ExportObjectFact: facts.ExportObjectFact,
|
||||
AllObjectFacts: facts.AllObjectFacts,
|
||||
ImportPackageFact: facts.ImportPackageFact,
|
||||
ExportPackageFact: facts.ExportPackageFact,
|
||||
AllPackageFacts: facts.AllPackageFacts,
|
||||
}
|
||||
|
||||
t0 := time.Now()
|
||||
|
|
|
@ -14,15 +14,14 @@ import (
|
|||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
"golang.org/x/tools/go/analysis/passes/findcall"
|
||||
"golang.org/x/tools/go/analysis/passes/printf"
|
||||
"golang.org/x/tools/go/analysis/unitchecker"
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -32,6 +31,7 @@ func TestMain(m *testing.M) {
|
|||
panic("unreachable")
|
||||
}
|
||||
|
||||
// test
|
||||
flag.Parse()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
@ -46,55 +46,31 @@ func main() {
|
|||
// This is a very basic integration test of modular
|
||||
// analysis with facts using unitchecker under "go vet".
|
||||
// It fork/execs the main function above.
|
||||
func TestIntegration(t *testing.T) { packagestest.TestAll(t, testIntegration) }
|
||||
func testIntegration(t *testing.T, exporter packagestest.Exporter) {
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
|
||||
func TestIntegration(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skipf("skipping fork/exec test on this platform")
|
||||
}
|
||||
|
||||
exported := packagestest.Export(t, exporter, []packagestest.Module{{
|
||||
Name: "golang.org/fake",
|
||||
Files: map[string]interface{}{
|
||||
"a/a.go": `package a
|
||||
testdata := analysistest.TestData()
|
||||
|
||||
func _() {
|
||||
MyFunc123()
|
||||
}
|
||||
|
||||
func MyFunc123() {}
|
||||
`,
|
||||
"b/b.go": `package b
|
||||
|
||||
import "golang.org/fake/a"
|
||||
|
||||
func _() {
|
||||
a.MyFunc123()
|
||||
MyFunc123()
|
||||
}
|
||||
|
||||
func MyFunc123() {}
|
||||
`,
|
||||
}}})
|
||||
defer exported.Cleanup()
|
||||
|
||||
const wantA = `# golang.org/fake/a
|
||||
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?a/a.go:4:11: call of MyFunc123\(...\)
|
||||
const wantA = `# a
|
||||
testdata/src/a/a.go:4:11: call of MyFunc123(...)
|
||||
`
|
||||
const wantB = `# golang.org/fake/b
|
||||
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?b/b.go:6:13: call of MyFunc123\(...\)
|
||||
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?b/b.go:7:11: call of MyFunc123\(...\)
|
||||
const wantB = `# b
|
||||
testdata/src/b/b.go:6:13: call of MyFunc123(...)
|
||||
testdata/src/b/b.go:7:11: call of MyFunc123(...)
|
||||
`
|
||||
const wantAJSON = `# golang.org/fake/a
|
||||
\{
|
||||
"golang.org/fake/a": \{
|
||||
"findcall": \[
|
||||
\{
|
||||
"posn": "([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?a/a.go:4:11",
|
||||
"message": "call of MyFunc123\(...\)"
|
||||
\}
|
||||
\]
|
||||
\}
|
||||
\}
|
||||
const wantAJSON = `# a
|
||||
{
|
||||
"a": {
|
||||
"findcall": [
|
||||
{
|
||||
"posn": "$GOPATH/src/a/a.go:4:11",
|
||||
"message": "call of MyFunc123(...)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
for _, test := range []struct {
|
||||
|
@ -102,16 +78,18 @@ func MyFunc123() {}
|
|||
wantOut string
|
||||
wantExit int
|
||||
}{
|
||||
{args: "golang.org/fake/a", wantOut: wantA, wantExit: 2},
|
||||
{args: "golang.org/fake/b", wantOut: wantB, wantExit: 2},
|
||||
{args: "golang.org/fake/a golang.org/fake/b", wantOut: wantA + wantB, wantExit: 2},
|
||||
{args: "-json golang.org/fake/a", wantOut: wantAJSON, wantExit: 0},
|
||||
{args: "-c=0 golang.org/fake/a", wantOut: wantA + "4 MyFunc123\\(\\)\n", wantExit: 2},
|
||||
{args: "a", wantOut: wantA, wantExit: 2},
|
||||
{args: "b", wantOut: wantB, wantExit: 2},
|
||||
{args: "a b", wantOut: wantA + wantB, wantExit: 2},
|
||||
{args: "-json a", wantOut: wantAJSON, wantExit: 0},
|
||||
{args: "-c=0 a", wantOut: wantA + "4 MyFunc123()\n", wantExit: 2},
|
||||
} {
|
||||
cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "-findcall.name=MyFunc123")
|
||||
cmd.Args = append(cmd.Args, strings.Fields(test.args)...)
|
||||
cmd.Env = append(exported.Config.Env, "UNITCHECKER_CHILD=1")
|
||||
cmd.Dir = exported.Config.Dir
|
||||
cmd.Env = append(os.Environ(),
|
||||
"UNITCHECKER_CHILD=1",
|
||||
"GOPATH="+testdata,
|
||||
)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
exitcode := 0
|
||||
|
@ -121,13 +99,10 @@ func MyFunc123() {}
|
|||
if exitcode != test.wantExit {
|
||||
t.Errorf("%s: got exit code %d, want %d", test.args, exitcode, test.wantExit)
|
||||
}
|
||||
got := strings.Replace(string(out), testdata, "$GOPATH", -1)
|
||||
|
||||
matched, err := regexp.Match(test.wantOut, out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !matched {
|
||||
t.Errorf("%s: got <<%s>>, want match of regexp <<%s>>", test.args, out, test.wantOut)
|
||||
if got != test.wantOut {
|
||||
t.Errorf("%s: got <<%s>>, want <<%s>>", test.args, got, test.wantOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
)
|
||||
|
||||
func TestAllPackages(t *testing.T) {
|
||||
|
@ -24,24 +23,7 @@ func TestAllPackages(t *testing.T) {
|
|||
t.Skip("gccgo has no standard packages")
|
||||
}
|
||||
|
||||
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{
|
||||
{Name: "golang.org/x/tools/go/buildutil", Files: packagestest.MustCopyFileTree(".")}})
|
||||
defer exported.Cleanup()
|
||||
|
||||
var gopath string
|
||||
for _, env := range exported.Config.Env {
|
||||
if !strings.HasPrefix(env, "GOPATH=") {
|
||||
continue
|
||||
}
|
||||
gopath = strings.TrimPrefix(env, "GOPATH=")
|
||||
}
|
||||
if gopath == "" {
|
||||
t.Fatal("Failed to fish GOPATH out of env: ", exported.Config.Env)
|
||||
}
|
||||
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = gopath
|
||||
all := buildutil.AllPackages(&buildContext)
|
||||
all := buildutil.AllPackages(&build.Default)
|
||||
|
||||
set := make(map[string]bool)
|
||||
for _, pkg := range all {
|
||||
|
|
|
@ -8,13 +8,10 @@ import (
|
|||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
)
|
||||
|
||||
func TestContainingPackage(t *testing.T) {
|
||||
|
@ -22,22 +19,9 @@ func TestContainingPackage(t *testing.T) {
|
|||
t.Skip("gccgo has no GOROOT")
|
||||
}
|
||||
|
||||
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{
|
||||
{Name: "golang.org/x/tools/go/buildutil", Files: packagestest.MustCopyFileTree(".")}})
|
||||
defer exported.Cleanup()
|
||||
|
||||
// unvirtualized:
|
||||
goroot := runtime.GOROOT()
|
||||
var gopath string
|
||||
for _, env := range exported.Config.Env {
|
||||
if !strings.HasPrefix(env, "GOPATH=") {
|
||||
continue
|
||||
}
|
||||
gopath = strings.TrimPrefix(env, "GOPATH=")
|
||||
}
|
||||
if gopath == "" {
|
||||
t.Fatal("Failed to fish GOPATH out of env: ", exported.Config.Env)
|
||||
}
|
||||
buildutildir := filepath.Join(gopath, "golang.org", "x", "tools", "go", "buildutil")
|
||||
gopath := gopathContainingTools(t)
|
||||
|
||||
type Test struct {
|
||||
gopath, filename, wantPkg string
|
||||
|
@ -76,7 +60,7 @@ func TestContainingPackage(t *testing.T) {
|
|||
var got string
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = test.gopath
|
||||
bp, err := buildutil.ContainingPackage(&buildContext, buildutildir, test.filename)
|
||||
bp, err := buildutil.ContainingPackage(&buildContext, ".", test.filename)
|
||||
if err != nil {
|
||||
got = "(not found)"
|
||||
} else {
|
||||
|
@ -87,4 +71,15 @@ func TestContainingPackage(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): test on virtualized GOPATH too.
|
||||
}
|
||||
|
||||
// gopathContainingTools returns the path of the GOPATH workspace
|
||||
// with golang.org/x/tools, or fails the test if it can't locate it.
|
||||
func gopathContainingTools(t *testing.T) string {
|
||||
p, err := build.Import("golang.org/x/tools", "", build.FindOnly)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p.Root
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ func (b *builder) branchStmt(s *ast.BranchStmt) {
|
|||
}
|
||||
|
||||
case token.FALLTHROUGH:
|
||||
for t := b.targets; t != nil && block == nil; t = t.tail {
|
||||
for t := b.targets; t != nil; t = t.tail {
|
||||
block = t._fallthrough
|
||||
}
|
||||
|
||||
|
|
|
@ -51,12 +51,7 @@ func ExampleRead() {
|
|||
}
|
||||
|
||||
// Print package information.
|
||||
members := pkg.Scope().Names()
|
||||
if members[0] == ".inittask" {
|
||||
// An improvement to init handling in 1.13 added ".inittask". Remove so go >= 1.13 and go < 1.13 both pass.
|
||||
members = members[1:]
|
||||
}
|
||||
fmt.Printf("Package members: %s...\n", members[:5])
|
||||
fmt.Printf("Package members: %s...\n", pkg.Scope().Names()[:5])
|
||||
println := pkg.Scope().Lookup("Println")
|
||||
posn := fset.Position(println.Pos())
|
||||
posn.Line = 123 // make example deterministic
|
||||
|
@ -75,15 +70,15 @@ func ExampleRead() {
|
|||
// ExampleNewImporter demonstrates usage of NewImporter to provide type
|
||||
// information for dependencies when type-checking Go source code.
|
||||
func ExampleNewImporter() {
|
||||
const src = `package myrpc
|
||||
const src = `package myscanner
|
||||
|
||||
// choosing a package that doesn't change across releases
|
||||
import "net/rpc"
|
||||
// choosing a package that is unlikely to change across releases
|
||||
import "text/scanner"
|
||||
|
||||
const serverError rpc.ServerError = ""
|
||||
const eof = scanner.EOF
|
||||
`
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "myrpc.go", src, 0)
|
||||
f, err := parser.ParseFile(fset, "myscanner.go", src, 0)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -91,22 +86,23 @@ const serverError rpc.ServerError = ""
|
|||
packages := make(map[string]*types.Package)
|
||||
imp := gcexportdata.NewImporter(fset, packages)
|
||||
conf := types.Config{Importer: imp}
|
||||
pkg, err := conf.Check("myrpc", fset, []*ast.File{f}, nil)
|
||||
pkg, err := conf.Check("myscanner", fset, []*ast.File{f}, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// object from imported package
|
||||
pi := packages["net/rpc"].Scope().Lookup("ServerError")
|
||||
fmt.Printf("type %s.%s %s // %s\n",
|
||||
pi := packages["text/scanner"].Scope().Lookup("EOF")
|
||||
fmt.Printf("const %s.%s %s = %s // %s\n",
|
||||
pi.Pkg().Path(),
|
||||
pi.Name(),
|
||||
pi.Type().Underlying(),
|
||||
pi.Type(),
|
||||
pi.(*types.Const).Val(),
|
||||
slashify(fset.Position(pi.Pos())),
|
||||
)
|
||||
|
||||
// object in source package
|
||||
twopi := pkg.Scope().Lookup("serverError")
|
||||
twopi := pkg.Scope().Lookup("eof")
|
||||
fmt.Printf("const %s %s = %s // %s\n",
|
||||
twopi.Name(),
|
||||
twopi.Type(),
|
||||
|
@ -116,8 +112,8 @@ const serverError rpc.ServerError = ""
|
|||
|
||||
// Output:
|
||||
//
|
||||
// type net/rpc.ServerError string // $GOROOT/src/net/rpc/client.go:20:1
|
||||
// const serverError net/rpc.ServerError = "" // myrpc.go:6:7
|
||||
// const text/scanner.EOF untyped int = -1 // $GOROOT/src/text/scanner/scanner.go:75:1
|
||||
// const eof untyped int = -1 // myscanner.go:6:7
|
||||
}
|
||||
|
||||
func slashify(posn token.Position) token.Position {
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
type importerTest struct {
|
||||
pkgpath, name, want, wantval string
|
||||
wantinits []string
|
||||
gccgoVersion int // minimum gccgo version (0 => any)
|
||||
}
|
||||
|
||||
func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) {
|
||||
|
@ -75,8 +74,6 @@ func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]Init
|
|||
}
|
||||
}
|
||||
|
||||
// When adding tests to this list, be sure to set the 'gccgoVersion'
|
||||
// field if the testcases uses a "recent" Go addition (ex: aliases).
|
||||
var importerTests = [...]importerTest{
|
||||
{pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"},
|
||||
{pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1 + -1i)"},
|
||||
|
@ -90,15 +87,13 @@ var importerTests = [...]importerTest{
|
|||
{pkgpath: "unicode", name: "MaxRune", want: "const MaxRune untyped rune", wantval: "1114111"},
|
||||
{pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import"}},
|
||||
{pkgpath: "importsar", name: "Hello", want: "var Hello string"},
|
||||
{pkgpath: "aliases", name: "A14", gccgoVersion: 7, want: "type A14 = func(int, T0) chan T2"},
|
||||
{pkgpath: "aliases", name: "C0", gccgoVersion: 7, want: "type C0 struct{f1 C1; f2 C1}"},
|
||||
{pkgpath: "aliases", name: "A14", want: "type A14 = func(int, T0) chan T2"},
|
||||
{pkgpath: "aliases", name: "C0", want: "type C0 struct{f1 C1; f2 C1}"},
|
||||
{pkgpath: "escapeinfo", name: "NewT", want: "func NewT(data []byte) *T"},
|
||||
{pkgpath: "issue27856", name: "M", gccgoVersion: 7, want: "type M struct{E F}"},
|
||||
{pkgpath: "issue27856", name: "M", want: "type M struct{E F}"},
|
||||
{pkgpath: "v1reflect", name: "Type", want: "type Type interface{Align() int; AssignableTo(u Type) bool; Bits() int; ChanDir() ChanDir; Elem() Type; Field(i int) StructField; FieldAlign() int; FieldByIndex(index []int) StructField; FieldByName(name string) (StructField, bool); FieldByNameFunc(match func(string) bool) (StructField, bool); Implements(u Type) bool; In(i int) Type; IsVariadic() bool; Key() Type; Kind() Kind; Len() int; Method(int) Method; MethodByName(string) (Method, bool); Name() string; NumField() int; NumIn() int; NumMethod() int; NumOut() int; Out(i int) Type; PkgPath() string; Size() uintptr; String() string; common() *commonType; rawString() string; runtimeType() *runtimeType; uncommon() *uncommonType}"},
|
||||
{pkgpath: "nointerface", name: "I", want: "type I int"},
|
||||
{pkgpath: "issue29198", name: "FooServer", gccgoVersion: 7, want: "type FooServer struct{FooServer *FooServer; user string; ctx context.Context}"},
|
||||
{pkgpath: "issue30628", name: "Apple", want: "type Apple struct{hey sync.RWMutex; x int; RQ [517]struct{Count uintptr; NumBytes uintptr; Last uintptr}}"},
|
||||
{pkgpath: "issue31540", name: "S", gccgoVersion: 7, want: "type S struct{b int; map[Y]Z}"},
|
||||
{pkgpath: "issue29198", name: "FooServer", want: "type FooServer struct{FooServer *FooServer; user string; ctx context.Context}"},
|
||||
}
|
||||
|
||||
func TestGoxImporter(t *testing.T) {
|
||||
|
@ -151,29 +146,27 @@ func TestObjImporter(t *testing.T) {
|
|||
}
|
||||
t.Logf("gccgo version %d.%d", major, minor)
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "TestObjImporter")
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
initmap := make(map[*types.Package]InitData)
|
||||
imp := GetImporter([]string{tmpdir}, initmap)
|
||||
|
||||
artmpdir, err := ioutil.TempDir("", "TestObjImporter")
|
||||
artmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(artmpdir)
|
||||
|
||||
arinitmap := make(map[*types.Package]InitData)
|
||||
arimp := GetImporter([]string{artmpdir}, arinitmap)
|
||||
|
||||
for _, test := range importerTests {
|
||||
if major < test.gccgoVersion {
|
||||
// Support for type aliases was added in GCC 7.
|
||||
t.Logf("skipping %q: not supported before gccgo version %d", test.pkgpath, test.gccgoVersion)
|
||||
continue
|
||||
// Support for type aliases was added in GCC 7.
|
||||
if test.pkgpath == "aliases" || test.pkgpath == "issue27856" || test.pkgpath == "issue29198" {
|
||||
if major < 7 {
|
||||
t.Logf("skipping %q: not supported before gccgo version 7", test.pkgpath)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
gofile := filepath.Join("testdata", test.pkgpath+".go")
|
||||
|
@ -208,4 +201,8 @@ func TestObjImporter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = os.Remove(tmpdir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ type parser struct {
|
|||
typeData []string // unparsed type data (v3 and later)
|
||||
fixups []fixupRecord // fixups to apply at end of parsing
|
||||
initdata InitData // package init priority data
|
||||
aliases map[int]string // maps saved type number to alias name
|
||||
}
|
||||
|
||||
// When reading export data it's possible to encounter a defined type
|
||||
|
@ -62,7 +61,6 @@ func (p *parser) init(filename string, src io.Reader, imports map[string]*types.
|
|||
p.scanner = new(scanner.Scanner)
|
||||
p.initScanner(filename, src)
|
||||
p.imports = imports
|
||||
p.aliases = make(map[int]string)
|
||||
p.typeList = make([]types.Type, 1 /* type numbers start at 1 */, 16)
|
||||
}
|
||||
|
||||
|
@ -244,22 +242,17 @@ func deref(typ types.Type) types.Type {
|
|||
// Field = Name Type [string] .
|
||||
func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) {
|
||||
name := p.parseName()
|
||||
typ, n := p.parseTypeExtended(pkg)
|
||||
typ := p.parseType(pkg)
|
||||
anon := false
|
||||
if name == "" {
|
||||
anon = true
|
||||
// Alias?
|
||||
if aname, ok := p.aliases[n]; ok {
|
||||
name = aname
|
||||
} else {
|
||||
switch typ := deref(typ).(type) {
|
||||
case *types.Basic:
|
||||
name = typ.Name()
|
||||
case *types.Named:
|
||||
name = typ.Obj().Name()
|
||||
default:
|
||||
p.error("anonymous field expected")
|
||||
}
|
||||
switch typ := deref(typ).(type) {
|
||||
case *types.Basic:
|
||||
name = typ.Name()
|
||||
case *types.Named:
|
||||
name = typ.Obj().Name()
|
||||
default:
|
||||
p.error("anonymous field expected")
|
||||
}
|
||||
}
|
||||
field = types.NewField(token.NoPos, pkg, name, typ, anon)
|
||||
|
@ -272,10 +265,6 @@ func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) {
|
|||
// Param = Name ["..."] Type .
|
||||
func (p *parser) parseParam(pkg *types.Package) (param *types.Var, isVariadic bool) {
|
||||
name := p.parseName()
|
||||
// Ignore names invented for inlinable functions.
|
||||
if strings.HasPrefix(name, "p.") || strings.HasPrefix(name, "r.") || strings.HasPrefix(name, "$ret") {
|
||||
name = ""
|
||||
}
|
||||
if p.tok == '<' && p.scanner.Peek() == 'e' {
|
||||
// EscInfo = "<esc:" int ">" . (optional and ignored)
|
||||
p.next()
|
||||
|
@ -301,14 +290,7 @@ func (p *parser) parseParam(pkg *types.Package) (param *types.Var, isVariadic bo
|
|||
// Var = Name Type .
|
||||
func (p *parser) parseVar(pkg *types.Package) *types.Var {
|
||||
name := p.parseName()
|
||||
v := types.NewVar(token.NoPos, pkg, name, p.parseType(pkg))
|
||||
if name[0] == '.' || name[0] == '<' {
|
||||
// This is an unexported variable,
|
||||
// or a variable defined in a different package.
|
||||
// We only want to record exported variables.
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
return types.NewVar(token.NoPos, pkg, name, p.parseType(pkg))
|
||||
}
|
||||
|
||||
// Conversion = "convert" "(" Type "," ConstValue ")" .
|
||||
|
@ -508,7 +490,6 @@ func (p *parser) parseNamedType(nlist []int) types.Type {
|
|||
// type alias
|
||||
if p.tok == '=' {
|
||||
p.next()
|
||||
p.aliases[nlist[len(nlist)-1]] = name
|
||||
if obj != nil {
|
||||
// use the previously imported (canonical) type
|
||||
t := obj.Type()
|
||||
|
@ -562,12 +543,10 @@ func (p *parser) parseNamedType(nlist []int) types.Type {
|
|||
for p.tok == scanner.Ident {
|
||||
p.expectKeyword("func")
|
||||
if p.tok == '/' {
|
||||
// Skip a /*nointerface*/ or /*asm ID */ comment.
|
||||
// Skip a /*nointerface*/ comment.
|
||||
p.expect('/')
|
||||
p.expect('*')
|
||||
if p.expect(scanner.Ident) == "asm" {
|
||||
p.parseUnquotedString()
|
||||
}
|
||||
p.expect(scanner.Ident)
|
||||
p.expect('*')
|
||||
p.expect('/')
|
||||
}
|
||||
|
@ -727,8 +706,7 @@ func (p *parser) parseResultList(pkg *types.Package) *types.Tuple {
|
|||
if p.tok == scanner.Ident && p.lit == "inl" {
|
||||
return nil
|
||||
}
|
||||
taa, _ := p.parseTypeAfterAngle(pkg)
|
||||
return types.NewTuple(types.NewParam(token.NoPos, pkg, "", taa))
|
||||
return types.NewTuple(types.NewParam(token.NoPos, pkg, "", p.parseTypeAfterAngle(pkg)))
|
||||
|
||||
case '(':
|
||||
params, _ := p.parseParamList(pkg)
|
||||
|
@ -753,29 +731,15 @@ func (p *parser) parseFunctionType(pkg *types.Package, nlist []int) *types.Signa
|
|||
|
||||
// Func = Name FunctionType [InlineBody] .
|
||||
func (p *parser) parseFunc(pkg *types.Package) *types.Func {
|
||||
if p.tok == '/' {
|
||||
// Skip an /*asm ID */ comment.
|
||||
p.expect('/')
|
||||
p.expect('*')
|
||||
if p.expect(scanner.Ident) == "asm" {
|
||||
p.parseUnquotedString()
|
||||
}
|
||||
p.expect('*')
|
||||
p.expect('/')
|
||||
}
|
||||
|
||||
name := p.parseName()
|
||||
f := types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg, nil))
|
||||
p.skipInlineBody()
|
||||
|
||||
if name[0] == '.' || name[0] == '<' || strings.ContainsRune(name, '$') {
|
||||
// This is an unexported function,
|
||||
// or a function defined in a different package,
|
||||
// or a type$equal or type$hash function.
|
||||
// We only want to record exported functions.
|
||||
if strings.ContainsRune(name, '$') {
|
||||
// This is a Type$equal or Type$hash function, which we don't want to parse,
|
||||
// except for the types.
|
||||
p.discardDirectiveWhileParsingTypes(pkg)
|
||||
return nil
|
||||
}
|
||||
|
||||
f := types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg, nil))
|
||||
p.skipInlineBody()
|
||||
return f
|
||||
}
|
||||
|
||||
|
@ -796,9 +760,7 @@ func (p *parser) parseInterfaceType(pkg *types.Package, nlist []int) types.Type
|
|||
embeddeds = append(embeddeds, p.parseType(pkg))
|
||||
} else {
|
||||
method := p.parseFunc(pkg)
|
||||
if method != nil {
|
||||
methods = append(methods, method)
|
||||
}
|
||||
methods = append(methods, method)
|
||||
}
|
||||
p.expect(';')
|
||||
}
|
||||
|
@ -918,18 +880,16 @@ func lookupBuiltinType(typ int) types.Type {
|
|||
//
|
||||
func (p *parser) parseType(pkg *types.Package, n ...int) types.Type {
|
||||
p.expect('<')
|
||||
t, _ := p.parseTypeAfterAngle(pkg, n...)
|
||||
return t
|
||||
return p.parseTypeAfterAngle(pkg, n...)
|
||||
}
|
||||
|
||||
// (*parser).Type after reading the "<".
|
||||
func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...int) (t types.Type, n1 int) {
|
||||
func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...int) (t types.Type) {
|
||||
p.expectKeyword("type")
|
||||
|
||||
n1 = 0
|
||||
switch p.tok {
|
||||
case scanner.Int:
|
||||
n1 = p.parseInt()
|
||||
n1 := p.parseInt()
|
||||
if p.tok == '>' {
|
||||
if len(p.typeData) > 0 && p.typeList[n1] == nil {
|
||||
p.parseSavedType(pkg, n1, n)
|
||||
|
@ -952,7 +912,7 @@ func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...int) (t types.Type
|
|||
|
||||
default:
|
||||
p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit)
|
||||
return nil, 0
|
||||
return nil
|
||||
}
|
||||
|
||||
if t == nil || t == reserved {
|
||||
|
@ -963,15 +923,6 @@ func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...int) (t types.Type
|
|||
return
|
||||
}
|
||||
|
||||
// parseTypeExtended is identical to parseType, but if the type in
|
||||
// question is a saved type, returns the index as well as the type
|
||||
// pointer (index returned is zero if we parsed a builtin).
|
||||
func (p *parser) parseTypeExtended(pkg *types.Package, n ...int) (t types.Type, n1 int) {
|
||||
p.expect('<')
|
||||
t, n1 = p.parseTypeAfterAngle(pkg, n...)
|
||||
return
|
||||
}
|
||||
|
||||
// InlineBody = "<inl:NN>" .{NN}
|
||||
// Reports whether a body was skipped.
|
||||
func (p *parser) skipInlineBody() {
|
||||
|
@ -1090,6 +1041,22 @@ func (p *parser) parsePackageInit() PackageInit {
|
|||
return PackageInit{Name: name, InitFunc: initfunc, Priority: priority}
|
||||
}
|
||||
|
||||
// Throw away tokens until we see a ';'. If we see a '<', attempt to parse as a type.
|
||||
func (p *parser) discardDirectiveWhileParsingTypes(pkg *types.Package) {
|
||||
for {
|
||||
switch p.tok {
|
||||
case '\n', ';':
|
||||
return
|
||||
case '<':
|
||||
p.parseType(pkg)
|
||||
case scanner.EOF:
|
||||
p.error("unexpected EOF")
|
||||
default:
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the package if we have parsed both the package path and package name.
|
||||
func (p *parser) maybeCreatePackage() {
|
||||
if p.pkgname != "" && p.pkgpath != "" {
|
||||
|
@ -1227,9 +1194,7 @@ func (p *parser) parseDirective() {
|
|||
case "var":
|
||||
p.next()
|
||||
v := p.parseVar(p.pkg)
|
||||
if v != nil {
|
||||
p.pkg.Scope().Insert(v)
|
||||
}
|
||||
p.pkg.Scope().Insert(v)
|
||||
p.expectEOL()
|
||||
|
||||
case "const":
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package issue30628
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const numR = int32(os.O_TRUNC + 5)
|
||||
|
||||
type Apple struct {
|
||||
hey sync.RWMutex
|
||||
x int
|
||||
RQ [numR]struct {
|
||||
Count uintptr
|
||||
NumBytes uintptr
|
||||
Last uintptr
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
v3;
|
||||
package issue30628
|
||||
pkgpath issue30628
|
||||
import os os "os"
|
||||
import sync sync "sync"
|
||||
init cpu internal..z2fcpu..import poll internal..z2fpoll..import testlog internal..z2ftestlog..import io io..import os os..import runtime runtime..import sys runtime..z2finternal..z2fsys..import sync sync..import syscall syscall..import time time..import
|
||||
init_graph 1 0 1 3 1 5 1 6 1 7 1 8 1 9 3 0 3 5 3 6 3 7 4 0 4 1 4 2 4 3 4 5 4 6 4 7 4 8 4 9 5 0 5 6 7 0 7 5 7 6 8 0 8 5 8 6 8 7 9 0 9 5 9 6 9 7 9 8
|
||||
types 13 2 24 84 208 17 30 41 147 86 17 64 25 75
|
||||
type 1 "Apple" <type 2>
|
||||
type 2 struct { .issue30628.hey <type 3>; .issue30628.x <type -11>; RQ <type 11>; }
|
||||
type 3 "sync.RWMutex" <type 7>
|
||||
func (rw <type 4>) Lock ()
|
||||
func (rw <esc:0x12> <type 4>) RLocker () ($ret8 <type 5>)
|
||||
func (rw <type 4>) RUnlock ()
|
||||
func (rw <type 4>) Unlock ()
|
||||
func (rw <type 4>) RLock ()
|
||||
type 4 *<type 3>
|
||||
type 5 "sync.Locker" <type 6>
|
||||
type 6 interface { Lock (); Unlock (); }
|
||||
type 7 struct { .sync.w <type 8>; .sync.writerSem <type -7>; .sync.readerSem <type -7>; .sync.readerCount <type -3>; .sync.readerWait <type -3>; }
|
||||
type 8 "sync.Mutex" <type 10>
|
||||
func (m <type 9>) Unlock ()
|
||||
func (m <type 9>) Lock ()
|
||||
type 9 *<type 8>
|
||||
type 10 struct { .sync.state <type -3>; .sync.sema <type -7>; }
|
||||
type 11 [517 ] <type 12>
|
||||
type 12 struct { Count <type -13>; NumBytes <type -13>; Last <type -13>; }
|
||||
checksum 199DCF6D3EE2FCF39F715B4E42B5F87F5B15D3AF
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright 2019 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 issue31540
|
||||
|
||||
type Y struct {
|
||||
q int
|
||||
}
|
||||
|
||||
type Z map[int]int
|
||||
|
||||
type X = map[Y]Z
|
||||
|
||||
type A1 = X
|
||||
|
||||
type A2 = A1
|
||||
|
||||
type S struct {
|
||||
b int
|
||||
A2
|
||||
}
|
||||
|
||||
func Hallo() S {
|
||||
return S{}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue