Compare commits

..

3 Commits

Author SHA1 Message Date
Daniel Martí aa82965741 [release-branch.go1.12] go/analysis: disable embedded struct tag check
This disables the enhancement added in golang.org/cl/115677. The
original change was done in the old cmd/vet location, so it would be
non-trivial to port a full revert of all the code changes. Simply
skipping anonymous struct fields is a simpler way to undo the effects of
the CL.

The reason we want to disable this enhancement of the check in the Go
1.12 release branch is because a false positive was uncovered, which we
want fixed for Go 1.12.1. It's possible that the check will instead be
tweaked for 1.13, but for 1.12.1 we want to play it safe.

Updates golang/go#30465.

Change-Id: I379b4547a560723b8023dad45ab26556b10ee308
Reviewed-on: https://go-review.googlesource.com/c/tools/+/164659
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-03-13 21:06:03 +00:00
Daniel Martí 86c5873b48 [release-branch.go1.12] go/analysis/passes/printf: fix big.Int false positive
It's possible to use a type which implements fmt.Formatter without
importing fmt directly, if the type is imported from another package
such as math/big.

On top of that, it's possible to use printf-like functions without
importing fmt directly, such as using testing.T.Logf.

These two scenarios combined can lead to the printf check not finding
the fmt.Formatter type, since it's not a direct dependency of the root
package.

fmt must still be in the import graph somewhere, so we could search for
it via types.Package.Imports. However, at that point it's simpler to
just look for the Format method manually via go/types.

Fixes #30399.

Change-Id: Id78454bb6a51b3c5e1bcb1984a7fbfb4a29a5be0
Reviewed-on: https://go-review.googlesource.com/c/163817
Run-TryBot: Daniel Martí <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
(cherry picked from commit 589c23e65e)
Reviewed-on: https://go-review.googlesource.com/c/tools/+/164657
Run-TryBot: Alan Donovan <adonovan@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-03-13 20:59:20 +00:00
Rhys Hiltner 49d818b077 [release-branch.go1.12] cmd/godoc: fix -url flag, add tests
This change adds a small number of integration tests for the godoc
command's -url flag, confirming that the behavior matches the local http
server tests in those cases. It fixes three bugs which prevent the -url
flag from working currently.

Fixes golang/go#30314

Change-Id: I0ca1fe81f9f186d0ca02b31674cc8654af434e92
Reviewed-on: https://go-review.googlesource.com/c/162907
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
(cherry picked from commit bb2d4193192d16d7b4f740befc0ddd495bf6f86b)
Reviewed-on: https://go-review.googlesource.com/c/162985
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
2019-02-19 17:54:48 +00:00
458 changed files with 74617 additions and 36595 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -22,21 +22,12 @@
// -compileflags 'list' // -compileflags 'list'
// Pass the space-separated list of flags to the compilation. // 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 // -count n
// Run each benchmark n times (default 1). // Run each benchmark n times (default 1).
// //
// -cpuprofile file // -cpuprofile file
// Write a CPU profile of the compiler to file. // Write a CPU profile of the compiler to file.
// //
// -go path
// Path to "go" command (default "go").
//
// -memprofile file // -memprofile file
// Write a memory profile of the compiler to file. // Write a memory profile of the compiler to file.
// //
@ -46,15 +37,12 @@
// -obj // -obj
// Report object file statistics. // Report object file statistics.
// //
// -pkg pkg // -pkg
// Benchmark compiling a single package. // Benchmark compiling a single package.
// //
// -run regexp // -run regexp
// Only run benchmarks with names matching regexp. // Only run benchmarks with names matching regexp.
// //
// -short
// Skip long-running benchmarks.
//
// Although -cpuprofile and -memprofile are intended to write a // Although -cpuprofile and -memprofile are intended to write a
// combined profile for all the executed benchmarks to file, // combined profile for all the executed benchmarks to file,
// today they write only the profile for the last benchmark executed. // today they write only the profile for the last benchmark executed.
@ -96,7 +84,6 @@ import (
var ( var (
goroot string goroot string
compiler string compiler string
linker string
runRE *regexp.Regexp runRE *regexp.Regexp
is6g bool is6g bool
) )
@ -107,8 +94,6 @@ var (
flagObj = flag.Bool("obj", false, "report object file stats") flagObj = flag.Bool("obj", false, "report object file stats")
flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary") flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary")
flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile") 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`") flagRun = flag.String("run", "", "run benchmarks matching `regexp`")
flagCount = flag.Int("count", 1, "run benchmarks `n` times") flagCount = flag.Int("count", 1, "run benchmarks `n` times")
flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`") flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`")
@ -118,31 +103,24 @@ var (
flagShort = flag.Bool("short", false, "skip long-running benchmarks") flagShort = flag.Bool("short", false, "skip long-running benchmarks")
) )
type test struct { var tests = []struct {
name string name string
r runner dir string
} long bool
}{
type runner interface { {"BenchmarkTemplate", "html/template", false},
long() bool {"BenchmarkUnicode", "unicode", false},
run(name string, count int) error {"BenchmarkGoTypes", "go/types", false},
} {"BenchmarkCompiler", "cmd/compile/internal/gc", false},
{"BenchmarkSSA", "cmd/compile/internal/ssa", false},
var tests = []test{ {"BenchmarkFlate", "compress/flate", false},
{"BenchmarkTemplate", compile{"html/template"}}, {"BenchmarkGoParser", "go/parser", false},
{"BenchmarkUnicode", compile{"unicode"}}, {"BenchmarkReflect", "reflect", false},
{"BenchmarkGoTypes", compile{"go/types"}}, {"BenchmarkTar", "archive/tar", false},
{"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}}, {"BenchmarkXML", "encoding/xml", false},
{"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}}, {"BenchmarkStdCmd", "", true},
{"BenchmarkFlate", compile{"compress/flate"}}, {"BenchmarkHelloSize", "", false},
{"BenchmarkGoParser", compile{"go/parser"}}, {"BenchmarkCmdGoSize", "", true},
{"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}},
} }
func usage() { func usage() {
@ -170,23 +148,16 @@ func main() {
compiler = *flagCompiler compiler = *flagCompiler
if compiler == "" { if compiler == "" {
var foundTool string out, err := exec.Command(*flagGoCmd, "tool", "-n", "compile").CombinedOutput()
foundTool, compiler = toolPath("compile", "6g") if err != nil {
if foundTool == "6g" { out, err = exec.Command(*flagGoCmd, "tool", "-n", "6g").CombinedOutput()
is6g = true 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)
} }
} }
compiler = strings.TrimSpace(string(out))
linker = *flagLinker
if linker == "" && !is6g { // TODO: Support 6l
_, linker = toolPath("link")
}
if is6g {
*flagMemprofilerate = -1
*flagAlloc = false
*flagCpuprofile = ""
*flagMemprofile = ""
} }
if *flagRun != "" { if *flagRun != "" {
@ -197,117 +168,67 @@ func main() {
runRE = r runRE = r
} }
if *flagPackage != "" {
tests = []test{
{"BenchmarkPkg", compile{*flagPackage}},
{"BenchmarkPkgLink", link{*flagPackage}},
}
runRE = nil
}
for i := 0; i < *flagCount; i++ { for i := 0; i < *flagCount; i++ {
if *flagPackage != "" {
runBuild("BenchmarkPkg", *flagPackage, i)
continue
}
for _, tt := range tests { for _, tt := range tests {
if tt.r.long() && *flagShort { if tt.long && *flagShort {
continue continue
} }
if runRE == nil || runRE.MatchString(tt.name) { if runRE == nil || runRE.MatchString(tt.name) {
if err := tt.r.run(tt.name, i); err != nil { runBuild(tt.name, tt.dir, i)
log.Printf("%s: %v", tt.name, err)
}
} }
} }
} }
} }
func toolPath(names ...string) (found, path string) { func runCmd(name string, cmd *exec.Cmd) {
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 {
start := time.Now() start := time.Now()
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { 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()) fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
return nil
} }
type goBuild struct{ pkgs []string } func runStdCmd() {
func (goBuild) long() bool { return true }
func (r goBuild) run(name string, count int) error {
args := []string{"build", "-a"} args := []string{"build", "-a"}
if *flagCompilerFlags != "" { if *flagCompilerFlags != "" {
args = append(args, "-gcflags", *flagCompilerFlags) args = append(args, "-gcflags", *flagCompilerFlags)
} }
args = append(args, r.pkgs...) args = append(args, "std", "cmd")
cmd := exec.Command(*flagGoCmd, args...) cmd := exec.Command(*flagGoCmd, args...)
cmd.Dir = filepath.Join(goroot, "src") 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 is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
path string func runSize(name, path string) {
isLong bool cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", path)
}
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)
cmd.Stdout = os.Stderr cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return err log.Print(err)
return
} }
defer os.Remove("_compilebenchout_") defer os.Remove("_compilebenchout_")
info, err := os.Stat("_compilebenchout_") info, err := os.Stat("_compilebenchout_")
if err != nil { if err != nil {
return err log.Print(err)
return
} }
out, err := exec.Command("size", "_compilebenchout_").CombinedOutput() out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
if err != nil { 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") lines := strings.Split(string(out), "\n")
if len(lines) < 2 { 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]) f := strings.Fields(lines[1])
if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X 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 { } 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()) 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. // 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 { 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. // 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 { 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"} 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)...) 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...) args = append(args, pkg.GoFiles...)
if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil { cmd := exec.Command(compiler, args...)
return err 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" opath := pkg.Dir + "/_compilebench_.o"
@ -358,147 +375,4 @@ func (c compile) run(name string, count int) error {
fmt.Println() fmt.Println()
os.Remove(opath) 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
} }

View File

@ -1,83 +1,18 @@
// Copyright 2019 The Go Authors. All rights reserved. // The digraph command performs queries over unlabelled directed graphs
// Use of this source code is governed by a BSD-style // represented in text form. It is intended to integrate nicely with
// license that can be found in the LICENSE file. // typical UNIX command pipelines.
//
/* // Since directed graphs (import graphs, reference graphs, call graphs,
The digraph command performs queries over unlabelled directed graphs // etc) often arise during software tool development and debugging, this
represented in text form. It is intended to integrate nicely with // command is included in the go.tools repository.
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"
// TODO(adonovan): // TODO(adonovan):
// - support input files other than stdin // - support input files other than stdin
// - support alternative formats (AT&T GraphViz, CSV, etc), // - support alternative formats (AT&T GraphViz, CSV, etc),
// a comment syntax, etc. // a comment syntax, etc.
// - allow queries to nest, like Blaze query language. // - allow queries to nest, like Blaze query language.
//
package main // import "golang.org/x/tools/cmd/digraph"
import ( import (
"bufio" "bufio"
@ -93,41 +28,74 @@ import (
"unicode/utf8" "unicode/utf8"
) )
func usage() { const Usage = `digraph: queries over directed graphs in text form.
fmt.Fprintf(os.Stderr, `Usage: your-application | digraph [command]
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:
The support commands are:
nodes nodes
the set of all nodes the set of all nodes
degree degree
the in-degree and out-degree of each node the in-degree and out-degree of each node.
preds <node> ... preds <label> ...
the set of immediate predecessors of the specified nodes the set of immediate predecessors of the specified nodes
succs <node> ... succs <label> ...
the set of immediate successors of the specified nodes the set of immediate successors of the specified nodes
forward <node> ... forward <label> ...
the set of nodes transitively reachable from the specified nodes the set of nodes transitively reachable from the specified nodes
reverse <node> ... reverse <label> ...
the set of nodes that transitively reach the specified nodes the set of nodes that transitively reach the specified nodes
somepath <node> <node> somepath <label> <label>
the list of nodes on some arbitrary path from the first node to the second the list of nodes on some arbitrary path from the first node to the second
allpaths <node> <node> allpaths <label> <label>
the set of nodes on all paths from the first node to the second the set of nodes on all paths from the first node to the second
sccs sccs
all strongly connected components (one per line) all strongly connected components (one per line)
scc <node> scc <label>
the set of nodes nodes strongly connected to the specified one the set of nodes nodes strongly connected to the specified one
`)
os.Exit(2) 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() { func main() {
flag.Usage = usage flag.Usage = func() { fmt.Fprintln(os.Stderr, Usage) }
flag.Parse() flag.Parse()
args := flag.Args() args := flag.Args()
if len(args) == 0 { if len(args) == 0 {
usage() fmt.Fprintln(os.Stderr, Usage)
return
} }
if err := digraph(args[0], args[1:]); err != nil { if err := digraph(args[0], args[1:]); err != nil {
@ -262,47 +230,6 @@ func (g graph) sccs() []nodeset {
return sccs 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) { func parse(rd io.Reader) (graph, error) {
g := make(graph) g := make(graph)
@ -325,7 +252,6 @@ func parse(rd io.Reader) (graph, error) {
return g, nil return g, nil
} }
// Overridable for testing purposes.
var stdin io.Reader = os.Stdin var stdin io.Reader = os.Stdin
var stdout io.Writer = os.Stdout var stdout io.Writer = os.Stdout
@ -440,7 +366,33 @@ func digraph(cmd string, args []string) error {
if g[to] == nil { if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to) 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": case "sccs":
if len(args) != 0 { if len(args) != 0 {

View File

@ -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 package main
import ( import (
@ -29,34 +26,35 @@ d c
` `
for _, test := range []struct { for _, test := range []struct {
name string
input string input string
cmd string cmd string
args []string args []string
want string want string
}{ }{
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"}, {g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"}, {g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"}, {g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"}, {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"}, {g2, "allpaths", []string{"a", "d"}, "a\nb\nc\nd\n"},
{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
{"preds", g2, "preds", []string{"c"}, "a\nd\n"}, {g2, "sccs", nil, "a\nb\nc d\n"},
{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\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"},
} { } {
t.Run(test.name, func(t *testing.T) {
stdin = strings.NewReader(test.input) stdin = strings.NewReader(test.input)
stdout = new(bytes.Buffer) stdout = new(bytes.Buffer)
if err := digraph(test.cmd, test.args); err != nil { if err := digraph(test.cmd, test.args); err != nil {
t.Fatal(err) t.Error(err)
continue
} }
got := stdout.(fmt.Stringer).String() got := stdout.(fmt.Stringer).String()
if got != test.want { if got != test.want {
t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want) t.Errorf("digraph(%s, %s) = %q, want %q", test.cmd, test.args, got, test.want)
} }
})
} }
// TODO(adonovan): // TODO(adonovan):
@ -64,110 +62,6 @@ d c
// - test errors // - 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) { func TestSplit(t *testing.T) {
for _, test := range []struct { for _, test := range []struct {
line string line string

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 package main

3
cmd/godoc/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
index.split.*
godoc.index
godoc.zip

67
cmd/godoc/Dockerfile.prod Normal file
View File

@ -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"]

80
cmd/godoc/Makefile Normal file
View File

@ -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 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

View File

@ -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)

13
cmd/godoc/app.dev.yaml Normal file
View File

@ -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

16
cmd/godoc/app.prod.yaml Normal file
View File

@ -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

155
cmd/godoc/appinit.go Normal file
View File

@ -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
}

88
cmd/godoc/autocert.go Normal file
View File

@ -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
}

25
cmd/godoc/cloudbuild.yaml Normal file
View File

@ -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.

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !golangorg
package main package main
import "net/http" import "net/http"

View File

@ -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 all show documentation for all declarations, not just the exported ones
methods show all embedded methods, not just those of unexported anonymous fields methods show all embedded methods, not just those of unexported anonymous fields
src show the original source code rather than the extracted documentation src show the original source code rather then the extracted documentation
flat present flat (not indented) directory listings using full paths
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation For instance, https://golang.org/pkg/math/big/?m=all shows the documentation
for all (not just the exported) declarations of package big. for all (not just the exported) declarations of package big.

72
cmd/godoc/generate-index.bash Executable file
View File

@ -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"

View File

@ -32,10 +32,6 @@ func buildGodoc(t *testing.T) (bin string, cleanup func()) {
if runtime.GOARCH == "arm" { if runtime.GOARCH == "arm" {
t.Skip("skipping test on arm platforms; too slow") 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-") tmp, err := ioutil.TempDir("", "godoc-regtest-")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -21,7 +21,7 @@ import (
"text/template" "text/template"
"golang.org/x/tools/godoc" "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/redirect"
"golang.org/x/tools/godoc/vfs" "golang.org/x/tools/godoc/vfs"
) )
@ -40,7 +40,7 @@ type hostEnforcerHandler struct {
} }
func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h hostEnforcerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !golangorgenv.EnforceHosts() { if !env.EnforceHosts() {
h.h.ServeHTTP(w, r) h.h.ServeHTTP(w, r)
return return
} }

Binary file not shown.

View File

@ -15,6 +15,8 @@
// http://godoc/pkg/compress/zlib) // http://godoc/pkg/compress/zlib)
// //
// +build !golangorg
package main package main
import ( import (

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !golangorg
package main package main
// This package registers "/compile" and "/share" handlers // This package registers "/compile" and "/share" handlers

171
cmd/godoc/regtest_test.go Normal file
View File

@ -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)
}
})
}
}

94
cmd/godoc/x.go Normal file
View File

@ -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>
`))

View File

@ -10,7 +10,6 @@ import (
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"go/build"
"go/scanner" "go/scanner"
"io" "io"
"io/ioutil" "io/ioutil"
@ -22,7 +21,7 @@ import (
"runtime/pprof" "runtime/pprof"
"strings" "strings"
"golang.org/x/tools/internal/imports" "golang.org/x/tools/imports"
) )
var ( var (
@ -31,7 +30,6 @@ var (
write = flag.Bool("w", false, "write result to (source) file instead of stdout") write = flag.Bool("w", false, "write result to (source) file instead of stdout")
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") 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.") 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 verbose bool // verbose logging
cpuProfile = flag.String("cpuprofile", "", "CPU profile output") cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
@ -43,19 +41,13 @@ var (
TabIndent: true, TabIndent: true,
Comments: true, Comments: true,
Fragment: 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 exitCode = 0
) )
func init() { func init() {
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)") 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.StringVar(&imports.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.")
} }
func report(err error) { func report(err error) {
@ -258,7 +250,7 @@ func gofmtMain() {
if verbose { if verbose {
log.SetFlags(log.LstdFlags | log.Lmicroseconds) log.SetFlags(log.LstdFlags | log.Lmicroseconds)
options.Env.Debug = true imports.Debug = true
} }
if options.TabWidth < 0 { if options.TabWidth < 0 {
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth) fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)

26
cmd/golsp/main.go Normal file
View File

@ -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:])
}

59
cmd/gopls/forward/main.go Normal file
View File

@ -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 {
}
}

View File

@ -10,11 +10,6 @@ import vscode = require('vscode');
import path = require('path'); import path = require('path');
export function activate(ctx: vscode.ExtensionContext): void { 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 document = vscode.window.activeTextEditor.document;
let config = vscode.workspace.getConfiguration('gopls', document.uri); let config = vscode.workspace.getConfiguration('gopls', document.uri);
let goplsCommand: string = config['command']; let goplsCommand: string = config['command'];
@ -29,20 +24,6 @@ export function activate(ctx: vscode.ExtensionContext): void {
(uri.scheme ? uri : uri.with({scheme: 'file'})).toString(), (uri.scheme ? uri : uri.with({scheme: 'file'})).toString(),
protocol2Code: (uri: string) => vscode.Uri.parse(uri), 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, revealOutputChannelOn: lsp.RevealOutputChannelOn.Never,
}; };
const c = new lsp.LanguageClient('gopls', serverOptions, clientOptions); const c = new lsp.LanguageClient('gopls', serverOptions, clientOptions);

View File

@ -13,11 +13,9 @@ import (
"os" "os"
"golang.org/x/tools/internal/lsp/cmd" "golang.org/x/tools/internal/lsp/cmd"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/tool" "golang.org/x/tools/internal/tool"
) )
func main() { func main() {
debug.Version += "-cmd.gopls" tool.Main(context.Background(), &cmd.Application{}, os.Args[1:])
tool.Main(context.Background(), cmd.New("gopls-legacy", "", nil), os.Args[1:])
} }

View File

@ -312,9 +312,6 @@ func g() { fmt.Println(test.Foo(3)) }
// buildGorename builds the gorename executable. // buildGorename builds the gorename executable.
// It returns its path, and a cleanup function. // It returns its path, and a cleanup function.
func buildGorename(t *testing.T) (tmp, bin string, cleanup func()) { 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-") tmp, err := ioutil.TempDir("", "gorename-regtest-")
if err != nil { if err != nil {

View File

@ -10,7 +10,6 @@ import (
"go/build" "go/build"
"go/token" "go/token"
"os" "os"
"path"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@ -203,7 +202,7 @@ func guessImportPath(filename string, buildContext *build.Context) (srcdir, impo
if d >= 0 && d < minD { if d >= 0 && d < minD {
minD = d minD = d
srcdir = gopathDir srcdir = gopathDir
importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...) importPath = strings.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:], string(os.PathSeparator))
} }
} }
if srcdir == "" { if srcdir == "" {

View File

@ -26,35 +26,12 @@ function toggleNotesWindow() {
initNotes(); 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() { function initNotes() {
notesWindow = window.open('', '', 'width=1000,height=700'); notesWindow = window.open('', '', 'width=1000,height=700');
var w = notesWindow; var w = notesWindow;
var slidesUrl = window.location.href; var slidesUrl = window.location.href;
// Hack to apply css. Requires existing html on notesWindow. var curSlide = parseInt(localStorage.getItem('destSlide'), 10);
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 formattedNotes = ''; var formattedNotes = '';
var section = sections[curSlide - 1]; var section = sections[curSlide - 1];
// curSlide is 0 when initialized from the first page of slides. // curSlide is 0 when initialized from the first page of slides.
@ -65,6 +42,15 @@ function initNotes() {
formattedNotes = formatNotes(titleNotes); 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 needed for Firefox
setTimeout(function() { setTimeout(function() {
slides.focus(); slides.focus();
@ -107,7 +93,7 @@ function updateNotes() {
// When triggered from parent window, notesWindow is null // When triggered from parent window, notesWindow is null
// The storage event listener on notesWindow will update notes // The storage event listener on notesWindow will update notes
if (!notesWindow) return; if (!notesWindow) return;
var destSlide = parseInt(localStorage.getItem(destSlideKey()), 10); var destSlide = parseInt(localStorage.getItem('destSlide'), 10);
var section = sections[destSlide - 1]; var section = sections[destSlide - 1];
var el = notesWindow.document.getElementById('presenter-notes'); var el = notesWindow.document.getElementById('presenter-notes');

View File

@ -212,7 +212,7 @@ function prevSlide() {
updateSlides(); updateSlides();
} }
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide); if (notesEnabled) localStorage.setItem('destSlide', curSlide);
}; };
function nextSlide() { function nextSlide() {
@ -223,7 +223,7 @@ function nextSlide() {
updateSlides(); updateSlides();
} }
if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide); if (notesEnabled) localStorage.setItem('destSlide', curSlide);
}; };
/* Slide events */ /* Slide events */
@ -602,7 +602,7 @@ function setupNotesSync() {
setupPlayCodeSync(); setupPlayCodeSync();
setupPlayResizeSync(); setupPlayResizeSync();
localStorage.setItem(destSlideKey(), curSlide); localStorage.setItem('destSlide', curSlide);
window.addEventListener('storage', updateOtherWindow, false); window.addEventListener('storage', updateOtherWindow, false);
} }
@ -613,7 +613,7 @@ function updateOtherWindow(e) {
var isRemoveStorageEvent = !e.newValue; var isRemoveStorageEvent = !e.newValue;
if (isRemoveStorageEvent) return; if (isRemoveStorageEvent) return;
var destSlide = localStorage.getItem(destSlideKey()); var destSlide = localStorage.getItem('destSlide');
while (destSlide > curSlide) { while (destSlide > curSlide) {
nextSlide(); nextSlide();
} }

19
cmd/splitdwarf/doc.go Normal file
View File

@ -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"

View File

@ -4,21 +4,7 @@
// +build !js,!nacl,!plan9,!solaris,!windows // +build !js,!nacl,!plan9,!solaris,!windows
/* package main
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"
import ( import (
"crypto/sha256" "crypto/sha256"

View File

@ -45,8 +45,8 @@ func TestEndToEnd(t *testing.T) {
t.Errorf("%s is not a Go file", name) t.Errorf("%s is not a Go file", name)
continue continue
} }
if strings.HasPrefix(name, "tag_") || strings.HasPrefix(name, "vary_") { if strings.HasPrefix(name, "tag_") {
// This file is used for tag processing in TestTags or TestConstValueChange, below. // This file is used for tag processing in TestTags, below.
continue continue
} }
if name == "cgo.go" && !build.Default.CgoEnabled { if name == "cgo.go" && !build.Default.CgoEnabled {
@ -68,10 +68,12 @@ func TestTags(t *testing.T) {
output = filepath.Join(dir, "const_string.go") output = filepath.Join(dir, "const_string.go")
) )
for _, file := range []string{"tag_main.go", "tag_tag.go"} { for _, file := range []string{"tag_main.go", "tag_tag.go"} {
err := copy(filepath.Join(dir, file), filepath.Join("testdata", file)) err := copy(filepath.Join(dir, file), filepath.Join("testdata", file))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
// Run stringer in the directory that contains the package files. // Run stringer in the directory that contains the package files.
// We cannot run stringer in the current directory for the following reasons: // 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. // buildStringer creates a temporary directory and installs stringer there.
func buildStringer(t *testing.T) (dir string, stringer string) { func buildStringer(t *testing.T) (dir string, stringer string) {
t.Helper() t.Helper()
@ -215,6 +175,5 @@ func runInDir(dir, name string, arg ...string) error {
cmd.Dir = dir cmd.Dir = dir
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), "GO111MODULE=auto")
return cmd.Run() return cmd.Run()
} }

View File

@ -52,19 +52,7 @@ const (
) )
` `
const day_out = `func _() { const day_out = `
// 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_name = "MondayTuesdayWednesdayThursdayFridaySaturdaySunday" const _Day_name = "MondayTuesdayWednesdayThursdayFridaySaturdaySunday"
var _Day_index = [...]uint8{0, 6, 13, 22, 30, 36, 44, 50} var _Day_index = [...]uint8{0, 6, 13, 22, 30, 36, 44, 50}
@ -89,15 +77,7 @@ const (
) )
` `
const offset_out = `func _() { const offset_out = `
// 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 _Number_name = "OneTwoThree" const _Number_name = "OneTwoThree"
var _Number_index = [...]uint8{0, 3, 6, 11} var _Number_index = [...]uint8{0, 3, 6, 11}
@ -125,20 +105,7 @@ const (
) )
` `
const gap_out = `func _() { const gap_out = `
// 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 ( const (
_Gap_name_0 = "TwoThree" _Gap_name_0 = "TwoThree"
_Gap_name_1 = "FiveSixSevenEightNine" _Gap_name_1 = "FiveSixSevenEightNine"
@ -177,17 +144,7 @@ const (
) )
` `
const num_out = `func _() { const num_out = `
// 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_name = "m_2m_1m0m1m2" const _Num_name = "m_2m_1m0m1m2"
var _Num_index = [...]uint8{0, 3, 6, 8, 10, 12} var _Num_index = [...]uint8{0, 3, 6, 8, 10, 12}
@ -215,17 +172,7 @@ const (
) )
` `
const unum_out = `func _() { const unum_out = `
// 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 ( const (
_Unum_name_0 = "m0m1m2" _Unum_name_0 = "m0m1m2"
_Unum_name_1 = "m_2m_1" _Unum_name_1 = "m_2m_1"
@ -270,26 +217,7 @@ const (
) )
` `
const prime_out = `func _() { const prime_out = `
// 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_name = "p2p3p5p7p11p13p17p19p23p29p37p41p43" const _Prime_name = "p2p3p5p7p11p13p17p19p23p29p37p41p43"
var _Prime_map = map[Prime]string{ var _Prime_map = map[Prime]string{
@ -328,19 +256,7 @@ const (
) )
` `
const prefix_out = `func _() { const prefix_out = `
// 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 _Type_name = "IntStringFloatRuneByteStructSlice" const _Type_name = "IntStringFloatRuneByteStructSlice"
var _Type_index = [...]uint8{0, 3, 9, 14, 18, 22, 28, 33} var _Type_index = [...]uint8{0, 3, 9, 14, 18, 22, 28, 33}
@ -370,21 +286,7 @@ const (
) )
` `
const tokens_out = `func _() { const tokens_out = `
// 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 _Token_name = "&|+-Ident.SingleBeforeinlineinline general" const _Token_name = "&|+-Ident.SingleBeforeinlineinline general"
var _Token_index = [...]uint8{0, 1, 2, 3, 4, 9, 10, 22, 28, 42} 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]) g.generate(tokens[1])
got := string(g.format()) got := string(g.format())
if got != test.output { 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)
} }
} }
} }

View File

@ -258,15 +258,6 @@ func (g *Generator) generate(typeName string) {
if len(values) == 0 { if len(values) == 0 {
log.Fatalf("no values defined for type %s", typeName) 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) runs := splitIntoRuns(values)
// The decision of which pattern to use depends on the number of // 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 // 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. // Value represents a declared constant.
type Value struct { type Value struct {
originalName string // The name of the constant. name string // The name of the constant.
name string // The name with trimmed prefix.
// The value is stored as a bit pattern alone. The boolean tells us // 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 // whether to interpret it as an int64 or a uint64; the only place
// this matters is when sorting. // this matters is when sorting.
@ -446,16 +436,15 @@ func (f *File) genDecl(node ast.Node) bool {
u64 = uint64(i64) u64 = uint64(i64)
} }
v := Value{ v := Value{
originalName: name.Name, name: name.Name,
value: u64, value: u64,
signed: info&types.IsUnsigned == 0, signed: info&types.IsUnsigned == 0,
str: value.String(), str: value.String(),
} }
if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 { if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 {
v.name = strings.TrimSpace(c.Text()) 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) f.values = append(f.values, v)
} }
} }

View File

@ -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)
}
}

View File

@ -54,7 +54,7 @@ Outer:
for n, test := range splitTests { for n, test := range splitTests {
values := make([]Value, len(test.input)) values := make([]Value, len(test.input))
for i, v := range 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) runs := splitIntoRuns(values)
if len(runs) != len(test.output) { if len(runs) != len(test.output) {

8
go.mod
View File

@ -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
View File

@ -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=

View File

@ -128,32 +128,10 @@ type Pass struct {
// See comments for ExportObjectFact. // See comments for ExportObjectFact.
ExportPackageFact func(fact Fact) 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. */ /* Further fields may be added in future. */
// For example, suggested or applied refactorings. // 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 // Reportf is a helper function that reports a Diagnostic using the
// specified position and formatted error message. // specified position and formatted error message.
func (pass *Pass) Reportf(pos token.Pos, format string, args ...interface{}) { 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}) 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 { func (pass *Pass) String() string {
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path()) return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
} }
@ -211,3 +180,14 @@ func (pass *Pass) String() string {
type Fact interface { type Fact interface {
AFact() // dummy method to avoid type errors 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
}

View File

@ -257,7 +257,6 @@ func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis
// Check the diagnostics match expectations. // Check the diagnostics match expectations.
for _, f := range diagnostics { for _, f := range diagnostics {
// TODO(matloob): Support ranges in analysistest.
posn := pass.Fset.Position(f.Pos) posn := pass.Fset.Position(f.Pos)
checkMessage(posn, "diagnostic", "", f.Message) checkMessage(posn, "diagnostic", "", f.Message)
} }

View File

@ -14,7 +14,7 @@ import (
func init() { func init() {
// This test currently requires GOPATH mode. // 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. // we'll also turn off GOPROXY just for good measure.
if err := os.Setenv("GO111MODULE", "off"); err != nil { if err := os.Setenv("GO111MODULE", "off"); err != nil {
log.Fatal(err) 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:5: in 'want' comment: unexpected ":"`,
`a/b.go:6: in 'want' comment: got String after foo, want ':'`, `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: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: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:14:9: unexpected diagnostic: call of println(...)`,
`a/b.go:11: no diagnostic was reported matching "wrong expectation text"`, `a/b.go:11: no diagnostic was reported matching "wrong expectation text"`,
`a/b.go:17: no diagnostic was reported matching "unsatisfied expectation"`, `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) { if !reflect.DeepEqual(got, want) {
t.Errorf("got:\n%s\nwant:\n%s", t.Errorf("got:\n%s\nwant:\n%s",
strings.Join(got, "\n"), strings.Join(got, "\n"),

View File

@ -26,11 +26,11 @@ import (
"golang.org/x/tools/go/analysis/passes/cgocall" "golang.org/x/tools/go/analysis/passes/cgocall"
"golang.org/x/tools/go/analysis/passes/composite" "golang.org/x/tools/go/analysis/passes/composite"
"golang.org/x/tools/go/analysis/passes/copylock" "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/httpresponse"
"golang.org/x/tools/go/analysis/passes/loopclosure" "golang.org/x/tools/go/analysis/passes/loopclosure"
"golang.org/x/tools/go/analysis/passes/lostcancel" "golang.org/x/tools/go/analysis/passes/lostcancel"
"golang.org/x/tools/go/analysis/passes/nilfunc" "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/printf"
"golang.org/x/tools/go/analysis/passes/shift" "golang.org/x/tools/go/analysis/passes/shift"
"golang.org/x/tools/go/analysis/passes/stdmethods" "golang.org/x/tools/go/analysis/passes/stdmethods"
@ -58,7 +58,6 @@ func main() {
cgocall.Analyzer, cgocall.Analyzer,
composite.Analyzer, composite.Analyzer,
copylock.Analyzer, copylock.Analyzer,
errorsas.Analyzer,
httpresponse.Analyzer, httpresponse.Analyzer,
loopclosure.Analyzer, loopclosure.Analyzer,
lostcancel.Analyzer, lostcancel.Analyzer,
@ -78,6 +77,6 @@ func main() {
// pkgfact.Analyzer, // pkgfact.Analyzer,
// uses SSA: // uses SSA:
// nilness.Analyzer, nilness.Analyzer,
) )
} }

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -8,7 +8,6 @@ package analysisflags
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/gob"
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
@ -33,14 +32,6 @@ var (
// including (in multi mode) a flag named after the analyzer, // including (in multi mode) a flag named after the analyzer,
// parses the flags, then filters and returns the list of // parses the flags, then filters and returns the list of
// analyzers enabled by flags. // 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 { func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
// Connect each analysis flag to the command line as -analysis.flag. // Connect each analysis flag to the command line as -analysis.flag.
enabled := make(map[*analysis.Analyzer]*triState) enabled := make(map[*analysis.Analyzer]*triState)
@ -97,8 +88,6 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
os.Exit(0) os.Exit(0)
} }
everything := expand(analyzers)
// If any -NAME flag is true, run only those analyzers. Otherwise, // If any -NAME flag is true, run only those analyzers. Otherwise,
// if any -NAME flag is false, run all but those analyzers. // if any -NAME flag is false, run all but those analyzers.
if multi { 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 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() { func printFlags() {
type jsonFlag struct { type jsonFlag struct {
Name string Name string
@ -189,14 +152,13 @@ func printFlags() {
// addVersionFlag registers a -V flag that, if set, // addVersionFlag registers a -V flag that, if set,
// prints the executable version and exits 0. // prints the executable version and exits 0.
// //
// If the -V flag already exists — for example, because it was already // It is a variable not a function to permit easy
// registered by a call to cmd/internal/objabi.AddVersionFlag — then // overriding in the copy vendored in $GOROOT/src/cmd/vet:
// addVersionFlag does nothing. //
func addVersionFlag() { // func init() { addVersionFlag = objabi.AddVersionFlag }
if flag.Lookup("V") == nil { var addVersionFlag = func() {
flag.Var(versionFlag{}, "V", "print version and exit") flag.Var(versionFlag{}, "V", "print version and exit")
} }
}
// versionFlag minimally complies with the -V protocol required by "go vet". // versionFlag minimally complies with the -V protocol required by "go vet".
type versionFlag struct{} type versionFlag struct{}
@ -323,14 +285,9 @@ func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
// -c=N: show offending line plus N lines of context. // -c=N: show offending line plus N lines of context.
if Context >= 0 { if Context >= 0 {
posn := fset.Position(diag.Pos)
end := fset.Position(diag.End)
if !end.IsValid() {
end = posn
}
data, _ := ioutil.ReadFile(posn.Filename) data, _ := ioutil.ReadFile(posn.Filename)
lines := strings.Split(string(data), "\n") 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) { if 1 <= i && i <= len(lines) {
fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1]) 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"` Message string `json:"message"`
} }
var diagnostics []jsonDiagnostic var diagnostics []jsonDiagnostic
// TODO(matloob): Should the JSON diagnostics contain ranges?
// If so, how should they be formatted?
for _, f := range diags { for _, f := range diags {
diagnostics = append(diagnostics, jsonDiagnostic{ diagnostics = append(diagnostics, jsonDiagnostic{
Category: f.Category, Category: f.Category,

View File

@ -4,7 +4,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"log" "log"
"os"
"sort" "sort"
"strings" "strings"
@ -48,7 +47,6 @@ func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
fs.Var(f.Value, f.Name, f.Usage) fs.Var(f.Value, f.Name, f.Usage)
} }
}) })
fs.SetOutput(os.Stdout)
fs.PrintDefaults() fs.PrintDefaults()
fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname) 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.Var(f.Value, a.Name+"."+f.Name, f.Usage)
}) })
fs.SetOutput(os.Stdout)
fs.PrintDefaults() fs.PrintDefaults()
if len(paras) > 1 { if len(paras) > 1 {

View File

@ -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. // Package checker defines the implementation of the checker commands.
// The same code drives the multi-analysis driver, the single-analysis // The same code drives the multi-analysis driver, the single-analysis
// driver that is conventionally provided for convenience along with // 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 // avoid double-reporting in source files that belong to
// multiple packages, such as foo and foo.test. // multiple packages, such as foo and foo.test.
type key struct { type key struct {
pos token.Position token.Position
end token.Position
*analysis.Analyzer *analysis.Analyzer
message string message string
} }
@ -314,8 +309,7 @@ func printDiagnostics(roots []*action) (exitcode int) {
// as most users don't care. // as most users don't care.
posn := act.pkg.Fset.Position(diag.Pos) posn := act.pkg.Fset.Position(diag.Pos)
end := act.pkg.Fset.Position(diag.End) k := key{posn, act.a, diag.Message}
k := key{posn, end, act.a, diag.Message}
if seen[k] { if seen[k] {
continue // duplicate continue // duplicate
} }
@ -505,8 +499,6 @@ func (act *action) execOnce() {
ExportObjectFact: act.exportObjectFact, ExportObjectFact: act.exportObjectFact,
ImportPackageFact: act.importPackageFact, ImportPackageFact: act.importPackageFact,
ExportPackageFact: act.exportPackageFact, ExportPackageFact: act.exportPackageFact,
AllObjectFacts: act.allObjectFacts,
AllPackageFacts: act.allPackageFacts,
} }
act.pass = pass act.pass = pass
@ -549,11 +541,11 @@ func inheritFacts(act, dep *action) {
// Optionally serialize/deserialize fact // Optionally serialize/deserialize fact
// to verify that it works across address spaces. // to verify that it works across address spaces.
if serialize { if serialize {
encodedFact, err := codeFact(fact) var err error
fact, err = codeFact(fact)
if err != nil { if err != nil {
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act) log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
} }
fact = encodedFact
} }
if false { if false {
@ -571,11 +563,11 @@ func inheritFacts(act, dep *action) {
// to verify that it works across address spaces // to verify that it works across address spaces
// and is deterministic. // and is deterministic.
if serialize { if serialize {
encodedFact, err := codeFact(fact) var err error
fact, err = codeFact(fact)
if err != nil { if err != nil {
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act) log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
} }
fact = encodedFact
} }
if false { 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. // importPackageFact implements Pass.ImportPackageFact.
// Given a non-nil pointer ptr of type *T, where *T satisfies Fact, // Given a non-nil pointer ptr of type *T, where *T satisfies Fact,
// fact copies the fact value to *ptr. // fact copies the fact value to *ptr.
@ -716,13 +699,4 @@ func factType(fact analysis.Fact) reflect.Type {
return t 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 } func dbg(b byte) bool { return strings.IndexByte(Debug, b) >= 0 }

View File

@ -99,16 +99,6 @@ func (s *Set) ExportObjectFact(obj types.Object, fact analysis.Fact) {
s.mu.Unlock() 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. // ImportPackageFact implements analysis.Pass.ImportPackageFact.
func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool { func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool {
if pkg == nil { if pkg == nil {
@ -132,16 +122,6 @@ func (s *Set) ExportPackageFact(fact analysis.Fact) {
s.mu.Unlock() 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. // gobFact is the Gob declaration of a serialized fact.
type gobFact struct { type gobFact struct {
PkgPath string // path of package PkgPath string // path of package

View File

@ -130,7 +130,7 @@ var (
asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`) asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
asmDATA = re(`\b(DATA|GLOBL)\b`) 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\))`) asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
@ -184,7 +184,6 @@ Files:
fnName string fnName string
localSize, argSize int localSize, argSize int
wroteSP bool wroteSP bool
noframe bool
haveRetArg bool haveRetArg bool
retLine []int 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 { if m := asmTEXT.FindStringSubmatch(line); m != nil {
flushRet() flushRet()
if arch == "" { if arch == "" {
@ -260,7 +254,7 @@ Files:
// identifiers to represent the directory separator. // identifiers to represent the directory separator.
pkgPath = strings.Replace(pkgPath, "", "/", -1) pkgPath = strings.Replace(pkgPath, "", "/", -1)
if pkgPath != pass.Pkg.Path() { 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 fn = nil
fnName = "" fnName = ""
continue continue
@ -281,8 +275,7 @@ Files:
localSize += archDef.intSize localSize += archDef.intSize
} }
argSize, _ = strconv.Atoi(m[5]) argSize, _ = strconv.Atoi(m[5])
noframe = strings.Contains(flag, "NOFRAME") if fn == nil && !strings.Contains(fnName, "<>") {
if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
badf("function %s missing Go declaration", fnName) badf("function %s missing Go declaration", fnName)
} }
wroteSP = false wroteSP = false
@ -312,18 +305,13 @@ Files:
continue 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 wroteSP = true
continue 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) { for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
if m[3] != archDef.stack || wroteSP || noframe { if m[3] != archDef.stack || wroteSP {
continue continue
} }
off := 0 off := 0
@ -383,7 +371,7 @@ Files:
} }
continue continue
} }
asmCheckVar(badf, fn, line, m[0], off, v, archDef) asmCheckVar(badf, fn, line, m[0], off, v)
} }
} }
flushRet() flushRet()
@ -601,7 +589,7 @@ func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc {
} }
// asmCheckVar checks a single variable reference. // 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) m := asmOpcode.FindStringSubmatch(line)
if m == nil { if m == nil {
if !strings.HasPrefix(strings.TrimSpace(line), "//") { if !strings.HasPrefix(strings.TrimSpace(line), "//") {
@ -610,8 +598,6 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri
return return
} }
addr := strings.HasPrefix(expr, "$")
// Determine operand sizes from instruction. // Determine operand sizes from instruction.
// Typically the suffix suffices, but there are exceptions. // Typically the suffix suffices, but there are exceptions.
var src, dst, kind asmKind 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. // They just take the address of it.
case "386.LEAL": case "386.LEAL":
dst = 4 dst = 4
addr = true
case "amd64.LEAQ": case "amd64.LEAQ":
dst = 8 dst = 8
addr = true
case "amd64p32.LEAL": case "amd64p32.LEAL":
dst = 4 dst = 4
addr = true
default: default:
switch fn.arch.name { switch fn.arch.name {
case "386", "amd64": case "386", "amd64":
@ -742,11 +725,6 @@ func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr stri
vs = v.inner[0].size vs = v.inner[0].size
vt = v.inner[0].typ vt = v.inner[0].typ
} }
if addr {
vk = asmKind(archDef.ptrSize)
vs = archDef.ptrSize
vt = "address"
}
if off != v.off { if off != v.off {
var inner bytes.Buffer var inner bytes.Buffer

View File

@ -5,33 +5,13 @@
package asmdecl_test package asmdecl_test
import ( import (
"os"
"strings"
"testing" "testing"
"golang.org/x/tools/go/analysis/analysistest" "golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/asmdecl" "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) { func Test(t *testing.T) {
testdata := analysistest.TestData() 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")
})
}
} }

View File

@ -4,11 +4,6 @@
// +build amd64 // +build amd64
// Commented-out code should be ignored.
//
// TEXT ·unknown(SB),0,$0
// RET
TEXT ·arg1(SB),0,$0-2 TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), AX MOVB x+0(FP), AX
// MOVB x+0(FP), AX // commented out instructions used to panic // 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` 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` 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` 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 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` 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` MOVL x_base+0(FP), AX // want `invalid MOVL of x_base\+0\(FP\); string base is 8-byte value`

View File

@ -186,6 +186,6 @@ TEXT ·noframe1(SB),0,$0-4
TEXT ·noframe2(SB),NOFRAME,$0-4 TEXT ·noframe2(SB),NOFRAME,$0-4
MOVW 0(R13), AX // Okay; caller's saved LR MOVW 0(R13), AX // Okay; caller's saved LR
MOVW x+4(R13), AX // Okay; x argument MOVW x+4(R13), AX // Okay; x argument
MOVW 8(R13), AX // Okay - NOFRAME is assumed special MOVW 8(R13), AX // want `use of 8\(R13\) points beyond argument frame`
MOVW 12(R13), AX // Okay - NOFRAME is assumed special MOVW 12(R13), AX // want `use of 12\(R13\) points beyond argument frame`
RET RET

View File

@ -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 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` 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 // 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_lo+0(FP), R1
MOVW x_hi+4(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\)` 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\)`

View File

@ -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`

View File

@ -30,13 +30,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
nodeFilter := []ast.Node{ nodeFilter := []ast.Node{
(*ast.BinaryExpr)(nil), (*ast.BinaryExpr)(nil),
} }
seen := make(map[*ast.BinaryExpr]bool)
inspect.Preorder(nodeFilter, func(n ast.Node) { inspect.Preorder(nodeFilter, func(n ast.Node) {
e := n.(*ast.BinaryExpr) e := n.(*ast.BinaryExpr)
if seen[e] {
// Already processed as a subexpression of an earlier node.
return
}
var op boolOp var op boolOp
switch e.Op { switch e.Op {
@ -48,7 +43,10 @@ func run(pass *analysis.Pass) (interface{}, error) {
return 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 { for _, exprs := range comm {
op.checkRedundant(pass, exprs) op.checkRedundant(pass, exprs)
op.checkSuspect(pass, exprs) op.checkSuspect(pass, exprs)
@ -72,9 +70,8 @@ var (
// expressions in e that are connected by op. // expressions in e that are connected by op.
// For example, given 'a || b || f() || c || d' with the or op, // For example, given 'a || b || f() || c || d' with the or op,
// commutativeSets returns {{b, a}, {d, c}}. // commutativeSets returns {{b, a}, {d, c}}.
// commutativeSets adds any expanded BinaryExprs to seen. func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr) [][]ast.Expr {
func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr { exprs := op.split(e)
exprs := op.split(e, seen)
// Partition the slice of expressions into commutative sets. // Partition the slice of expressions into commutative sets.
i := 0 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. // 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, // For example, given 'a || (b || c) || d' with the or op,
// split returns []{d, c, b, a}. // 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) (exprs []ast.Expr) {
func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
for { for {
e = unparen(e) e = unparen(e)
if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok { if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
seen[b] = true exprs = append(exprs, op.split(b.Y)...)
exprs = append(exprs, op.split(b.Y, seen)...)
e = b.X e = b.X
} else { } else {
exprs = append(exprs, e) exprs = append(exprs, e)

View File

@ -46,17 +46,22 @@ func RatherStupidConditions() {
_ = i+1 == 1 || i+1 == 1 // want `redundant or: i\+1 == 1 \|\| i\+1 == 1` _ = 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 || 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 _ = 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` _ = f() == 1 || i == 1 || i == 1 // want `redundant or: i == 1 \|\| i == 1`
// Test partition edge cases // 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` _ = 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 || 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 || 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` _ = 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` _ = 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 || f() == 1 || f() == 1 || i == 1
_ = i == 1 || (i == 1 || i == 2) // want `redundant or: i == 1 \|\| i == 1` _ = i == 1 || (i == 1 || i == 2) // want `redundant or: i == 1 \|\| i == 1`
@ -71,9 +76,9 @@ func RatherStupidConditions() {
_ = j == 0 || _ = j == 0 ||
i == 1 || i == 1 ||
f() == 1 || f() == 1 ||
j == 0 || // want `redundant or: j == 0 \|\| j == 0` j == 0 || // want `redundant or: j == 0 \|\| j == 0` `r.*`
i == 1 || // want `redundant or: i == 1 \|\| i == 1` i == 1 || // want `redundant or: i == 1 \|\| i == 1` `r.*` `r.*` `r.*`
i == 1 || // want `redundant or: i == 1 \|\| i == 1` i == 1 || // want `redundant or: i == 1 \|\| i == 1` `r.*` `r.*`
i == 1 || i == 1 ||
j == 0 || j == 0 ||
k == 0 k == 0
@ -89,7 +94,7 @@ func RatherStupidConditions() {
_ = 0 != <-c && 0 != <-c // OK subsequent receives may yield different values _ = 0 != <-c && 0 != <-c // OK subsequent receives may yield different values
_ = f() != 0 && f() != 0 // OK f might have side effects _ = f() != 0 && f() != 0 // OK f might have side effects
_ = f != nil && f != nil // want `redundant and: f != nil && f != nil` _ = 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 _ = 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` _ = f() != 1 && i != 1 && i != 1 // want `redundant and: i != 1 && i != 1`
} }

View File

@ -119,9 +119,3 @@ var badNamedPointerSliceLiteral = []*unicode.CaseRange{
{1, 2, delta}, // want "unkeyed fields" {1, 2, delta}, // want "unkeyed fields"
&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}

View File

@ -24,7 +24,6 @@ var unkeyedLiteral = map[string]bool{
"image.Uniform": true, "image.Uniform": true,
"unicode.Range16": true, "unicode.Range16": true,
"unicode.Range32": true,
// These three structs are used in generated test main files, // These three structs are used in generated test main files,
// but the generator can be trusted. // but the generator can be trusted.

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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())
}

View File

@ -45,8 +45,6 @@ var contextPackage = "context"
// control-flow path from the call to a return statement and that path // control-flow path from the call to a return statement and that path
// does not "use" the cancel function. Any reference to the variable // does not "use" the cancel function. Any reference to the variable
// counts as a use, even within a nested function literal. // 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. // checkLostCancel analyzes a single named or literal function.
func run(pass *analysis.Pass) (interface{}, error) { 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) { 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. // Maps each cancel variable to its defining ValueSpec/AssignStmt.
cancelvars := make(map[*types.Var]ast.Node) 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", "the cancel function returned by context.%s should be called, not discarded, to avoid a context leak",
n.(*ast.SelectorExpr).Sel.Name) n.(*ast.SelectorExpr).Sel.Name)
} else if v, ok := pass.TypesInfo.Uses[id].(*types.Var); ok { } 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 { } else if v, ok := pass.TypesInfo.Defs[id].(*types.Var); ok {
cancelvars[v] = stmt cancelvars[v] = stmt
} }

View File

@ -171,22 +171,3 @@ var _ = func() (ctx context.Context, cancel func()) {
ctx, cancel = context.WithCancel(bg) ctx, cancel = context.WithCancel(bg)
return 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)
}

View File

@ -739,7 +739,6 @@ var printVerbs = []printVerb{
{'T', "-", anyType}, {'T', "-", anyType},
{'U', "-#", argRune | argInt}, {'U', "-#", argRune | argInt},
{'v', allFlags, anyType}, {'v', allFlags, anyType},
{'w', noFlag, anyType},
{'x', sharpNumFlag, argRune | argInt | argString | argPointer}, {'x', sharpNumFlag, argRune | argInt | argString | argPointer},
{'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 return false
} }
sig := stringMethod.Type().(*types.Signature) // Is it the receiver r, or &r?
if !isStringer(sig) { recv := stringMethod.Type().(*types.Signature).Recv()
if recv == nil {
return false return false
} }
// Is it the receiver r, or &r?
if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND { if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND {
e = u.X // strip off & from &r e = u.X // strip off & from &r
} }
if id, ok := e.(*ast.Ident); ok { if id, ok := e.(*ast.Ident); ok {
return pass.TypesInfo.Uses[id] == sig.Recv() return pass.TypesInfo.Uses[id] == recv
} }
return false 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. // 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. // It is almost always a mistake to print a function value.
func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool { func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {

View File

@ -63,7 +63,6 @@ func PrintfTests() {
var imap map[int]int var imap map[int]int
var fslice []float64 var fslice []float64
var c complex64 var c complex64
var err error
// Some good format/argtypes // Some good format/argtypes
fmt.Printf("") fmt.Printf("")
fmt.Printf("%b %b %b", 3, i, x) fmt.Printf("%b %b %b", 3, i, x)
@ -97,7 +96,6 @@ func PrintfTests() {
fmt.Printf("%T", notstringerv) fmt.Printf("%T", notstringerv)
fmt.Printf("%q", stringerarrayv) fmt.Printf("%q", stringerarrayv)
fmt.Printf("%v", stringerarrayv) fmt.Printf("%v", stringerarrayv)
fmt.Printf("%w", err)
fmt.Printf("%s", stringerarrayv) fmt.Printf("%s", stringerarrayv)
fmt.Printf("%v", notstringerarrayv) fmt.Printf("%v", notstringerarrayv)
fmt.Printf("%T", 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" 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 { type cons struct {
car int car int
cdr *cons cdr *cons

View File

@ -8,6 +8,7 @@ package stdmethods
import ( import (
"go/ast" "go/ast"
"go/token"
"go/types" "go/types"
"strings" "strings"
@ -116,13 +117,6 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident) {
args := sign.Params() args := sign.Params()
results := sign.Results() 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? // Do the =s (if any) all match?
if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") { if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") {
return return
@ -169,7 +163,7 @@ func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, pref
if i >= actual.Len() { if i >= actual.Len() {
return false return false
} }
if !matchParamType(x, actual.At(i).Type()) { if !matchParamType(pass.Fset, pass.Pkg, x, actual.At(i).Type()) {
return false return false
} }
} }
@ -180,8 +174,13 @@ func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, pref
} }
// Does this one type match? // 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, "=") 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. // Overkill but easy.
return typeString(actual) == expect return typeString(actual) == expect
} }

View File

@ -15,7 +15,3 @@ func Test(t *testing.T) {
testdata := analysistest.TestData() testdata := analysistest.TestData()
analysistest.Run(t, testdata, stdmethods.Analyzer, "a") analysistest.Run(t, testdata, stdmethods.Analyzer, "a")
} }
func TestAnalyzeEncodingXML(t *testing.T) {
analysistest.Run(t, "", stdmethods.Analyzer, "encoding/xml")
}

View File

@ -7,7 +7,6 @@ package a
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io"
) )
type T int type T int
@ -29,10 +28,6 @@ func (U) UnmarshalXML(*xml.Decoder, xml.StartElement) error { // no error: signa
return nil 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 { type I interface {
ReadByte() byte // want `should have signature ReadByte\(\) \(byte, error\)` ReadByte() byte // want `should have signature ReadByte\(\) \(byte, error\)`
} }

View File

@ -41,7 +41,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
} }
inspect.Preorder(nodeFilter, func(n ast.Node) { inspect.Preorder(nodeFilter, func(n ast.Node) {
styp := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct) 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++ { for i := 0; i < styp.NumFields(); i++ {
field := styp.Field(i) field := styp.Field(i)
tag := styp.Tag(i) tag := styp.Tag(i)
@ -51,47 +51,13 @@ func run(pass *analysis.Pass) (interface{}, error) {
return nil, nil 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 checkTagDups = []string{"json", "xml"}
var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true} var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
// checkCanonicalFieldTag checks a single struct field tag. // checkCanonicalFieldTag checks a single struct field tag.
func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *namesSeen) { func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *map[[2]string]token.Pos) {
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
}
for _, key := range checkTagDups { 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 { if err := validateStructTag(tag); err != nil {
@ -122,17 +88,19 @@ func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, s
// checkTagDuplicates checks a single struct field tag to see if any tags are // 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, // duplicated. nearest is the field that's closest to the field being checked,
// while still being part of the top-level struct type. // 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) val := reflect.StructTag(tag).Get(key)
if val == "-" { if val == "-" {
// Ignored, even if the field is anonymous. // Ignored, even if the field is anonymous.
return return
} }
if val == "" || val[0] == ',' { if val == "" || val[0] == ',' {
if !field.Anonymous() { if field.Anonymous() {
// Ignored if the field isn't 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 return
}
typ, ok := field.Type().Underlying().(*types.Struct) typ, ok := field.Type().Underlying().(*types.Struct)
if !ok { if !ok {
return return
@ -143,8 +111,10 @@ func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *ty
continue continue
} }
tag := typ.Tag(i) tag := typ.Tag(i)
checkTagDuplicates(pass, tag, key, nearest, field, seen, level+1) checkTagDuplicates(pass, tag, key, nearest, field, seen)
} }
}
// Ignored if the field isn't anonymous.
return return
} }
if key == "xml" && field.Name() == "XMLName" { if key == "xml" && field.Name() == "XMLName" {
@ -167,7 +137,10 @@ func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *ty
} }
val = val[:i] 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 := pass.Fset.Position(pos)
alsoPos.Column = 0 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) pass.Reportf(nearest.Pos(), "struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos)
} else { } else {
seen.Set(key, val, level, field.Pos()) (*seen)[[2]string{key, val}] = field.Pos()
} }
} }

View File

@ -75,27 +75,29 @@ type DuplicateJSONFields struct {
} }
AnonymousJSON `json:"a"` // want "struct field AnonymousJSON repeats json tag .a. also at a.go:64" AnonymousJSON `json:"a"` // want "struct field AnonymousJSON repeats json tag .a. also at a.go:64"
AnonymousJSONField
XML int `xml:"a"` 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:"-"` IgnoredXML int `xml:"-"`
OtherIgnoredXML int `xml:"-"` OtherIgnoredXML int `xml:"-"`
OmitXML int `xml:",omitempty"` OmitXML int `xml:",omitempty"`
OtherOmitXML 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"` NonXML int `foo:"a"`
DuplicateNonXML int `foo:"a"` DuplicateNonXML int `foo:"a"`
Embedded2 struct { Embedded2 struct {
DuplicateXML int `xml:"a"` // OK because it's not in the same struct type 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 { Attribute struct {
XMLName xml.Name `xml:"b"` XMLName xml.Name `xml:"b"`
NoDup int `xml:"b"` // OK because XMLName above affects enclosing struct. 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. 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" 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:94" 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 AnonymousJSONField2 `json:"not_anon"` // ok; fields aren't embedded in JSON
@ -122,17 +124,10 @@ type UnexpectedSpacetest struct {
Q int `foo:" doesn't care "` 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 { type DuplicateWithAnotherPackage struct {
b.AnonymousJSONField 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"`
} }

View File

@ -1,8 +0,0 @@
package b
type Foo struct {
}
func (f *Foo) F() {
}

View File

@ -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"
}

View File

@ -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 The tests checker walks Test, Benchmark and Example functions checking
malformed names, wrong signatures and examples documenting non-existent malformed names, wrong signatures and examples documenting non-existent
identifiers. identifiers.`
Please see the documentation for package testing in golang.org/pkg/testing
for the conventions that are enforced for Tests, Benchmarks, and Examples.`
var Analyzer = &analysis.Analyzer{ var Analyzer = &analysis.Analyzer{
Name: "tests", Name: "tests",
@ -87,25 +84,23 @@ func isTestParam(typ ast.Expr, wantType string) bool {
return false 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 { if o := pkg.Scope().Lookup(name); o != nil {
return []types.Object{o} return o
} }
var ret []types.Object // If this package is ".../foo_test" and it imports a package
// Search through the imports to see if any of them define name. // ".../foo", try looking in the latter package.
// It's hard to tell in general which package is being tested, so // This heuristic should work even on build systems that do not
// for the purposes of the analysis, allow the object to appear // record any special link between the packages.
// in any of the imports. This guarantees there are no false positives if basePath := strings.TrimSuffix(pkg.Path(), "_test"); basePath != pkg.Path() {
// 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() { for _, imp := range pkg.Imports() {
if obj := imp.Scope().Lookup(name); obj != nil { if imp.Path() == basePath {
ret = append(ret, obj) return imp.Scope().Lookup(name)
} }
} }
return ret }
return nil
} }
func checkExample(pass *analysis.Pass, fn *ast.FuncDecl) { 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") exName = strings.TrimPrefix(fnName, "Example")
elems = strings.SplitN(exName, "_", 3) elems = strings.SplitN(exName, "_", 3)
ident = elems[0] 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. // Check ExampleFoo and ExampleBadFoo.
pass.Reportf(fn.Pos(), "%s refers to unknown identifier: %s", fnName, ident) pass.Reportf(fn.Pos(), "%s refers to unknown identifier: %s", fnName, ident)
// Abort since obj is absent and no subsequent checks can be performed. // 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] mmbr := elems[1]
if !isExampleSuffix(mmbr) { if !isExampleSuffix(mmbr) {
// Check ExampleFoo_Method and ExampleFoo_BadMethod. // Check ExampleFoo_Method and ExampleFoo_BadMethod.
found := false if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil {
// 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 {
pass.Reportf(fn.Pos(), "%s refers to unknown field or method: %s.%s", fnName, ident, mmbr) pass.Reportf(fn.Pos(), "%s refers to unknown field or method: %s.%s", fnName, ident, mmbr)
} }
} }

View File

@ -16,7 +16,6 @@ func Test(t *testing.T) {
analysistest.Run(t, testdata, tests.Analyzer, analysistest.Run(t, testdata, tests.Analyzer,
"a", // loads "a", "a [a.test]", and "a.test" "a", // loads "a", "a [a.test]", and "a.test"
"b_x_test", // loads "b" and "b_x_test"
"divergent", "divergent",
) )
} }

View File

@ -29,13 +29,6 @@ var Analyzer = &analysis.Analyzer{
} }
func run(pass *analysis.Pass) (interface{}, error) { 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) inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{ nodeFilter := []ast.Node{

View File

@ -52,11 +52,11 @@ func Main(a *analysis.Analyzer) {
flag.Usage = func() { flag.Usage = func() {
paras := strings.Split(a.Doc, "\n\n") paras := strings.Split(a.Doc, "\n\n")
fmt.Fprintf(os.Stderr, "%s: %s\n\n", a.Name, paras[0]) 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 { 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() flag.PrintDefaults()
} }

View File

@ -0,0 +1,7 @@
package a
func _() {
MyFunc123()
}
func MyFunc123() {}

View File

@ -0,0 +1,10 @@
package b
import "a"
func _() {
a.MyFunc123()
MyFunc123()
}
func MyFunc123() {}

View File

@ -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) }, Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
ImportObjectFact: facts.ImportObjectFact, ImportObjectFact: facts.ImportObjectFact,
ExportObjectFact: facts.ExportObjectFact, ExportObjectFact: facts.ExportObjectFact,
AllObjectFacts: facts.AllObjectFacts,
ImportPackageFact: facts.ImportPackageFact, ImportPackageFact: facts.ImportPackageFact,
ExportPackageFact: facts.ExportPackageFact, ExportPackageFact: facts.ExportPackageFact,
AllPackageFacts: facts.AllPackageFacts,
} }
t0 := time.Now() t0 := time.Now()

View File

@ -14,15 +14,14 @@ import (
"flag" "flag"
"os" "os"
"os/exec" "os/exec"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/findcall" "golang.org/x/tools/go/analysis/passes/findcall"
"golang.org/x/tools/go/analysis/passes/printf" "golang.org/x/tools/go/analysis/passes/printf"
"golang.org/x/tools/go/analysis/unitchecker" "golang.org/x/tools/go/analysis/unitchecker"
"golang.org/x/tools/go/packages/packagestest"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -32,6 +31,7 @@ func TestMain(m *testing.M) {
panic("unreachable") panic("unreachable")
} }
// test
flag.Parse() flag.Parse()
os.Exit(m.Run()) os.Exit(m.Run())
} }
@ -46,55 +46,31 @@ func main() {
// This is a very basic integration test of modular // This is a very basic integration test of modular
// analysis with facts using unitchecker under "go vet". // analysis with facts using unitchecker under "go vet".
// It fork/execs the main function above. // It fork/execs the main function above.
func TestIntegration(t *testing.T) { packagestest.TestAll(t, testIntegration) } func TestIntegration(t *testing.T) {
func testIntegration(t *testing.T, exporter packagestest.Exporter) { if runtime.GOOS != "linux" {
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skipf("skipping fork/exec test on this platform") t.Skipf("skipping fork/exec test on this platform")
} }
exported := packagestest.Export(t, exporter, []packagestest.Module{{ testdata := analysistest.TestData()
Name: "golang.org/fake",
Files: map[string]interface{}{
"a/a.go": `package a
func _() { const wantA = `# a
MyFunc123() testdata/src/a/a.go:4:11: call of 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 wantB = `# golang.org/fake/b const wantB = `# b
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?b/b.go:6:13: call of MyFunc123\(...\) testdata/src/b/b.go:6:13: call of MyFunc123(...)
([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?b/b.go:7:11: call of MyFunc123\(...\) testdata/src/b/b.go:7:11: call of MyFunc123(...)
` `
const wantAJSON = `# golang.org/fake/a const wantAJSON = `# a
\{ {
"golang.org/fake/a": \{ "a": {
"findcall": \[ "findcall": [
\{ {
"posn": "([/._\-a-zA-Z0-9]+[\\/]fake[\\/])?a/a.go:4:11", "posn": "$GOPATH/src/a/a.go:4:11",
"message": "call of MyFunc123\(...\)" "message": "call of MyFunc123(...)"
\} }
\] ]
\} }
\} }
` `
for _, test := range []struct { for _, test := range []struct {
@ -102,16 +78,18 @@ func MyFunc123() {}
wantOut string wantOut string
wantExit int wantExit int
}{ }{
{args: "golang.org/fake/a", wantOut: wantA, wantExit: 2}, {args: "a", wantOut: wantA, wantExit: 2},
{args: "golang.org/fake/b", wantOut: wantB, wantExit: 2}, {args: "b", wantOut: wantB, wantExit: 2},
{args: "golang.org/fake/a golang.org/fake/b", wantOut: wantA + wantB, wantExit: 2}, {args: "a b", wantOut: wantA + wantB, wantExit: 2},
{args: "-json golang.org/fake/a", wantOut: wantAJSON, wantExit: 0}, {args: "-json a", wantOut: wantAJSON, wantExit: 0},
{args: "-c=0 golang.org/fake/a", wantOut: wantA + "4 MyFunc123\\(\\)\n", wantExit: 2}, {args: "-c=0 a", wantOut: wantA + "4 MyFunc123()\n", wantExit: 2},
} { } {
cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "-findcall.name=MyFunc123") cmd := exec.Command("go", "vet", "-vettool="+os.Args[0], "-findcall.name=MyFunc123")
cmd.Args = append(cmd.Args, strings.Fields(test.args)...) cmd.Args = append(cmd.Args, strings.Fields(test.args)...)
cmd.Env = append(exported.Config.Env, "UNITCHECKER_CHILD=1") cmd.Env = append(os.Environ(),
cmd.Dir = exported.Config.Dir "UNITCHECKER_CHILD=1",
"GOPATH="+testdata,
)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
exitcode := 0 exitcode := 0
@ -121,13 +99,10 @@ func MyFunc123() {}
if exitcode != test.wantExit { if exitcode != test.wantExit {
t.Errorf("%s: got exit code %d, want %d", test.args, 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 got != test.wantOut {
if err != nil { t.Errorf("%s: got <<%s>>, want <<%s>>", test.args, got, test.wantOut)
t.Fatal(err)
}
if !matched {
t.Errorf("%s: got <<%s>>, want match of regexp <<%s>>", test.args, out, test.wantOut)
} }
} }
} }

View File

@ -16,7 +16,6 @@ import (
"testing" "testing"
"golang.org/x/tools/go/buildutil" "golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/packages/packagestest"
) )
func TestAllPackages(t *testing.T) { func TestAllPackages(t *testing.T) {
@ -24,24 +23,7 @@ func TestAllPackages(t *testing.T) {
t.Skip("gccgo has no standard packages") t.Skip("gccgo has no standard packages")
} }
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{ all := buildutil.AllPackages(&build.Default)
{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)
set := make(map[string]bool) set := make(map[string]bool)
for _, pkg := range all { for _, pkg := range all {

View File

@ -8,13 +8,10 @@ import (
"go/build" "go/build"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings"
"testing" "testing"
"golang.org/x/tools/go/buildutil" "golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/packages/packagestest"
) )
func TestContainingPackage(t *testing.T) { func TestContainingPackage(t *testing.T) {
@ -22,22 +19,9 @@ func TestContainingPackage(t *testing.T) {
t.Skip("gccgo has no GOROOT") t.Skip("gccgo has no GOROOT")
} }
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{ // unvirtualized:
{Name: "golang.org/x/tools/go/buildutil", Files: packagestest.MustCopyFileTree(".")}})
defer exported.Cleanup()
goroot := runtime.GOROOT() goroot := runtime.GOROOT()
var gopath string gopath := gopathContainingTools(t)
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")
type Test struct { type Test struct {
gopath, filename, wantPkg string gopath, filename, wantPkg string
@ -76,7 +60,7 @@ func TestContainingPackage(t *testing.T) {
var got string var got string
var buildContext = build.Default var buildContext = build.Default
buildContext.GOPATH = test.gopath buildContext.GOPATH = test.gopath
bp, err := buildutil.ContainingPackage(&buildContext, buildutildir, test.filename) bp, err := buildutil.ContainingPackage(&buildContext, ".", test.filename)
if err != nil { if err != nil {
got = "(not found)" got = "(not found)"
} else { } 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
} }

View File

@ -149,7 +149,7 @@ func (b *builder) branchStmt(s *ast.BranchStmt) {
} }
case token.FALLTHROUGH: 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 block = t._fallthrough
} }

View File

@ -51,12 +51,7 @@ func ExampleRead() {
} }
// Print package information. // Print package information.
members := pkg.Scope().Names() fmt.Printf("Package members: %s...\n", pkg.Scope().Names()[:5])
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])
println := pkg.Scope().Lookup("Println") println := pkg.Scope().Lookup("Println")
posn := fset.Position(println.Pos()) posn := fset.Position(println.Pos())
posn.Line = 123 // make example deterministic posn.Line = 123 // make example deterministic
@ -75,15 +70,15 @@ func ExampleRead() {
// ExampleNewImporter demonstrates usage of NewImporter to provide type // ExampleNewImporter demonstrates usage of NewImporter to provide type
// information for dependencies when type-checking Go source code. // information for dependencies when type-checking Go source code.
func ExampleNewImporter() { func ExampleNewImporter() {
const src = `package myrpc const src = `package myscanner
// choosing a package that doesn't change across releases // choosing a package that is unlikely to change across releases
import "net/rpc" import "text/scanner"
const serverError rpc.ServerError = "" const eof = scanner.EOF
` `
fset := token.NewFileSet() fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "myrpc.go", src, 0) f, err := parser.ParseFile(fset, "myscanner.go", src, 0)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -91,22 +86,23 @@ const serverError rpc.ServerError = ""
packages := make(map[string]*types.Package) packages := make(map[string]*types.Package)
imp := gcexportdata.NewImporter(fset, packages) imp := gcexportdata.NewImporter(fset, packages)
conf := types.Config{Importer: imp} 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// object from imported package // object from imported package
pi := packages["net/rpc"].Scope().Lookup("ServerError") pi := packages["text/scanner"].Scope().Lookup("EOF")
fmt.Printf("type %s.%s %s // %s\n", fmt.Printf("const %s.%s %s = %s // %s\n",
pi.Pkg().Path(), pi.Pkg().Path(),
pi.Name(), pi.Name(),
pi.Type().Underlying(), pi.Type(),
pi.(*types.Const).Val(),
slashify(fset.Position(pi.Pos())), slashify(fset.Position(pi.Pos())),
) )
// object in source package // object in source package
twopi := pkg.Scope().Lookup("serverError") twopi := pkg.Scope().Lookup("eof")
fmt.Printf("const %s %s = %s // %s\n", fmt.Printf("const %s %s = %s // %s\n",
twopi.Name(), twopi.Name(),
twopi.Type(), twopi.Type(),
@ -116,8 +112,8 @@ const serverError rpc.ServerError = ""
// Output: // Output:
// //
// type net/rpc.ServerError string // $GOROOT/src/net/rpc/client.go:20:1 // const text/scanner.EOF untyped int = -1 // $GOROOT/src/text/scanner/scanner.go:75:1
// const serverError net/rpc.ServerError = "" // myrpc.go:6:7 // const eof untyped int = -1 // myscanner.go:6:7
} }
func slashify(posn token.Position) token.Position { func slashify(posn token.Position) token.Position {

View File

@ -22,7 +22,6 @@ import (
type importerTest struct { type importerTest struct {
pkgpath, name, want, wantval string pkgpath, name, want, wantval string
wantinits []string wantinits []string
gccgoVersion int // minimum gccgo version (0 => any)
} }
func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) { 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{ var importerTests = [...]importerTest{
{pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"}, {pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"},
{pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1 + -1i)"}, {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: "unicode", name: "MaxRune", want: "const MaxRune untyped rune", wantval: "1114111"},
{pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import"}}, {pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import"}},
{pkgpath: "importsar", name: "Hello", want: "var Hello string"}, {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: "A14", 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: "C0", want: "type C0 struct{f1 C1; f2 C1}"},
{pkgpath: "escapeinfo", name: "NewT", want: "func NewT(data []byte) *T"}, {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: "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: "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: "issue29198", name: "FooServer", 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}"},
} }
func TestGoxImporter(t *testing.T) { func TestGoxImporter(t *testing.T) {
@ -151,30 +146,28 @@ func TestObjImporter(t *testing.T) {
} }
t.Logf("gccgo version %d.%d", major, minor) t.Logf("gccgo version %d.%d", major, minor)
tmpdir, err := ioutil.TempDir("", "TestObjImporter") tmpdir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer os.RemoveAll(tmpdir)
initmap := make(map[*types.Package]InitData) initmap := make(map[*types.Package]InitData)
imp := GetImporter([]string{tmpdir}, initmap) imp := GetImporter([]string{tmpdir}, initmap)
artmpdir, err := ioutil.TempDir("", "TestObjImporter") artmpdir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer os.RemoveAll(artmpdir)
arinitmap := make(map[*types.Package]InitData) arinitmap := make(map[*types.Package]InitData)
arimp := GetImporter([]string{artmpdir}, arinitmap) arimp := GetImporter([]string{artmpdir}, arinitmap)
for _, test := range importerTests { for _, test := range importerTests {
if major < test.gccgoVersion {
// Support for type aliases was added in GCC 7. // Support for type aliases was added in GCC 7.
t.Logf("skipping %q: not supported before gccgo version %d", test.pkgpath, test.gccgoVersion) 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 continue
} }
}
gofile := filepath.Join("testdata", test.pkgpath+".go") gofile := filepath.Join("testdata", test.pkgpath+".go")
if _, err := os.Stat(gofile); os.IsNotExist(err) { if _, err := os.Stat(gofile); os.IsNotExist(err) {
@ -208,4 +201,8 @@ func TestObjImporter(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
if err = os.Remove(tmpdir); err != nil {
t.Fatal(err)
}
} }

View File

@ -35,7 +35,6 @@ type parser struct {
typeData []string // unparsed type data (v3 and later) typeData []string // unparsed type data (v3 and later)
fixups []fixupRecord // fixups to apply at end of parsing fixups []fixupRecord // fixups to apply at end of parsing
initdata InitData // package init priority data 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 // 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.scanner = new(scanner.Scanner)
p.initScanner(filename, src) p.initScanner(filename, src)
p.imports = imports p.imports = imports
p.aliases = make(map[int]string)
p.typeList = make([]types.Type, 1 /* type numbers start at 1 */, 16) p.typeList = make([]types.Type, 1 /* type numbers start at 1 */, 16)
} }
@ -244,14 +242,10 @@ func deref(typ types.Type) types.Type {
// Field = Name Type [string] . // Field = Name Type [string] .
func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) { func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) {
name := p.parseName() name := p.parseName()
typ, n := p.parseTypeExtended(pkg) typ := p.parseType(pkg)
anon := false anon := false
if name == "" { if name == "" {
anon = true anon = true
// Alias?
if aname, ok := p.aliases[n]; ok {
name = aname
} else {
switch typ := deref(typ).(type) { switch typ := deref(typ).(type) {
case *types.Basic: case *types.Basic:
name = typ.Name() name = typ.Name()
@ -261,7 +255,6 @@ func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) {
p.error("anonymous field expected") p.error("anonymous field expected")
} }
} }
}
field = types.NewField(token.NoPos, pkg, name, typ, anon) field = types.NewField(token.NoPos, pkg, name, typ, anon)
if p.tok == scanner.String { if p.tok == scanner.String {
tag = p.parseString() tag = p.parseString()
@ -272,10 +265,6 @@ func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) {
// Param = Name ["..."] Type . // Param = Name ["..."] Type .
func (p *parser) parseParam(pkg *types.Package) (param *types.Var, isVariadic bool) { func (p *parser) parseParam(pkg *types.Package) (param *types.Var, isVariadic bool) {
name := p.parseName() 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' { if p.tok == '<' && p.scanner.Peek() == 'e' {
// EscInfo = "<esc:" int ">" . (optional and ignored) // EscInfo = "<esc:" int ">" . (optional and ignored)
p.next() p.next()
@ -301,14 +290,7 @@ func (p *parser) parseParam(pkg *types.Package) (param *types.Var, isVariadic bo
// Var = Name Type . // Var = Name Type .
func (p *parser) parseVar(pkg *types.Package) *types.Var { func (p *parser) parseVar(pkg *types.Package) *types.Var {
name := p.parseName() name := p.parseName()
v := types.NewVar(token.NoPos, pkg, name, p.parseType(pkg)) return 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
} }
// Conversion = "convert" "(" Type "," ConstValue ")" . // Conversion = "convert" "(" Type "," ConstValue ")" .
@ -508,7 +490,6 @@ func (p *parser) parseNamedType(nlist []int) types.Type {
// type alias // type alias
if p.tok == '=' { if p.tok == '=' {
p.next() p.next()
p.aliases[nlist[len(nlist)-1]] = name
if obj != nil { if obj != nil {
// use the previously imported (canonical) type // use the previously imported (canonical) type
t := obj.Type() t := obj.Type()
@ -562,12 +543,10 @@ func (p *parser) parseNamedType(nlist []int) types.Type {
for p.tok == scanner.Ident { for p.tok == scanner.Ident {
p.expectKeyword("func") p.expectKeyword("func")
if p.tok == '/' { if p.tok == '/' {
// Skip a /*nointerface*/ or /*asm ID */ comment. // Skip a /*nointerface*/ comment.
p.expect('/') p.expect('/')
p.expect('*') p.expect('*')
if p.expect(scanner.Ident) == "asm" { p.expect(scanner.Ident)
p.parseUnquotedString()
}
p.expect('*') p.expect('*')
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" { if p.tok == scanner.Ident && p.lit == "inl" {
return nil return nil
} }
taa, _ := p.parseTypeAfterAngle(pkg) return types.NewTuple(types.NewParam(token.NoPos, pkg, "", p.parseTypeAfterAngle(pkg)))
return types.NewTuple(types.NewParam(token.NoPos, pkg, "", taa))
case '(': case '(':
params, _ := p.parseParamList(pkg) params, _ := p.parseParamList(pkg)
@ -753,29 +731,15 @@ func (p *parser) parseFunctionType(pkg *types.Package, nlist []int) *types.Signa
// Func = Name FunctionType [InlineBody] . // Func = Name FunctionType [InlineBody] .
func (p *parser) parseFunc(pkg *types.Package) *types.Func { 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() name := p.parseName()
f := types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg, nil)) if strings.ContainsRune(name, '$') {
p.skipInlineBody() // This is a Type$equal or Type$hash function, which we don't want to parse,
// except for the types.
if name[0] == '.' || name[0] == '<' || strings.ContainsRune(name, '$') { p.discardDirectiveWhileParsingTypes(pkg)
// 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.
return nil return nil
} }
f := types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg, nil))
p.skipInlineBody()
return f return f
} }
@ -796,10 +760,8 @@ func (p *parser) parseInterfaceType(pkg *types.Package, nlist []int) types.Type
embeddeds = append(embeddeds, p.parseType(pkg)) embeddeds = append(embeddeds, p.parseType(pkg))
} else { } else {
method := p.parseFunc(pkg) method := p.parseFunc(pkg)
if method != nil {
methods = append(methods, method) methods = append(methods, method)
} }
}
p.expect(';') p.expect(';')
} }
p.expect('}') p.expect('}')
@ -918,18 +880,16 @@ func lookupBuiltinType(typ int) types.Type {
// //
func (p *parser) parseType(pkg *types.Package, n ...int) types.Type { func (p *parser) parseType(pkg *types.Package, n ...int) types.Type {
p.expect('<') p.expect('<')
t, _ := p.parseTypeAfterAngle(pkg, n...) return p.parseTypeAfterAngle(pkg, n...)
return t
} }
// (*parser).Type after reading the "<". // (*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") p.expectKeyword("type")
n1 = 0
switch p.tok { switch p.tok {
case scanner.Int: case scanner.Int:
n1 = p.parseInt() n1 := p.parseInt()
if p.tok == '>' { if p.tok == '>' {
if len(p.typeData) > 0 && p.typeList[n1] == nil { if len(p.typeData) > 0 && p.typeList[n1] == nil {
p.parseSavedType(pkg, n1, n) p.parseSavedType(pkg, n1, n)
@ -952,7 +912,7 @@ func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...int) (t types.Type
default: default:
p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit) p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit)
return nil, 0 return nil
} }
if t == nil || t == reserved { if t == nil || t == reserved {
@ -963,15 +923,6 @@ func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...int) (t types.Type
return 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} // InlineBody = "<inl:NN>" .{NN}
// Reports whether a body was skipped. // Reports whether a body was skipped.
func (p *parser) skipInlineBody() { func (p *parser) skipInlineBody() {
@ -1090,6 +1041,22 @@ func (p *parser) parsePackageInit() PackageInit {
return PackageInit{Name: name, InitFunc: initfunc, Priority: priority} 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. // Create the package if we have parsed both the package path and package name.
func (p *parser) maybeCreatePackage() { func (p *parser) maybeCreatePackage() {
if p.pkgname != "" && p.pkgpath != "" { if p.pkgname != "" && p.pkgpath != "" {
@ -1227,9 +1194,7 @@ func (p *parser) parseDirective() {
case "var": case "var":
p.next() p.next()
v := p.parseVar(p.pkg) v := p.parseVar(p.pkg)
if v != nil {
p.pkg.Scope().Insert(v) p.pkg.Scope().Insert(v)
}
p.expectEOL() p.expectEOL()
case "const": case "const":

View File

@ -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
}
}

View File

@ -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

View File

@ -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