pax_global_header 0000666 0000000 0000000 00000000064 14177515506 0014525 g ustar 00root root 0000000 0000000 52 comment=1e24bb6ed0de070f3d23711008405e42187e28be
golang-golang-x-tools-0.1.9+ds/ 0000775 0000000 0000000 00000000000 14177515506 0016255 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/.gitattributes 0000664 0000000 0000000 00000000531 14177515506 0021147 0 ustar 00root root 0000000 0000000 # Treat all files in this repo as binary, with no git magic updating
# line endings. Windows users contributing to Go will need to use a
# modern version of git and editors capable of LF line endings.
#
# We'll prevent accidental CRLF line endings from entering the repo
# via the git-review gofmt checks.
#
# See golang.org/issue/9281
* -text
golang-golang-x-tools-0.1.9+ds/.gitignore 0000664 0000000 0000000 00000000125 14177515506 0020243 0 ustar 00root root 0000000 0000000 # Add no patterns to .gitignore except for files generated by the build.
last-change
golang-golang-x-tools-0.1.9+ds/.prettierrc 0000664 0000000 0000000 00000000063 14177515506 0020440 0 ustar 00root root 0000000 0000000 {
"singleQuote": true,
"trailingComma": "es5"
} golang-golang-x-tools-0.1.9+ds/AUTHORS 0000664 0000000 0000000 00000000255 14177515506 0017327 0 ustar 00root root 0000000 0000000 # This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at http://tip.golang.org/AUTHORS.
golang-golang-x-tools-0.1.9+ds/CONTRIBUTING.md 0000664 0000000 0000000 00000001621 14177515506 0020506 0 ustar 00root root 0000000 0000000 # Contributing to Go
Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
## Contributing code
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches.
Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file.
golang-golang-x-tools-0.1.9+ds/CONTRIBUTORS 0000664 0000000 0000000 00000000252 14177515506 0020134 0 ustar 00root root 0000000 0000000 # This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at http://tip.golang.org/CONTRIBUTORS.
golang-golang-x-tools-0.1.9+ds/LICENSE 0000664 0000000 0000000 00000002707 14177515506 0017270 0 ustar 00root root 0000000 0000000 Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang-golang-x-tools-0.1.9+ds/PATENTS 0000664 0000000 0000000 00000002427 14177515506 0017323 0 ustar 00root root 0000000 0000000 Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.
golang-golang-x-tools-0.1.9+ds/README.md 0000664 0000000 0000000 00000002435 14177515506 0017540 0 ustar 00root root 0000000 0000000 # Go Tools
[](https://pkg.go.dev/golang.org/x/tools)
This subrepository holds the source for various packages and tools that support
the Go programming language.
Some of the tools, `godoc` and `vet` for example, are included in binary Go
distributions.
Others, including the Go `guru` and the test coverage tool, can be fetched with
`go install`.
Packages include a type-checker for Go and an implementation of the
Static Single Assignment form (SSA) representation for Go programs.
## Download/Install
The easiest way to install is to run `go install golang.org/x/tools/...@latest`.
## JS/CSS Formatting
This repository uses [prettier](https://prettier.io/) to format JS and CSS files.
The version of `prettier` used is 1.18.2.
It is encouraged that all JS and CSS code be run through this before submitting
a change. However, it is not a strict requirement enforced by CI.
## Report Issues / Send Patches
This repository uses Gerrit for code changes. To learn how to submit changes to
this repository, see https://golang.org/doc/contribute.html.
The main issue tracker for the tools repository is located at
https://github.com/golang/go/issues. Prefix your issue with "x/tools/(your
subdir):" in the subject line, so it is easy to find.
golang-golang-x-tools-0.1.9+ds/benchmark/ 0000775 0000000 0000000 00000000000 14177515506 0020207 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/benchmark/parse/ 0000775 0000000 0000000 00000000000 14177515506 0021321 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/benchmark/parse/parse.go 0000664 0000000 0000000 00000006613 14177515506 0022770 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 parse provides support for parsing benchmark results as
// generated by 'go test -bench'.
package parse // import "golang.org/x/tools/benchmark/parse"
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
// Flags used by Benchmark.Measured to indicate
// which measurements a Benchmark contains.
const (
NsPerOp = 1 << iota
MBPerS
AllocedBytesPerOp
AllocsPerOp
)
// Benchmark is one run of a single benchmark.
type Benchmark struct {
Name string // benchmark name
N int // number of iterations
NsPerOp float64 // nanoseconds per iteration
AllocedBytesPerOp uint64 // bytes allocated per iteration
AllocsPerOp uint64 // allocs per iteration
MBPerS float64 // MB processed per second
Measured int // which measurements were recorded
Ord int // ordinal position within a benchmark run
}
// ParseLine extracts a Benchmark from a single line of testing.B
// output.
func ParseLine(line string) (*Benchmark, error) {
fields := strings.Fields(line)
// Two required, positional fields: Name and iterations.
if len(fields) < 2 {
return nil, fmt.Errorf("two fields required, have %d", len(fields))
}
if !strings.HasPrefix(fields[0], "Benchmark") {
return nil, fmt.Errorf(`first field does not start with "Benchmark"`)
}
n, err := strconv.Atoi(fields[1])
if err != nil {
return nil, err
}
b := &Benchmark{Name: fields[0], N: n}
// Parse any remaining pairs of fields; we've parsed one pair already.
for i := 1; i < len(fields)/2; i++ {
b.parseMeasurement(fields[i*2], fields[i*2+1])
}
return b, nil
}
func (b *Benchmark) parseMeasurement(quant string, unit string) {
switch unit {
case "ns/op":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.NsPerOp = f
b.Measured |= NsPerOp
}
case "MB/s":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.MBPerS = f
b.Measured |= MBPerS
}
case "B/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocedBytesPerOp = i
b.Measured |= AllocedBytesPerOp
}
case "allocs/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocsPerOp = i
b.Measured |= AllocsPerOp
}
}
}
func (b *Benchmark) String() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "%s %d", b.Name, b.N)
if (b.Measured & NsPerOp) != 0 {
fmt.Fprintf(buf, " %.2f ns/op", b.NsPerOp)
}
if (b.Measured & MBPerS) != 0 {
fmt.Fprintf(buf, " %.2f MB/s", b.MBPerS)
}
if (b.Measured & AllocedBytesPerOp) != 0 {
fmt.Fprintf(buf, " %d B/op", b.AllocedBytesPerOp)
}
if (b.Measured & AllocsPerOp) != 0 {
fmt.Fprintf(buf, " %d allocs/op", b.AllocsPerOp)
}
return buf.String()
}
// Set is a collection of benchmarks from one
// testing.B run, keyed by name to facilitate comparison.
type Set map[string][]*Benchmark
// ParseSet extracts a Set from testing.B output.
// ParseSet preserves the order of benchmarks that have identical
// names.
func ParseSet(r io.Reader) (Set, error) {
bb := make(Set)
scan := bufio.NewScanner(r)
ord := 0
for scan.Scan() {
if b, err := ParseLine(scan.Text()); err == nil {
b.Ord = ord
ord++
bb[b.Name] = append(bb[b.Name], b)
}
}
if err := scan.Err(); err != nil {
return nil, err
}
return bb, nil
}
golang-golang-x-tools-0.1.9+ds/benchmark/parse/parse_test.go 0000664 0000000 0000000 00000012152 14177515506 0024022 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 parse
import (
"reflect"
"strings"
"testing"
)
func TestParseLine(t *testing.T) {
cases := []struct {
line string
want *Benchmark
err bool // expect an error
}{
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77,
Measured: NsPerOp | MBPerS,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 5 allocs/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77, AllocsPerOp: 5,
Measured: NsPerOp | MBPerS | AllocsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 3 B/op 5 allocs/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77, AllocedBytesPerOp: 3, AllocsPerOp: 5,
Measured: NsPerOp | MBPerS | AllocedBytesPerOp | AllocsPerOp,
},
},
// error handling cases
{
line: "BenchPress 100 19.6 ns/op", // non-benchmark
err: true,
},
{
line: "BenchmarkEncrypt lots 19.6 ns/op", // non-int iterations
err: true,
},
{
line: "BenchmarkBridge 100000000 19.6 smoots", // unknown unit
want: &Benchmark{
Name: "BenchmarkBridge",
N: 100000000,
},
},
{
line: "PASS",
err: true,
},
}
for _, tt := range cases {
have, err := ParseLine(tt.line)
if tt.err && err == nil {
t.Errorf("parsing line %q should have failed", tt.line)
continue
}
if !reflect.DeepEqual(have, tt.want) {
t.Errorf("parsed line %q incorrectly, want %v have %v", tt.line, tt.want, have)
}
}
}
func TestParseSet(t *testing.T) {
// Test two things:
// 1. The noise that can accompany testing.B output gets ignored.
// 2. Benchmarks with the same name have their order preserved.
in := `
? crypto [no test files]
PASS
pem_decrypt_test.go:17: test 4. %!s(x509.PEMCipher=5)
... [output truncated]
BenchmarkEncrypt 100000000 19.6 ns/op
BenchmarkEncrypt 5000000 517 ns/op
=== RUN TestChunk
--- PASS: TestChunk (0.00 seconds)
--- SKIP: TestLinuxSendfile (0.00 seconds)
fs_test.go:716: skipping; linux-only test
BenchmarkReadRequestApachebench 1000000 2960 ns/op 27.70 MB/s 839 B/op 9 allocs/op
BenchmarkClientServerParallel64 50000 59192 ns/op 7028 B/op 60 allocs/op
ok net/http 95.783s
`
want := Set{
"BenchmarkReadRequestApachebench": []*Benchmark{
{
Name: "BenchmarkReadRequestApachebench",
N: 1000000, NsPerOp: 2960, MBPerS: 27.70, AllocedBytesPerOp: 839, AllocsPerOp: 9,
Measured: NsPerOp | MBPerS | AllocedBytesPerOp | AllocsPerOp,
Ord: 2,
},
},
"BenchmarkClientServerParallel64": []*Benchmark{
{
Name: "BenchmarkClientServerParallel64",
N: 50000, NsPerOp: 59192, AllocedBytesPerOp: 7028, AllocsPerOp: 60,
Measured: NsPerOp | AllocedBytesPerOp | AllocsPerOp,
Ord: 3,
},
},
"BenchmarkEncrypt": []*Benchmark{
{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
Ord: 0,
},
{
Name: "BenchmarkEncrypt",
N: 5000000, NsPerOp: 517,
Measured: NsPerOp,
Ord: 1,
},
},
}
have, err := ParseSet(strings.NewReader(in))
if err != nil {
t.Fatalf("unexpected err during ParseSet: %v", err)
}
if !reflect.DeepEqual(want, have) {
t.Errorf("parsed bench set incorrectly, want %v have %v", want, have)
}
}
func TestString(t *testing.T) {
tests := []struct {
name string
input *Benchmark
wanted string
}{
{
name: "nsTest",
input: &Benchmark{
Name: "BenchmarkTest",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
},
wanted: "BenchmarkTest 100000000 19.60 ns/op",
},
{
name: "mbTest",
input: &Benchmark{
Name: "BenchmarkTest",
N: 100000000, MBPerS: 19.6,
Measured: MBPerS,
},
wanted: "BenchmarkTest 100000000 19.60 MB/s",
},
{
name: "allocatedBytesTest",
input: &Benchmark{
Name: "BenchmarkTest",
N: 100000000, AllocedBytesPerOp: 5,
Measured: AllocedBytesPerOp,
},
wanted: "BenchmarkTest 100000000 5 B/op",
},
{
name: "allocsTest",
input: &Benchmark{
Name: "BenchmarkTest",
N: 100000000, AllocsPerOp: 5,
Measured: AllocsPerOp,
},
wanted: "BenchmarkTest 100000000 5 allocs/op",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.input.String()
if result != tt.wanted {
t.Errorf("String() is called, want %q, have %q", tt.wanted, result)
}
})
}
}
golang-golang-x-tools-0.1.9+ds/blog/ 0000775 0000000 0000000 00000000000 14177515506 0017200 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/blog/atom/ 0000775 0000000 0000000 00000000000 14177515506 0020140 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/blog/atom/atom.go 0000664 0000000 0000000 00000003047 14177515506 0021433 0 ustar 00root root 0000000 0000000 // Copyright 2009 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.
// Adapted from encoding/xml/read_test.go.
// Package atom defines XML data structures for an Atom feed.
package atom // import "golang.org/x/tools/blog/atom"
import (
"encoding/xml"
"time"
)
type Feed struct {
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
Title string `xml:"title"`
ID string `xml:"id"`
Link []Link `xml:"link"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Entry []*Entry `xml:"entry"`
}
type Entry struct {
Title string `xml:"title"`
ID string `xml:"id"`
Link []Link `xml:"link"`
Published TimeStr `xml:"published"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Summary *Text `xml:"summary"`
Content *Text `xml:"content"`
}
type Link struct {
Rel string `xml:"rel,attr,omitempty"`
Href string `xml:"href,attr"`
Type string `xml:"type,attr,omitempty"`
HrefLang string `xml:"hreflang,attr,omitempty"`
Title string `xml:"title,attr,omitempty"`
Length uint `xml:"length,attr,omitempty"`
}
type Person struct {
Name string `xml:"name"`
URI string `xml:"uri,omitempty"`
Email string `xml:"email,omitempty"`
InnerXML string `xml:",innerxml"`
}
type Text struct {
Type string `xml:"type,attr"`
Body string `xml:",chardata"`
}
type TimeStr string
func Time(t time.Time) TimeStr {
return TimeStr(t.Format("2006-01-02T15:04:05-07:00"))
}
golang-golang-x-tools-0.1.9+ds/blog/blog.go 0000664 0000000 0000000 00000030356 14177515506 0020461 0 ustar 00root root 0000000 0000000 // 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.
// Package blog implements a web server for articles written in present format.
package blog // import "golang.org/x/tools/blog"
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"golang.org/x/tools/blog/atom"
"golang.org/x/tools/present"
)
var (
validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
// used to serve relative paths when ServeLocalLinks is enabled.
golangOrgAbsLinkReplacer = strings.NewReplacer(
`href="https://golang.org/pkg`, `href="/pkg`,
`href="https://golang.org/cmd`, `href="/cmd`,
)
)
// Config specifies Server configuration values.
type Config struct {
ContentPath string // Relative or absolute location of article files and related content.
TemplatePath string // Relative or absolute location of template files.
BaseURL string // Absolute base URL (for permalinks; no trailing slash).
BasePath string // Base URL path relative to server root (no trailing slash).
GodocURL string // The base URL of godoc (for menu bar; no trailing slash).
Hostname string // Server host name, used for rendering ATOM feeds.
AnalyticsHTML template.HTML // Optional analytics HTML to insert at the beginning of
.
HomeArticles int // Articles to display on the home page.
FeedArticles int // Articles to include in Atom and JSON feeds.
FeedTitle string // The title of the Atom XML feed
PlayEnabled bool
ServeLocalLinks bool // rewrite golang.org/{pkg,cmd} links to host-less, relative paths.
}
// Doc represents an article adorned with presentation data.
type Doc struct {
*present.Doc
Permalink string // Canonical URL for this document.
Path string // Path relative to server root (including base).
HTML template.HTML // rendered article
Related []*Doc
Newer, Older *Doc
}
// Server implements an http.Handler that serves blog articles.
type Server struct {
cfg Config
docs []*Doc
redirects map[string]string
tags []string
docPaths map[string]*Doc // key is path without BasePath.
docTags map[string][]*Doc
template struct {
home, index, article, doc *template.Template
}
atomFeed []byte // pre-rendered Atom feed
jsonFeed []byte // pre-rendered JSON feed
content http.Handler
}
// NewServer constructs a new Server using the specified config.
func NewServer(cfg Config) (*Server, error) {
present.PlayEnabled = cfg.PlayEnabled
if notExist(cfg.TemplatePath) {
return nil, fmt.Errorf("template directory not found: %s", cfg.TemplatePath)
}
root := filepath.Join(cfg.TemplatePath, "root.tmpl")
parse := func(name string) (*template.Template, error) {
path := filepath.Join(cfg.TemplatePath, name)
if notExist(path) {
return nil, fmt.Errorf("template %s was not found in %s", name, cfg.TemplatePath)
}
t := template.New("").Funcs(funcMap)
return t.ParseFiles(root, path)
}
s := &Server{cfg: cfg}
// Parse templates.
var err error
s.template.home, err = parse("home.tmpl")
if err != nil {
return nil, err
}
s.template.index, err = parse("index.tmpl")
if err != nil {
return nil, err
}
s.template.article, err = parse("article.tmpl")
if err != nil {
return nil, err
}
p := present.Template().Funcs(funcMap)
s.template.doc, err = p.ParseFiles(filepath.Join(cfg.TemplatePath, "doc.tmpl"))
if err != nil {
return nil, err
}
// Load content.
content := filepath.Clean(cfg.ContentPath)
err = s.loadDocs(content)
if err != nil {
return nil, err
}
err = s.renderAtomFeed()
if err != nil {
return nil, err
}
err = s.renderJSONFeed()
if err != nil {
return nil, err
}
// Set up content file server.
s.content = http.StripPrefix(s.cfg.BasePath, http.FileServer(http.Dir(cfg.ContentPath)))
return s, nil
}
var funcMap = template.FuncMap{
"sectioned": sectioned,
"authors": authors,
}
// sectioned returns true if the provided Doc contains more than one section.
// This is used to control whether to display the table of contents and headings.
func sectioned(d *present.Doc) bool {
return len(d.Sections) > 1
}
// authors returns a comma-separated list of author names.
func authors(authors []present.Author) string {
var b bytes.Buffer
last := len(authors) - 1
for i, a := range authors {
if i > 0 {
if i == last {
if len(authors) > 2 {
b.WriteString(",")
}
b.WriteString(" and ")
} else {
b.WriteString(", ")
}
}
b.WriteString(authorName(a))
}
return b.String()
}
// authorName returns the first line of the Author text: the author's name.
func authorName(a present.Author) string {
el := a.TextElem()
if len(el) == 0 {
return ""
}
text, ok := el[0].(present.Text)
if !ok || len(text.Lines) == 0 {
return ""
}
return text.Lines[0]
}
// loadDocs reads all content from the provided file system root, renders all
// the articles it finds, adds them to the Server's docs field, computes the
// denormalized docPaths, docTags, and tags fields, and populates the various
// helper fields (Next, Previous, Related) for each Doc.
func (s *Server) loadDocs(root string) error {
// Read content into docs field.
const ext = ".article"
fn := func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if filepath.Ext(p) != ext {
return nil
}
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
d, err := present.Parse(f, p, 0)
if err != nil {
return err
}
var html bytes.Buffer
err = d.Render(&html, s.template.doc)
if err != nil {
return err
}
p = p[len(root) : len(p)-len(ext)] // trim root and extension
p = filepath.ToSlash(p)
s.docs = append(s.docs, &Doc{
Doc: d,
Path: s.cfg.BasePath + p,
Permalink: s.cfg.BaseURL + p,
HTML: template.HTML(html.String()),
})
return nil
}
err := filepath.Walk(root, fn)
if err != nil {
return err
}
sort.Sort(docsByTime(s.docs))
// Pull out doc paths and tags and put in reverse-associating maps.
s.docPaths = make(map[string]*Doc)
s.docTags = make(map[string][]*Doc)
s.redirects = make(map[string]string)
for _, d := range s.docs {
s.docPaths[strings.TrimPrefix(d.Path, s.cfg.BasePath)] = d
for _, t := range d.Tags {
s.docTags[t] = append(s.docTags[t], d)
}
}
for _, d := range s.docs {
for _, old := range d.OldURL {
if !strings.HasPrefix(old, "/") {
old = "/" + old
}
if _, ok := s.docPaths[old]; ok {
return fmt.Errorf("redirect %s -> %s conflicts with document %s", old, d.Path, old)
}
if new, ok := s.redirects[old]; ok {
return fmt.Errorf("redirect %s -> %s conflicts with redirect %s -> %s", old, d.Path, old, new)
}
s.redirects[old] = d.Path
}
}
// Pull out unique sorted list of tags.
for t := range s.docTags {
s.tags = append(s.tags, t)
}
sort.Strings(s.tags)
// Set up presentation-related fields, Newer, Older, and Related.
for _, doc := range s.docs {
// Newer, Older: docs adjacent to doc
for i := range s.docs {
if s.docs[i] != doc {
continue
}
if i > 0 {
doc.Newer = s.docs[i-1]
}
if i+1 < len(s.docs) {
doc.Older = s.docs[i+1]
}
break
}
// Related: all docs that share tags with doc.
related := make(map[*Doc]bool)
for _, t := range doc.Tags {
for _, d := range s.docTags[t] {
if d != doc {
related[d] = true
}
}
}
for d := range related {
doc.Related = append(doc.Related, d)
}
sort.Sort(docsByTime(doc.Related))
}
return nil
}
// renderAtomFeed generates an XML Atom feed and stores it in the Server's
// atomFeed field.
func (s *Server) renderAtomFeed() error {
var updated time.Time
if len(s.docs) > 0 {
updated = s.docs[0].Time
}
feed := atom.Feed{
Title: s.cfg.FeedTitle,
ID: "tag:" + s.cfg.Hostname + ",2013:" + s.cfg.Hostname,
Updated: atom.Time(updated),
Link: []atom.Link{{
Rel: "self",
Href: s.cfg.BaseURL + "/feed.atom",
}},
}
for i, doc := range s.docs {
if i >= s.cfg.FeedArticles {
break
}
// Use original article path as ID in atom feed
// to avoid articles being treated as new when renamed.
idPath := doc.Path
if len(doc.OldURL) > 0 {
old := doc.OldURL[0]
if !strings.HasPrefix(old, "/") {
old = "/" + old
}
idPath = old
}
e := &atom.Entry{
Title: doc.Title,
ID: feed.ID + idPath,
Link: []atom.Link{{
Rel: "alternate",
Href: doc.Permalink,
}},
Published: atom.Time(doc.Time),
Updated: atom.Time(doc.Time),
Summary: &atom.Text{
Type: "html",
Body: summary(doc),
},
Content: &atom.Text{
Type: "html",
Body: string(doc.HTML),
},
Author: &atom.Person{
Name: authors(doc.Authors),
},
}
feed.Entry = append(feed.Entry, e)
}
data, err := xml.Marshal(&feed)
if err != nil {
return err
}
s.atomFeed = data
return nil
}
type jsonItem struct {
Title string
Link string
Time time.Time
Summary string
Content string
Author string
}
// renderJSONFeed generates a JSON feed and stores it in the Server's jsonFeed
// field.
func (s *Server) renderJSONFeed() error {
var feed []jsonItem
for i, doc := range s.docs {
if i >= s.cfg.FeedArticles {
break
}
item := jsonItem{
Title: doc.Title,
Link: doc.Permalink,
Time: doc.Time,
Summary: summary(doc),
Content: string(doc.HTML),
Author: authors(doc.Authors),
}
feed = append(feed, item)
}
data, err := json.Marshal(feed)
if err != nil {
return err
}
s.jsonFeed = data
return nil
}
// summary returns the first paragraph of text from the provided Doc.
func summary(d *Doc) string {
if len(d.Sections) == 0 {
return ""
}
for _, elem := range d.Sections[0].Elem {
text, ok := elem.(present.Text)
if !ok || text.Pre {
// skip everything but non-text elements
continue
}
var buf bytes.Buffer
for _, s := range text.Lines {
buf.WriteString(string(present.Style(s)))
buf.WriteByte('\n')
}
return buf.String()
}
return ""
}
// rootData encapsulates data destined for the root template.
type rootData struct {
Doc *Doc
BasePath string
GodocURL string
AnalyticsHTML template.HTML
Data interface{}
}
// ServeHTTP serves the front, index, and article pages
// as well as the ATOM and JSON feeds.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var (
d = rootData{
BasePath: s.cfg.BasePath,
GodocURL: s.cfg.GodocURL,
AnalyticsHTML: s.cfg.AnalyticsHTML,
}
t *template.Template
)
switch p := strings.TrimPrefix(r.URL.Path, s.cfg.BasePath); p {
case "/":
d.Data = s.docs
if len(s.docs) > s.cfg.HomeArticles {
d.Data = s.docs[:s.cfg.HomeArticles]
}
t = s.template.home
case "/index":
d.Data = s.docs
t = s.template.index
case "/feed.atom", "/feeds/posts/default":
w.Header().Set("Content-type", "application/atom+xml; charset=utf-8")
w.Write(s.atomFeed)
return
case "/.json":
if p := r.FormValue("jsonp"); validJSONPFunc.MatchString(p) {
w.Header().Set("Content-type", "application/javascript; charset=utf-8")
fmt.Fprintf(w, "%v(%s)", p, s.jsonFeed)
return
}
w.Header().Set("Content-type", "application/json; charset=utf-8")
w.Write(s.jsonFeed)
return
default:
if redir, ok := s.redirects[p]; ok {
http.Redirect(w, r, redir, http.StatusMovedPermanently)
return
}
doc, ok := s.docPaths[p]
if !ok {
// Not a doc; try to just serve static content.
s.content.ServeHTTP(w, r)
return
}
d.Doc = doc
t = s.template.article
}
var err error
if s.cfg.ServeLocalLinks {
var buf bytes.Buffer
err = t.ExecuteTemplate(&buf, "root", d)
if err != nil {
log.Println(err)
return
}
_, err = golangOrgAbsLinkReplacer.WriteString(w, buf.String())
} else {
err = t.ExecuteTemplate(w, "root", d)
}
if err != nil {
log.Println(err)
}
}
// docsByTime implements sort.Interface, sorting Docs by their Time field.
type docsByTime []*Doc
func (s docsByTime) Len() int { return len(s) }
func (s docsByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) }
// notExist reports whether the path exists or not.
func notExist(path string) bool {
_, err := os.Stat(path)
return os.IsNotExist(err)
}
golang-golang-x-tools-0.1.9+ds/blog/blog_test.go 0000664 0000000 0000000 00000003674 14177515506 0021523 0 ustar 00root root 0000000 0000000 // 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 blog
import (
"bytes"
"testing"
)
func TestLinkRewrite(t *testing.T) {
tests := []struct {
input string
output string
}{
{
`For instance, the bytes package from the standard library exports the Buffer
type.`,
`For instance, the bytes package from the standard library exports the Buffer
type.`},
{
`(The gofmt command has a -r
flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
`(The gofmt command has a -r
flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
},
{
`BSD license . Terms of Service `,
`BSD license . Terms of Service `,
},
{
`For instance, the websocket
package from the go.net
sub-repository has an import path of "golang.org/x/net/websocket"
.`,
`For instance, the websocket
package from the go.net
sub-repository has an import path of "golang.org/x/net/websocket"
.`,
},
}
for _, test := range tests {
var buf bytes.Buffer
_, err := golangOrgAbsLinkReplacer.WriteString(&buf, test.input)
if err != nil {
t.Errorf("unexpected error during replacing links. Got: %#v, Want: nil.\n", err)
continue
}
if got, want := buf.String(), test.output; got != want {
t.Errorf("WriteString(%q) = %q. Expected: %q", test.input, got, want)
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/ 0000775 0000000 0000000 00000000000 14177515506 0017020 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/auth/ 0000775 0000000 0000000 00000000000 14177515506 0017761 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/auth/authtest/ 0000775 0000000 0000000 00000000000 14177515506 0021622 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/auth/authtest/authtest.go 0000664 0000000 0000000 00000012344 14177515506 0024016 0 ustar 00root root 0000000 0000000 // 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"
exec "golang.org/x/sys/execabs"
"io"
"log"
"net/http"
"net/textproto"
"net/url"
"os"
"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
}
golang-golang-x-tools-0.1.9+ds/cmd/auth/cookieauth/ 0000775 0000000 0000000 00000000000 14177515506 0022114 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/auth/cookieauth/cookieauth.go 0000664 0000000 0000000 00000007136 14177515506 0024605 0 ustar 00root root 0000000 0000000 // 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
}
golang-golang-x-tools-0.1.9+ds/cmd/auth/gitauth/ 0000775 0000000 0000000 00000000000 14177515506 0021426 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/auth/gitauth/gitauth.go 0000664 0000000 0000000 00000011303 14177515506 0023420 0 ustar 00root root 0000000 0000000 // 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"
exec "golang.org/x/sys/execabs"
"log"
"net/http"
"net/url"
"os"
"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()
}
golang-golang-x-tools-0.1.9+ds/cmd/auth/netrcauth/ 0000775 0000000 0000000 00000000000 14177515506 0021756 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/auth/netrcauth/netrcauth.go 0000664 0000000 0000000 00000006110 14177515506 0024300 0 ustar 00root root 0000000 0000000 // 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
}
golang-golang-x-tools-0.1.9+ds/cmd/benchcmp/ 0000775 0000000 0000000 00000000000 14177515506 0020577 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/benchcmp/benchcmp.go 0000664 0000000 0000000 00000010320 14177515506 0022701 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"os"
"sort"
"strconv"
"text/tabwriter"
"golang.org/x/tools/benchmark/parse"
)
var (
changedOnly = flag.Bool("changed", false, "show only benchmarks that have changed")
magSort = flag.Bool("mag", false, "sort benchmarks by magnitude of change")
best = flag.Bool("best", false, "compare best times from old and new")
)
const usageFooter = `
Each input file should be from:
go test -run=NONE -bench=. > [old,new].txt
Benchcmp compares old and new for each benchmark.
If -test.benchmem=true is added to the "go test" command
benchcmp will also compare memory allocations.
`
func main() {
fmt.Fprintf(os.Stderr, "benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat\n")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s old.txt new.txt\n\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprint(os.Stderr, usageFooter)
os.Exit(2)
}
flag.Parse()
if flag.NArg() != 2 {
flag.Usage()
}
before := parseFile(flag.Arg(0))
after := parseFile(flag.Arg(1))
cmps, warnings := Correlate(before, after)
for _, warn := range warnings {
fmt.Fprintln(os.Stderr, warn)
}
if len(cmps) == 0 {
fatal("benchcmp: no repeated benchmarks")
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 0, 5, ' ', 0)
defer w.Flush()
var header bool // Has the header has been displayed yet for a given block?
if *magSort {
sort.Sort(ByDeltaNsPerOp(cmps))
} else {
sort.Sort(ByParseOrder(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.NsPerOp) {
continue
}
if delta := cmp.DeltaNsPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "benchmark\told ns/op\tnew ns/op\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cmp.Name(), formatNs(cmp.Before.NsPerOp), formatNs(cmp.After.NsPerOp), delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaMBPerS(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.MBPerS) {
continue
}
if delta := cmp.DeltaMBPerS(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told MB/s\tnew MB/s\tspeedup\n")
header = true
}
fmt.Fprintf(w, "%s\t%.2f\t%.2f\t%s\n", cmp.Name(), cmp.Before.MBPerS, cmp.After.MBPerS, delta.Multiple())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaAllocsPerOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.AllocsPerOp) {
continue
}
if delta := cmp.DeltaAllocsPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told allocs\tnew allocs\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocsPerOp, cmp.After.AllocsPerOp, delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaAllocedBytesPerOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.AllocedBytesPerOp) {
continue
}
if delta := cmp.DeltaAllocedBytesPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told bytes\tnew bytes\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocedBytesPerOp, cmp.After.AllocedBytesPerOp, cmp.DeltaAllocedBytesPerOp().Percent())
}
}
}
func fatal(msg interface{}) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}
func parseFile(path string) parse.Set {
f, err := os.Open(path)
if err != nil {
fatal(err)
}
defer f.Close()
bb, err := parse.ParseSet(f)
if err != nil {
fatal(err)
}
if *best {
selectBest(bb)
}
return bb
}
func selectBest(bs parse.Set) {
for name, bb := range bs {
if len(bb) < 2 {
continue
}
ord := bb[0].Ord
best := bb[0]
for _, b := range bb {
if b.NsPerOp < best.NsPerOp {
b.Ord = ord
best = b
}
}
bs[name] = []*parse.Benchmark{best}
}
}
// formatNs formats ns measurements to expose a useful amount of
// precision. It mirrors the ns precision logic of testing.B.
func formatNs(ns float64) string {
prec := 0
switch {
case ns < 10:
prec = 2
case ns < 100:
prec = 1
}
return strconv.FormatFloat(ns, 'f', prec, 64)
}
golang-golang-x-tools-0.1.9+ds/cmd/benchcmp/benchcmp_test.go 0000664 0000000 0000000 00000003376 14177515506 0023755 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"reflect"
"testing"
"golang.org/x/tools/benchmark/parse"
)
func TestSelectBest(t *testing.T) {
have := parse.Set{
"Benchmark1": []*parse.Benchmark{
{
Name: "Benchmark1",
N: 10, NsPerOp: 100, Measured: parse.NsPerOp,
Ord: 0,
},
{
Name: "Benchmark1",
N: 10, NsPerOp: 50, Measured: parse.NsPerOp,
Ord: 3,
},
},
"Benchmark2": []*parse.Benchmark{
{
Name: "Benchmark2",
N: 10, NsPerOp: 60, Measured: parse.NsPerOp,
Ord: 1,
},
{
Name: "Benchmark2",
N: 10, NsPerOp: 500, Measured: parse.NsPerOp,
Ord: 2,
},
},
}
want := parse.Set{
"Benchmark1": []*parse.Benchmark{
{
Name: "Benchmark1",
N: 10, NsPerOp: 50, Measured: parse.NsPerOp,
Ord: 0,
},
},
"Benchmark2": []*parse.Benchmark{
{
Name: "Benchmark2",
N: 10, NsPerOp: 60, Measured: parse.NsPerOp,
Ord: 1,
},
},
}
selectBest(have)
if !reflect.DeepEqual(want, have) {
t.Errorf("filtered bench set incorrectly, want %v have %v", want, have)
}
}
func TestFormatNs(t *testing.T) {
tests := []struct {
input float64
expected string
}{
{input: 0, expected: "0.00"},
{input: 0.2, expected: "0.20"},
{input: 2, expected: "2.00"},
{input: 2.2, expected: "2.20"},
{input: 4, expected: "4.00"},
{input: 16, expected: "16.0"},
{input: 16.08, expected: "16.1"},
{input: 128, expected: "128"},
{input: 256.2, expected: "256"},
}
for _, tt := range tests {
actual := formatNs(tt.input)
if actual != tt.expected {
t.Fatalf("%f. got %q, want %q", tt.input, actual, tt.expected)
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/benchcmp/compare.go 0000664 0000000 0000000 00000012362 14177515506 0022560 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"math"
"golang.org/x/tools/benchmark/parse"
)
// BenchCmp is a pair of benchmarks.
type BenchCmp struct {
Before *parse.Benchmark
After *parse.Benchmark
}
// Correlate correlates benchmarks from two BenchSets.
func Correlate(before, after parse.Set) (cmps []BenchCmp, warnings []string) {
cmps = make([]BenchCmp, 0, len(after))
for name, beforebb := range before {
afterbb := after[name]
if len(beforebb) != len(afterbb) {
warnings = append(warnings, fmt.Sprintf("ignoring %s: before has %d instances, after has %d", name, len(beforebb), len(afterbb)))
continue
}
for i, beforeb := range beforebb {
afterb := afterbb[i]
cmps = append(cmps, BenchCmp{beforeb, afterb})
}
}
return
}
func (c BenchCmp) Name() string { return c.Before.Name }
func (c BenchCmp) String() string { return fmt.Sprintf("<%s, %s>", c.Before, c.After) }
func (c BenchCmp) Measured(flag int) bool { return (c.Before.Measured & c.After.Measured & flag) != 0 }
func (c BenchCmp) DeltaNsPerOp() Delta { return Delta{c.Before.NsPerOp, c.After.NsPerOp} }
func (c BenchCmp) DeltaMBPerS() Delta { return Delta{c.Before.MBPerS, c.After.MBPerS} }
func (c BenchCmp) DeltaAllocedBytesPerOp() Delta {
return Delta{float64(c.Before.AllocedBytesPerOp), float64(c.After.AllocedBytesPerOp)}
}
func (c BenchCmp) DeltaAllocsPerOp() Delta {
return Delta{float64(c.Before.AllocsPerOp), float64(c.After.AllocsPerOp)}
}
// Delta is the before and after value for a benchmark measurement.
// Both must be non-negative.
type Delta struct {
Before float64
After float64
}
// mag calculates the magnitude of a change, regardless of the direction of
// the change. mag is intended for sorting and has no independent meaning.
func (d Delta) mag() float64 {
switch {
case d.Before != 0 && d.After != 0 && d.Before >= d.After:
return d.After / d.Before
case d.Before != 0 && d.After != 0 && d.Before < d.After:
return d.Before / d.After
case d.Before == 0 && d.After == 0:
return 1
default:
// 0 -> 1 or 1 -> 0
// These are significant changes and worth surfacing.
return math.Inf(1)
}
}
// Changed reports whether the benchmark quantities are different.
func (d Delta) Changed() bool { return d.Before != d.After }
// Float64 returns After / Before. If Before is 0, Float64 returns
// 1 if After is also 0, and +Inf otherwise.
func (d Delta) Float64() float64 {
switch {
case d.Before != 0:
return d.After / d.Before
case d.After == 0:
return 1
default:
return math.Inf(1)
}
}
// Percent formats a Delta as a percent change, ranging from -100% up.
func (d Delta) Percent() string {
return fmt.Sprintf("%+.2f%%", 100*d.Float64()-100)
}
// Multiple formats a Delta as a multiplier, ranging from 0.00x up.
func (d Delta) Multiple() string {
return fmt.Sprintf("%.2fx", d.Float64())
}
func (d Delta) String() string {
return fmt.Sprintf("Δ(%f, %f)", d.Before, d.After)
}
// ByParseOrder sorts BenchCmps to match the order in
// which the Before benchmarks were presented to Parse.
type ByParseOrder []BenchCmp
func (x ByParseOrder) Len() int { return len(x) }
func (x ByParseOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByParseOrder) Less(i, j int) bool { return x[i].Before.Ord < x[j].Before.Ord }
// lessByDelta provides lexicographic ordering:
// * largest delta by magnitude
// * alphabetic by name
func lessByDelta(i, j BenchCmp, calcDelta func(BenchCmp) Delta) bool {
iDelta, jDelta := calcDelta(i).mag(), calcDelta(j).mag()
if iDelta != jDelta {
return iDelta < jDelta
}
return i.Name() < j.Name()
}
// ByDeltaNsPerOp sorts BenchCmps lexicographically by change
// in ns/op, descending, then by benchmark name.
type ByDeltaNsPerOp []BenchCmp
func (x ByDeltaNsPerOp) Len() int { return len(x) }
func (x ByDeltaNsPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaNsPerOp) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaNsPerOp) }
// ByDeltaMBPerS sorts BenchCmps lexicographically by change
// in MB/s, descending, then by benchmark name.
type ByDeltaMBPerS []BenchCmp
func (x ByDeltaMBPerS) Len() int { return len(x) }
func (x ByDeltaMBPerS) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaMBPerS) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaMBPerS) }
// ByDeltaAllocedBytesPerOp sorts BenchCmps lexicographically by change
// in B/op, descending, then by benchmark name.
type ByDeltaAllocedBytesPerOp []BenchCmp
func (x ByDeltaAllocedBytesPerOp) Len() int { return len(x) }
func (x ByDeltaAllocedBytesPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaAllocedBytesPerOp) Less(i, j int) bool {
return lessByDelta(x[i], x[j], BenchCmp.DeltaAllocedBytesPerOp)
}
// ByDeltaAllocsPerOp sorts BenchCmps lexicographically by change
// in allocs/op, descending, then by benchmark name.
type ByDeltaAllocsPerOp []BenchCmp
func (x ByDeltaAllocsPerOp) Len() int { return len(x) }
func (x ByDeltaAllocsPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaAllocsPerOp) Less(i, j int) bool {
return lessByDelta(x[i], x[j], BenchCmp.DeltaAllocsPerOp)
}
golang-golang-x-tools-0.1.9+ds/cmd/benchcmp/compare_test.go 0000664 0000000 0000000 00000011332 14177515506 0023613 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"math"
"reflect"
"sort"
"testing"
"golang.org/x/tools/benchmark/parse"
)
func TestDelta(t *testing.T) {
cases := []struct {
before float64
after float64
mag float64
f float64
changed bool
pct string
mult string
}{
{before: 1, after: 1, mag: 1, f: 1, changed: false, pct: "+0.00%", mult: "1.00x"},
{before: 1, after: 2, mag: 0.5, f: 2, changed: true, pct: "+100.00%", mult: "2.00x"},
{before: 2, after: 1, mag: 0.5, f: 0.5, changed: true, pct: "-50.00%", mult: "0.50x"},
{before: 0, after: 0, mag: 1, f: 1, changed: false, pct: "+0.00%", mult: "1.00x"},
{before: 1, after: 0, mag: math.Inf(1), f: 0, changed: true, pct: "-100.00%", mult: "0.00x"},
{before: 0, after: 1, mag: math.Inf(1), f: math.Inf(1), changed: true, pct: "+Inf%", mult: "+Infx"},
}
for _, tt := range cases {
d := Delta{tt.before, tt.after}
if want, have := tt.mag, d.mag(); want != have {
t.Errorf("%s.mag(): want %f have %f", d, want, have)
}
if want, have := tt.f, d.Float64(); want != have {
t.Errorf("%s.Float64(): want %f have %f", d, want, have)
}
if want, have := tt.changed, d.Changed(); want != have {
t.Errorf("%s.Changed(): want %t have %t", d, want, have)
}
if want, have := tt.pct, d.Percent(); want != have {
t.Errorf("%s.Percent(): want %q have %q", d, want, have)
}
if want, have := tt.mult, d.Multiple(); want != have {
t.Errorf("%s.Multiple(): want %q have %q", d, want, have)
}
}
}
func TestCorrelate(t *testing.T) {
// Benches that are going to be successfully correlated get N thus:
// 0x
// Read this: " of , from ".
before := parse.Set{
"BenchmarkOneEach": []*parse.Benchmark{{Name: "BenchmarkOneEach", N: 0x11b}},
"BenchmarkOneToNone": []*parse.Benchmark{{Name: "BenchmarkOneToNone"}},
"BenchmarkOneToTwo": []*parse.Benchmark{{Name: "BenchmarkOneToTwo"}},
"BenchmarkTwoToOne": []*parse.Benchmark{
{Name: "BenchmarkTwoToOne"},
{Name: "BenchmarkTwoToOne"},
},
"BenchmarkTwoEach": []*parse.Benchmark{
{Name: "BenchmarkTwoEach", N: 0x12b},
{Name: "BenchmarkTwoEach", N: 0x22b},
},
}
after := parse.Set{
"BenchmarkOneEach": []*parse.Benchmark{{Name: "BenchmarkOneEach", N: 0x11a}},
"BenchmarkNoneToOne": []*parse.Benchmark{{Name: "BenchmarkNoneToOne"}},
"BenchmarkTwoToOne": []*parse.Benchmark{{Name: "BenchmarkTwoToOne"}},
"BenchmarkOneToTwo": []*parse.Benchmark{
{Name: "BenchmarkOneToTwo"},
{Name: "BenchmarkOneToTwo"},
},
"BenchmarkTwoEach": []*parse.Benchmark{
{Name: "BenchmarkTwoEach", N: 0x12a},
{Name: "BenchmarkTwoEach", N: 0x22a},
},
}
pairs, errs := Correlate(before, after)
// Fail to match: BenchmarkOneToNone, BenchmarkOneToTwo, BenchmarkTwoToOne.
// Correlate does not notice BenchmarkNoneToOne.
if len(errs) != 3 {
t.Errorf("Correlated expected 4 errors, got %d: %v", len(errs), errs)
}
// Want three correlated pairs: one BenchmarkOneEach, two BenchmarkTwoEach.
if len(pairs) != 3 {
t.Fatalf("Correlated expected 3 pairs, got %v", pairs)
}
for _, pair := range pairs {
if pair.Before.N&0xF != 0xb {
t.Errorf("unexpected Before in pair %s", pair)
}
if pair.After.N&0xF != 0xa {
t.Errorf("unexpected After in pair %s", pair)
}
if pair.Before.N>>4 != pair.After.N>>4 {
t.Errorf("mismatched pair %s", pair)
}
}
}
func TestBenchCmpSorting(t *testing.T) {
c := []BenchCmp{
{&parse.Benchmark{Name: "BenchmarkMuchFaster", NsPerOp: 10, Ord: 3}, &parse.Benchmark{Name: "BenchmarkMuchFaster", NsPerOp: 1}},
{&parse.Benchmark{Name: "BenchmarkSameB", NsPerOp: 5, Ord: 1}, &parse.Benchmark{Name: "BenchmarkSameB", NsPerOp: 5}},
{&parse.Benchmark{Name: "BenchmarkSameA", NsPerOp: 5, Ord: 2}, &parse.Benchmark{Name: "BenchmarkSameA", NsPerOp: 5}},
{&parse.Benchmark{Name: "BenchmarkSlower", NsPerOp: 10, Ord: 0}, &parse.Benchmark{Name: "BenchmarkSlower", NsPerOp: 11}},
}
// Test just one magnitude-based sort order; they are symmetric.
sort.Sort(ByDeltaNsPerOp(c))
want := []string{"BenchmarkMuchFaster", "BenchmarkSlower", "BenchmarkSameA", "BenchmarkSameB"}
have := []string{c[0].Name(), c[1].Name(), c[2].Name(), c[3].Name()}
if !reflect.DeepEqual(want, have) {
t.Errorf("ByDeltaNsOp incorrect sorting: want %v have %v", want, have)
}
sort.Sort(ByParseOrder(c))
want = []string{"BenchmarkSlower", "BenchmarkSameB", "BenchmarkSameA", "BenchmarkMuchFaster"}
have = []string{c[0].Name(), c[1].Name(), c[2].Name(), c[3].Name()}
if !reflect.DeepEqual(want, have) {
t.Errorf("ByParseOrder incorrect sorting: want %v have %v", want, have)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/benchcmp/doc.go 0000664 0000000 0000000 00000002345 14177515506 0021677 0 ustar 00root root 0000000 0000000 // Copyright 2014 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.
/*
Deprecated: benchcmp is deprecated in favor of benchstat: golang.org/x/perf/cmd/benchstat
The benchcmp command displays performance changes between benchmarks.
Benchcmp parses the output of two 'go test' benchmark runs,
correlates the results per benchmark, and displays the deltas.
To measure the performance impact of a change, use 'go test'
to run benchmarks before and after the change:
go test -run=NONE -bench=. ./... > old.txt
# make changes
go test -run=NONE -bench=. ./... > new.txt
Then feed the benchmark results to benchcmp:
benchcmp old.txt new.txt
Benchcmp will summarize and display the performance changes,
in a format like this:
$ benchcmp old.txt new.txt
benchmark old ns/op new ns/op delta
BenchmarkConcat 523 68.6 -86.88%
benchmark old allocs new allocs delta
BenchmarkConcat 3 1 -66.67%
benchmark old bytes new bytes delta
BenchmarkConcat 80 48 -40.00%
*/
package main // import "golang.org/x/tools/cmd/benchcmp"
golang-golang-x-tools-0.1.9+ds/cmd/bundle/ 0000775 0000000 0000000 00000000000 14177515506 0020271 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/bundle/.gitignore 0000664 0000000 0000000 00000000021 14177515506 0022252 0 ustar 00root root 0000000 0000000 testdata/out.got
golang-golang-x-tools-0.1.9+ds/cmd/bundle/main.go 0000664 0000000 0000000 00000031370 14177515506 0021550 0 ustar 00root root 0000000 0000000 // Copyright 2015 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.
// Bundle creates a single-source-file version of a source package
// suitable for inclusion in a particular target package.
//
// Usage:
//
// bundle [-o file] [-dst path] [-pkg name] [-prefix p] [-import old=new] [-tags build_constraints]
//
// The src argument specifies the import path of the package to bundle.
// The bundling of a directory of source files into a single source file
// necessarily imposes a number of constraints.
// The package being bundled must not use cgo; must not use conditional
// file compilation, whether with build tags or system-specific file names
// like code_amd64.go; must not depend on any special comments, which
// may not be preserved; must not use any assembly sources;
// must not use renaming imports; and must not use reflection-based APIs
// that depend on the specific names of types or struct fields.
//
// By default, bundle writes the bundled code to standard output.
// If the -o argument is given, bundle writes to the named file
// and also includes a ``//go:generate'' comment giving the exact
// command line used, for regenerating the file with ``go generate.''
//
// Bundle customizes its output for inclusion in a particular package, the destination package.
// By default bundle assumes the destination is the package in the current directory,
// but the destination package can be specified explicitly using the -dst option,
// which takes an import path as its argument.
// If the source package imports the destination package, bundle will remove
// those imports and rewrite any references to use direct references to the
// corresponding symbols.
// Bundle also must write a package declaration in the output and must
// choose a name to use in that declaration.
// If the -pkg option is given, bundle uses that name.
// Otherwise, the name of the destination package is used.
// Build constraints for the generated file can be specified using the -tags option.
//
// To avoid collisions, bundle inserts a prefix at the beginning of
// every package-level const, func, type, and var identifier in src's code,
// updating references accordingly. The default prefix is the package name
// of the source package followed by an underscore. The -prefix option
// specifies an alternate prefix.
//
// Occasionally it is necessary to rewrite imports during the bundling
// process. The -import option, which may be repeated, specifies that
// an import of "old" should be rewritten to import "new" instead.
//
// Example
//
// Bundle archive/zip for inclusion in cmd/dist:
//
// cd $GOROOT/src/cmd/dist
// bundle -o zip.go archive/zip
//
// Bundle golang.org/x/net/http2 for inclusion in net/http,
// prefixing all identifiers by "http2" instead of "http2_", and
// including a "!nethttpomithttp2" build constraint:
//
// cd $GOROOT/src/net/http
// bundle -o h2_bundle.go -prefix http2 -tags '!nethttpomithttp2' golang.org/x/net/http2
//
// Update the http2 bundle in net/http:
//
// go generate net/http
//
// Update all bundles in the standard library:
//
// go generate -run bundle std
//
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/printer"
"go/token"
"go/types"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"golang.org/x/tools/go/packages"
)
var (
outputFile = flag.String("o", "", "write output to `file` (default standard output)")
dstPath = flag.String("dst", ".", "set destination import `path`")
pkgName = flag.String("pkg", "", "set destination package `name`")
prefix = flag.String("prefix", "&_", "set bundled identifier prefix to `p` (default is \"&_\", where & stands for the original name)")
buildTags = flag.String("tags", "", "the build constraints to be inserted into the generated file")
importMap = map[string]string{}
)
func init() {
flag.Var(flagFunc(addImportMap), "import", "rewrite import using `map`, of form old=new (can be repeated)")
}
func addImportMap(s string) {
if strings.Count(s, "=") != 1 {
log.Fatal("-import argument must be of the form old=new")
}
i := strings.Index(s, "=")
old, new := s[:i], s[i+1:]
if old == "" || new == "" {
log.Fatal("-import argument must be of the form old=new; old and new must be non-empty")
}
importMap[old] = new
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: bundle [options] \n")
flag.PrintDefaults()
}
func main() {
log.SetPrefix("bundle: ")
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) != 1 {
usage()
os.Exit(2)
}
cfg := &packages.Config{Mode: packages.NeedName}
pkgs, err := packages.Load(cfg, *dstPath)
if err != nil {
log.Fatalf("cannot load destination package: %v", err)
}
if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
log.Fatalf("failed to load destination package")
}
if *pkgName == "" {
*pkgName = pkgs[0].Name
}
code, err := bundle(args[0], pkgs[0].PkgPath, *pkgName, *prefix, *buildTags)
if err != nil {
log.Fatal(err)
}
if *outputFile != "" {
err := ioutil.WriteFile(*outputFile, code, 0666)
if err != nil {
log.Fatal(err)
}
} else {
_, err := os.Stdout.Write(code)
if err != nil {
log.Fatal(err)
}
}
}
// isStandardImportPath is copied from cmd/go in the standard library.
func isStandardImportPath(path string) bool {
i := strings.Index(path, "/")
if i < 0 {
i = len(path)
}
elem := path[:i]
return !strings.Contains(elem, ".")
}
var testingOnlyPackagesConfig *packages.Config
func bundle(src, dst, dstpkg, prefix, buildTags string) ([]byte, error) {
// Load the initial package.
cfg := &packages.Config{}
if testingOnlyPackagesConfig != nil {
*cfg = *testingOnlyPackagesConfig
} else {
// Bypass default vendor mode, as we need a package not available in the
// std module vendor folder.
cfg.Env = append(os.Environ(), "GOFLAGS=-mod=mod")
}
cfg.Mode = packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo
pkgs, err := packages.Load(cfg, src)
if err != nil {
return nil, err
}
if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
return nil, fmt.Errorf("failed to load source package")
}
pkg := pkgs[0]
if strings.Contains(prefix, "&") {
prefix = strings.Replace(prefix, "&", pkg.Syntax[0].Name.Name, -1)
}
objsToUpdate := make(map[types.Object]bool)
var rename func(from types.Object)
rename = func(from types.Object) {
if !objsToUpdate[from] {
objsToUpdate[from] = true
// Renaming a type that is used as an embedded field
// requires renaming the field too. e.g.
// type T int // if we rename this to U..
// var s struct {T}
// print(s.T) // ...this must change too
if _, ok := from.(*types.TypeName); ok {
for id, obj := range pkg.TypesInfo.Uses {
if obj == from {
if field := pkg.TypesInfo.Defs[id]; field != nil {
rename(field)
}
}
}
}
}
}
// Rename each package-level object.
scope := pkg.Types.Scope()
for _, name := range scope.Names() {
rename(scope.Lookup(name))
}
var out bytes.Buffer
if buildTags != "" {
fmt.Fprintf(&out, "//go:build %s\n", buildTags)
fmt.Fprintf(&out, "// +build %s\n\n", buildTags)
}
fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.\n")
if *outputFile != "" && buildTags == "" {
fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " "))
} else {
fmt.Fprintf(&out, "// $ bundle %s\n", strings.Join(os.Args[1:], " "))
}
fmt.Fprintf(&out, "\n")
// Concatenate package comments from all files...
for _, f := range pkg.Syntax {
if doc := f.Doc.Text(); strings.TrimSpace(doc) != "" {
for _, line := range strings.Split(doc, "\n") {
fmt.Fprintf(&out, "// %s\n", line)
}
}
}
// ...but don't let them become the actual package comment.
fmt.Fprintln(&out)
fmt.Fprintf(&out, "package %s\n\n", dstpkg)
// BUG(adonovan,shurcooL): bundle may generate incorrect code
// due to shadowing between identifiers and imported package names.
//
// The generated code will either fail to compile or
// (unlikely) compile successfully but have different behavior
// than the original package. The risk of this happening is higher
// when the original package has renamed imports (they're typically
// renamed in order to resolve a shadow inside that particular .go file).
// TODO(adonovan,shurcooL):
// - detect shadowing issues, and either return error or resolve them
// - preserve comments from the original import declarations.
// pkgStd and pkgExt are sets of printed import specs. This is done
// to deduplicate instances of the same import name and path.
var pkgStd = make(map[string]bool)
var pkgExt = make(map[string]bool)
for _, f := range pkg.Syntax {
for _, imp := range f.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil {
log.Fatalf("invalid import path string: %v", err) // Shouldn't happen here since packages.Load succeeded.
}
if path == dst {
continue
}
if newPath, ok := importMap[path]; ok {
path = newPath
}
var name string
if imp.Name != nil {
name = imp.Name.Name
}
spec := fmt.Sprintf("%s %q", name, path)
if isStandardImportPath(path) {
pkgStd[spec] = true
} else {
pkgExt[spec] = true
}
}
}
// Print a single declaration that imports all necessary packages.
fmt.Fprintln(&out, "import (")
for p := range pkgStd {
fmt.Fprintf(&out, "\t%s\n", p)
}
if len(pkgExt) > 0 {
fmt.Fprintln(&out)
}
for p := range pkgExt {
fmt.Fprintf(&out, "\t%s\n", p)
}
fmt.Fprint(&out, ")\n\n")
// Modify and print each file.
for _, f := range pkg.Syntax {
// Update renamed identifiers.
for id, obj := range pkg.TypesInfo.Defs {
if objsToUpdate[obj] {
id.Name = prefix + obj.Name()
}
}
for id, obj := range pkg.TypesInfo.Uses {
if objsToUpdate[obj] {
id.Name = prefix + obj.Name()
}
}
// For each qualified identifier that refers to the
// destination package, remove the qualifier.
// The "@@@." strings are removed in postprocessing.
ast.Inspect(f, func(n ast.Node) bool {
if sel, ok := n.(*ast.SelectorExpr); ok {
if id, ok := sel.X.(*ast.Ident); ok {
if obj, ok := pkg.TypesInfo.Uses[id].(*types.PkgName); ok {
if obj.Imported().Path() == dst {
id.Name = "@@@"
}
}
}
}
return true
})
last := f.Package
if len(f.Imports) > 0 {
imp := f.Imports[len(f.Imports)-1]
last = imp.End()
if imp.Comment != nil {
if e := imp.Comment.End(); e > last {
last = e
}
}
}
// Pretty-print package-level declarations.
// but no package or import declarations.
var buf bytes.Buffer
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
continue
}
beg, end := sourceRange(decl)
printComments(&out, f.Comments, last, beg)
buf.Reset()
format.Node(&buf, pkg.Fset, &printer.CommentedNode{Node: decl, Comments: f.Comments})
// Remove each "@@@." in the output.
// TODO(adonovan): not hygienic.
out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
last = printSameLineComment(&out, f.Comments, pkg.Fset, end)
out.WriteString("\n\n")
}
printLastComments(&out, f.Comments, last)
}
// Now format the entire thing.
result, err := format.Source(out.Bytes())
if err != nil {
log.Fatalf("formatting failed: %v", err)
}
return result, nil
}
// sourceRange returns the [beg, end) interval of source code
// belonging to decl (incl. associated comments).
func sourceRange(decl ast.Decl) (beg, end token.Pos) {
beg = decl.Pos()
end = decl.End()
var doc, com *ast.CommentGroup
switch d := decl.(type) {
case *ast.GenDecl:
doc = d.Doc
if len(d.Specs) > 0 {
switch spec := d.Specs[len(d.Specs)-1].(type) {
case *ast.ValueSpec:
com = spec.Comment
case *ast.TypeSpec:
com = spec.Comment
}
}
case *ast.FuncDecl:
doc = d.Doc
}
if doc != nil {
beg = doc.Pos()
}
if com != nil && com.End() > end {
end = com.End()
}
return beg, end
}
func printComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos, end token.Pos) {
for _, cg := range comments {
if pos <= cg.Pos() && cg.Pos() < end {
for _, c := range cg.List {
fmt.Fprintln(out, c.Text)
}
fmt.Fprintln(out)
}
}
}
const infinity = 1 << 30
func printLastComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos token.Pos) {
printComments(out, comments, pos, infinity)
}
func printSameLineComment(out *bytes.Buffer, comments []*ast.CommentGroup, fset *token.FileSet, pos token.Pos) token.Pos {
tf := fset.File(pos)
for _, cg := range comments {
if pos <= cg.Pos() && tf.Line(cg.Pos()) == tf.Line(pos) {
for _, c := range cg.List {
fmt.Fprintln(out, c.Text)
}
return cg.End()
}
}
return pos
}
type flagFunc func(string)
func (f flagFunc) Set(s string) error {
f(s)
return nil
}
func (f flagFunc) String() string { return "" }
golang-golang-x-tools-0.1.9+ds/cmd/bundle/main_test.go 0000664 0000000 0000000 00000003534 14177515506 0022610 0 ustar 00root root 0000000 0000000 // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"runtime"
"testing"
"golang.org/x/tools/go/packages/packagestest"
)
func TestBundle(t *testing.T) { packagestest.TestAll(t, testBundle) }
func testBundle(t *testing.T, x packagestest.Exporter) {
load := func(name string) string {
data, err := ioutil.ReadFile(name)
if err != nil {
t.Fatal(err)
}
return string(data)
}
e := packagestest.Export(t, x, []packagestest.Module{
{
Name: "initial",
Files: map[string]interface{}{
"a.go": load("testdata/src/initial/a.go"),
"b.go": load("testdata/src/initial/b.go"),
"c.go": load("testdata/src/initial/c.go"),
},
},
{
Name: "domain.name/importdecl",
Files: map[string]interface{}{
"p.go": load("testdata/src/domain.name/importdecl/p.go"),
},
},
})
defer e.Cleanup()
testingOnlyPackagesConfig = e.Config
os.Args = os.Args[:1] // avoid e.g. -test=short in the output
out, err := bundle("initial", "github.com/dest", "dest", "prefix", "tag")
if err != nil {
t.Fatal(err)
}
if got, want := string(out), load("testdata/out.golden"); got != want {
t.Errorf("-- got --\n%s\n-- want --\n%s\n-- diff --", got, want)
if err := ioutil.WriteFile("testdata/out.got", out, 0644); err != nil {
t.Fatal(err)
}
t.Log(diff("testdata/out.golden", "testdata/out.got"))
}
}
func diff(a, b string) string {
var cmd *exec.Cmd
switch runtime.GOOS {
case "plan9":
cmd = exec.Command("/bin/diff", "-c", a, b)
default:
cmd = exec.Command("/usr/bin/diff", "-u", a, b)
}
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
cmd.Run() // nonzero exit is expected
if out.Len() == 0 {
return "(failed to compute diff)"
}
return out.String()
}
golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/ 0000775 0000000 0000000 00000000000 14177515506 0022102 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/out.golden 0000664 0000000 0000000 00000001521 14177515506 0024102 0 ustar 00root root 0000000 0000000 //go:build tag
// +build tag
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
// $ bundle
// The package doc comment
//
package dest
import (
"fmt"
. "fmt"
_ "fmt"
renamedfmt "fmt"
renamedfmt2 "fmt"
"domain.name/importdecl"
)
// init functions are not renamed
func init() { prefixfoo() }
// Type S.
type prefixS struct {
prefixt
u int
} /* multi-line
comment
*/
// non-associated comment
/*
non-associated comment2
*/
// Function bar.
func prefixbar(s *prefixS) {
fmt.Println(s.prefixt, s.u) // comment inside function
}
// file-end comment
type prefixt int // type1
// const1
const prefixc = 1 // const2
func prefixfoo() {
fmt.Println(importdecl.F())
}
// zinit
const (
prefixz1 = iota // z1
prefixz2 // z2
) // zend
func prefixbaz() {
renamedfmt.Println()
renamedfmt2.Println()
Println()
}
golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/ 0000775 0000000 0000000 00000000000 14177515506 0022671 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/domain.name/ 0000775 0000000 0000000 00000000000 14177515506 0025057 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/domain.name/importdecl/ 0000775 0000000 0000000 00000000000 14177515506 0027221 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/domain.name/importdecl/p.go 0000664 0000000 0000000 00000000056 14177515506 0030010 0 ustar 00root root 0000000 0000000 package importdecl
func F() int { return 1 }
golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/initial/ 0000775 0000000 0000000 00000000000 14177515506 0024322 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/initial/a.go 0000664 0000000 0000000 00000000545 14177515506 0025075 0 ustar 00root root 0000000 0000000 package initial
import "fmt" // this comment should not be visible
// init functions are not renamed
func init() { foo() }
// Type S.
type S struct {
t
u int
} /* multi-line
comment
*/
// non-associated comment
/*
non-associated comment2
*/
// Function bar.
func bar(s *S) {
fmt.Println(s.t, s.u) // comment inside function
}
// file-end comment
golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/initial/b.go 0000664 0000000 0000000 00000000372 14177515506 0025074 0 ustar 00root root 0000000 0000000 // The package doc comment
package initial
import (
"fmt"
"domain.name/importdecl"
)
type t int // type1
// const1
const c = 1 // const2
func foo() {
fmt.Println(importdecl.F())
}
// zinit
const (
z1 = iota // z1
z2 // z2
) // zend
golang-golang-x-tools-0.1.9+ds/cmd/bundle/testdata/src/initial/c.go 0000664 0000000 0000000 00000000250 14177515506 0025070 0 ustar 00root root 0000000 0000000 package initial
import _ "fmt"
import renamedfmt "fmt"
import renamedfmt2 "fmt"
import . "fmt"
func baz() {
renamedfmt.Println()
renamedfmt2.Println()
Println()
}
golang-golang-x-tools-0.1.9+ds/cmd/callgraph/ 0000775 0000000 0000000 00000000000 14177515506 0020755 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/callgraph/main.go 0000664 0000000 0000000 00000022574 14177515506 0022242 0 ustar 00root root 0000000 0000000 // Copyright 2014 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.
// callgraph: a tool for reporting the call graph of a Go program.
// See Usage for details, or run with -help.
package main // import "golang.org/x/tools/cmd/callgraph"
// TODO(adonovan):
//
// Features:
// - restrict graph to a single package
// - output
// - functions reachable from root (use digraph tool?)
// - unreachable functions (use digraph tool?)
// - dynamic (runtime) types
// - indexed output (numbered nodes)
// - JSON output
// - additional template fields:
// callee file/line/col
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/build"
"go/token"
"io"
"log"
"os"
"runtime"
"text/template"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/callgraph/rta"
"golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// flags
var (
algoFlag = flag.String("algo", "rta",
`Call graph construction algorithm (static, cha, rta, pta)`)
testFlag = flag.Bool("test", false,
"Loads test code (*_test.go) for imported packages")
formatFlag = flag.String("format",
"{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}",
"A template expression specifying how to format an edge")
ptalogFlag = flag.String("ptalog", "",
"Location of the points-to analysis log file, or empty to disable logging.")
)
func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
}
const Usage = `callgraph: display the call graph of a Go program.
Usage:
callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] package...
Flags:
-algo Specifies the call-graph construction algorithm, one of:
static static calls only (unsound)
cha Class Hierarchy Analysis
rta Rapid Type Analysis
pta inclusion-based Points-To Analysis
The algorithms are ordered by increasing precision in their
treatment of dynamic calls (and thus also computational cost).
RTA and PTA require a whole program (main or test), and
include only functions reachable from main.
-test Include the package's tests in the analysis.
-format Specifies the format in which each call graph edge is displayed.
One of:
digraph output suitable for input to
golang.org/x/tools/cmd/digraph.
graphviz output in AT&T GraphViz (.dot) format.
All other values are interpreted using text/template syntax.
The default value is:
{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}
The structure passed to the template is (effectively):
type Edge struct {
Caller *ssa.Function // calling function
Callee *ssa.Function // called function
// Call site:
Filename string // containing file
Offset int // offset within file of '('
Line int // line number
Column int // column number of call
Dynamic string // "static" or "dynamic"
Description string // e.g. "static method call"
}
Caller and Callee are *ssa.Function values, which print as
"(*sync/atomic.Mutex).Lock", but other attributes may be
derived from them, e.g. Caller.Pkg.Pkg.Path yields the
import path of the enclosing package. Consult the go/ssa
API documentation for details.
Examples:
Show the call graph of the trivial web server application:
callgraph -format digraph $GOROOT/src/net/http/triv.go
Same, but show only the packages of each function:
callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \
$GOROOT/src/net/http/triv.go | sort | uniq
Show functions that make dynamic calls into the 'fmt' test package,
using the pointer analysis algorithm:
callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=pta fmt |
sed -ne 's/-dynamic-/--/p' |
sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq
Show all functions directly called by the callgraph tool's main function:
callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
digraph succs golang.org/x/tools/cmd/callgraph.main
`
func init() {
// If $GOMAXPROCS isn't set, use the full capacity of the machine.
// For small machines, use at least 4 threads.
if os.Getenv("GOMAXPROCS") == "" {
n := runtime.NumCPU()
if n < 4 {
n = 4
}
runtime.GOMAXPROCS(n)
}
}
func main() {
flag.Parse()
if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
os.Exit(1)
}
}
var stdout io.Writer = os.Stdout
func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error {
if len(args) == 0 {
fmt.Fprint(os.Stderr, Usage)
return nil
}
cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Tests: tests,
Dir: dir,
}
if gopath != "" {
cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
}
initial, err := packages.Load(cfg, args...)
if err != nil {
return err
}
if packages.PrintErrors(initial) > 0 {
return fmt.Errorf("packages contain errors")
}
// Create and build SSA-form program representation.
prog, pkgs := ssautil.AllPackages(initial, 0)
prog.Build()
// -- call graph construction ------------------------------------------
var cg *callgraph.Graph
switch algo {
case "static":
cg = static.CallGraph(prog)
case "cha":
cg = cha.CallGraph(prog)
case "pta":
// Set up points-to analysis log file.
var ptalog io.Writer
if *ptalogFlag != "" {
if f, err := os.Create(*ptalogFlag); err != nil {
log.Fatalf("Failed to create PTA log file: %s", err)
} else {
buf := bufio.NewWriter(f)
ptalog = buf
defer func() {
if err := buf.Flush(); err != nil {
log.Printf("flush: %s", err)
}
if err := f.Close(); err != nil {
log.Printf("close: %s", err)
}
}()
}
}
mains, err := mainPackages(pkgs)
if err != nil {
return err
}
config := &pointer.Config{
Mains: mains,
BuildCallGraph: true,
Log: ptalog,
}
ptares, err := pointer.Analyze(config)
if err != nil {
return err // internal error in pointer analysis
}
cg = ptares.CallGraph
case "rta":
mains, err := mainPackages(pkgs)
if err != nil {
return err
}
var roots []*ssa.Function
for _, main := range mains {
roots = append(roots, main.Func("init"), main.Func("main"))
}
rtares := rta.Analyze(roots, true)
cg = rtares.CallGraph
// NB: RTA gives us Reachable and RuntimeTypes too.
default:
return fmt.Errorf("unknown algorithm: %s", algo)
}
cg.DeleteSyntheticNodes()
// -- output------------------------------------------------------------
var before, after string
// Pre-canned formats.
switch format {
case "digraph":
format = `{{printf "%q %q" .Caller .Callee}}`
case "graphviz":
before = "digraph callgraph {\n"
after = "}\n"
format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}`
}
tmpl, err := template.New("-format").Parse(format)
if err != nil {
return fmt.Errorf("invalid -format template: %v", err)
}
// Allocate these once, outside the traversal.
var buf bytes.Buffer
data := Edge{fset: prog.Fset}
fmt.Fprint(stdout, before)
if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
data.position.Offset = -1
data.edge = edge
data.Caller = edge.Caller.Func
data.Callee = edge.Callee.Func
buf.Reset()
if err := tmpl.Execute(&buf, &data); err != nil {
return err
}
stdout.Write(buf.Bytes())
if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
fmt.Fprintln(stdout)
}
return nil
}); err != nil {
return err
}
fmt.Fprint(stdout, after)
return nil
}
// mainPackages returns the main packages to analyze.
// Each resulting package is named "main" and has a main function.
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
var mains []*ssa.Package
for _, p := range pkgs {
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
mains = append(mains, p)
}
}
if len(mains) == 0 {
return nil, fmt.Errorf("no main packages")
}
return mains, nil
}
type Edge struct {
Caller *ssa.Function
Callee *ssa.Function
edge *callgraph.Edge
fset *token.FileSet
position token.Position // initialized lazily
}
func (e *Edge) pos() *token.Position {
if e.position.Offset == -1 {
e.position = e.fset.Position(e.edge.Pos()) // called lazily
}
return &e.position
}
func (e *Edge) Filename() string { return e.pos().Filename }
func (e *Edge) Column() int { return e.pos().Column }
func (e *Edge) Line() int { return e.pos().Line }
func (e *Edge) Offset() int { return e.pos().Offset }
func (e *Edge) Dynamic() string {
if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
return "dynamic"
}
return "static"
}
func (e *Edge) Description() string { return e.edge.Description() }
golang-golang-x-tools-0.1.9+ds/cmd/callgraph/main_test.go 0000664 0000000 0000000 00000005172 14177515506 0023274 0 ustar 00root root 0000000 0000000 // Copyright 2014 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.
// No testdata on Android.
//go:build !android && go1.11
// +build !android,go1.11
package main
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/internal/testenv"
)
func init() {
// This test currently requires GOPATH mode.
// Explicitly disabling module mode should suffix, but
// we'll also turn off GOPROXY just for good measure.
if err := os.Setenv("GO111MODULE", "off"); err != nil {
log.Fatal(err)
}
if err := os.Setenv("GOPROXY", "off"); err != nil {
log.Fatal(err)
}
}
func TestCallgraph(t *testing.T) {
if b := os.Getenv("GO_BUILDER_NAME"); b == "windows-arm64-10" {
t.Skipf("skipping due to suspected file corruption bug on %s builder (https://go.dev/issue/50706)", b)
}
testenv.NeedsTool(t, "go")
gopath, err := filepath.Abs("testdata")
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
algo string
tests bool
want []string
}{
{"rta", false, []string{
// rta imprecisely shows cross product of {main,main2} x {C,D}
`pkg.main --> (pkg.C).f`,
`pkg.main --> (pkg.D).f`,
`pkg.main --> pkg.main2`,
`pkg.main2 --> (pkg.C).f`,
`pkg.main2 --> (pkg.D).f`,
}},
{"pta", false, []string{
// pta distinguishes main->C, main2->D. Also has a root node.
` --> pkg.init`,
` --> pkg.main`,
`pkg.main --> (pkg.C).f`,
`pkg.main --> pkg.main2`,
`pkg.main2 --> (pkg.D).f`,
}},
// tests: both the package's main and the test's main are called.
// The callgraph includes all the guts of the "testing" package.
{"rta", true, []string{
`pkg.test.main --> testing.MainStart`,
`testing.runExample --> pkg.Example`,
`pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}},
{"pta", true, []string{
` --> pkg.test.main`,
` --> pkg.main`,
`pkg.test.main --> testing.MainStart`,
`testing.runExample --> pkg.Example`,
`pkg.Example --> (pkg.C).f`,
`pkg.main --> (pkg.C).f`,
}},
} {
const format = "{{.Caller}} --> {{.Callee}}"
stdout = new(bytes.Buffer)
if err := doCallgraph("testdata/src", gopath, test.algo, format, test.tests, []string{"pkg"}); err != nil {
t.Error(err)
continue
}
edges := make(map[string]bool)
for _, line := range strings.Split(fmt.Sprint(stdout), "\n") {
edges[line] = true
}
for _, edge := range test.want {
if !edges[edge] {
t.Errorf("callgraph(%q, %t): missing edge: %s",
test.algo, test.tests, edge)
}
}
if t.Failed() {
t.Log("got:\n", stdout)
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/callgraph/testdata/ 0000775 0000000 0000000 00000000000 14177515506 0022566 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/callgraph/testdata/src/ 0000775 0000000 0000000 00000000000 14177515506 0023355 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/callgraph/testdata/src/pkg/ 0000775 0000000 0000000 00000000000 14177515506 0024136 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/callgraph/testdata/src/pkg/pkg.go 0000664 0000000 0000000 00000000335 14177515506 0025247 0 ustar 00root root 0000000 0000000 package main
type I interface {
f()
}
type C int
func (C) f() {}
type D int
func (D) f() {}
func main() {
var i I = C(0)
i.f() // dynamic call
main2()
}
func main2() {
var i I = D(0)
i.f() // dynamic call
}
golang-golang-x-tools-0.1.9+ds/cmd/callgraph/testdata/src/pkg/pkg_test.go 0000664 0000000 0000000 00000000277 14177515506 0026313 0 ustar 00root root 0000000 0000000 package main
// An Example function must have an "Output:" comment for the go build
// system to generate a call to it from the test main package.
func Example() {
C(0).f()
// Output:
}
golang-golang-x-tools-0.1.9+ds/cmd/compilebench/ 0000775 0000000 0000000 00000000000 14177515506 0021450 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/compilebench/main.go 0000664 0000000 0000000 00000033305 14177515506 0022727 0 ustar 00root root 0000000 0000000 // Copyright 2015 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.
// Compilebench benchmarks the speed of the Go compiler.
//
// Usage:
//
// compilebench [options]
//
// It times the compilation of various packages and prints results in
// the format used by package testing (and expected by golang.org/x/perf/cmd/benchstat).
//
// The options are:
//
// -alloc
// Report allocations.
//
// -compile exe
// Use exe as the path to the cmd/compile binary.
//
// -compileflags 'list'
// Pass the space-separated list of flags to the compilation.
//
// -link exe
// Use exe as the path to the cmd/link binary.
//
// -linkflags 'list'
// Pass the space-separated list of flags to the linker.
//
// -count n
// Run each benchmark n times (default 1).
//
// -cpuprofile file
// Write a CPU profile of the compiler to file.
//
// -go path
// Path to "go" command (default "go").
//
// -memprofile file
// Write a memory profile of the compiler to file.
//
// -memprofilerate rate
// Set runtime.MemProfileRate during compilation.
//
// -obj
// Report object file statistics.
//
// -pkg pkg
// Benchmark compiling a single package.
//
// -run regexp
// Only run benchmarks with names matching regexp.
//
// -short
// Skip long-running benchmarks.
//
// Although -cpuprofile and -memprofile are intended to write a
// combined profile for all the executed benchmarks to file,
// today they write only the profile for the last benchmark executed.
//
// The default memory profiling rate is one profile sample per 512 kB
// allocated (see ``go doc runtime.MemProfileRate'').
// Lowering the rate (for example, -memprofilerate 64000) produces
// a more fine-grained and therefore accurate profile, but it also incurs
// execution cost. For benchmark comparisons, never use timings
// obtained with a low -memprofilerate option.
//
// Example
//
// Assuming the base version of the compiler has been saved with
// ``toolstash save,'' this sequence compares the old and new compiler:
//
// compilebench -count 10 -compile $(toolstash -n compile) >old.txt
// compilebench -count 10 >new.txt
// benchstat old.txt new.txt
//
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
exec "golang.org/x/sys/execabs"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
var (
goroot string
compiler string
linker string
runRE *regexp.Regexp
is6g bool
)
var (
flagGoCmd = flag.String("go", "go", "path to \"go\" command")
flagAlloc = flag.Bool("alloc", false, "report allocations")
flagObj = flag.Bool("obj", false, "report object file stats")
flagCompiler = flag.String("compile", "", "use `exe` as the cmd/compile binary")
flagCompilerFlags = flag.String("compileflags", "", "additional `flags` to pass to compile")
flagLinker = flag.String("link", "", "use `exe` as the cmd/link binary")
flagLinkerFlags = flag.String("linkflags", "", "additional `flags` to pass to link")
flagRun = flag.String("run", "", "run benchmarks matching `regexp`")
flagCount = flag.Int("count", 1, "run benchmarks `n` times")
flagCpuprofile = flag.String("cpuprofile", "", "write CPU profile to `file`")
flagMemprofile = flag.String("memprofile", "", "write memory profile to `file`")
flagMemprofilerate = flag.Int64("memprofilerate", -1, "set memory profile `rate`")
flagPackage = flag.String("pkg", "", "if set, benchmark the package at path `pkg`")
flagShort = flag.Bool("short", false, "skip long-running benchmarks")
)
type test struct {
name string
r runner
}
type runner interface {
long() bool
run(name string, count int) error
}
var tests = []test{
{"BenchmarkTemplate", compile{"html/template"}},
{"BenchmarkUnicode", compile{"unicode"}},
{"BenchmarkGoTypes", compile{"go/types"}},
{"BenchmarkCompiler", compile{"cmd/compile/internal/gc"}},
{"BenchmarkSSA", compile{"cmd/compile/internal/ssa"}},
{"BenchmarkFlate", compile{"compress/flate"}},
{"BenchmarkGoParser", compile{"go/parser"}},
{"BenchmarkReflect", compile{"reflect"}},
{"BenchmarkTar", compile{"archive/tar"}},
{"BenchmarkXML", compile{"encoding/xml"}},
{"BenchmarkLinkCompiler", link{"cmd/compile", ""}},
{"BenchmarkExternalLinkCompiler", link{"cmd/compile", "-linkmode=external"}},
{"BenchmarkLinkWithoutDebugCompiler", link{"cmd/compile", "-w"}},
{"BenchmarkStdCmd", goBuild{[]string{"std", "cmd"}}},
{"BenchmarkHelloSize", size{"$GOROOT/test/helloworld.go", false}},
{"BenchmarkCmdGoSize", size{"cmd/go", true}},
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: compilebench [options]\n")
fmt.Fprintf(os.Stderr, "options:\n")
flag.PrintDefaults()
os.Exit(2)
}
func main() {
log.SetFlags(0)
log.SetPrefix("compilebench: ")
flag.Usage = usage
flag.Parse()
if flag.NArg() != 0 {
usage()
}
s, err := exec.Command(*flagGoCmd, "env", "GOROOT").CombinedOutput()
if err != nil {
log.Fatalf("%s env GOROOT: %v", *flagGoCmd, err)
}
goroot = strings.TrimSpace(string(s))
os.Setenv("GOROOT", goroot) // for any subcommands
compiler = *flagCompiler
if compiler == "" {
var foundTool string
foundTool, compiler = toolPath("compile", "6g")
if foundTool == "6g" {
is6g = true
}
}
linker = *flagLinker
if linker == "" && !is6g { // TODO: Support 6l
_, linker = toolPath("link")
}
if is6g {
*flagMemprofilerate = -1
*flagAlloc = false
*flagCpuprofile = ""
*flagMemprofile = ""
}
if *flagRun != "" {
r, err := regexp.Compile(*flagRun)
if err != nil {
log.Fatalf("invalid -run argument: %v", err)
}
runRE = r
}
if *flagPackage != "" {
tests = []test{
{"BenchmarkPkg", compile{*flagPackage}},
{"BenchmarkPkgLink", link{*flagPackage, ""}},
}
runRE = nil
}
for i := 0; i < *flagCount; i++ {
for _, tt := range tests {
if tt.r.long() && *flagShort {
continue
}
if runRE == nil || runRE.MatchString(tt.name) {
if err := tt.r.run(tt.name, i); err != nil {
log.Printf("%s: %v", tt.name, err)
}
}
}
}
}
func toolPath(names ...string) (found, path string) {
var out1 []byte
var err1 error
for i, name := range names {
out, err := exec.Command(*flagGoCmd, "tool", "-n", name).CombinedOutput()
if err == nil {
return name, strings.TrimSpace(string(out))
}
if i == 0 {
out1, err1 = out, err
}
}
log.Fatalf("go tool -n %s: %v\n%s", names[0], err1, out1)
return "", ""
}
type Pkg struct {
Dir string
GoFiles []string
}
func goList(dir string) (*Pkg, error) {
var pkg Pkg
out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output()
if err != nil {
return nil, fmt.Errorf("go list -json %s: %v", 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()
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%v\n%s", err, out)
}
fmt.Printf("%s 1 %d ns/op\n", name, time.Since(start).Nanoseconds())
return nil
}
type goBuild struct{ pkgs []string }
func (goBuild) long() bool { return true }
func (r goBuild) run(name string, count int) error {
args := []string{"build", "-a"}
if *flagCompilerFlags != "" {
args = append(args, "-gcflags", *flagCompilerFlags)
}
args = append(args, r.pkgs...)
cmd := exec.Command(*flagGoCmd, args...)
cmd.Dir = filepath.Join(goroot, "src")
return runCmd(name, cmd)
}
type size struct {
// path is either a path to a file ("$GOROOT/test/helloworld.go") or a package path ("cmd/go").
path string
isLong bool
}
func (r size) long() bool { return r.isLong }
func (r size) run(name string, count int) error {
if strings.HasPrefix(r.path, "$GOROOT/") {
r.path = goroot + "/" + r.path[len("$GOROOT/"):]
}
cmd := exec.Command(*flagGoCmd, "build", "-o", "_compilebenchout_", r.path)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
defer os.Remove("_compilebenchout_")
info, err := os.Stat("_compilebenchout_")
if err != nil {
return err
}
out, err := exec.Command("size", "_compilebenchout_").CombinedOutput()
if err != nil {
return fmt.Errorf("size: %v\n%s", err, out)
}
lines := strings.Split(string(out), "\n")
if len(lines) < 2 {
return fmt.Errorf("not enough output from size: %s", out)
}
f := strings.Fields(lines[1])
if strings.HasPrefix(lines[0], "__TEXT") && len(f) >= 2 { // OS X
fmt.Printf("%s 1 %s text-bytes %s data-bytes %v exe-bytes\n", name, f[0], f[1], info.Size())
} else if strings.Contains(lines[0], "bss") && len(f) >= 3 {
fmt.Printf("%s 1 %s text-bytes %s data-bytes %s bss-bytes %v exe-bytes\n", name, f[0], f[1], f[2], info.Size())
}
return nil
}
type compile struct{ dir string }
func (compile) long() bool { return false }
func (c compile) run(name string, count int) error {
// Make sure dependencies needed by go tool compile are installed to GOROOT/pkg.
out, err := exec.Command(*flagGoCmd, "build", "-i", c.dir).CombinedOutput()
if err != nil {
return fmt.Errorf("go build -i %s: %v\n%s", c.dir, err, out)
}
// Find dir and source file list.
pkg, err := goList(c.dir)
if err != nil {
return err
}
args := []string{"-o", "_compilebench_.o"}
args = append(args, strings.Fields(*flagCompilerFlags)...)
args = append(args, pkg.GoFiles...)
if err := runBuildCmd(name, count, pkg.Dir, compiler, args); err != nil {
return err
}
opath := pkg.Dir + "/_compilebench_.o"
if *flagObj {
// TODO(josharian): object files are big; just read enough to find what we seek.
data, err := ioutil.ReadFile(opath)
if err != nil {
log.Print(err)
}
// Find start of export data.
i := bytes.Index(data, []byte("\n$$B\n")) + len("\n$$B\n")
// Count bytes to end of export data.
nexport := bytes.Index(data[i:], []byte("\n$$\n"))
fmt.Printf(" %d object-bytes %d export-bytes", len(data), nexport)
}
fmt.Println()
os.Remove(opath)
return nil
}
type link struct{ dir, flags 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, strings.Fields(r.flags)...)
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, haveRSS := false, false
var allocs, allocbytes, rssbytes 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
case "MaxRSS":
haveRSS = true
rssbytes = 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)
}
if haveRSS {
fmt.Printf(" %d maxRSS/op", rssbytes)
}
return nil
}
golang-golang-x-tools-0.1.9+ds/cmd/cover/ 0000775 0000000 0000000 00000000000 14177515506 0020136 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/cover/README.md 0000664 0000000 0000000 00000000200 14177515506 0021405 0 ustar 00root root 0000000 0000000 # Deprecated
NOTE: For Go releases 1.5 and later, this tool lives in the standard repository. The code here is not maintained.
golang-golang-x-tools-0.1.9+ds/cmd/cover/cover.go 0000664 0000000 0000000 00000047212 14177515506 0021611 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
)
const usageMessage = "" +
`Usage of 'go tool cover':
Given a coverage profile produced by 'go test':
go test -coverprofile=c.out
Open a web browser displaying annotated source code:
go tool cover -html=c.out
Write out an HTML file instead of launching a web browser:
go tool cover -html=c.out -o coverage.html
Display coverage percentages to stdout for each function:
go tool cover -func=c.out
Finally, to generate modified source code with coverage annotations
(what go test -cover does):
go tool cover -mode=set -var=CoverageVariableName program.go
`
func usage() {
fmt.Fprint(os.Stderr, usageMessage)
fmt.Fprintln(os.Stderr, "\nFlags:")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "\n Only one of -html, -func, or -mode may be set.")
os.Exit(2)
}
var (
mode = flag.String("mode", "", "coverage mode: set, count, atomic")
varVar = flag.String("var", "GoCover", "name of coverage variable to generate")
output = flag.String("o", "", "file for output; default: stdout")
htmlOut = flag.String("html", "", "generate HTML representation of coverage profile")
funcOut = flag.String("func", "", "output coverage profile information for each function")
)
var profile string // The profile to read; the value of -html or -func
var counterStmt func(*File, ast.Expr) ast.Stmt
const (
atomicPackagePath = "sync/atomic"
atomicPackageName = "_cover_atomic_"
)
func main() {
flag.Usage = usage
flag.Parse()
// Usage information when no arguments.
if flag.NFlag() == 0 && flag.NArg() == 0 {
flag.Usage()
}
err := parseFlags()
if err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, `For usage information, run "go tool cover -help"`)
os.Exit(2)
}
// Generate coverage-annotated source.
if *mode != "" {
annotate(flag.Arg(0))
return
}
// Output HTML or function coverage information.
if *htmlOut != "" {
err = htmlOutput(profile, *output)
} else {
err = funcOutput(profile, *output)
}
if err != nil {
fmt.Fprintf(os.Stderr, "cover: %v\n", err)
os.Exit(2)
}
}
// parseFlags sets the profile and counterStmt globals and performs validations.
func parseFlags() error {
profile = *htmlOut
if *funcOut != "" {
if profile != "" {
return fmt.Errorf("too many options")
}
profile = *funcOut
}
// Must either display a profile or rewrite Go source.
if (profile == "") == (*mode == "") {
return fmt.Errorf("too many options")
}
if *mode != "" {
switch *mode {
case "set":
counterStmt = setCounterStmt
case "count":
counterStmt = incCounterStmt
case "atomic":
counterStmt = atomicCounterStmt
default:
return fmt.Errorf("unknown -mode %v", *mode)
}
if flag.NArg() == 0 {
return fmt.Errorf("missing source file")
} else if flag.NArg() == 1 {
return nil
}
} else if flag.NArg() == 0 {
return nil
}
return fmt.Errorf("too many arguments")
}
// Block represents the information about a basic block to be recorded in the analysis.
// Note: Our definition of basic block is based on control structures; we don't break
// apart && and ||. We could but it doesn't seem important enough to bother.
type Block struct {
startByte token.Pos
endByte token.Pos
numStmt int
}
// File is a wrapper for the state of a file used in the parser.
// The basic parse tree walker is a method of this type.
type File struct {
fset *token.FileSet
name string // Name of file.
astFile *ast.File
blocks []Block
atomicPkg string // Package name for "sync/atomic" in this file.
}
// Visit implements the ast.Visitor interface.
func (f *File) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.BlockStmt:
// If it's a switch or select, the body is a list of case clauses; don't tag the block itself.
if len(n.List) > 0 {
switch n.List[0].(type) {
case *ast.CaseClause: // switch
for _, n := range n.List {
clause := n.(*ast.CaseClause)
clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false)
}
return f
case *ast.CommClause: // select
for _, n := range n.List {
clause := n.(*ast.CommClause)
clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false)
}
return f
}
}
n.List = f.addCounters(n.Lbrace, n.Rbrace+1, n.List, true) // +1 to step past closing brace.
case *ast.IfStmt:
ast.Walk(f, n.Body)
if n.Else == nil {
return nil
}
// The elses are special, because if we have
// if x {
// } else if y {
// }
// we want to cover the "if y". To do this, we need a place to drop the counter,
// so we add a hidden block:
// if x {
// } else {
// if y {
// }
// }
switch stmt := n.Else.(type) {
case *ast.IfStmt:
block := &ast.BlockStmt{
Lbrace: n.Body.End(), // Start at end of the "if" block so the covered part looks like it starts at the "else".
List: []ast.Stmt{stmt},
Rbrace: stmt.End(),
}
n.Else = block
case *ast.BlockStmt:
stmt.Lbrace = n.Body.End() // Start at end of the "if" block so the covered part looks like it starts at the "else".
default:
panic("unexpected node type in if")
}
ast.Walk(f, n.Else)
return nil
case *ast.SelectStmt:
// Don't annotate an empty select - creates a syntax error.
if n.Body == nil || len(n.Body.List) == 0 {
return nil
}
case *ast.SwitchStmt:
// Don't annotate an empty switch - creates a syntax error.
if n.Body == nil || len(n.Body.List) == 0 {
return nil
}
case *ast.TypeSwitchStmt:
// Don't annotate an empty type switch - creates a syntax error.
if n.Body == nil || len(n.Body.List) == 0 {
return nil
}
}
return f
}
// unquote returns the unquoted string.
func unquote(s string) string {
t, err := strconv.Unquote(s)
if err != nil {
log.Fatalf("cover: improperly quoted string %q\n", s)
}
return t
}
// addImport adds an import for the specified path, if one does not already exist, and returns
// the local package name.
func (f *File) addImport(path string) string {
// Does the package already import it?
for _, s := range f.astFile.Imports {
if unquote(s.Path.Value) == path {
if s.Name != nil {
return s.Name.Name
}
return filepath.Base(path)
}
}
newImport := &ast.ImportSpec{
Name: ast.NewIdent(atomicPackageName),
Path: &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf("%q", path),
},
}
impDecl := &ast.GenDecl{
Tok: token.IMPORT,
Specs: []ast.Spec{
newImport,
},
}
// Make the new import the first Decl in the file.
astFile := f.astFile
astFile.Decls = append(astFile.Decls, nil)
copy(astFile.Decls[1:], astFile.Decls[0:])
astFile.Decls[0] = impDecl
astFile.Imports = append(astFile.Imports, newImport)
// Now refer to the package, just in case it ends up unused.
// That is, append to the end of the file the declaration
// var _ = _cover_atomic_.AddUint32
reference := &ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{
&ast.ValueSpec{
Names: []*ast.Ident{
ast.NewIdent("_"),
},
Values: []ast.Expr{
&ast.SelectorExpr{
X: ast.NewIdent(atomicPackageName),
Sel: ast.NewIdent("AddUint32"),
},
},
},
},
}
astFile.Decls = append(astFile.Decls, reference)
return atomicPackageName
}
var slashslash = []byte("//")
// initialComments returns the prefix of content containing only
// whitespace and line comments. Any +build directives must appear
// within this region. This approach is more reliable than using
// go/printer to print a modified AST containing comments.
//
func initialComments(content []byte) []byte {
// Derived from go/build.Context.shouldBuild.
end := 0
p := content
for len(p) > 0 {
line := p
if i := bytes.IndexByte(line, '\n'); i >= 0 {
line, p = line[:i], p[i+1:]
} else {
p = p[len(p):]
}
line = bytes.TrimSpace(line)
if len(line) == 0 { // Blank line.
end = len(content) - len(p)
continue
}
if !bytes.HasPrefix(line, slashslash) { // Not comment line.
break
}
}
return content[:end]
}
func annotate(name string) {
fset := token.NewFileSet()
content, err := ioutil.ReadFile(name)
if err != nil {
log.Fatalf("cover: %s: %s", name, err)
}
parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments)
if err != nil {
log.Fatalf("cover: %s: %s", name, err)
}
parsedFile.Comments = trimComments(parsedFile, fset)
file := &File{
fset: fset,
name: name,
astFile: parsedFile,
}
if *mode == "atomic" {
file.atomicPkg = file.addImport(atomicPackagePath)
}
ast.Walk(file, file.astFile)
fd := os.Stdout
if *output != "" {
var err error
fd, err = os.Create(*output)
if err != nil {
log.Fatalf("cover: %s", err)
}
}
fd.Write(initialComments(content)) // Retain '// +build' directives.
file.print(fd)
// After printing the source tree, add some declarations for the counters etc.
// We could do this by adding to the tree, but it's easier just to print the text.
file.addVariables(fd)
}
// trimComments drops all but the //go: comments, some of which are semantically important.
// We drop all others because they can appear in places that cause our counters
// to appear in syntactically incorrect places. //go: appears at the beginning of
// the line and is syntactically safe.
func trimComments(file *ast.File, fset *token.FileSet) []*ast.CommentGroup {
var comments []*ast.CommentGroup
for _, group := range file.Comments {
var list []*ast.Comment
for _, comment := range group.List {
if strings.HasPrefix(comment.Text, "//go:") && fset.Position(comment.Slash).Column == 1 {
list = append(list, comment)
}
}
if list != nil {
comments = append(comments, &ast.CommentGroup{List: list})
}
}
return comments
}
func (f *File) print(w io.Writer) {
printer.Fprint(w, f.fset, f.astFile)
}
// intLiteral returns an ast.BasicLit representing the integer value.
func (f *File) intLiteral(i int) *ast.BasicLit {
node := &ast.BasicLit{
Kind: token.INT,
Value: fmt.Sprint(i),
}
return node
}
// index returns an ast.BasicLit representing the number of counters present.
func (f *File) index() *ast.BasicLit {
return f.intLiteral(len(f.blocks))
}
// setCounterStmt returns the expression: __count[23] = 1.
func setCounterStmt(f *File, counter ast.Expr) ast.Stmt {
return &ast.AssignStmt{
Lhs: []ast.Expr{counter},
Tok: token.ASSIGN,
Rhs: []ast.Expr{f.intLiteral(1)},
}
}
// incCounterStmt returns the expression: __count[23]++.
func incCounterStmt(f *File, counter ast.Expr) ast.Stmt {
return &ast.IncDecStmt{
X: counter,
Tok: token.INC,
}
}
// atomicCounterStmt returns the expression: atomic.AddUint32(&__count[23], 1)
func atomicCounterStmt(f *File, counter ast.Expr) ast.Stmt {
return &ast.ExprStmt{
X: &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: ast.NewIdent(f.atomicPkg),
Sel: ast.NewIdent("AddUint32"),
},
Args: []ast.Expr{&ast.UnaryExpr{
Op: token.AND,
X: counter,
},
f.intLiteral(1),
},
},
}
}
// newCounter creates a new counter expression of the appropriate form.
func (f *File) newCounter(start, end token.Pos, numStmt int) ast.Stmt {
counter := &ast.IndexExpr{
X: &ast.SelectorExpr{
X: ast.NewIdent(*varVar),
Sel: ast.NewIdent("Count"),
},
Index: f.index(),
}
stmt := counterStmt(f, counter)
f.blocks = append(f.blocks, Block{start, end, numStmt})
return stmt
}
// addCounters takes a list of statements and adds counters to the beginning of
// each basic block at the top level of that list. For instance, given
//
// S1
// if cond {
// S2
// }
// S3
//
// counters will be added before S1 and before S3. The block containing S2
// will be visited in a separate call.
// TODO: Nested simple blocks get unnecessary (but correct) counters
func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt {
// Special case: make sure we add a counter to an empty block. Can't do this below
// or we will add a counter to an empty statement list after, say, a return statement.
if len(list) == 0 {
return []ast.Stmt{f.newCounter(pos, blockEnd, 0)}
}
// We have a block (statement list), but it may have several basic blocks due to the
// appearance of statements that affect the flow of control.
var newList []ast.Stmt
for {
// Find first statement that affects flow of control (break, continue, if, etc.).
// It will be the last statement of this basic block.
var last int
end := blockEnd
for last = 0; last < len(list); last++ {
end = f.statementBoundary(list[last])
if f.endsBasicSourceBlock(list[last]) {
extendToClosingBrace = false // Block is broken up now.
last++
break
}
}
if extendToClosingBrace {
end = blockEnd
}
if pos != end { // Can have no source to cover if e.g. blocks abut.
newList = append(newList, f.newCounter(pos, end, last))
}
newList = append(newList, list[0:last]...)
list = list[last:]
if len(list) == 0 {
break
}
pos = list[0].Pos()
}
return newList
}
// hasFuncLiteral reports the existence and position of the first func literal
// in the node, if any. If a func literal appears, it usually marks the termination
// of a basic block because the function body is itself a block.
// Therefore we draw a line at the start of the body of the first function literal we find.
// TODO: what if there's more than one? Probably doesn't matter much.
func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
if n == nil {
return false, 0
}
var literal funcLitFinder
ast.Walk(&literal, n)
return literal.found(), token.Pos(literal)
}
// statementBoundary finds the location in s that terminates the current basic
// block in the source.
func (f *File) statementBoundary(s ast.Stmt) token.Pos {
// Control flow statements are easy.
switch s := s.(type) {
case *ast.BlockStmt:
// Treat blocks like basic blocks to avoid overlapping counters.
return s.Lbrace
case *ast.IfStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Cond)
if found {
return pos
}
return s.Body.Lbrace
case *ast.ForStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Cond)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Post)
if found {
return pos
}
return s.Body.Lbrace
case *ast.LabeledStmt:
return f.statementBoundary(s.Stmt)
case *ast.RangeStmt:
found, pos := hasFuncLiteral(s.X)
if found {
return pos
}
return s.Body.Lbrace
case *ast.SwitchStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
found, pos = hasFuncLiteral(s.Tag)
if found {
return pos
}
return s.Body.Lbrace
case *ast.SelectStmt:
return s.Body.Lbrace
case *ast.TypeSwitchStmt:
found, pos := hasFuncLiteral(s.Init)
if found {
return pos
}
return s.Body.Lbrace
}
// If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal.
// If it does, that's tricky because we want to exclude the body of the function from this block.
// Draw a line at the start of the body of the first function literal we find.
// TODO: what if there's more than one? Probably doesn't matter much.
found, pos := hasFuncLiteral(s)
if found {
return pos
}
return s.End()
}
// endsBasicSourceBlock reports whether s changes the flow of control: break, if, etc.,
// or if it's just problematic, for instance contains a function literal, which will complicate
// accounting due to the block-within-an expression.
func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
switch s := s.(type) {
case *ast.BlockStmt:
// Treat blocks like basic blocks to avoid overlapping counters.
return true
case *ast.BranchStmt:
return true
case *ast.ForStmt:
return true
case *ast.IfStmt:
return true
case *ast.LabeledStmt:
return f.endsBasicSourceBlock(s.Stmt)
case *ast.RangeStmt:
return true
case *ast.SwitchStmt:
return true
case *ast.SelectStmt:
return true
case *ast.TypeSwitchStmt:
return true
case *ast.ExprStmt:
// Calls to panic change the flow.
// We really should verify that "panic" is the predefined function,
// but without type checking we can't and the likelihood of it being
// an actual problem is vanishingly small.
if call, ok := s.X.(*ast.CallExpr); ok {
if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
return true
}
}
}
found, _ := hasFuncLiteral(s)
return found
}
// funcLitFinder implements the ast.Visitor pattern to find the location of any
// function literal in a subtree.
type funcLitFinder token.Pos
func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
if f.found() {
return nil // Prune search.
}
switch n := node.(type) {
case *ast.FuncLit:
*f = funcLitFinder(n.Body.Lbrace)
return nil // Prune search.
}
return f
}
func (f *funcLitFinder) found() bool {
return token.Pos(*f) != token.NoPos
}
// Sort interface for []block1; used for self-check in addVariables.
type block1 struct {
Block
index int
}
type blockSlice []block1
func (b blockSlice) Len() int { return len(b) }
func (b blockSlice) Less(i, j int) bool { return b[i].startByte < b[j].startByte }
func (b blockSlice) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
// offset translates a token position into a 0-indexed byte offset.
func (f *File) offset(pos token.Pos) int {
return f.fset.Position(pos).Offset
}
// addVariables adds to the end of the file the declarations to set up the counter and position variables.
func (f *File) addVariables(w io.Writer) {
// Self-check: Verify that the instrumented basic blocks are disjoint.
t := make([]block1, len(f.blocks))
for i := range f.blocks {
t[i].Block = f.blocks[i]
t[i].index = i
}
sort.Sort(blockSlice(t))
for i := 1; i < len(t); i++ {
if t[i-1].endByte > t[i].startByte {
fmt.Fprintf(os.Stderr, "cover: internal error: block %d overlaps block %d\n", t[i-1].index, t[i].index)
// Note: error message is in byte positions, not token positions.
fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n",
f.name, f.offset(t[i-1].startByte), f.offset(t[i-1].endByte),
f.name, f.offset(t[i].startByte), f.offset(t[i].endByte))
}
}
// Declare the coverage struct as a package-level variable.
fmt.Fprintf(w, "\nvar %s = struct {\n", *varVar)
fmt.Fprintf(w, "\tCount [%d]uint32\n", len(f.blocks))
fmt.Fprintf(w, "\tPos [3 * %d]uint32\n", len(f.blocks))
fmt.Fprintf(w, "\tNumStmt [%d]uint16\n", len(f.blocks))
fmt.Fprintf(w, "} {\n")
// Initialize the position array field.
fmt.Fprintf(w, "\tPos: [3 * %d]uint32{\n", len(f.blocks))
// A nice long list of positions. Each position is encoded as follows to reduce size:
// - 32-bit starting line number
// - 32-bit ending line number
// - (16 bit ending column number << 16) | (16-bit starting column number).
for i, block := range f.blocks {
start := f.fset.Position(block.startByte)
end := f.fset.Position(block.endByte)
fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
}
// Close the position array.
fmt.Fprintf(w, "\t},\n")
// Initialize the position array field.
fmt.Fprintf(w, "\tNumStmt: [%d]uint16{\n", len(f.blocks))
// A nice long list of statements-per-block, so we can give a conventional
// valuation of "percent covered". To save space, it's a 16-bit number, so we
// clamp it if it overflows - won't matter in practice.
for i, block := range f.blocks {
n := block.numStmt
if n > 1<<16-1 {
n = 1<<16 - 1
}
fmt.Fprintf(w, "\t\t%d, // %d\n", n, i)
}
// Close the statements-per-block array.
fmt.Fprintf(w, "\t},\n")
// Close the struct initialization.
fmt.Fprintf(w, "}\n")
}
golang-golang-x-tools-0.1.9+ds/cmd/cover/cover_test.go 0000664 0000000 0000000 00000005220 14177515506 0022641 0 ustar 00root root 0000000 0000000 // 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.
// No testdata on Android.
//go:build !android
// +build !android
package main_test
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
"golang.org/x/tools/internal/testenv"
)
const (
// Data directory, also the package directory for the test.
testdata = "testdata"
)
var debug = false // Keeps the rewritten files around if set.
// Run this shell script, but do it in Go so it can be run by "go test".
//
// replace the word LINE with the line number < testdata/test.go > testdata/test_line.go
// go build -o ./testcover
// ./testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go
// go run ./testdata/main.go ./testdata/test.go
//
func TestCover(t *testing.T) {
testenv.NeedsTool(t, "go")
tmpdir, err := ioutil.TempDir("", "TestCover")
if err != nil {
t.Fatal(err)
}
defer func() {
if debug {
fmt.Printf("test files left in %s\n", tmpdir)
} else {
os.RemoveAll(tmpdir)
}
}()
testcover := filepath.Join(tmpdir, "testcover.exe")
testMain := filepath.Join(tmpdir, "main.go")
testTest := filepath.Join(tmpdir, "test.go")
coverInput := filepath.Join(tmpdir, "test_line.go")
coverOutput := filepath.Join(tmpdir, "test_cover.go")
for _, f := range []string{testMain, testTest} {
data, err := ioutil.ReadFile(filepath.Join(testdata, filepath.Base(f)))
if err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(f, data, 0644); err != nil {
t.Fatal(err)
}
}
// Read in the test file (testTest) and write it, with LINEs specified, to coverInput.
file, err := ioutil.ReadFile(testTest)
if err != nil {
t.Fatal(err)
}
lines := bytes.Split(file, []byte("\n"))
for i, line := range lines {
lines[i] = bytes.Replace(line, []byte("LINE"), []byte(fmt.Sprint(i+1)), -1)
}
err = ioutil.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666)
if err != nil {
t.Fatal(err)
}
// go build -o testcover
cmd := exec.Command("go", "build", "-o", testcover)
run(cmd, t)
// ./testcover -mode=count -var=coverTest -o ./testdata/test_cover.go testdata/test_line.go
cmd = exec.Command(testcover, "-mode=count", "-var=coverTest", "-o", coverOutput, coverInput)
run(cmd, t)
// defer removal of ./testdata/test_cover.go
if !debug {
defer os.Remove(coverOutput)
}
// go run ./testdata/main.go ./testdata/test.go
cmd = exec.Command("go", "run", testMain, coverOutput)
run(cmd, t)
}
func run(c *exec.Cmd, t *testing.T) {
c.Stdout = os.Stdout
c.Stderr = os.Stderr
err := c.Run()
if err != nil {
t.Fatal(err)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/cover/doc.go 0000664 0000000 0000000 00000001760 14177515506 0021236 0 ustar 00root root 0000000 0000000 // 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.
/*
Cover is a program for analyzing the coverage profiles generated by
'go test -coverprofile=cover.out'.
Deprecated: For Go releases 1.5 and later, this tool lives in the
standard repository. The code here is not maintained.
Cover is also used by 'go test -cover' to rewrite the source code with
annotations to track which parts of each function are executed.
It operates on one Go source file at a time, computing approximate
basic block information by studying the source. It is thus more portable
than binary-rewriting coverage tools, but also a little less capable.
For instance, it does not probe inside && and || expressions, and can
be mildly confused by single statements with multiple function literals.
For usage information, please see:
go help testflag
go tool cover -help
*/
package main // import "golang.org/x/tools/cmd/cover"
golang-golang-x-tools-0.1.9+ds/cmd/cover/func.go 0000664 0000000 0000000 00000010623 14177515506 0021422 0 ustar 00root root 0000000 0000000 // 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 implements the visitor that computes the (line, column)-(line-column) range for each function.
package main
import (
"bufio"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"path/filepath"
"text/tabwriter"
"golang.org/x/tools/cover"
)
// funcOutput takes two file names as arguments, a coverage profile to read as input and an output
// file to write ("" means to write to standard output). The function reads the profile and produces
// as output the coverage data broken down by function, like this:
//
// fmt/format.go:30: init 100.0%
// fmt/format.go:57: clearflags 100.0%
// ...
// fmt/scan.go:1046: doScan 100.0%
// fmt/scan.go:1075: advance 96.2%
// fmt/scan.go:1119: doScanf 96.8%
// total: (statements) 91.9%
func funcOutput(profile, outputFile string) error {
profiles, err := cover.ParseProfiles(profile)
if err != nil {
return err
}
var out *bufio.Writer
if outputFile == "" {
out = bufio.NewWriter(os.Stdout)
} else {
fd, err := os.Create(outputFile)
if err != nil {
return err
}
defer fd.Close()
out = bufio.NewWriter(fd)
}
defer out.Flush()
tabber := tabwriter.NewWriter(out, 1, 8, 1, '\t', 0)
defer tabber.Flush()
var total, covered int64
for _, profile := range profiles {
fn := profile.FileName
file, err := findFile(fn)
if err != nil {
return err
}
funcs, err := findFuncs(file)
if err != nil {
return err
}
// Now match up functions and profile blocks.
for _, f := range funcs {
c, t := f.coverage(profile)
fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n", fn, f.startLine, f.name, 100.0*float64(c)/float64(t))
total += t
covered += c
}
}
fmt.Fprintf(tabber, "total:\t(statements)\t%.1f%%\n", 100.0*float64(covered)/float64(total))
return nil
}
// findFuncs parses the file and returns a slice of FuncExtent descriptors.
func findFuncs(name string) ([]*FuncExtent, error) {
fset := token.NewFileSet()
parsedFile, err := parser.ParseFile(fset, name, nil, 0)
if err != nil {
return nil, err
}
visitor := &FuncVisitor{
fset: fset,
name: name,
astFile: parsedFile,
}
ast.Walk(visitor, visitor.astFile)
return visitor.funcs, nil
}
// FuncExtent describes a function's extent in the source by file and position.
type FuncExtent struct {
name string
startLine int
startCol int
endLine int
endCol int
}
// FuncVisitor implements the visitor that builds the function position list for a file.
type FuncVisitor struct {
fset *token.FileSet
name string // Name of file.
astFile *ast.File
funcs []*FuncExtent
}
// Visit implements the ast.Visitor interface.
func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.FuncDecl:
start := v.fset.Position(n.Pos())
end := v.fset.Position(n.End())
fe := &FuncExtent{
name: n.Name.Name,
startLine: start.Line,
startCol: start.Column,
endLine: end.Line,
endCol: end.Column,
}
v.funcs = append(v.funcs, fe)
}
return v
}
// coverage returns the fraction of the statements in the function that were covered, as a numerator and denominator.
func (f *FuncExtent) coverage(profile *cover.Profile) (num, den int64) {
// We could avoid making this n^2 overall by doing a single scan and annotating the functions,
// but the sizes of the data structures is never very large and the scan is almost instantaneous.
var covered, total int64
// The blocks are sorted, so we can stop counting as soon as we reach the end of the relevant block.
for _, b := range profile.Blocks {
if b.StartLine > f.endLine || (b.StartLine == f.endLine && b.StartCol >= f.endCol) {
// Past the end of the function.
break
}
if b.EndLine < f.startLine || (b.EndLine == f.startLine && b.EndCol <= f.startCol) {
// Before the beginning of the function
continue
}
total += int64(b.NumStmt)
if b.Count > 0 {
covered += int64(b.NumStmt)
}
}
if total == 0 {
total = 1 // Avoid zero denominator.
}
return covered, total
}
// findFile finds the location of the named file in GOROOT, GOPATH etc.
func findFile(file string) (string, error) {
dir, file := filepath.Split(file)
pkg, err := build.Import(dir, ".", build.FindOnly)
if err != nil {
return "", fmt.Errorf("can't find %q: %v", file, err)
}
return filepath.Join(pkg.Dir, file), nil
}
golang-golang-x-tools-0.1.9+ds/cmd/cover/html.go 0000664 0000000 0000000 00000014355 14177515506 0021441 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"bufio"
"bytes"
"fmt"
exec "golang.org/x/sys/execabs"
"html/template"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"runtime"
"golang.org/x/tools/cover"
)
// htmlOutput reads the profile data from profile and generates an HTML
// coverage report, writing it to outfile. If outfile is empty,
// it writes the report to a temporary file and opens it in a web browser.
func htmlOutput(profile, outfile string) error {
profiles, err := cover.ParseProfiles(profile)
if err != nil {
return err
}
var d templateData
for _, profile := range profiles {
fn := profile.FileName
if profile.Mode == "set" {
d.Set = true
}
file, err := findFile(fn)
if err != nil {
return err
}
src, err := ioutil.ReadFile(file)
if err != nil {
return fmt.Errorf("can't read %q: %v", fn, err)
}
var buf bytes.Buffer
err = htmlGen(&buf, src, profile.Boundaries(src))
if err != nil {
return err
}
d.Files = append(d.Files, &templateFile{
Name: fn,
Body: template.HTML(buf.String()),
Coverage: percentCovered(profile),
})
}
var out *os.File
if outfile == "" {
var dir string
dir, err = ioutil.TempDir("", "cover")
if err != nil {
return err
}
out, err = os.Create(filepath.Join(dir, "coverage.html"))
} else {
out, err = os.Create(outfile)
}
if err != nil {
return err
}
err = htmlTemplate.Execute(out, d)
if err == nil {
err = out.Close()
}
if err != nil {
return err
}
if outfile == "" {
if !startBrowser("file://" + out.Name()) {
fmt.Fprintf(os.Stderr, "HTML output written to %s\n", out.Name())
}
}
return nil
}
// percentCovered returns, as a percentage, the fraction of the statements in
// the profile covered by the test run.
// In effect, it reports the coverage of a given source file.
func percentCovered(p *cover.Profile) float64 {
var total, covered int64
for _, b := range p.Blocks {
total += int64(b.NumStmt)
if b.Count > 0 {
covered += int64(b.NumStmt)
}
}
if total == 0 {
return 0
}
return float64(covered) / float64(total) * 100
}
// htmlGen generates an HTML coverage report with the provided filename,
// source code, and tokens, and writes it to the given Writer.
func htmlGen(w io.Writer, src []byte, boundaries []cover.Boundary) error {
dst := bufio.NewWriter(w)
for i := range src {
for len(boundaries) > 0 && boundaries[0].Offset == i {
b := boundaries[0]
if b.Start {
n := 0
if b.Count > 0 {
n = int(math.Floor(b.Norm*9)) + 1
}
fmt.Fprintf(dst, ``, n, b.Count)
} else {
dst.WriteString(" ")
}
boundaries = boundaries[1:]
}
switch b := src[i]; b {
case '>':
dst.WriteString(">")
case '<':
dst.WriteString("<")
case '&':
dst.WriteString("&")
case '\t':
dst.WriteString(" ")
default:
dst.WriteByte(b)
}
}
return dst.Flush()
}
// startBrowser tries to open the URL in a browser
// and reports whether it succeeds.
func startBrowser(url string) bool {
// try to start the browser
var args []string
switch runtime.GOOS {
case "darwin":
args = []string{"open"}
case "windows":
args = []string{"cmd", "/c", "start"}
default:
args = []string{"xdg-open"}
}
cmd := exec.Command(args[0], append(args[1:], url)...)
return cmd.Start() == nil
}
// rgb returns an rgb value for the specified coverage value
// between 0 (no coverage) and 10 (max coverage).
func rgb(n int) string {
if n == 0 {
return "rgb(192, 0, 0)" // Red
}
// Gradient from gray to green.
r := 128 - 12*(n-1)
g := 128 + 12*(n-1)
b := 128 + 3*(n-1)
return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b)
}
// colors generates the CSS rules for coverage colors.
func colors() template.CSS {
var buf bytes.Buffer
for i := 0; i < 11; i++ {
fmt.Fprintf(&buf, ".cov%v { color: %v }\n", i, rgb(i))
}
return template.CSS(buf.String())
}
var htmlTemplate = template.Must(template.New("html").Funcs(template.FuncMap{
"colors": colors,
}).Parse(tmplHTML))
type templateData struct {
Files []*templateFile
Set bool
}
type templateFile struct {
Name string
Body template.HTML
Coverage float64
}
const tmplHTML = `
{{range $i, $f := .Files}}
{{$f.Name}} ({{printf "%.1f" $f.Coverage}}%)
{{end}}
not tracked
{{if .Set}}
not covered
covered
{{else}}
no coverage
low coverage
*
*
*
*
*
*
*
*
high coverage
{{end}}
{{range $i, $f := .Files}}
{{$f.Body}}
{{end}}
`
golang-golang-x-tools-0.1.9+ds/cmd/cover/testdata/ 0000775 0000000 0000000 00000000000 14177515506 0021747 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/cover/testdata/main.go 0000664 0000000 0000000 00000005431 14177515506 0023225 0 ustar 00root root 0000000 0000000 // 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.
// Test runner for coverage test. This file is not coverage-annotated; test.go is.
// It knows the coverage counter is called "coverTest".
package main
import (
"fmt"
"os"
)
func main() {
testAll()
verify()
}
type block struct {
count uint32
line uint32
}
var counters = make(map[block]bool)
// check records the location and expected value for a counter.
func check(line, count uint32) {
b := block{
count,
line,
}
counters[b] = true
}
// checkVal is a version of check that returns its extra argument,
// so it can be used in conditionals.
func checkVal(line, count uint32, val int) int {
b := block{
count,
line,
}
counters[b] = true
return val
}
var PASS = true
// verify checks the expected counts against the actual. It runs after the test has completed.
func verify() {
for b := range counters {
got, index := count(b.line)
if b.count == anything && got != 0 {
got = anything
}
if got != b.count {
fmt.Fprintf(os.Stderr, "test_go:%d expected count %d got %d [counter %d]\n", b.line, b.count, got, index)
PASS = false
}
}
verifyPanic()
if !PASS {
fmt.Fprintf(os.Stderr, "FAIL\n")
os.Exit(2)
}
}
// verifyPanic is a special check for the known counter that should be
// after the panic call in testPanic.
func verifyPanic() {
if coverTest.Count[panicIndex-1] != 1 {
// Sanity check for test before panic.
fmt.Fprintf(os.Stderr, "bad before panic")
PASS = false
}
if coverTest.Count[panicIndex] != 0 {
fmt.Fprintf(os.Stderr, "bad at panic: %d should be 0\n", coverTest.Count[panicIndex])
PASS = false
}
if coverTest.Count[panicIndex+1] != 1 {
fmt.Fprintf(os.Stderr, "bad after panic")
PASS = false
}
}
// count returns the count and index for the counter at the specified line.
func count(line uint32) (uint32, int) {
// Linear search is fine. Choose perfect fit over approximate.
// We can have a closing brace for a range on the same line as a condition for an "else if"
// and we don't want that brace to steal the count for the condition on the "if".
// Therefore we test for a perfect (lo==line && hi==line) match, but if we can't
// find that we take the first imperfect match.
index := -1
indexLo := uint32(1e9)
for i := range coverTest.Count {
lo, hi := coverTest.Pos[3*i], coverTest.Pos[3*i+1]
if lo == line && line == hi {
return coverTest.Count[i], i
}
// Choose the earliest match (the counters are in unpredictable order).
if lo <= line && line <= hi && indexLo > lo {
index = i
indexLo = lo
}
}
if index == -1 {
fmt.Fprintln(os.Stderr, "cover_test: no counter for line", line)
PASS = false
return 0, 0
}
return coverTest.Count[index], index
}
golang-golang-x-tools-0.1.9+ds/cmd/cover/testdata/test.go 0000664 0000000 0000000 00000007475 14177515506 0023272 0 ustar 00root root 0000000 0000000 // 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 program is processed by the cover command, and then testAll is called.
// The test driver in main.go can then compare the coverage statistics with expectation.
// The word LINE is replaced by the line number in this file. When the file is executed,
// the coverage processing has changed the line numbers, so we can't use runtime.Caller.
package main
const anything = 1e9 // Just some unlikely value that means "we got here, don't care how often"
func testAll() {
testSimple()
testBlockRun()
testIf()
testFor()
testRange()
testSwitch()
testTypeSwitch()
testSelect1()
testSelect2()
testPanic()
testEmptySwitches()
}
// The indexes of the counters in testPanic are known to main.go
const panicIndex = 3
// This test appears first because the index of its counters is known to main.go
func testPanic() {
defer func() {
recover()
}()
check(LINE, 1)
panic("should not get next line")
check(LINE, 0) // this is GoCover.Count[panicIndex]
// The next counter is in testSimple and it will be non-zero.
// If the panic above does not trigger a counter, the test will fail
// because GoCover.Count[panicIndex] will be the one in testSimple.
}
func testSimple() {
check(LINE, 1)
}
func testIf() {
if true {
check(LINE, 1)
} else {
check(LINE, 0)
}
if false {
check(LINE, 0)
} else {
check(LINE, 1)
}
for i := 0; i < 3; i++ {
if checkVal(LINE, 3, i) <= 2 {
check(LINE, 3)
}
if checkVal(LINE, 3, i) <= 1 {
check(LINE, 2)
}
if checkVal(LINE, 3, i) <= 0 {
check(LINE, 1)
}
}
for i := 0; i < 3; i++ {
if checkVal(LINE, 3, i) <= 1 {
check(LINE, 2)
} else {
check(LINE, 1)
}
}
for i := 0; i < 3; i++ {
if checkVal(LINE, 3, i) <= 0 {
check(LINE, 1)
} else if checkVal(LINE, 2, i) <= 1 {
check(LINE, 1)
} else if checkVal(LINE, 1, i) <= 2 {
check(LINE, 1)
} else if checkVal(LINE, 0, i) <= 3 {
check(LINE, 0)
}
}
if func(a, b int) bool { return a < b }(3, 4) {
check(LINE, 1)
}
}
func testFor() {
for i := 0; i < 10; func() { i++; check(LINE, 10) }() {
check(LINE, 10)
}
}
func testRange() {
for _, f := range []func(){
func() { check(LINE, 1) },
} {
f()
check(LINE, 1)
}
}
func testBlockRun() {
check(LINE, 1)
{
check(LINE, 1)
}
{
check(LINE, 1)
}
check(LINE, 1)
{
check(LINE, 1)
}
{
check(LINE, 1)
}
check(LINE, 1)
}
func testSwitch() {
for i := 0; i < 5; func() { i++; check(LINE, 5) }() {
switch i {
case 0:
check(LINE, 1)
case 1:
check(LINE, 1)
case 2:
check(LINE, 1)
default:
check(LINE, 2)
}
}
}
func testTypeSwitch() {
var x = []interface{}{1, 2.0, "hi"}
for _, v := range x {
switch func() { check(LINE, 3) }(); v.(type) {
case int:
check(LINE, 1)
case float64:
check(LINE, 1)
case string:
check(LINE, 1)
case complex128:
check(LINE, 0)
default:
check(LINE, 0)
}
}
}
func testSelect1() {
c := make(chan int)
go func() {
for i := 0; i < 1000; i++ {
c <- i
}
}()
for {
select {
case <-c:
check(LINE, anything)
case <-c:
check(LINE, anything)
default:
check(LINE, 1)
return
}
}
}
func testSelect2() {
c1 := make(chan int, 1000)
c2 := make(chan int, 1000)
for i := 0; i < 1000; i++ {
c1 <- i
c2 <- i
}
for {
select {
case <-c1:
check(LINE, 1000)
case <-c2:
check(LINE, 1000)
default:
check(LINE, 1)
return
}
}
}
// Empty control statements created syntax errors. This function
// is here just to be sure that those are handled correctly now.
func testEmptySwitches() {
check(LINE, 1)
switch 3 {
}
check(LINE, 1)
switch i := (interface{})(3).(int); i {
}
check(LINE, 1)
c := make(chan int)
go func() {
check(LINE, 1)
c <- 1
select {}
}()
<-c
check(LINE, 1)
}
golang-golang-x-tools-0.1.9+ds/cmd/digraph/ 0000775 0000000 0000000 00000000000 14177515506 0020436 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/digraph/digraph.go 0000664 0000000 0000000 00000035204 14177515506 0022407 0 ustar 00root root 0000000 0000000 // Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
The digraph command performs queries over unlabelled directed graphs
represented in text form. It is intended to integrate nicely with
typical UNIX command pipelines.
Usage:
your-application | digraph [command]
The support commands are:
nodes
the set of all nodes
degree
the in-degree and out-degree of each node
transpose
the reverse of the input edges
preds ...
the set of immediate predecessors of the specified nodes
succs ...
the set of immediate successors of the specified nodes
forward ...
the set of nodes transitively reachable from the specified nodes
reverse ...
the set of nodes that transitively reach the specified nodes
somepath
the list of nodes on some arbitrary path from the first node to the second
allpaths
the set of nodes on all paths from the first node to the second
sccs
all strongly connected components (one per line)
scc
the set of nodes nodes strongly connected to the specified one
focus
the subgraph containing all directed paths that pass through the specified node
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):
// - support input files other than stdin
// - support alternative formats (AT&T GraphViz, CSV, etc),
// a comment syntax, etc.
// - allow queries to nest, like Blaze query language.
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
func usage() {
fmt.Fprintf(os.Stderr, `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
transpose
the reverse of the input edges
preds ...
the set of immediate predecessors of the specified nodes
succs ...
the set of immediate successors of the specified nodes
forward ...
the set of nodes transitively reachable from the specified nodes
reverse ...
the set of nodes that transitively reach the specified nodes
somepath
the list of nodes on some arbitrary path from the first node to the second
allpaths
the set of nodes on all paths from the first node to the second
sccs
all strongly connected components (one per line)
scc
the set of nodes nodes strongly connected to the specified one
focus
the subgraph containing all directed paths that pass through the specified node
`)
os.Exit(2)
}
func main() {
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
usage()
}
if err := digraph(args[0], args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "digraph: %s\n", err)
os.Exit(1)
}
}
type nodelist []string
func (l nodelist) println(sep string) {
for i, node := range l {
if i > 0 {
fmt.Fprint(stdout, sep)
}
fmt.Fprint(stdout, node)
}
fmt.Fprintln(stdout)
}
type nodeset map[string]bool // TODO(deklerk): change bool to struct to reduce memory footprint
func (s nodeset) sort() nodelist {
nodes := make(nodelist, len(s))
var i int
for node := range s {
nodes[i] = node
i++
}
sort.Strings(nodes)
return nodes
}
func (s nodeset) addAll(x nodeset) {
for node := range x {
s[node] = true
}
}
// A graph maps nodes to the non-nil set of their immediate successors.
type graph map[string]nodeset
func (g graph) addNode(node string) nodeset {
edges := g[node]
if edges == nil {
edges = make(nodeset)
g[node] = edges
}
return edges
}
func (g graph) addEdges(from string, to ...string) {
edges := g.addNode(from)
for _, to := range to {
g.addNode(to)
edges[to] = true
}
}
func (g graph) reachableFrom(roots nodeset) nodeset {
seen := make(nodeset)
var visit func(node string)
visit = func(node string) {
if !seen[node] {
seen[node] = true
for e := range g[node] {
visit(e)
}
}
}
for root := range roots {
visit(root)
}
return seen
}
func (g graph) transpose() graph {
rev := make(graph)
for node, edges := range g {
rev.addNode(node)
for succ := range edges {
rev.addEdges(succ, node)
}
}
return rev
}
func (g graph) sccs() []nodeset {
// Kosaraju's algorithm---Tarjan is overkill here.
// Forward pass.
S := make(nodelist, 0, len(g)) // postorder stack
seen := make(nodeset)
var visit func(node string)
visit = func(node string) {
if !seen[node] {
seen[node] = true
for e := range g[node] {
visit(e)
}
S = append(S, node)
}
}
for node := range g {
visit(node)
}
// Reverse pass.
rev := g.transpose()
var scc nodeset
seen = make(nodeset)
var rvisit func(node string)
rvisit = func(node string) {
if !seen[node] {
seen[node] = true
scc[node] = true
for e := range rev[node] {
rvisit(e)
}
}
}
var sccs []nodeset
for len(S) > 0 {
top := S[len(S)-1]
S = S[:len(S)-1] // pop
if !seen[top] {
scc = make(nodeset)
rvisit(top)
sccs = append(sccs, scc)
}
}
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 (g graph) somepath(from, to string) error {
type edge struct{ from, to string }
seen := make(nodeset)
var dfs func(path []edge, from string) bool
dfs = func(path []edge, from string) bool {
if !seen[from] {
seen[from] = true
if from == to {
// fmt.Println(path, len(path), cap(path))
// Print and unwind.
for _, e := range path {
fmt.Fprintln(stdout, e.from+" "+e.to)
}
return true
}
for e := range g[from] {
if dfs(append(path, edge{from: from, to: e}), e) {
return true
}
}
}
return false
}
maxEdgesInGraph := len(g) * (len(g) - 1)
if !dfs(make([]edge, 0, maxEdgesInGraph), from) {
return fmt.Errorf("no path from %q to %q", from, to)
}
return nil
}
func parse(rd io.Reader) (graph, error) {
g := make(graph)
var linenum int
in := bufio.NewScanner(rd)
for in.Scan() {
linenum++
// Split into words, honoring double-quotes per Go spec.
words, err := split(in.Text())
if err != nil {
return nil, fmt.Errorf("at line %d: %v", linenum, err)
}
if len(words) > 0 {
g.addEdges(words[0], words[1:]...)
}
}
if err := in.Err(); err != nil {
return nil, err
}
return g, nil
}
// Overridable for testing purposes.
var stdin io.Reader = os.Stdin
var stdout io.Writer = os.Stdout
func digraph(cmd string, args []string) error {
// Parse the input graph.
g, err := parse(stdin)
if err != nil {
return err
}
// Parse the command line.
switch cmd {
case "nodes":
if len(args) != 0 {
return fmt.Errorf("usage: digraph nodes")
}
nodes := make(nodeset)
for node := range g {
nodes[node] = true
}
nodes.sort().println("\n")
case "degree":
if len(args) != 0 {
return fmt.Errorf("usage: digraph degree")
}
nodes := make(nodeset)
for node := range g {
nodes[node] = true
}
rev := g.transpose()
for _, node := range nodes.sort() {
fmt.Fprintf(stdout, "%d\t%d\t%s\n", len(rev[node]), len(g[node]), node)
}
case "transpose":
if len(args) != 0 {
return fmt.Errorf("usage: digraph transpose")
}
var revEdges []string
for node, succs := range g.transpose() {
for succ := range succs {
revEdges = append(revEdges, fmt.Sprintf("%s %s", node, succ))
}
}
sort.Strings(revEdges) // make output deterministic
for _, e := range revEdges {
fmt.Fprintln(stdout, e)
}
case "succs", "preds":
if len(args) == 0 {
return fmt.Errorf("usage: digraph %s ... ", cmd)
}
g := g
if cmd == "preds" {
g = g.transpose()
}
result := make(nodeset)
for _, root := range args {
edges := g[root]
if edges == nil {
return fmt.Errorf("no such node %q", root)
}
result.addAll(edges)
}
result.sort().println("\n")
case "forward", "reverse":
if len(args) == 0 {
return fmt.Errorf("usage: digraph %s ... ", cmd)
}
roots := make(nodeset)
for _, root := range args {
if g[root] == nil {
return fmt.Errorf("no such node %q", root)
}
roots[root] = true
}
g := g
if cmd == "reverse" {
g = g.transpose()
}
g.reachableFrom(roots).sort().println("\n")
case "somepath":
if len(args) != 2 {
return fmt.Errorf("usage: digraph somepath ")
}
from, to := args[0], args[1]
if g[from] == nil {
return fmt.Errorf("no such 'from' node %q", from)
}
if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to)
}
if err := g.somepath(from, to); err != nil {
return err
}
case "allpaths":
if len(args) != 2 {
return fmt.Errorf("usage: digraph allpaths ")
}
from, to := args[0], args[1]
if g[from] == nil {
return fmt.Errorf("no such 'from' node %q", from)
}
if g[to] == nil {
return fmt.Errorf("no such 'to' node %q", to)
}
if err := g.allpaths(from, to); err != nil {
return err
}
case "sccs":
if len(args) != 0 {
return fmt.Errorf("usage: digraph sccs")
}
for _, scc := range g.sccs() {
scc.sort().println(" ")
}
case "scc":
if len(args) != 1 {
return fmt.Errorf("usage: digraph scc ")
}
node := args[0]
if g[node] == nil {
return fmt.Errorf("no such node %q", node)
}
for _, scc := range g.sccs() {
if scc[node] {
scc.sort().println("\n")
break
}
}
case "focus":
if len(args) != 1 {
return fmt.Errorf("usage: digraph focus ")
}
node := args[0]
if g[node] == nil {
return fmt.Errorf("no such node %q", node)
}
edges := make(map[string]struct{})
for from := range g.reachableFrom(nodeset{node: true}) {
for to := range g[from] {
edges[fmt.Sprintf("%s %s", from, to)] = struct{}{}
}
}
gtrans := g.transpose()
for from := range gtrans.reachableFrom(nodeset{node: true}) {
for to := range gtrans[from] {
edges[fmt.Sprintf("%s %s", to, from)] = struct{}{}
}
}
edgesSorted := make([]string, 0, len(edges))
for e := range edges {
edgesSorted = append(edgesSorted, e)
}
sort.Strings(edgesSorted)
fmt.Fprintln(stdout, strings.Join(edgesSorted, "\n"))
default:
return fmt.Errorf("no such command %q", cmd)
}
return nil
}
// -- Utilities --------------------------------------------------------
// split splits a line into words, which are generally separated by
// spaces, but Go-style double-quoted string literals are also supported.
// (This approximates the behaviour of the Bourne shell.)
//
// `one "two three"` -> ["one" "two three"]
// `a"\n"b` -> ["a\nb"]
//
func split(line string) ([]string, error) {
var (
words []string
inWord bool
current bytes.Buffer
)
for len(line) > 0 {
r, size := utf8.DecodeRuneInString(line)
if unicode.IsSpace(r) {
if inWord {
words = append(words, current.String())
current.Reset()
inWord = false
}
} else if r == '"' {
var ok bool
size, ok = quotedLength(line)
if !ok {
return nil, errors.New("invalid quotation")
}
s, err := strconv.Unquote(line[:size])
if err != nil {
return nil, err
}
current.WriteString(s)
inWord = true
} else {
current.WriteRune(r)
inWord = true
}
line = line[size:]
}
if inWord {
words = append(words, current.String())
}
return words, nil
}
// quotedLength returns the length in bytes of the prefix of input that
// contain a possibly-valid double-quoted Go string literal.
//
// On success, n is at least two (""); input[:n] may be passed to
// strconv.Unquote to interpret its value, and input[n:] contains the
// rest of the input.
//
// On failure, quotedLength returns false, and the entire input can be
// passed to strconv.Unquote if an informative error message is desired.
//
// quotedLength does not and need not detect all errors, such as
// invalid hex or octal escape sequences, since it assumes
// strconv.Unquote will be applied to the prefix. It guarantees only
// that if there is a prefix of input containing a valid string literal,
// its length is returned.
//
// TODO(adonovan): move this into a strconv-like utility package.
//
func quotedLength(input string) (n int, ok bool) {
var offset int
// next returns the rune at offset, or -1 on EOF.
// offset advances to just after that rune.
next := func() rune {
if offset < len(input) {
r, size := utf8.DecodeRuneInString(input[offset:])
offset += size
return r
}
return -1
}
if next() != '"' {
return // error: not a quotation
}
for {
r := next()
if r == '\n' || r < 0 {
return // error: string literal not terminated
}
if r == '"' {
return offset, true // success
}
if r == '\\' {
var skip int
switch next() {
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"':
skip = 0
case '0', '1', '2', '3', '4', '5', '6', '7':
skip = 2
case 'x':
skip = 2
case 'u':
skip = 4
case 'U':
skip = 8
default:
return // error: invalid escape
}
for i := 0; i < skip; i++ {
next()
}
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/digraph/digraph_test.go 0000664 0000000 0000000 00000020127 14177515506 0023444 0 ustar 00root root 0000000 0000000 // Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"fmt"
"reflect"
"sort"
"strings"
"testing"
)
func TestDigraph(t *testing.T) {
const g1 = `
socks shoes
shorts pants
pants belt shoes
shirt tie sweater
sweater jacket
hat
`
const g2 = `
a b c
b d
c d
d c
`
for _, test := range []struct {
name string
input string
cmd string
args []string
want string
}{
{"nodes", g1, "nodes", nil, "belt\nhat\njacket\npants\nshirt\nshoes\nshorts\nsocks\nsweater\ntie\n"},
{"reverse", g1, "reverse", []string{"jacket"}, "jacket\nshirt\nsweater\n"},
{"transpose", g1, "transpose", nil, "belt pants\njacket sweater\npants shorts\nshoes pants\nshoes socks\nsweater shirt\ntie shirt\n"},
{"forward", g1, "forward", []string{"socks"}, "shoes\nsocks\n"},
{"forward multiple args", g1, "forward", []string{"socks", "sweater"}, "jacket\nshoes\nsocks\nsweater\n"},
{"scss", g2, "sccs", nil, "a\nb\nc d\n"},
{"scc", g2, "scc", []string{"d"}, "c\nd\n"},
{"succs", g2, "succs", []string{"a"}, "b\nc\n"},
{"preds", g2, "preds", []string{"c"}, "a\nd\n"},
{"preds multiple args", g2, "preds", []string{"c", "d"}, "a\nb\nc\nd\n"},
} {
t.Run(test.name, func(t *testing.T) {
stdin = strings.NewReader(test.input)
stdout = new(bytes.Buffer)
if err := digraph(test.cmd, test.args); err != nil {
t.Fatal(err)
}
got := stdout.(fmt.Stringer).String()
if got != test.want {
t.Errorf("digraph(%s, %s) = got %q, want %q", test.cmd, test.args, got, test.want)
}
})
}
// TODO(adonovan):
// - test somepath (it's nondeterministic).
// - 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 TestSomepath(t *testing.T) {
for _, test := range []struct {
name string
in string
to string
// somepath is non-deterministic, so we have to provide all the
// possible options. Each option is separated with |.
wantAnyOf string
}{
{
name: "Basic",
in: "A B\n",
to: "B",
wantAnyOf: "A B",
},
{
name: "Basic With Cycle",
in: "A B\nB A",
to: "B",
wantAnyOf: "A B",
},
{
name: "Two Paths",
// /-> B --\
// A -- -> D
// \-> C --/
in: "A B\nA C\nB D\nC D",
to: "D",
wantAnyOf: "A B\nB D|A C\nC D",
},
} {
t.Run(test.name, func(t *testing.T) {
stdin = strings.NewReader(test.in)
stdout = new(bytes.Buffer)
if err := digraph("somepath", []string{"A", test.to}); err != nil {
t.Fatal(err)
}
got := stdout.(fmt.Stringer).String()
lines := strings.Split(got, "\n")
sort.Strings(lines)
got = strings.Join(lines[1:], "\n")
var oneMatch bool
for _, want := range strings.Split(test.wantAnyOf, "|") {
if got == want {
oneMatch = true
}
}
if !oneMatch {
t.Errorf("digraph(somepath, A, %s) = got %q, want any of\n%s", test.to, got, test.wantAnyOf)
}
})
}
}
func TestSplit(t *testing.T) {
for _, test := range []struct {
line string
want []string
}{
{`one "2a 2b" three`, []string{"one", "2a 2b", "three"}},
{`one tw"\n\x0a\u000a\012"o three`, []string{"one", "tw\n\n\n\no", "three"}},
} {
got, err := split(test.line)
if err != nil {
t.Errorf("split(%s) failed: %v", test.line, err)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("split(%s) = %v, want %v", test.line, got, test.want)
}
}
}
func TestQuotedLength(t *testing.T) {
for _, test := range []struct {
input string
want int
}{
{`"abc"`, 5},
{`"abc"def`, 5},
{`"abc\"d"ef`, 8}, // "abc\"d" is consumed, ef is residue
{`"\012\n\x0a\u000a\U0000000a"`, 28},
{"\"\xff\"", 3}, // bad UTF-8 is ok
{`"\xff"`, 6}, // hex escape for bad UTF-8 is ok
} {
got, ok := quotedLength(test.input)
if !ok {
got = 0
}
if got != test.want {
t.Errorf("quotedLength(%s) = %d, want %d", test.input, got, test.want)
}
}
// errors
for _, input := range []string{
``, // not a quotation
`a`, // not a quotation
`'a'`, // not a quotation
`"a`, // not terminated
`"\0"`, // short octal escape
`"\x1"`, // short hex escape
`"\u000"`, // short \u escape
`"\U0000000"`, // short \U escape
`"\k"`, // invalid escape
"\"ab\nc\"", // newline
} {
if n, ok := quotedLength(input); ok {
t.Errorf("quotedLength(%s) = %d, want !ok", input, n)
}
}
}
func TestFocus(t *testing.T) {
for _, test := range []struct {
name string
in string
focus string
want string
}{
{
name: "Basic",
in: "A B",
focus: "B",
want: "A B\n",
},
{
name: "Some Nodes Not Included",
// C does not have a path involving B, and should not be included
// in the output.
in: "A B\nA C",
focus: "B",
want: "A B\n",
},
{
name: "Cycle In Path",
// A <-> B -> C
in: "A B\nB A\nB C",
focus: "C",
want: "A B\nB A\nB C\n",
},
{
name: "Cycle Out Of Path",
// C <- A <->B
in: "A B\nB A\nB C",
focus: "C",
want: "A B\nB A\nB C\n",
},
{
name: "Complex",
// Paths in and out from focus.
// /-> F
// /-> B -> D --
// A -- \-> E
// \-> C
in: "A B\nA C\nB D\nD F\nD E",
focus: "D",
want: "A B\nB D\nD E\nD F\n",
},
} {
t.Run(test.name, func(t *testing.T) {
stdin = strings.NewReader(test.in)
stdout = new(bytes.Buffer)
if err := digraph("focus", []string{test.focus}); err != nil {
t.Fatal(err)
}
got := stdout.(fmt.Stringer).String()
if got != test.want {
t.Errorf("digraph(focus, %s) = got %q, want %q", test.focus, got, test.want)
}
})
}
}
golang-golang-x-tools-0.1.9+ds/cmd/eg/ 0000775 0000000 0000000 00000000000 14177515506 0017413 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/eg/eg.go 0000664 0000000 0000000 00000012142 14177515506 0020335 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 eg command performs example-based refactoring.
// For documentation, run the command, or see Help in
// golang.org/x/tools/refactor/eg.
package main // import "golang.org/x/tools/cmd/eg"
import (
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"strings"
exec "golang.org/x/sys/execabs"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/refactor/eg"
)
var (
beforeeditFlag = flag.String("beforeedit", "", "A command to exec before each file is edited (e.g. chmod, checkout). Whitespace delimits argument words. The string '{}' is replaced by the file name.")
helpFlag = flag.Bool("help", false, "show detailed help message")
templateFlag = flag.String("t", "", "template.go file specifying the refactoring")
transitiveFlag = flag.Bool("transitive", false, "apply refactoring to all dependencies too")
writeFlag = flag.Bool("w", false, "rewrite input files in place (by default, the results are printed to standard output)")
verboseFlag = flag.Bool("v", false, "show verbose matcher diagnostics")
)
const usage = `eg: an example-based refactoring tool.
Usage: eg -t template.go [-w] [-transitive]
-help show detailed help message
-t template.go specifies the template file (use -help to see explanation)
-w causes files to be re-written in place.
-transitive causes all dependencies to be refactored too.
-v show verbose matcher diagnostics
-beforeedit cmd a command to exec before each file is modified.
"{}" represents the name of the file.
`
func main() {
if err := doMain(); err != nil {
fmt.Fprintf(os.Stderr, "eg: %s\n", err)
os.Exit(1)
}
}
func doMain() error {
flag.Parse()
args := flag.Args()
if *helpFlag {
help := eg.Help // hide %s from vet
fmt.Fprint(os.Stderr, help)
os.Exit(2)
}
if len(args) == 0 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}
if *templateFlag == "" {
return fmt.Errorf("no -t template.go file specified")
}
tAbs, err := filepath.Abs(*templateFlag)
if err != nil {
return err
}
template, err := ioutil.ReadFile(tAbs)
if err != nil {
return err
}
cfg := &packages.Config{
Fset: token.NewFileSet(),
Mode: packages.NeedTypesInfo | packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps | packages.NeedCompiledGoFiles,
Tests: true,
}
pkgs, err := packages.Load(cfg, args...)
if err != nil {
return err
}
tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments)
if err != nil {
return err
}
// Type-check the template.
tInfo := types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
conf := types.Config{
Importer: pkgsImporter(pkgs),
}
tPkg, err := conf.Check("egtemplate", cfg.Fset, []*ast.File{tFile}, &tInfo)
if err != nil {
return err
}
// Analyze the template.
xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag)
if err != nil {
return err
}
// Apply it to the input packages.
var all []*packages.Package
if *transitiveFlag {
packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) })
} else {
all = pkgs
}
var hadErrors bool
for _, pkg := range pkgs {
for i, filename := range pkg.CompiledGoFiles {
if filename == tAbs {
// Don't rewrite the template file.
continue
}
file := pkg.Syntax[i]
n := xform.Transform(pkg.TypesInfo, pkg.Types, file)
if n == 0 {
continue
}
fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n)
if *writeFlag {
// Run the before-edit command (e.g. "chmod +w", "checkout") if any.
if *beforeeditFlag != "" {
args := strings.Fields(*beforeeditFlag)
// Replace "{}" with the filename, like find(1).
for i := range args {
if i > 0 {
args[i] = strings.Replace(args[i], "{}", filename, -1)
}
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: edit hook %q failed (%s)\n",
args, err)
}
}
if err := eg.WriteAST(cfg.Fset, filename, file); err != nil {
fmt.Fprintf(os.Stderr, "eg: %s\n", err)
hadErrors = true
}
} else {
format.Node(os.Stdout, cfg.Fset, file)
}
}
}
if hadErrors {
os.Exit(1)
}
return nil
}
type pkgsImporter []*packages.Package
func (p pkgsImporter) Import(path string) (tpkg *types.Package, err error) {
packages.Visit([]*packages.Package(p), func(pkg *packages.Package) bool {
if pkg.PkgPath == path {
tpkg = pkg.Types
return false
}
return true
}, nil)
if tpkg != nil {
return tpkg, nil
}
return nil, fmt.Errorf("package %q not found", path)
}
golang-golang-x-tools-0.1.9+ds/cmd/file2fuzz/ 0000775 0000000 0000000 00000000000 14177515506 0020740 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/file2fuzz/main.go 0000664 0000000 0000000 00000006601 14177515506 0022216 0 ustar 00root root 0000000 0000000 // Copyright 2021 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.
// file2fuzz converts binary files, such as those used by go-fuzz, to the Go
// fuzzing corpus format.
//
// Usage:
//
// file2fuzz [-o output] [input...]
//
// The defualt behavior is to read input from stdin and write the converted
// output to stdout. If any position arguments are provided stdin is ignored
// and the arguments are assumed to be input files to convert.
//
// The -o flag provides an path to write output files to. If only one positional
// argument is specified it may be a file path or an existing directory, if there are
// multiple inputs specified it must be a directory. If a directory is provided
// the name of the file will be the SHA-256 hash of its contents.
//
package main
import (
"crypto/sha256"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
)
// encVersion1 is version 1 Go fuzzer corpus encoding.
var encVersion1 = "go test fuzz v1"
func encodeByteSlice(b []byte) []byte {
return []byte(fmt.Sprintf("%s\n[]byte(%q)", encVersion1, b))
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: file2fuzz [-o output] [input...]\nconverts files to Go fuzzer corpus format\n")
fmt.Fprintf(os.Stderr, "\tinput: files to convert\n")
fmt.Fprintf(os.Stderr, "\t-o: where to write converted file(s)\n")
os.Exit(2)
}
func dirWriter(dir string) func([]byte) error {
return func(b []byte) error {
sum := fmt.Sprintf("%x", sha256.Sum256(b))
name := filepath.Join(dir, sum)
if err := os.MkdirAll(dir, 0777); err != nil {
return err
}
if err := ioutil.WriteFile(name, b, 0666); err != nil {
os.Remove(name)
return err
}
return nil
}
}
func convert(inputArgs []string, outputArg string) error {
var input []io.Reader
if args := inputArgs; len(args) == 0 {
input = []io.Reader{os.Stdin}
} else {
for _, a := range args {
f, err := os.Open(a)
if err != nil {
return fmt.Errorf("unable to open %q: %s", a, err)
}
defer f.Close()
if fi, err := f.Stat(); err != nil {
return fmt.Errorf("unable to open %q: %s", a, err)
} else if fi.IsDir() {
return fmt.Errorf("%q is a directory, not a file", a)
}
input = append(input, f)
}
}
var output func([]byte) error
if outputArg == "" {
if len(inputArgs) > 1 {
return errors.New("-o required with multiple input files")
}
output = func(b []byte) error {
_, err := os.Stdout.Write(b)
return err
}
} else {
if len(inputArgs) > 1 {
output = dirWriter(outputArg)
} else {
if fi, err := os.Stat(outputArg); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to open %q for writing: %s", outputArg, err)
} else if err == nil && fi.IsDir() {
output = dirWriter(outputArg)
} else {
output = func(b []byte) error {
return ioutil.WriteFile(outputArg, b, 0666)
}
}
}
}
for _, f := range input {
b, err := ioutil.ReadAll(f)
if err != nil {
return fmt.Errorf("unable to read input: %s", err)
}
if err := output(encodeByteSlice(b)); err != nil {
return fmt.Errorf("unable to write output: %s", err)
}
}
return nil
}
func main() {
log.SetFlags(0)
log.SetPrefix("file2fuzz: ")
output := flag.String("o", "", "where to write converted file(s)")
flag.Usage = usage
flag.Parse()
if err := convert(flag.Args(), *output); err != nil {
log.Fatal(err)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/file2fuzz/main_test.go 0000664 0000000 0000000 00000012612 14177515506 0023254 0 ustar 00root root 0000000 0000000 // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
)
// The setup for this test is mostly cribbed from x/exp/txtar.
var buildBin struct {
once sync.Once
name string
err error
}
func binPath(t *testing.T) string {
t.Helper()
if _, err := exec.LookPath("go"); err != nil {
t.Skipf("cannot build file2fuzz binary: %v", err)
}
buildBin.once.Do(func() {
exe, err := ioutil.TempFile("", "file2fuzz-*.exe")
if err != nil {
buildBin.err = err
return
}
exe.Close()
buildBin.name = exe.Name()
cmd := exec.Command("go", "build", "-o", buildBin.name, ".")
out, err := cmd.CombinedOutput()
if err != nil {
buildBin.err = fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
}
})
if buildBin.err != nil {
if runtime.GOOS == "android" {
t.Skipf("skipping test after failing to build file2fuzz binary: go_android_exec may have failed to copy needed dependencies (see https://golang.org/issue/37088)")
}
t.Fatal(buildBin.err)
}
return buildBin.name
}
func TestMain(m *testing.M) {
os.Exit(m.Run())
if buildBin.name != "" {
os.Remove(buildBin.name)
}
}
func file2fuzz(t *testing.T, dir string, args []string, stdin string) (string, bool) {
t.Helper()
cmd := exec.Command(binPath(t), args...)
cmd.Dir = dir
if stdin != "" {
cmd.Stdin = strings.NewReader(stdin)
}
out, err := cmd.CombinedOutput()
if err != nil {
return string(out), true
}
return string(out), false
}
func TestFile2Fuzz(t *testing.T) {
type file struct {
name string
dir bool
content string
}
tests := []struct {
name string
args []string
stdin string
inputFiles []file
expectedStdout string
expectedFiles []file
expectedError string
}{
{
name: "stdin, stdout",
stdin: "hello",
expectedStdout: "go test fuzz v1\n[]byte(\"hello\")",
},
{
name: "stdin, output file",
stdin: "hello",
args: []string{"-o", "output"},
expectedFiles: []file{{name: "output", content: "go test fuzz v1\n[]byte(\"hello\")"}},
},
{
name: "stdin, output directory",
stdin: "hello",
args: []string{"-o", "output"},
inputFiles: []file{{name: "output", dir: true}},
expectedFiles: []file{{name: "output/ffc7b87a0377262d4f77926bd235551d78e6037bbe970d81ec39ac1d95542f7b", content: "go test fuzz v1\n[]byte(\"hello\")"}},
},
{
name: "input file, output file",
args: []string{"-o", "output", "input"},
inputFiles: []file{{name: "input", content: "hello"}},
expectedFiles: []file{{name: "output", content: "go test fuzz v1\n[]byte(\"hello\")"}},
},
{
name: "input file, output directory",
args: []string{"-o", "output", "input"},
inputFiles: []file{{name: "output", dir: true}, {name: "input", content: "hello"}},
expectedFiles: []file{{name: "output/ffc7b87a0377262d4f77926bd235551d78e6037bbe970d81ec39ac1d95542f7b", content: "go test fuzz v1\n[]byte(\"hello\")"}},
},
{
name: "input files, output directory",
args: []string{"-o", "output", "input", "input-2"},
inputFiles: []file{{name: "output", dir: true}, {name: "input", content: "hello"}, {name: "input-2", content: "hello :)"}},
expectedFiles: []file{
{name: "output/ffc7b87a0377262d4f77926bd235551d78e6037bbe970d81ec39ac1d95542f7b", content: "go test fuzz v1\n[]byte(\"hello\")"},
{name: "output/28059db30ce420ff65b2c29b749804c69c601aeca21b3cbf0644244ff080d7a5", content: "go test fuzz v1\n[]byte(\"hello :)\")"},
},
},
{
name: "input files, no output",
args: []string{"input", "input-2"},
inputFiles: []file{{name: "output", dir: true}, {name: "input", content: "hello"}, {name: "input-2", content: "hello :)"}},
expectedError: "file2fuzz: -o required with multiple input files\n",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tmp, err := ioutil.TempDir(os.TempDir(), "file2fuzz")
if err != nil {
t.Fatalf("ioutil.TempDir failed: %s", err)
}
defer os.RemoveAll(tmp)
for _, f := range tc.inputFiles {
if f.dir {
if err := os.Mkdir(filepath.Join(tmp, f.name), 0777); err != nil {
t.Fatalf("failed to create test directory: %s", err)
}
} else {
if err := ioutil.WriteFile(filepath.Join(tmp, f.name), []byte(f.content), 0666); err != nil {
t.Fatalf("failed to create test input file: %s", err)
}
}
}
out, failed := file2fuzz(t, tmp, tc.args, tc.stdin)
if failed && tc.expectedError == "" {
t.Fatalf("file2fuzz failed unexpectedly: %s", out)
} else if failed && out != tc.expectedError {
t.Fatalf("file2fuzz returned unexpected error: got %q, want %q", out, tc.expectedError)
}
if !failed && out != tc.expectedStdout {
t.Fatalf("file2fuzz unexpected stdout: got %q, want %q", out, tc.expectedStdout)
}
for _, f := range tc.expectedFiles {
c, err := ioutil.ReadFile(filepath.Join(tmp, f.name))
if err != nil {
t.Fatalf("failed to read expected output file %q: %s", f.name, err)
}
if string(c) != f.content {
t.Fatalf("expected output file %q contains unexpected content: got %s, want %s", f.name, string(c), f.content)
}
}
})
}
}
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/ 0000775 0000000 0000000 00000000000 14177515506 0021224 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/main.go 0000664 0000000 0000000 00000035357 14177515506 0022514 0 ustar 00root root 0000000 0000000 // Copyright 2015 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 fiximports command fixes import declarations to use the canonical
// import path for packages that have an "import comment" as defined by
// https://golang.org/s/go14customimport.
//
//
// Background
//
// The Go 1 custom import path mechanism lets the maintainer of a
// package give it a stable name by which clients may import and "go
// get" it, independent of the underlying version control system (such
// as Git) or server (such as github.com) that hosts it. Requests for
// the custom name are redirected to the underlying name. This allows
// packages to be migrated from one underlying server or system to
// another without breaking existing clients.
//
// Because this redirect mechanism creates aliases for existing
// packages, it's possible for a single program to import the same
// package by its canonical name and by an alias. The resulting
// executable will contain two copies of the package, which is wasteful
// at best and incorrect at worst.
//
// To avoid this, "go build" reports an error if it encounters a special
// comment like the one below, and if the import path in the comment
// does not match the path of the enclosing package relative to
// GOPATH/src:
//
// $ grep ^package $GOPATH/src/github.com/bob/vanity/foo/foo.go
// package foo // import "vanity.com/foo"
//
// The error from "go build" indicates that the package canonically
// known as "vanity.com/foo" is locally installed under the
// non-canonical name "github.com/bob/vanity/foo".
//
//
// Usage
//
// When a package that you depend on introduces a custom import comment,
// and your workspace imports it by the non-canonical name, your build
// will stop working as soon as you update your copy of that package
// using "go get -u".
//
// The purpose of the fiximports tool is to fix up all imports of the
// non-canonical path within a Go workspace, replacing them with imports
// of the canonical path. Following a run of fiximports, the workspace
// will no longer depend on the non-canonical copy of the package, so it
// should be safe to delete. It may be necessary to run "go get -u"
// again to ensure that the package is locally installed under its
// canonical path, if it was not already.
//
// The fiximports tool operates locally; it does not make HTTP requests
// and does not discover new custom import comments. It only operates
// on non-canonical packages present in your workspace.
//
// The -baddomains flag is a list of domain names that should always be
// considered non-canonical. You can use this if you wish to make sure
// that you no longer have any dependencies on packages from that
// domain, even those that do not yet provide a canonical import path
// comment. For example, the default value of -baddomains includes the
// moribund code hosting site code.google.com, so fiximports will report
// an error for each import of a package from this domain remaining
// after canonicalization.
//
// To see the changes fiximports would make without applying them, use
// the -n flag.
//
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/build"
"go/format"
"go/parser"
"go/token"
exec "golang.org/x/sys/execabs"
"io"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
)
// flags
var (
dryrun = flag.Bool("n", false, "dry run: show changes, but don't apply them")
badDomains = flag.String("baddomains", "code.google.com",
"a comma-separated list of domains from which packages should not be imported")
replaceFlag = flag.String("replace", "",
"a comma-separated list of noncanonical=canonical pairs of package paths. If both items in a pair end with '...', they are treated as path prefixes.")
)
// seams for testing
var (
stderr io.Writer = os.Stderr
writeFile = ioutil.WriteFile
)
const usage = `fiximports: rewrite import paths to use canonical package names.
Usage: fiximports [-n] package...
The package... arguments specify a list of packages
in the style of the go tool; see "go help packages".
Hint: use "all" or "..." to match the entire workspace.
For details, see https://pkg.go.dev/golang.org/x/tools/cmd/fiximports
Flags:
-n: dry run: show changes, but don't apply them
-baddomains a comma-separated list of domains from which packages
should not be imported
`
func main() {
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Fprint(stderr, usage)
os.Exit(1)
}
if !fiximports(flag.Args()...) {
os.Exit(1)
}
}
type canonicalName struct{ path, name string }
// fiximports fixes imports in the specified packages.
// Invariant: a false result implies an error was already printed.
func fiximports(packages ...string) bool {
// importedBy is the transpose of the package import graph.
importedBy := make(map[string]map[*build.Package]bool)
// addEdge adds an edge to the import graph.
addEdge := func(from *build.Package, to string) {
if to == "C" || to == "unsafe" {
return // fake
}
pkgs := importedBy[to]
if pkgs == nil {
pkgs = make(map[*build.Package]bool)
importedBy[to] = pkgs
}
pkgs[from] = true
}
// List metadata for all packages in the workspace.
pkgs, err := list("...")
if err != nil {
fmt.Fprintf(stderr, "importfix: %v\n", err)
return false
}
// packageName maps each package's path to its name.
packageName := make(map[string]string)
for _, p := range pkgs {
packageName[p.ImportPath] = p.Package.Name
}
// canonical maps each non-canonical package path to
// its canonical path and name.
// A present nil value indicates that the canonical package
// is unknown: hosted on a bad domain with no redirect.
canonical := make(map[string]canonicalName)
domains := strings.Split(*badDomains, ",")
type replaceItem struct {
old, new string
matchPrefix bool
}
var replace []replaceItem
for _, pair := range strings.Split(*replaceFlag, ",") {
if pair == "" {
continue
}
words := strings.Split(pair, "=")
if len(words) != 2 {
fmt.Fprintf(stderr, "importfix: -replace: %q is not of the form \"canonical=noncanonical\".\n", pair)
return false
}
replace = append(replace, replaceItem{
old: strings.TrimSuffix(words[0], "..."),
new: strings.TrimSuffix(words[1], "..."),
matchPrefix: strings.HasSuffix(words[0], "...") &&
strings.HasSuffix(words[1], "..."),
})
}
// Find non-canonical packages and populate importedBy graph.
for _, p := range pkgs {
if p.Error != nil {
msg := p.Error.Err
if strings.Contains(msg, "code in directory") &&
strings.Contains(msg, "expects import") {
// don't show the very errors we're trying to fix
} else {
fmt.Fprintln(stderr, p.Error)
}
}
for _, imp := range p.Imports {
addEdge(&p.Package, imp)
}
for _, imp := range p.TestImports {
addEdge(&p.Package, imp)
}
for _, imp := range p.XTestImports {
addEdge(&p.Package, imp)
}
// Does package have an explicit import comment?
if p.ImportComment != "" {
if p.ImportComment != p.ImportPath {
canonical[p.ImportPath] = canonicalName{
path: p.Package.ImportComment,
name: p.Package.Name,
}
}
} else {
// Is package matched by a -replace item?
var newPath string
for _, item := range replace {
if item.matchPrefix {
if strings.HasPrefix(p.ImportPath, item.old) {
newPath = item.new + p.ImportPath[len(item.old):]
break
}
} else if p.ImportPath == item.old {
newPath = item.new
break
}
}
if newPath != "" {
newName := packageName[newPath]
if newName == "" {
newName = filepath.Base(newPath) // a guess
}
canonical[p.ImportPath] = canonicalName{
path: newPath,
name: newName,
}
continue
}
// Is package matched by a -baddomains item?
for _, domain := range domains {
slash := strings.Index(p.ImportPath, "/")
if slash < 0 {
continue // no slash: standard package
}
if p.ImportPath[:slash] == domain {
// Package comes from bad domain and has no import comment.
// Report an error each time this package is imported.
canonical[p.ImportPath] = canonicalName{}
// TODO(adonovan): should we make an HTTP request to
// see if there's an HTTP redirect, a "go-import" meta tag,
// or an import comment in the latest revision?
// It would duplicate a lot of logic from "go get".
}
break
}
}
}
// Find all clients (direct importers) of canonical packages.
// These are the packages that need fixing up.
clients := make(map[*build.Package]bool)
for path := range canonical {
for client := range importedBy[path] {
clients[client] = true
}
}
// Restrict rewrites to the set of packages specified by the user.
if len(packages) == 1 && (packages[0] == "all" || packages[0] == "...") {
// no restriction
} else {
pkgs, err := list(packages...)
if err != nil {
fmt.Fprintf(stderr, "importfix: %v\n", err)
return false
}
seen := make(map[string]bool)
for _, p := range pkgs {
seen[p.ImportPath] = true
}
for client := range clients {
if !seen[client.ImportPath] {
delete(clients, client)
}
}
}
// Rewrite selected client packages.
ok := true
for client := range clients {
if !rewritePackage(client, canonical) {
ok = false
// There were errors.
// Show direct and indirect imports of client.
seen := make(map[string]bool)
var direct, indirect []string
for p := range importedBy[client.ImportPath] {
direct = append(direct, p.ImportPath)
seen[p.ImportPath] = true
}
var visit func(path string)
visit = func(path string) {
for q := range importedBy[path] {
qpath := q.ImportPath
if !seen[qpath] {
seen[qpath] = true
indirect = append(indirect, qpath)
visit(qpath)
}
}
}
if direct != nil {
fmt.Fprintf(stderr, "\timported directly by:\n")
sort.Strings(direct)
for _, path := range direct {
fmt.Fprintf(stderr, "\t\t%s\n", path)
visit(path)
}
if indirect != nil {
fmt.Fprintf(stderr, "\timported indirectly by:\n")
sort.Strings(indirect)
for _, path := range indirect {
fmt.Fprintf(stderr, "\t\t%s\n", path)
}
}
}
}
}
return ok
}
// Invariant: false result => error already printed.
func rewritePackage(client *build.Package, canonical map[string]canonicalName) bool {
ok := true
used := make(map[string]bool)
var filenames []string
filenames = append(filenames, client.GoFiles...)
filenames = append(filenames, client.TestGoFiles...)
filenames = append(filenames, client.XTestGoFiles...)
var first bool
for _, filename := range filenames {
if !first {
first = true
fmt.Fprintf(stderr, "%s\n", client.ImportPath)
}
err := rewriteFile(filepath.Join(client.Dir, filename), canonical, used)
if err != nil {
fmt.Fprintf(stderr, "\tERROR: %v\n", err)
ok = false
}
}
// Show which imports were renamed in this package.
var keys []string
for key := range used {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
if p := canonical[key]; p.path != "" {
fmt.Fprintf(stderr, "\tfixed: %s -> %s\n", key, p.path)
} else {
fmt.Fprintf(stderr, "\tERROR: %s has no import comment\n", key)
ok = false
}
}
return ok
}
// rewrite reads, modifies, and writes filename, replacing all imports
// of packages P in canonical by canonical[P].
// It records in used which canonical packages were imported.
// used[P]=="" indicates that P was imported but its canonical path is unknown.
func rewriteFile(filename string, canonical map[string]canonicalName, used map[string]bool) error {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return err
}
var changed bool
for _, imp := range f.Imports {
impPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
log.Printf("%s: bad import spec %q: %v",
fset.Position(imp.Pos()), imp.Path.Value, err)
continue
}
canon, ok := canonical[impPath]
if !ok {
continue // import path is canonical
}
used[impPath] = true
if canon.path == "" {
// The canonical path is unknown (a -baddomain).
// Show the offending import.
// TODO(adonovan): should we show the actual source text?
fmt.Fprintf(stderr, "\t%s:%d: import %q\n",
shortPath(filename),
fset.Position(imp.Pos()).Line, impPath)
continue
}
changed = true
imp.Path.Value = strconv.Quote(canon.path)
// Add a renaming import if necessary.
//
// This is a guess at best. We can't see whether a 'go
// get' of the canonical import path would have the same
// name or not. Assume it's the last segment.
newBase := path.Base(canon.path)
if imp.Name == nil && newBase != canon.name {
imp.Name = &ast.Ident{Name: canon.name}
}
}
if changed && !*dryrun {
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {
return fmt.Errorf("%s: couldn't format file: %v", filename, err)
}
return writeFile(filename, buf.Bytes(), 0644)
}
return nil
}
// listPackage is a copy of cmd/go/list.Package.
// It has more fields than build.Package and we need some of them.
type listPackage struct {
build.Package
Error *packageError // error loading package
}
// A packageError describes an error loading information about a package.
type packageError struct {
ImportStack []string // shortest path from package named on command line to this one
Pos string // position of error
Err string // the error itself
}
func (e packageError) Error() string {
if e.Pos != "" {
return e.Pos + ": " + e.Err
}
return e.Err
}
// list runs 'go list' with the specified arguments and returns the
// metadata for matching packages.
func list(args ...string) ([]*listPackage, error) {
cmd := exec.Command("go", append([]string{"list", "-e", "-json"}, args...)...)
cmd.Stdout = new(bytes.Buffer)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return nil, err
}
dec := json.NewDecoder(cmd.Stdout.(io.Reader))
var pkgs []*listPackage
for {
var p listPackage
if err := dec.Decode(&p); err == io.EOF {
break
} else if err != nil {
return nil, err
}
pkgs = append(pkgs, &p)
}
return pkgs, nil
}
// cwd contains the current working directory of the tool.
//
// It is initialized directly so that its value will be set for any other
// package variables or init functions that depend on it, such as the gopath
// variable in main_test.go.
var cwd string = func() string {
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("os.Getwd: %v", err)
}
return cwd
}()
// shortPath returns an absolute or relative name for path, whatever is shorter.
// Plundered from $GOROOT/src/cmd/go/build.go.
func shortPath(path string) string {
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
return rel
}
return path
}
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/main_test.go 0000664 0000000 0000000 00000015744 14177515506 0023551 0 ustar 00root root 0000000 0000000 // Copyright 2015 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.
// No testdata on Android.
//go:build !android
// +build !android
package main
import (
"bytes"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/tools/internal/testenv"
)
// TODO(adonovan):
// - test introduction of renaming imports.
// - test induced failures of rewriteFile.
// Guide to the test packages:
//
// new.com/one -- canonical name for old.com/one
// old.com/one -- non-canonical; has import comment "new.com/one"
// old.com/bad -- has a parse error
// fruit.io/orange \
// fruit.io/banana } orange -> pear -> banana -> titanic.biz/bar
// fruit.io/pear /
// titanic.biz/bar -- domain is sinking; package has jumped ship to new.com/bar
// titanic.biz/foo -- domain is sinking but package has no import comment yet
var gopath = filepath.Join(cwd, "testdata")
func init() {
if err := os.Setenv("GOPATH", gopath); err != nil {
log.Fatal(err)
}
// This test currently requires GOPATH mode.
// Explicitly disabling module mode should suffix, but
// we'll also turn off GOPROXY just for good measure.
if err := os.Setenv("GO111MODULE", "off"); err != nil {
log.Fatal(err)
}
if err := os.Setenv("GOPROXY", "off"); err != nil {
log.Fatal(err)
}
}
func TestFixImports(t *testing.T) {
testenv.NeedsTool(t, "go")
defer func() {
stderr = os.Stderr
*badDomains = "code.google.com"
*replaceFlag = ""
}()
for i, test := range []struct {
packages []string // packages to rewrite, "go list" syntax
badDomains string // -baddomains flag
replaceFlag string // -replace flag
wantOK bool
wantStderr string
wantRewrite map[string]string
}{
// #0. No errors.
{
packages: []string{"all"},
badDomains: "code.google.com",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/one"
_ "titanic.biz/foo"
)`,
},
},
// #1. No packages needed rewriting.
{
packages: []string{"titanic.biz/...", "old.com/...", "new.com/..."},
badDomains: "code.google.com",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
`,
},
// #2. Some packages without import comments matched bad domains.
{
packages: []string{"all"},
badDomains: "titanic.biz",
wantOK: false,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
testdata/src/fruit.io/banana/banana.go:6: import "titanic.biz/foo"
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
ERROR: titanic.biz/foo has no import comment
imported directly by:
fruit.io/pear
imported indirectly by:
fruit.io/orange
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/one"
_ "titanic.biz/foo"
)`,
},
},
// #3. The -replace flag lets user supply missing import comments.
{
packages: []string{"all"},
replaceFlag: "titanic.biz/foo=new.com/foo",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
fixed: titanic.biz/foo -> new.com/foo
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/foo"
_ "new.com/one"
)`,
},
},
// #4. The -replace flag supports wildcards.
// An explicit import comment takes precedence.
{
packages: []string{"all"},
replaceFlag: "titanic.biz/...=new.com/...",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
fixed: titanic.biz/foo -> new.com/foo
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/foo"
_ "new.com/one"
)`,
},
},
// #5. The -replace flag trumps -baddomains.
{
packages: []string{"all"},
badDomains: "titanic.biz",
replaceFlag: "titanic.biz/foo=new.com/foo",
wantOK: true,
wantStderr: `
testdata/src/old.com/bad/bad.go:2:43: expected 'package', found 'EOF'
fruit.io/banana
fixed: old.com/one -> new.com/one
fixed: titanic.biz/bar -> new.com/bar
fixed: titanic.biz/foo -> new.com/foo
`,
wantRewrite: map[string]string{
"$GOPATH/src/fruit.io/banana/banana.go": `package banana
import (
_ "new.com/bar"
_ "new.com/foo"
_ "new.com/one"
)`,
},
},
} {
*badDomains = test.badDomains
*replaceFlag = test.replaceFlag
stderr = new(bytes.Buffer)
gotRewrite := make(map[string]string)
writeFile = func(filename string, content []byte, mode os.FileMode) error {
filename = strings.Replace(filename, gopath, "$GOPATH", 1)
filename = filepath.ToSlash(filename)
gotRewrite[filename] = string(bytes.TrimSpace(content))
return nil
}
if runtime.GOOS == "windows" {
test.wantStderr = strings.Replace(test.wantStderr, `testdata/src/old.com/bad/bad.go`, `testdata\src\old.com\bad\bad.go`, -1)
test.wantStderr = strings.Replace(test.wantStderr, `testdata/src/fruit.io/banana/banana.go`, `testdata\src\fruit.io\banana\banana.go`, -1)
}
test.wantStderr = strings.TrimSpace(test.wantStderr)
// Check status code.
if fiximports(test.packages...) != test.wantOK {
t.Errorf("#%d. fiximports() = %t", i, !test.wantOK)
}
// Compare stderr output.
if got := strings.TrimSpace(stderr.(*bytes.Buffer).String()); got != test.wantStderr {
if strings.Contains(got, "vendor/golang_org/x/text/unicode/norm") {
t.Skip("skipping known-broken test; see golang.org/issue/17417")
}
t.Errorf("#%d. stderr: got <<\n%s\n>>, want <<\n%s\n>>",
i, got, test.wantStderr)
}
// Compare rewrites.
for k, v := range gotRewrite {
if test.wantRewrite[k] != v {
t.Errorf("#%d. rewrite[%s] = <<%s>>, want <<%s>>",
i, k, v, test.wantRewrite[k])
}
delete(test.wantRewrite, k)
}
for k, v := range test.wantRewrite {
t.Errorf("#%d. rewrite[%s] missing, want <<%s>>", i, k, v)
}
}
}
// TestDryRun tests that the -n flag suppresses calls to writeFile.
func TestDryRun(t *testing.T) {
if os.Getenv("GO_BUILDER_NAME") == "plan9-arm" {
t.Skipf("skipping test that times out on plan9-arm; see https://go.dev/issue/50775")
}
testenv.NeedsTool(t, "go")
*dryrun = true
defer func() { *dryrun = false }() // restore
stderr = new(bytes.Buffer)
writeFile = func(filename string, content []byte, mode os.FileMode) error {
t.Fatalf("writeFile(%s) called in dryrun mode", filename)
return nil
}
if !fiximports("all") {
t.Fatalf("fiximports failed: %s", stderr)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/ 0000775 0000000 0000000 00000000000 14177515506 0023035 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/ 0000775 0000000 0000000 00000000000 14177515506 0023624 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/fruit.io/ 0000775 0000000 0000000 00000000000 14177515506 0025363 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/fruit.io/banana/ 0000775 0000000 0000000 00000000000 14177515506 0026603 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/fruit.io/banana/banana.go 0000664 0000000 0000000 00000000126 14177515506 0030351 0 ustar 00root root 0000000 0000000 package banana
import (
_ "old.com/one"
_ "titanic.biz/bar"
_ "titanic.biz/foo"
)
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/fruit.io/orange/ 0000775 0000000 0000000 00000000000 14177515506 0026636 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/fruit.io/orange/orange.go 0000664 0000000 0000000 00000000051 14177515506 0030434 0 ustar 00root root 0000000 0000000 package orange
import _ "fruit.io/pear"
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/fruit.io/pear/ 0000775 0000000 0000000 00000000000 14177515506 0026312 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/fruit.io/pear/pear.go 0000664 0000000 0000000 00000000051 14177515506 0027564 0 ustar 00root root 0000000 0000000 package pear
import _ "fruit.io/banana"
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/new.com/ 0000775 0000000 0000000 00000000000 14177515506 0025172 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/new.com/one/ 0000775 0000000 0000000 00000000000 14177515506 0025753 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/new.com/one/one.go 0000664 0000000 0000000 00000000044 14177515506 0027061 0 ustar 00root root 0000000 0000000 package one // import "new.com/one"
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/old.com/ 0000775 0000000 0000000 00000000000 14177515506 0025157 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/old.com/bad/ 0000775 0000000 0000000 00000000000 14177515506 0025705 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/old.com/bad/bad.go 0000664 0000000 0000000 00000000161 14177515506 0026760 0 ustar 00root root 0000000 0000000 // This ill-formed Go source file is here to ensure the tool is robust
// against bad packages in the workspace.
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/old.com/one/ 0000775 0000000 0000000 00000000000 14177515506 0025740 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/old.com/one/one.go 0000664 0000000 0000000 00000000044 14177515506 0027046 0 ustar 00root root 0000000 0000000 package one // import "new.com/one"
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/titanic.biz/ 0000775 0000000 0000000 00000000000 14177515506 0026042 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/titanic.biz/bar/ 0000775 0000000 0000000 00000000000 14177515506 0026606 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/titanic.biz/bar/bar.go 0000664 0000000 0000000 00000000116 14177515506 0027677 0 ustar 00root root 0000000 0000000 // This package is moving to new.com too.
package bar // import "new.com/bar"
golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/titanic.biz/foo/ 0000775 0000000 0000000 00000000000 14177515506 0026625 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/fiximports/testdata/src/titanic.biz/foo/foo.go 0000664 0000000 0000000 00000000064 14177515506 0027737 0 ustar 00root root 0000000 0000000 // This package hasn't jumped ship yet.
package foo
golang-golang-x-tools-0.1.9+ds/cmd/getgo/ 0000775 0000000 0000000 00000000000 14177515506 0020125 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/getgo/.dockerignore 0000664 0000000 0000000 00000000060 14177515506 0022575 0 ustar 00root root 0000000 0000000 .git
.dockerignore
LICENSE
README.md
.gitignore
golang-golang-x-tools-0.1.9+ds/cmd/getgo/.gitignore 0000664 0000000 0000000 00000000026 14177515506 0022113 0 ustar 00root root 0000000 0000000 build
testgetgo
getgo
golang-golang-x-tools-0.1.9+ds/cmd/getgo/Dockerfile 0000664 0000000 0000000 00000000654 14177515506 0022124 0 ustar 00root root 0000000 0000000 FROM golang:latest
ENV SHELL /bin/bash
ENV HOME /root
WORKDIR $HOME
COPY . /go/src/golang.org/x/tools/cmd/getgo
RUN ( \
cd /go/src/golang.org/x/tools/cmd/getgo \
&& go build \
&& mv getgo /usr/local/bin/getgo \
)
# undo the adding of GOPATH to env for testing
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV GOPATH ""
# delete /go and /usr/local/go for testing
RUN rm -rf /go /usr/local/go
golang-golang-x-tools-0.1.9+ds/cmd/getgo/LICENSE 0000664 0000000 0000000 00000002707 14177515506 0021140 0 ustar 00root root 0000000 0000000 Copyright (c) 2017 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang-golang-x-tools-0.1.9+ds/cmd/getgo/README.md 0000664 0000000 0000000 00000004734 14177515506 0021414 0 ustar 00root root 0000000 0000000 # getgo
A proof-of-concept command-line installer for Go.
This installer is designed to both install Go as well as do the initial configuration
of setting up the right environment variables and paths.
It will install the Go distribution (tools & stdlib) to "/.go" inside your home directory by default.
It will setup "$HOME/go" as your GOPATH.
This is where third party libraries and apps will be installed as well as where you will write your Go code.
If Go is already installed via this installer it will upgrade it to the latest version of Go.
Currently the installer supports Windows, \*nix and macOS on x86 & x64.
It supports Bash and Zsh on all of these platforms as well as powershell & cmd.exe on Windows.
## Usage
Windows Powershell/cmd.exe:
`(New-Object System.Net.WebClient).DownloadFile('https://get.golang.org/installer.exe', 'installer.exe'); Start-Process -Wait -NonewWindow installer.exe; Remove-Item installer.exe`
Shell (Linux/macOS/Windows):
`curl -LO https://get.golang.org/$(uname)/go_installer && chmod +x go_installer && ./go_installer && rm go_installer`
## To Do
* Check if Go is already installed (via a different method) and update it in place or at least notify the user
* Lots of testing. It's only had limited testing so far.
* Add support for additional shells.
## Development instructions
### Testing
There are integration tests in [`main_test.go`](main_test.go). Please add more
tests there.
#### On unix/linux with the Dockerfile
The Dockerfile automatically builds the binary, moves it to
`/usr/local/bin/getgo` and then unsets `$GOPATH` and removes all `$GOPATH` from
`$PATH`.
```bash
$ docker build --rm --force-rm -t getgo .
...
$ docker run --rm -it getgo bash
root@78425260fad0:~# getgo -v
Welcome to the Go installer!
Downloading Go version go1.8.3 to /usr/local/go
This may take a bit of time...
Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin" to /root/.bashrc
Downloaded!
Setting up GOPATH
Adding "export GOPATH=/root/go" to /root/.bashrc
Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin" to /root/.bashrc
GOPATH has been setup!
root@78425260fad0:~# which go
/usr/local/go/bin/go
root@78425260fad0:~# echo $GOPATH
/root/go
root@78425260fad0:~# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin
```
## Release instructions
To upload a new release of getgo, run `./make.bash && ./upload.bash`.
golang-golang-x-tools-0.1.9+ds/cmd/getgo/download.go 0000664 0000000 0000000 00000007750 14177515506 0022274 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
package main
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
)
const (
downloadURLPrefix = "https://dl.google.com/go"
)
// downloadGoVersion downloads and upacks the specific go version to dest/go.
func downloadGoVersion(version, ops, arch, dest string) error {
suffix := "tar.gz"
if ops == "windows" {
suffix = "zip"
}
uri := fmt.Sprintf("%s/%s.%s-%s.%s", downloadURLPrefix, version, ops, arch, suffix)
verbosef("Downloading %s", uri)
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return err
}
req.Header.Add("User-Agent", fmt.Sprintf("golang.org-getgo/%s", version))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("Downloading Go from %s failed: %v", uri, err)
}
if resp.StatusCode > 299 {
return fmt.Errorf("Downloading Go from %s failed with HTTP status %s", uri, resp.Status)
}
defer resp.Body.Close()
tmpf, err := ioutil.TempFile("", "go")
if err != nil {
return err
}
defer os.Remove(tmpf.Name())
h := sha256.New()
w := io.MultiWriter(tmpf, h)
if _, err := io.Copy(w, resp.Body); err != nil {
return err
}
verbosef("Downloading SHA %s.sha256", uri)
sresp, err := http.Get(uri + ".sha256")
if err != nil {
return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed: %v", uri, err)
}
defer sresp.Body.Close()
if sresp.StatusCode > 299 {
return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed with HTTP status %s", uri, sresp.Status)
}
shasum, err := ioutil.ReadAll(sresp.Body)
if err != nil {
return err
}
// Check the shasum.
sum := fmt.Sprintf("%x", h.Sum(nil))
if sum != string(shasum) {
return fmt.Errorf("Shasum mismatch %s vs. %s", sum, string(shasum))
}
unpackFunc := unpackTar
if ops == "windows" {
unpackFunc = unpackZip
}
if err := unpackFunc(tmpf.Name(), dest); err != nil {
return fmt.Errorf("Unpacking Go to %s failed: %v", dest, err)
}
return nil
}
func unpack(dest, name string, fi os.FileInfo, r io.Reader) error {
if strings.HasPrefix(name, "go/") {
name = name[len("go/"):]
}
path := filepath.Join(dest, name)
if fi.IsDir() {
return os.MkdirAll(path, fi.Mode())
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
func unpackTar(src, dest string) error {
r, err := os.Open(src)
if err != nil {
return err
}
defer r.Close()
archive, err := gzip.NewReader(r)
if err != nil {
return err
}
defer archive.Close()
tarReader := tar.NewReader(archive)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
if err := unpack(dest, header.Name, header.FileInfo(), tarReader); err != nil {
return err
}
}
return nil
}
func unpackZip(src, dest string) error {
zr, err := zip.OpenReader(src)
if err != nil {
return err
}
for _, f := range zr.File {
fr, err := f.Open()
if err != nil {
return err
}
if err := unpack(dest, f.Name, f.FileInfo(), fr); err != nil {
return err
}
fr.Close()
}
return nil
}
func getLatestGoVersion() (string, error) {
resp, err := http.Get("https://golang.org/dl/?mode=json")
if err != nil {
return "", fmt.Errorf("Getting current Go version failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
return "", fmt.Errorf("Could not get current Go release: HTTP %d: %q", resp.StatusCode, b)
}
var releases []struct {
Version string
}
err = json.NewDecoder(resp.Body).Decode(&releases)
if err != nil {
return "", err
}
if len(releases) < 1 {
return "", fmt.Errorf("Could not get at least one Go release")
}
return releases[0].Version, nil
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/download_test.go 0000664 0000000 0000000 00000001423 14177515506 0023322 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestDownloadGoVersion(t *testing.T) {
if testing.Short() {
t.Skipf("Skipping download in short mode")
}
tmpd, err := ioutil.TempDir("", "go")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpd)
if err := downloadGoVersion("go1.8.1", "linux", "amd64", filepath.Join(tmpd, "go")); err != nil {
t.Fatal(err)
}
// Ensure the VERSION file exists.
vf := filepath.Join(tmpd, "go", "VERSION")
if _, err := os.Stat(vf); os.IsNotExist(err) {
t.Fatalf("file %s does not exist and should", vf)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/main.go 0000664 0000000 0000000 00000004605 14177515506 0021405 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
// The getgo command installs Go to the user's system.
package main
import (
"bufio"
"context"
"errors"
"flag"
"fmt"
exec "golang.org/x/sys/execabs"
"os"
"strings"
)
var (
interactive = flag.Bool("i", false, "Interactive mode, prompt for inputs.")
verbose = flag.Bool("v", false, "Verbose.")
setupOnly = flag.Bool("skip-dl", false, "Don't download - only set up environment variables")
goVersion = flag.String("version", "", `Version of Go to install (e.g. "1.8.3"). If empty, uses the latest version.`)
version = "devel"
)
var errExitCleanly error = errors.New("exit cleanly sentinel value")
func main() {
flag.Parse()
if *goVersion != "" && !strings.HasPrefix(*goVersion, "go") {
*goVersion = "go" + *goVersion
}
ctx := context.Background()
verbosef("version " + version)
runStep := func(s step) {
err := s(ctx)
if err == errExitCleanly {
os.Exit(0)
}
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
}
if !*setupOnly {
runStep(welcome)
runStep(checkOthers)
runStep(chooseVersion)
runStep(downloadGo)
}
runStep(setupGOPATH)
}
func verbosef(format string, v ...interface{}) {
if !*verbose {
return
}
fmt.Printf(format+"\n", v...)
}
func prompt(ctx context.Context, query, defaultAnswer string) (string, error) {
if !*interactive {
return defaultAnswer, nil
}
fmt.Printf("%s [%s]: ", query, defaultAnswer)
type result struct {
answer string
err error
}
ch := make(chan result, 1)
go func() {
s := bufio.NewScanner(os.Stdin)
if !s.Scan() {
ch <- result{"", s.Err()}
return
}
answer := s.Text()
if answer == "" {
answer = defaultAnswer
}
ch <- result{answer, nil}
}()
select {
case r := <-ch:
return r.answer, r.err
case <-ctx.Done():
return "", ctx.Err()
}
}
func runCommand(ctx context.Context, prog string, args ...string) ([]byte, error) {
verbosef("Running command: %s %v", prog, args)
cmd := exec.CommandContext(ctx, prog, args...)
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("running cmd '%s %s' failed: %s err: %v", prog, strings.Join(args, " "), string(out), err)
}
if out != nil && err == nil && len(out) != 0 {
verbosef("%s", out)
}
return out, nil
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/main_test.go 0000664 0000000 0000000 00000006552 14177515506 0022447 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"runtime"
"testing"
)
const (
testbin = "testgetgo"
)
var (
exeSuffix string // ".exe" on Windows
)
func init() {
if runtime.GOOS == "windows" {
exeSuffix = ".exe"
}
}
// TestMain creates a getgo command for testing purposes and
// deletes it after the tests have been run.
func TestMain(m *testing.M) {
if os.Getenv("GOGET_INTEGRATION") == "" {
fmt.Fprintln(os.Stderr, "main_test: Skipping integration tests with GOGET_INTEGRATION unset")
return
}
args := []string{"build", "-tags", testbin, "-o", testbin + exeSuffix}
out, err := exec.Command("go", args...).CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "building %s failed: %v\n%s", testbin, err, out)
os.Exit(2)
}
// Don't let these environment variables confuse the test.
os.Unsetenv("GOBIN")
os.Unsetenv("GOPATH")
os.Unsetenv("GIT_ALLOW_PROTOCOL")
os.Unsetenv("PATH")
r := m.Run()
os.Remove(testbin + exeSuffix)
os.Exit(r)
}
func createTmpHome(t *testing.T) string {
tmpd, err := ioutil.TempDir("", "testgetgo")
if err != nil {
t.Fatalf("creating test tempdir failed: %v", err)
}
os.Setenv("HOME", tmpd)
return tmpd
}
// doRun runs the test getgo command, recording stdout and stderr and
// returning exit status.
func doRun(t *testing.T, args ...string) error {
var stdout, stderr bytes.Buffer
t.Logf("running %s %v", testbin, args)
cmd := exec.Command("./"+testbin+exeSuffix, args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = os.Environ()
status := cmd.Run()
if stdout.Len() > 0 {
t.Log("standard output:")
t.Log(stdout.String())
}
if stderr.Len() > 0 {
t.Log("standard error:")
t.Log(stderr.String())
}
return status
}
func TestCommandVerbose(t *testing.T) {
tmpd := createTmpHome(t)
defer os.RemoveAll(tmpd)
err := doRun(t, "-v")
if err != nil {
t.Fatal(err)
}
// make sure things are in path
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
home, err := getHomeDir()
if err != nil {
t.Fatal(err)
}
expected := fmt.Sprintf(`
export PATH=$PATH:%s/.go/bin
export GOPATH=%s/go
export PATH=$PATH:%s/go/bin
`, home, home, home)
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
}
func TestCommandPathExists(t *testing.T) {
tmpd := createTmpHome(t)
defer os.RemoveAll(tmpd)
// run once
err := doRun(t, "-skip-dl")
if err != nil {
t.Fatal(err)
}
// make sure things are in path
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
home, err := getHomeDir()
if err != nil {
t.Fatal(err)
}
expected := fmt.Sprintf(`
export GOPATH=%s/go
export PATH=$PATH:%s/go/bin
`, home, home)
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
// run twice
if err := doRun(t, "-skip-dl"); err != nil {
t.Fatal(err)
}
b, err = ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
if string(b) != expected {
t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
}
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/make.bash 0000775 0000000 0000000 00000000745 14177515506 0021712 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Copyright 2017 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.
set -e -o -x
LDFLAGS="-X main.version=$(git describe --always --dirty='*')"
GOOS=windows GOARCH=386 go build -o build/installer.exe -ldflags="$LDFLAGS"
GOOS=linux GOARCH=386 go build -o build/installer_linux -ldflags="$LDFLAGS"
GOOS=darwin GOARCH=386 go build -o build/installer_darwin -ldflags="$LDFLAGS"
golang-golang-x-tools-0.1.9+ds/cmd/getgo/path.go 0000664 0000000 0000000 00000006125 14177515506 0021414 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
package main
import (
"bufio"
"context"
"fmt"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
)
const (
bashConfig = ".bash_profile"
zshConfig = ".zshrc"
)
// appendToPATH adds the given path to the PATH environment variable and
// persists it for future sessions.
func appendToPATH(value string) error {
if isInPATH(value) {
return nil
}
return persistEnvVar("PATH", pathVar+envSeparator+value)
}
func isInPATH(dir string) bool {
p := os.Getenv("PATH")
paths := strings.Split(p, envSeparator)
for _, d := range paths {
if d == dir {
return true
}
}
return false
}
func getHomeDir() (string, error) {
home := os.Getenv(homeKey)
if home != "" {
return home, nil
}
u, err := user.Current()
if err != nil {
return "", err
}
return u.HomeDir, nil
}
func checkStringExistsFile(filename, value string) (bool, error) {
file, err := os.OpenFile(filename, os.O_RDONLY, 0600)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if line == value {
return true, nil
}
}
return false, scanner.Err()
}
func appendToFile(filename, value string) error {
verbosef("Adding %q to %s", value, filename)
ok, err := checkStringExistsFile(filename, value)
if err != nil {
return err
}
if ok {
// Nothing to do.
return nil
}
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(lineEnding + value + lineEnding)
return err
}
func isShell(name string) bool {
return strings.Contains(currentShell(), name)
}
// persistEnvVarWindows sets an environment variable in the Windows
// registry.
func persistEnvVarWindows(name, value string) error {
_, err := runCommand(context.Background(), "powershell", "-command",
fmt.Sprintf(`[Environment]::SetEnvironmentVariable("%s", "%s", "User")`, name, value))
return err
}
func persistEnvVar(name, value string) error {
if runtime.GOOS == "windows" {
if err := persistEnvVarWindows(name, value); err != nil {
return err
}
if isShell("cmd.exe") || isShell("powershell.exe") {
return os.Setenv(strings.ToUpper(name), value)
}
// User is in bash, zsh, etc.
// Also set the environment variable in their shell config.
}
rc, err := shellConfigFile()
if err != nil {
return err
}
line := fmt.Sprintf("export %s=%s", strings.ToUpper(name), value)
if err := appendToFile(rc, line); err != nil {
return err
}
return os.Setenv(strings.ToUpper(name), value)
}
func shellConfigFile() (string, error) {
home, err := getHomeDir()
if err != nil {
return "", err
}
switch {
case isShell("bash"):
return filepath.Join(home, bashConfig), nil
case isShell("zsh"):
return filepath.Join(home, zshConfig), nil
default:
return "", fmt.Errorf("%q is not a supported shell", currentShell())
}
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/path_test.go 0000664 0000000 0000000 00000002415 14177515506 0022451 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
package main
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestAppendPath(t *testing.T) {
tmpd, err := ioutil.TempDir("", "go")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpd)
if err := os.Setenv("HOME", tmpd); err != nil {
t.Fatal(err)
}
GOPATH := os.Getenv("GOPATH")
if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
t.Fatal(err)
}
shellConfig, err := shellConfigFile()
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
expected := "export PATH=" + pathVar + envSeparator + filepath.Join(GOPATH, "bin")
if strings.TrimSpace(string(b)) != expected {
t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
}
// Check that appendToPATH is idempotent.
if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
t.Fatal(err)
}
b, err = ioutil.ReadFile(shellConfig)
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(string(b)) != expected {
t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
}
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/server/ 0000775 0000000 0000000 00000000000 14177515506 0021433 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/getgo/server/.gcloudignore 0000664 0000000 0000000 00000001261 14177515506 0024115 0 ustar 00root root 0000000 0000000 # This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out golang-golang-x-tools-0.1.9+ds/cmd/getgo/server/README.md 0000664 0000000 0000000 00000000130 14177515506 0022704 0 ustar 00root root 0000000 0000000 # getgo server
## Deployment
```
gcloud app deploy --promote --project golang-org
```
golang-golang-x-tools-0.1.9+ds/cmd/getgo/server/app.yaml 0000664 0000000 0000000 00000000034 14177515506 0023074 0 ustar 00root root 0000000 0000000 runtime: go112
service: get
golang-golang-x-tools-0.1.9+ds/cmd/getgo/server/main.go 0000664 0000000 0000000 00000003253 14177515506 0022711 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
// Command server serves get.golang.org, redirecting users to the appropriate
// getgo installer based on the request path.
package main
import (
"fmt"
"net/http"
"os"
"strings"
"time"
)
const (
base = "https://dl.google.com/go/getgo/"
windowsInstaller = base + "installer.exe"
linuxInstaller = base + "installer_linux"
macInstaller = base + "installer_darwin"
)
// substring-based redirects.
var stringMatch = map[string]string{
// via uname, from bash
"MINGW": windowsInstaller, // Reported as MINGW64_NT-10.0 in git bash
"Linux": linuxInstaller,
"Darwin": macInstaller,
}
func main() {
http.HandleFunc("/", handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
fmt.Printf("Defaulting to port %s", port)
}
fmt.Printf("Listening on port %s", port)
if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil {
fmt.Fprintf(os.Stderr, "http.ListenAndServe: %v", err)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
if containsIgnoreCase(r.URL.Path, "installer.exe") {
// cache bust
http.Redirect(w, r, windowsInstaller+cacheBust(), http.StatusFound)
return
}
for match, redirect := range stringMatch {
if containsIgnoreCase(r.URL.Path, match) {
http.Redirect(w, r, redirect, http.StatusFound)
return
}
}
http.NotFound(w, r)
}
func containsIgnoreCase(s, substr string) bool {
return strings.Contains(
strings.ToLower(s),
strings.ToLower(substr),
)
}
func cacheBust() string {
return fmt.Sprintf("?%d", time.Now().Nanosecond())
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/steps.go 0000664 0000000 0000000 00000005542 14177515506 0021620 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
type step func(context.Context) error
func welcome(ctx context.Context) error {
fmt.Println("Welcome to the Go installer!")
answer, err := prompt(ctx, "Would you like to install Go? Y/n", "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Exiting install.")
return errExitCleanly
}
return nil
}
func checkOthers(ctx context.Context) error {
// TODO: if go is currently installed install new version over that
path, err := whichGo(ctx)
if err != nil {
fmt.Printf("Cannot check if Go is already installed:\n%v\n", err)
}
if path == "" {
return nil
}
if path != installPath {
fmt.Printf("Go is already installed at %v; remove it from your PATH.\n", path)
}
return nil
}
func chooseVersion(ctx context.Context) error {
if *goVersion != "" {
return nil
}
var err error
*goVersion, err = getLatestGoVersion()
if err != nil {
return err
}
answer, err := prompt(ctx, fmt.Sprintf("The latest Go version is %s, install that? Y/n", *goVersion), "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
// TODO: handle passing a version
fmt.Println("Aborting install.")
return errExitCleanly
}
return nil
}
func downloadGo(ctx context.Context) error {
answer, err := prompt(ctx, fmt.Sprintf("Download Go version %s to %s? Y/n", *goVersion, installPath), "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Aborting install.")
return errExitCleanly
}
fmt.Printf("Downloading Go version %s to %s\n", *goVersion, installPath)
fmt.Println("This may take a bit of time...")
if err := downloadGoVersion(*goVersion, runtime.GOOS, arch, installPath); err != nil {
return err
}
if err := appendToPATH(filepath.Join(installPath, "bin")); err != nil {
return err
}
fmt.Println("Downloaded!")
return nil
}
func setupGOPATH(ctx context.Context) error {
answer, err := prompt(ctx, "Would you like us to setup your GOPATH? Y/n", "Y")
if err != nil {
return err
}
if strings.ToLower(answer) != "y" {
fmt.Println("Exiting and not setting up GOPATH.")
return errExitCleanly
}
fmt.Println("Setting up GOPATH")
home, err := getHomeDir()
if err != nil {
return err
}
gopath := os.Getenv("GOPATH")
if gopath == "" {
// set $GOPATH
gopath = filepath.Join(home, "go")
if err := persistEnvVar("GOPATH", gopath); err != nil {
return err
}
fmt.Println("GOPATH has been set up!")
} else {
verbosef("GOPATH is already set to %s", gopath)
}
if err := appendToPATH(filepath.Join(gopath, "bin")); err != nil {
return err
}
return persistEnvChangesForSession()
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/system.go 0000664 0000000 0000000 00000001617 14177515506 0022005 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !plan9
// +build !plan9
package main
import (
"bytes"
"context"
exec "golang.org/x/sys/execabs"
"runtime"
"strings"
)
// arch contains either amd64 or 386.
var arch = func() string {
cmd := exec.Command("uname", "-m") // "x86_64"
if runtime.GOOS == "windows" {
cmd = exec.Command("powershell", "-command", "(Get-WmiObject -Class Win32_ComputerSystem).SystemType") // "x64-based PC"
}
out, err := cmd.Output()
if err != nil {
// a sensible default?
return "amd64"
}
if bytes.Contains(out, []byte("64")) {
return "amd64"
}
return "386"
}()
func findGo(ctx context.Context, cmd string) (string, error) {
out, err := exec.CommandContext(ctx, cmd, "go").CombinedOutput()
return strings.TrimSpace(string(out)), err
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/system_unix.go 0000664 0000000 0000000 00000002214 14177515506 0023042 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build aix || darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package main
import (
"context"
"fmt"
"os"
"path/filepath"
)
const (
envSeparator = ":"
homeKey = "HOME"
lineEnding = "\n"
pathVar = "$PATH"
)
var installPath = func() string {
home, err := getHomeDir()
if err != nil {
return "/usr/local/go"
}
return filepath.Join(home, ".go")
}()
func whichGo(ctx context.Context) (string, error) {
return findGo(ctx, "which")
}
func isWindowsXP() bool {
return false
}
func currentShell() string {
return os.Getenv("SHELL")
}
func persistEnvChangesForSession() error {
shellConfig, err := shellConfigFile()
if err != nil {
return err
}
fmt.Println()
fmt.Printf("One more thing! Run `source %s` to persist the\n", shellConfig)
fmt.Println("new environment variables to your current session, or open a")
fmt.Println("new shell prompt.")
return nil
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/system_windows.go 0000664 0000000 0000000 00000003452 14177515506 0023556 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build windows
// +build windows
package main
import (
"context"
"log"
"os"
"syscall"
"unsafe"
)
const (
envSeparator = ";"
homeKey = "USERPROFILE"
lineEnding = "/r/n"
pathVar = "$env:Path"
)
var installPath = `c:\go`
func isWindowsXP() bool {
v, err := syscall.GetVersion()
if err != nil {
log.Fatalf("GetVersion failed: %v", err)
}
major := byte(v)
return major < 6
}
func whichGo(ctx context.Context) (string, error) {
return findGo(ctx, "where")
}
// currentShell reports the current shell.
// It might be "powershell.exe", "cmd.exe" or any of the *nix shells.
//
// Returns empty string if the shell is unknown.
func currentShell() string {
shell := os.Getenv("SHELL")
if shell != "" {
return shell
}
pid := os.Getppid()
pe, err := getProcessEntry(pid)
if err != nil {
verbosef("getting shell from process entry failed: %v", err)
return ""
}
return syscall.UTF16ToString(pe.ExeFile[:])
}
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
// From https://go.googlesource.com/go/+/go1.8.3/src/syscall/syscall_windows.go#941
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(snapshot)
var procEntry syscall.ProcessEntry32
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
return nil, err
}
for {
if procEntry.ProcessID == uint32(pid) {
return &procEntry, nil
}
if err := syscall.Process32Next(snapshot, &procEntry); err != nil {
return nil, err
}
}
}
func persistEnvChangesForSession() error {
return nil
}
golang-golang-x-tools-0.1.9+ds/cmd/getgo/upload.bash 0000775 0000000 0000000 00000000706 14177515506 0022256 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Copyright 2017 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.
if ! command -v gsutil 2>&1 > /dev/null; then
echo "Install gsutil:"
echo
echo " https://cloud.google.com/storage/docs/gsutil_install#sdk-install"
fi
if [ ! -d build ]; then
echo "Run make.bash first"
fi
set -e -o -x
gsutil -m cp -a public-read build/* gs://golang/getgo
golang-golang-x-tools-0.1.9+ds/cmd/go-contrib-init/ 0000775 0000000 0000000 00000000000 14177515506 0022024 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/go-contrib-init/contrib.go 0000664 0000000 0000000 00000017330 14177515506 0024017 0 ustar 00root root 0000000 0000000 // Copyright 2017 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 go-contrib-init command helps new Go contributors get their development
// environment set up for the Go contribution process.
//
// It aims to be a complement or alternative to https://golang.org/doc/contribute.html.
package main
import (
"bytes"
"flag"
"fmt"
"go/build"
exec "golang.org/x/sys/execabs"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
)
var (
repo = flag.String("repo", detectrepo(), "Which go repo you want to contribute to. Use \"go\" for the core, or e.g. \"net\" for golang.org/x/net/*")
dry = flag.Bool("dry-run", false, "Fail with problems instead of trying to fix things.")
)
func main() {
log.SetFlags(0)
flag.Parse()
checkCLA()
checkGoroot()
checkWorkingDir()
checkGitOrigin()
checkGitCodeReview()
fmt.Print("All good. Happy hacking!\n" +
"Remember to squash your revised commits and preserve the magic Change-Id lines.\n" +
"Next steps: https://golang.org/doc/contribute.html#commit_changes\n")
}
func detectrepo() string {
wd, err := os.Getwd()
if err != nil {
return "go"
}
for _, path := range filepath.SplitList(build.Default.GOPATH) {
rightdir := filepath.Join(path, "src", "golang.org", "x") + string(os.PathSeparator)
if strings.HasPrefix(wd, rightdir) {
tail := wd[len(rightdir):]
end := strings.Index(tail, string(os.PathSeparator))
if end > 0 {
repo := tail[:end]
return repo
}
}
}
return "go"
}
var googleSourceRx = regexp.MustCompile(`(?m)^(go|go-review)?\.googlesource.com\b`)
func checkCLA() {
slurp, err := ioutil.ReadFile(cookiesFile())
if err != nil && !os.IsNotExist(err) {
log.Fatal(err)
}
if googleSourceRx.Match(slurp) {
// Probably good.
return
}
log.Fatal("Your .gitcookies file isn't configured.\n" +
"Next steps:\n" +
" * Submit a CLA (https://golang.org/doc/contribute.html#cla) if not done\n" +
" * Go to https://go.googlesource.com/ and click \"Generate Password\" at the top,\n" +
" then follow instructions.\n" +
" * Run go-contrib-init again.\n")
}
func expandUser(s string) string {
env := "HOME"
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else if runtime.GOOS == "plan9" {
env = "home"
}
home := os.Getenv(env)
if home == "" {
return s
}
if len(s) >= 2 && s[0] == '~' && os.IsPathSeparator(s[1]) {
if runtime.GOOS == "windows" {
s = filepath.ToSlash(filepath.Join(home, s[2:]))
} else {
s = filepath.Join(home, s[2:])
}
}
return os.Expand(s, func(env string) string {
if env == "HOME" {
return home
}
return os.Getenv(env)
})
}
func cookiesFile() string {
out, _ := exec.Command("git", "config", "http.cookiefile").Output()
if s := strings.TrimSpace(string(out)); s != "" {
if strings.HasPrefix(s, "~") {
s = expandUser(s)
}
return s
}
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("USERPROFILE"), ".gitcookies")
}
return filepath.Join(os.Getenv("HOME"), ".gitcookies")
}
func checkGoroot() {
v := os.Getenv("GOROOT")
if v == "" {
return
}
if *repo == "go" {
if strings.HasPrefix(v, "/usr/") {
log.Fatalf("Your GOROOT environment variable is set to %q\n"+
"This is almost certainly not what you want. Either unset\n"+
"your GOROOT or set it to the path of your development version\n"+
"of Go.", v)
}
slurp, err := ioutil.ReadFile(filepath.Join(v, "VERSION"))
if err == nil {
slurp = bytes.TrimSpace(slurp)
log.Fatalf("Your GOROOT environment variable is set to %q\n"+
"But that path is to a binary release of Go, with VERSION file %q.\n"+
"You should hack on Go in a fresh checkout of Go. Fix or unset your GOROOT.\n",
v, slurp)
}
}
}
func checkWorkingDir() {
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
if *repo == "go" {
if inGoPath(wd) {
log.Fatalf(`You can't work on Go from within your GOPATH. Please checkout Go outside of your GOPATH
Current directory: %s
GOPATH: %s
`, wd, os.Getenv("GOPATH"))
}
return
}
gopath := firstGoPath()
if gopath == "" {
log.Fatal("Your GOPATH is not set, please set it")
}
rightdir := filepath.Join(gopath, "src", "golang.org", "x", *repo)
if !strings.HasPrefix(wd, rightdir) {
dirExists, err := exists(rightdir)
if err != nil {
log.Fatal(err)
}
if !dirExists {
log.Fatalf("The repo you want to work on is currently not on your system.\n"+
"Run %q to obtain this repo\n"+
"then go to the directory %q\n",
"go get -d golang.org/x/"+*repo, rightdir)
}
log.Fatalf("Your current directory is:%q\n"+
"Working on golang/x/%v requires you be in %q\n",
wd, *repo, rightdir)
}
}
func firstGoPath() string {
list := filepath.SplitList(build.Default.GOPATH)
if len(list) < 1 {
return ""
}
return list[0]
}
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
func inGoPath(wd string) bool {
if os.Getenv("GOPATH") == "" {
return false
}
for _, path := range filepath.SplitList(os.Getenv("GOPATH")) {
if strings.HasPrefix(wd, filepath.Join(path, "src")) {
return true
}
}
return false
}
// mostly check that they didn't clone from github
func checkGitOrigin() {
if _, err := exec.LookPath("git"); err != nil {
log.Fatalf("You don't appear to have git installed. Do that.")
}
wantRemote := "https://go.googlesource.com/" + *repo
remotes, err := exec.Command("git", "remote", "-v").Output()
if err != nil {
msg := cmdErr(err)
if strings.Contains(msg, "Not a git repository") {
log.Fatalf("Your current directory is not in a git checkout of %s", wantRemote)
}
log.Fatalf("Error running git remote -v: %v", msg)
}
matches := 0
for _, line := range strings.Split(string(remotes), "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "origin") {
continue
}
if !strings.Contains(line, wantRemote) {
curRemote := strings.Fields(strings.TrimPrefix(line, "origin"))[0]
// TODO: if not in dryRun mode, just fix it?
log.Fatalf("Current directory's git was cloned from %q; origin should be %q", curRemote, wantRemote)
}
matches++
}
if matches == 0 {
log.Fatalf("git remote -v output didn't contain expected %q. Got:\n%s", wantRemote, remotes)
}
}
func cmdErr(err error) string {
if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
return fmt.Sprintf("%s: %s", err, ee.Stderr)
}
return fmt.Sprint(err)
}
func checkGitCodeReview() {
if _, err := exec.LookPath("git-codereview"); err != nil {
if *dry {
log.Fatalf("You don't appear to have git-codereview tool. While this is technically optional,\n" +
"almost all Go contributors use it. Our documentation and this tool assume it is used.\n" +
"To install it, run:\n\n\t$ go get golang.org/x/review/git-codereview\n\n(Then run go-contrib-init again)")
}
err := exec.Command("go", "get", "golang.org/x/review/git-codereview").Run()
if err != nil {
log.Fatalf("Error running go get golang.org/x/review/git-codereview: %v", cmdErr(err))
}
log.Printf("Installed git-codereview (ran `go get golang.org/x/review/git-codereview`)")
}
missing := false
for _, cmd := range []string{"change", "gofmt", "mail", "pending", "submit", "sync"} {
v, _ := exec.Command("git", "config", "alias."+cmd).Output()
if strings.Contains(string(v), "codereview") {
continue
}
if *dry {
log.Printf("Missing alias. Run:\n\t$ git config alias.%s \"codereview %s\"", cmd, cmd)
missing = true
} else {
err := exec.Command("git", "config", "alias."+cmd, "codereview "+cmd).Run()
if err != nil {
log.Fatalf("Error setting alias.%s: %v", cmd, cmdErr(err))
}
}
}
if missing {
log.Fatalf("Missing aliases. (While optional, this tool assumes you use them.)")
}
}
golang-golang-x-tools-0.1.9+ds/cmd/go-contrib-init/contrib_test.go 0000664 0000000 0000000 00000002356 14177515506 0025060 0 ustar 00root root 0000000 0000000 // Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"os"
"os/exec"
"runtime"
"testing"
)
func TestExpandUser(t *testing.T) {
env := "HOME"
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else if runtime.GOOS == "plan9" {
env = "home"
}
oldenv := os.Getenv(env)
os.Setenv(env, "/home/gopher")
defer os.Setenv(env, oldenv)
tests := []struct {
input string
want string
}{
{input: "~/foo", want: "/home/gopher/foo"},
{input: "${HOME}/foo", want: "/home/gopher/foo"},
{input: "/~/foo", want: "/~/foo"},
}
for _, tt := range tests {
got := expandUser(tt.input)
if got != tt.want {
t.Fatalf("want %q, but %q", tt.want, got)
}
}
}
func TestCmdErr(t *testing.T) {
tests := []struct {
input error
want string
}{
{input: errors.New("cmd error"), want: "cmd error"},
{input: &exec.ExitError{ProcessState: nil, Stderr: nil}, want: ""},
{input: &exec.ExitError{ProcessState: nil, Stderr: []byte("test")}, want: ": test"},
}
for i, tt := range tests {
got := cmdErr(tt.input)
if got != tt.want {
t.Fatalf("%d. got %q, want %q", i, got, tt.want)
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/ 0000775 0000000 0000000 00000000000 14177515506 0020126 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/godex/doc.go 0000664 0000000 0000000 00000004653 14177515506 0021232 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 godex command prints (dumps) exported information of packages
// or selected package objects.
//
// In contrast to godoc, godex extracts this information from compiled
// object files. Hence the exported data is truly what a compiler will
// see, at the cost of missing commentary.
//
// Usage: godex [flags] {path[.name]}
//
// Each argument must be a (possibly partial) package path, optionally
// followed by a dot and the name of a package object:
//
// godex math
// godex math.Sin
// godex math.Sin fmt.Printf
// godex go/types
//
// godex automatically tries all possible package path prefixes if only a
// partial package path is given. For instance, for the path "go/types",
// godex prepends "golang.org/x/tools".
//
// The prefixes are computed by searching the directories specified by
// the GOROOT and GOPATH environment variables (and by excluding the
// build OS- and architecture-specific directory names from the path).
// The search order is depth-first and alphabetic; for a partial path
// "foo", a package "a/foo" is found before "b/foo".
//
// Absolute and relative paths may be provided, which disable automatic
// prefix generation:
//
// godex $GOROOT/pkg/darwin_amd64/sort
// godex ./sort
//
// All but the last path element may contain dots; a dot in the last path
// element separates the package path from the package object name. If the
// last path element contains a dot, terminate the argument with another
// dot (indicating an empty object name). For instance, the path for a
// package foo.bar would be specified as in:
//
// godex foo.bar.
//
// The flags are:
//
// -s=""
// only consider packages from src, where src is one of the supported compilers
// -v=false
// verbose mode
//
// The following sources (-s arguments) are supported:
//
// gc
// gc-generated object files
// gccgo
// gccgo-generated object files
// gccgo-new
// gccgo-generated object files using a condensed format (experimental)
// source
// (uncompiled) source code (not yet implemented)
//
// If no -s argument is provided, godex will try to find a matching source.
//
package main // import "golang.org/x/tools/cmd/godex"
// BUG(gri): support for -s=source is not yet implemented
// BUG(gri): gccgo-importing appears to have occasional problems stalling godex; try -s=gc as work-around
golang-golang-x-tools-0.1.9+ds/cmd/godex/gc.go 0000664 0000000 0000000 00000000547 14177515506 0021054 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 implements access to gc-generated export data.
package main
import (
"go/importer"
"go/token"
)
func init() {
register("gc", importer.ForCompiler(token.NewFileSet(), "gc", nil))
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/gccgo.go 0000664 0000000 0000000 00000001566 14177515506 0021547 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 implements access to gccgo-generated export data.
package main
import (
"go/importer"
"go/token"
"go/types"
)
func init() {
register("gccgo", importer.ForCompiler(token.NewFileSet(), "gccgo", nil))
}
// Print the extra gccgo compiler data for this package, if it exists.
func (p *printer) printGccgoExtra(pkg *types.Package) {
// Disabled for now.
// TODO(gri) address this at some point.
// if initdata, ok := initmap[pkg]; ok {
// p.printf("/*\npriority %d\n", initdata.Priority)
// p.printDecl("init", len(initdata.Inits), func() {
// for _, init := range initdata.Inits {
// p.printf("%s %s %d\n", init.Name, init.InitFunc, init.Priority)
// }
// })
// p.print("*/\n")
// }
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/godex.go 0000664 0000000 0000000 00000012274 14177515506 0021571 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"flag"
"fmt"
"go/build"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
var (
source = flag.String("s", "", "only consider packages from src, where src is one of the supported compilers")
verbose = flag.Bool("v", false, "verbose mode")
)
// lists of registered sources and corresponding importers
var (
sources []string
importers []types.Importer
errImportFailed = errors.New("import failed")
)
func usage() {
fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}")
flag.PrintDefaults()
os.Exit(2)
}
func report(msg string) {
fmt.Fprintln(os.Stderr, "error: "+msg)
os.Exit(2)
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() == 0 {
report("no package name, path, or file provided")
}
var imp types.Importer = new(tryImporters)
if *source != "" {
imp = lookup(*source)
if imp == nil {
report("source (-s argument) must be one of: " + strings.Join(sources, ", "))
}
}
for _, arg := range flag.Args() {
path, name := splitPathIdent(arg)
logf("\tprocessing %q: path = %q, name = %s\n", arg, path, name)
// generate possible package path prefixes
// (at the moment we do this for each argument - should probably cache the generated prefixes)
prefixes := make(chan string)
go genPrefixes(prefixes, !filepath.IsAbs(path) && !build.IsLocalImport(path))
// import package
pkg, err := tryPrefixes(prefixes, path, imp)
if err != nil {
logf("\t=> ignoring %q: %s\n", path, err)
continue
}
// filter objects if needed
var filter func(types.Object) bool
if name != "" {
filter = func(obj types.Object) bool {
// TODO(gri) perhaps use regular expression matching here?
return obj.Name() == name
}
}
// print contents
print(os.Stdout, pkg, filter)
}
}
func logf(format string, args ...interface{}) {
if *verbose {
fmt.Fprintf(os.Stderr, format, args...)
}
}
// splitPathIdent splits a path.name argument into its components.
// All but the last path element may contain dots.
func splitPathIdent(arg string) (path, name string) {
if i := strings.LastIndex(arg, "."); i >= 0 {
if j := strings.LastIndex(arg, "/"); j < i {
// '.' is not part of path
path = arg[:i]
name = arg[i+1:]
return
}
}
path = arg
return
}
// tryPrefixes tries to import the package given by (the possibly partial) path using the given importer imp
// by prepending all possible prefixes to path. It returns with the first package that it could import, or
// with an error.
func tryPrefixes(prefixes chan string, path string, imp types.Importer) (pkg *types.Package, err error) {
for prefix := range prefixes {
actual := path
if prefix == "" {
// don't use filepath.Join as it will sanitize the path and remove
// a leading dot and then the path is not recognized as a relative
// package path by the importers anymore
logf("\ttrying no prefix\n")
} else {
actual = filepath.Join(prefix, path)
logf("\ttrying prefix %q\n", prefix)
}
pkg, err = imp.Import(actual)
if err == nil {
break
}
logf("\t=> importing %q failed: %s\n", actual, err)
}
return
}
// tryImporters is an importer that tries all registered importers
// successively until one of them succeeds or all of them failed.
type tryImporters struct{}
func (t *tryImporters) Import(path string) (pkg *types.Package, err error) {
for i, imp := range importers {
logf("\t\ttrying %s import\n", sources[i])
pkg, err = imp.Import(path)
if err == nil {
break
}
logf("\t\t=> %s import failed: %s\n", sources[i], err)
}
return
}
type protector struct {
imp types.Importer
}
func (p *protector) Import(path string) (pkg *types.Package, err error) {
defer func() {
if recover() != nil {
pkg = nil
err = errImportFailed
}
}()
return p.imp.Import(path)
}
// protect protects an importer imp from panics and returns the protected importer.
func protect(imp types.Importer) types.Importer {
return &protector{imp}
}
// register registers an importer imp for a given source src.
func register(src string, imp types.Importer) {
if lookup(src) != nil {
panic(src + " importer already registered")
}
sources = append(sources, src)
importers = append(importers, protect(imp))
}
// lookup returns the importer imp for a given source src.
func lookup(src string) types.Importer {
for i, s := range sources {
if s == src {
return importers[i]
}
}
return nil
}
func genPrefixes(out chan string, all bool) {
out <- ""
if all {
platform := build.Default.GOOS + "_" + build.Default.GOARCH
dirnames := append([]string{build.Default.GOROOT}, filepath.SplitList(build.Default.GOPATH)...)
for _, dirname := range dirnames {
walkDir(filepath.Join(dirname, "pkg", platform), "", out)
}
}
close(out)
}
func walkDir(dirname, prefix string, out chan string) {
fiList, err := ioutil.ReadDir(dirname)
if err != nil {
return
}
for _, fi := range fiList {
if fi.IsDir() && !strings.HasPrefix(fi.Name(), ".") {
prefix := filepath.Join(prefix, fi.Name())
out <- prefix
walkDir(filepath.Join(dirname, fi.Name()), prefix, out)
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/isAlias18.go 0000664 0000000 0000000 00000000511 14177515506 0022210 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !go1.9
// +build !go1.9
package main
import "go/types"
func isAlias(obj *types.TypeName) bool {
return false // there are no type aliases before Go 1.9
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/isAlias19.go 0000664 0000000 0000000 00000000444 14177515506 0022216 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build go1.9
// +build go1.9
package main
import "go/types"
func isAlias(obj *types.TypeName) bool {
return obj.IsAlias()
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/print.go 0000664 0000000 0000000 00000021746 14177515506 0021623 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"fmt"
"go/constant"
"go/token"
"go/types"
"io"
"math/big"
)
// TODO(gri) use tabwriter for alignment?
func print(w io.Writer, pkg *types.Package, filter func(types.Object) bool) {
var p printer
p.pkg = pkg
p.printPackage(pkg, filter)
p.printGccgoExtra(pkg)
io.Copy(w, &p.buf)
}
type printer struct {
pkg *types.Package
buf bytes.Buffer
indent int // current indentation level
last byte // last byte written
}
func (p *printer) print(s string) {
// Write the string one byte at a time. We care about the presence of
// newlines for indentation which we will see even in the presence of
// (non-corrupted) Unicode; no need to read one rune at a time.
for i := 0; i < len(s); i++ {
ch := s[i]
if ch != '\n' && p.last == '\n' {
// Note: This could lead to a range overflow for very large
// indentations, but it's extremely unlikely to happen for
// non-pathological code.
p.buf.WriteString("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"[:p.indent])
}
p.buf.WriteByte(ch)
p.last = ch
}
}
func (p *printer) printf(format string, args ...interface{}) {
p.print(fmt.Sprintf(format, args...))
}
// methodsFor returns the named type and corresponding methods if the type
// denoted by obj is not an interface and has methods. Otherwise it returns
// the zero value.
func methodsFor(obj *types.TypeName) (*types.Named, []*types.Selection) {
named, _ := obj.Type().(*types.Named)
if named == nil {
// A type name's type can also be the
// exported basic type unsafe.Pointer.
return nil, nil
}
if _, ok := named.Underlying().(*types.Interface); ok {
// ignore interfaces
return nil, nil
}
methods := combinedMethodSet(named)
if len(methods) == 0 {
return nil, nil
}
return named, methods
}
func (p *printer) printPackage(pkg *types.Package, filter func(types.Object) bool) {
// collect objects by kind
var (
consts []*types.Const
typem []*types.Named // non-interface types with methods
typez []*types.TypeName // interfaces or types without methods
vars []*types.Var
funcs []*types.Func
builtins []*types.Builtin
methods = make(map[*types.Named][]*types.Selection) // method sets for named types
)
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if obj.Exported() {
// collect top-level exported and possibly filtered objects
if filter == nil || filter(obj) {
switch obj := obj.(type) {
case *types.Const:
consts = append(consts, obj)
case *types.TypeName:
// group into types with methods and types without
if named, m := methodsFor(obj); named != nil {
typem = append(typem, named)
methods[named] = m
} else {
typez = append(typez, obj)
}
case *types.Var:
vars = append(vars, obj)
case *types.Func:
funcs = append(funcs, obj)
case *types.Builtin:
// for unsafe.Sizeof, etc.
builtins = append(builtins, obj)
}
}
} else if filter == nil {
// no filtering: collect top-level unexported types with methods
if obj, _ := obj.(*types.TypeName); obj != nil {
// see case *types.TypeName above
if named, m := methodsFor(obj); named != nil {
typem = append(typem, named)
methods[named] = m
}
}
}
}
p.printf("package %s // %q\n", pkg.Name(), pkg.Path())
p.printDecl("const", len(consts), func() {
for _, obj := range consts {
p.printObj(obj)
p.print("\n")
}
})
p.printDecl("var", len(vars), func() {
for _, obj := range vars {
p.printObj(obj)
p.print("\n")
}
})
p.printDecl("type", len(typez), func() {
for _, obj := range typez {
p.printf("%s ", obj.Name())
typ := obj.Type()
if isAlias(obj) {
p.print("= ")
p.writeType(p.pkg, typ)
} else {
p.writeType(p.pkg, typ.Underlying())
}
p.print("\n")
}
})
// non-interface types with methods
for _, named := range typem {
first := true
if obj := named.Obj(); obj.Exported() {
if first {
p.print("\n")
first = false
}
p.printf("type %s ", obj.Name())
p.writeType(p.pkg, named.Underlying())
p.print("\n")
}
for _, m := range methods[named] {
if obj := m.Obj(); obj.Exported() {
if first {
p.print("\n")
first = false
}
p.printFunc(m.Recv(), obj.(*types.Func))
p.print("\n")
}
}
}
if len(funcs) > 0 {
p.print("\n")
for _, obj := range funcs {
p.printFunc(nil, obj)
p.print("\n")
}
}
// TODO(gri) better handling of builtins (package unsafe only)
if len(builtins) > 0 {
p.print("\n")
for _, obj := range builtins {
p.printf("func %s() // builtin\n", obj.Name())
}
}
p.print("\n")
}
func (p *printer) printDecl(keyword string, n int, printGroup func()) {
switch n {
case 0:
// nothing to do
case 1:
p.printf("\n%s ", keyword)
printGroup()
default:
p.printf("\n%s (\n", keyword)
p.indent++
printGroup()
p.indent--
p.print(")\n")
}
}
// absInt returns the absolute value of v as a *big.Int.
// v must be a numeric value.
func absInt(v constant.Value) *big.Int {
// compute big-endian representation of v
b := constant.Bytes(v) // little-endian
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return new(big.Int).SetBytes(b)
}
var (
one = big.NewRat(1, 1)
ten = big.NewRat(10, 1)
)
// floatString returns the string representation for a
// numeric value v in normalized floating-point format.
func floatString(v constant.Value) string {
if constant.Sign(v) == 0 {
return "0.0"
}
// x != 0
// convert |v| into a big.Rat x
x := new(big.Rat).SetFrac(absInt(constant.Num(v)), absInt(constant.Denom(v)))
// normalize x and determine exponent e
// (This is not very efficient, but also not speed-critical.)
var e int
for x.Cmp(ten) >= 0 {
x.Quo(x, ten)
e++
}
for x.Cmp(one) < 0 {
x.Mul(x, ten)
e--
}
// TODO(gri) Values such as 1/2 are easier to read in form 0.5
// rather than 5.0e-1. Similarly, 1.0e1 is easier to read as
// 10.0. Fine-tune best exponent range for readability.
s := x.FloatString(100) // good-enough precision
// trim trailing 0's
i := len(s)
for i > 0 && s[i-1] == '0' {
i--
}
s = s[:i]
// add a 0 if the number ends in decimal point
if len(s) > 0 && s[len(s)-1] == '.' {
s += "0"
}
// add exponent and sign
if e != 0 {
s += fmt.Sprintf("e%+d", e)
}
if constant.Sign(v) < 0 {
s = "-" + s
}
// TODO(gri) If v is a "small" fraction (i.e., numerator and denominator
// are just a small number of decimal digits), add the exact fraction as
// a comment. For instance: 3.3333...e-1 /* = 1/3 */
return s
}
// valString returns the string representation for the value v.
// Setting floatFmt forces an integer value to be formatted in
// normalized floating-point format.
// TODO(gri) Move this code into package constant.
func valString(v constant.Value, floatFmt bool) string {
switch v.Kind() {
case constant.Int:
if floatFmt {
return floatString(v)
}
case constant.Float:
return floatString(v)
case constant.Complex:
re := constant.Real(v)
im := constant.Imag(v)
var s string
if constant.Sign(re) != 0 {
s = floatString(re)
if constant.Sign(im) >= 0 {
s += " + "
} else {
s += " - "
im = constant.UnaryOp(token.SUB, im, 0) // negate im
}
}
// im != 0, otherwise v would be constant.Int or constant.Float
return s + floatString(im) + "i"
}
return v.String()
}
func (p *printer) printObj(obj types.Object) {
p.print(obj.Name())
typ, basic := obj.Type().Underlying().(*types.Basic)
if basic && typ.Info()&types.IsUntyped != 0 {
// don't write untyped types
} else {
p.print(" ")
p.writeType(p.pkg, obj.Type())
}
if obj, ok := obj.(*types.Const); ok {
floatFmt := basic && typ.Info()&(types.IsFloat|types.IsComplex) != 0
p.print(" = ")
p.print(valString(obj.Val(), floatFmt))
}
}
func (p *printer) printFunc(recvType types.Type, obj *types.Func) {
p.print("func ")
sig := obj.Type().(*types.Signature)
if recvType != nil {
p.print("(")
p.writeType(p.pkg, recvType)
p.print(") ")
}
p.print(obj.Name())
p.writeSignature(p.pkg, sig)
}
// combinedMethodSet returns the method set for a named type T
// merged with all the methods of *T that have different names than
// the methods of T.
//
// combinedMethodSet is analogous to types/typeutil.IntuitiveMethodSet
// but doesn't require a MethodSetCache.
// TODO(gri) If this functionality doesn't change over time, consider
// just calling IntuitiveMethodSet eventually.
func combinedMethodSet(T *types.Named) []*types.Selection {
// method set for T
mset := types.NewMethodSet(T)
var res []*types.Selection
for i, n := 0, mset.Len(); i < n; i++ {
res = append(res, mset.At(i))
}
// add all *T methods with names different from T methods
pmset := types.NewMethodSet(types.NewPointer(T))
for i, n := 0, pmset.Len(); i < n; i++ {
pm := pmset.At(i)
if obj := pm.Obj(); mset.Lookup(obj.Pkg(), obj.Name()) == nil {
res = append(res, pm)
}
}
return res
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/source.go 0000664 0000000 0000000 00000000660 14177515506 0021757 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 implements access to export data from source.
package main
import "go/types"
func init() {
register("source", sourceImporter{})
}
type sourceImporter struct{}
func (sourceImporter) Import(path string) (*types.Package, error) {
panic("unimplemented")
}
golang-golang-x-tools-0.1.9+ds/cmd/godex/writetype.go 0000664 0000000 0000000 00000013270 14177515506 0022514 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 implements writing of types. The functionality is lifted
// directly from go/types, but now contains various modifications for
// nicer output.
//
// TODO(gri) back-port once we have a fixed interface and once the
// go/types API is not frozen anymore for the 1.3 release; and remove
// this implementation if possible.
package main
import "go/types"
func (p *printer) writeType(this *types.Package, typ types.Type) {
p.writeTypeInternal(this, typ, make([]types.Type, 8))
}
// From go/types - leave for now to ease back-porting this code.
const GcCompatibilityMode = false
func (p *printer) writeTypeInternal(this *types.Package, typ types.Type, visited []types.Type) {
// Theoretically, this is a quadratic lookup algorithm, but in
// practice deeply nested composite types with unnamed component
// types are uncommon. This code is likely more efficient than
// using a map.
for _, t := range visited {
if t == typ {
p.printf("○%T", typ) // cycle to typ
return
}
}
visited = append(visited, typ)
switch t := typ.(type) {
case nil:
p.print("")
case *types.Basic:
if t.Kind() == types.UnsafePointer {
p.print("unsafe.")
}
if GcCompatibilityMode {
// forget the alias names
switch t.Kind() {
case types.Byte:
t = types.Typ[types.Uint8]
case types.Rune:
t = types.Typ[types.Int32]
}
}
p.print(t.Name())
case *types.Array:
p.printf("[%d]", t.Len())
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Slice:
p.print("[]")
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Struct:
n := t.NumFields()
if n == 0 {
p.print("struct{}")
return
}
p.print("struct {\n")
p.indent++
for i := 0; i < n; i++ {
f := t.Field(i)
if !f.Anonymous() {
p.printf("%s ", f.Name())
}
p.writeTypeInternal(this, f.Type(), visited)
if tag := t.Tag(i); tag != "" {
p.printf(" %q", tag)
}
p.print("\n")
}
p.indent--
p.print("}")
case *types.Pointer:
p.print("*")
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Tuple:
p.writeTuple(this, t, false, visited)
case *types.Signature:
p.print("func")
p.writeSignatureInternal(this, t, visited)
case *types.Interface:
// We write the source-level methods and embedded types rather
// than the actual method set since resolved method signatures
// may have non-printable cycles if parameters have anonymous
// interface types that (directly or indirectly) embed the
// current interface. For instance, consider the result type
// of m:
//
// type T interface{
// m() interface{ T }
// }
//
n := t.NumMethods()
if n == 0 {
p.print("interface{}")
return
}
p.print("interface {\n")
p.indent++
if GcCompatibilityMode {
// print flattened interface
// (useful to compare against gc-generated interfaces)
for i := 0; i < n; i++ {
m := t.Method(i)
p.print(m.Name())
p.writeSignatureInternal(this, m.Type().(*types.Signature), visited)
p.print("\n")
}
} else {
// print explicit interface methods and embedded types
for i, n := 0, t.NumExplicitMethods(); i < n; i++ {
m := t.ExplicitMethod(i)
p.print(m.Name())
p.writeSignatureInternal(this, m.Type().(*types.Signature), visited)
p.print("\n")
}
for i, n := 0, t.NumEmbeddeds(); i < n; i++ {
typ := t.EmbeddedType(i)
p.writeTypeInternal(this, typ, visited)
p.print("\n")
}
}
p.indent--
p.print("}")
case *types.Map:
p.print("map[")
p.writeTypeInternal(this, t.Key(), visited)
p.print("]")
p.writeTypeInternal(this, t.Elem(), visited)
case *types.Chan:
var s string
var parens bool
switch t.Dir() {
case types.SendRecv:
s = "chan "
// chan (<-chan T) requires parentheses
if c, _ := t.Elem().(*types.Chan); c != nil && c.Dir() == types.RecvOnly {
parens = true
}
case types.SendOnly:
s = "chan<- "
case types.RecvOnly:
s = "<-chan "
default:
panic("unreachable")
}
p.print(s)
if parens {
p.print("(")
}
p.writeTypeInternal(this, t.Elem(), visited)
if parens {
p.print(")")
}
case *types.Named:
s := ""
if obj := t.Obj(); obj != nil {
if pkg := obj.Pkg(); pkg != nil {
if pkg != this {
p.print(pkg.Path())
p.print(".")
}
// TODO(gri): function-local named types should be displayed
// differently from named types at package level to avoid
// ambiguity.
}
s = obj.Name()
}
p.print(s)
default:
// For externally defined implementations of Type.
p.print(t.String())
}
}
func (p *printer) writeTuple(this *types.Package, tup *types.Tuple, variadic bool, visited []types.Type) {
p.print("(")
for i, n := 0, tup.Len(); i < n; i++ {
if i > 0 {
p.print(", ")
}
v := tup.At(i)
if name := v.Name(); name != "" {
p.print(name)
p.print(" ")
}
typ := v.Type()
if variadic && i == n-1 {
p.print("...")
typ = typ.(*types.Slice).Elem()
}
p.writeTypeInternal(this, typ, visited)
}
p.print(")")
}
func (p *printer) writeSignature(this *types.Package, sig *types.Signature) {
p.writeSignatureInternal(this, sig, make([]types.Type, 8))
}
func (p *printer) writeSignatureInternal(this *types.Package, sig *types.Signature, visited []types.Type) {
p.writeTuple(this, sig.Params(), sig.Variadic(), visited)
res := sig.Results()
n := res.Len()
if n == 0 {
// no result
return
}
p.print(" ")
if n == 1 && res.At(0).Name() == "" {
// single unnamed result
p.writeTypeInternal(this, res.At(0).Type(), visited)
return
}
// multiple or named result(s)
p.writeTuple(this, res, false, visited)
}
golang-golang-x-tools-0.1.9+ds/cmd/godoc/ 0000775 0000000 0000000 00000000000 14177515506 0020113 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/godoc/doc.go 0000664 0000000 0000000 00000010424 14177515506 0021210 0 ustar 00root root 0000000 0000000 // Copyright 2009 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.
/*
Godoc extracts and generates documentation for Go programs.
It runs as a web server and presents the documentation as a
web page.
godoc -http=:6060
Usage:
godoc [flag]
The flags are:
-v
verbose mode
-timestamps=true
show timestamps with directory listings
-index
enable identifier and full text search index
(no search box is shown if -index is not set)
-index_files=""
glob pattern specifying index files; if not empty,
the index is read from these files in sorted order
-index_throttle=0.75
index throttle value; a value of 0 means no time is allocated
to the indexer (the indexer will never finish), a value of 1.0
means that index creation is running at full throttle (other
goroutines may get no time while the index is built)
-index_interval=0
interval of indexing; a value of 0 sets it to 5 minutes, a
negative value indexes only once at startup
-play=false
enable playground
-links=true
link identifiers to their declarations
-write_index=false
write index to a file; the file name must be specified with
-index_files
-maxresults=10000
maximum number of full text search results shown
(no full text index is built if maxresults <= 0)
-notes="BUG"
regular expression matching note markers to show
(e.g., "BUG|TODO", ".*")
-goroot=$GOROOT
Go root directory
-http=addr
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
-templates=""
directory containing alternate template files; if set,
the directory may provide alternative template files
for the files in $GOROOT/lib/godoc
-url=path
print to standard output the data that would be served by
an HTTP request for path
-zip=""
zip file providing the file system to serve; disabled if empty
By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set).
This behavior can be altered by providing an alternative $GOROOT with the -goroot
flag.
When the -index flag is set, a search index is maintained.
The index is created at startup.
The index contains both identifier and full text search information (searchable
via regular expressions). The maximum number of full text search results shown
can be set with the -maxresults flag; if set to 0, no full text results are
shown, and only an identifier index but no full text search index is created.
By default, godoc uses the system's GOOS/GOARCH. You can provide the URL parameters
"GOOS" and "GOARCH" to set the output on the web page for the target system.
The presentation mode of web pages served by godoc can be controlled with the
"m" URL parameter; it accepts a comma-separated list of flag names as value:
all show documentation for all declarations, not just the exported ones
methods show all embedded methods, not just those of unexported anonymous fields
src show the original source code rather than the extracted documentation
flat present flat (not indented) directory listings using full paths
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation
for all (not just the exported) declarations of package big.
By default, godoc serves files from the file system of the underlying OS.
Instead, a .zip file may be provided via the -zip flag, which contains
the file system to serve. The file paths stored in the .zip file must use
slash ('/') as path separator; and they must be unrooted. $GOROOT (or -goroot)
must be set to the .zip file directory path containing the Go root directory.
For instance, for a .zip file created by the command:
zip -r go.zip $HOME/go
one may run godoc as follows:
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
Godoc documentation is converted to HTML or to text using the go/doc package;
see https://golang.org/pkg/go/doc/#ToHTML for the exact rules.
Godoc also shows example code that is runnable by the testing package;
see https://golang.org/pkg/testing/#hdr-Examples for the conventions.
See "Godoc: documenting Go code" for how to write good comments for godoc:
https://golang.org/doc/articles/godoc_documenting_go_code.html
Deprecated: godoc cannot select what version of a package is displayed.
Instead, use golang.org/x/pkgsite/cmd/pkgsite.
*/
package main // import "golang.org/x/tools/cmd/godoc"
golang-golang-x-tools-0.1.9+ds/cmd/godoc/godoc_test.go 0000664 0000000 0000000 00000027333 14177515506 0022604 0 ustar 00root root 0000000 0000000 // 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.
package main_test
import (
"bytes"
"fmt"
"go/build"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
"time"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/testenv"
)
// buildGodoc builds the godoc executable.
// It returns its path, and a cleanup function.
//
// TODO(adonovan): opt: do this at most once, and do the cleanup
// exactly once. How though? There's no atexit.
func buildGodoc(t *testing.T) (bin string, cleanup func()) {
t.Helper()
if runtime.GOARCH == "arm" {
t.Skip("skipping test on arm platforms; too slow")
}
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
testenv.NeedsTool(t, "go")
tmp, err := ioutil.TempDir("", "godoc-regtest-")
if err != nil {
t.Fatal(err)
}
defer func() {
if cleanup == nil { // probably, go build failed.
os.RemoveAll(tmp)
}
}()
bin = filepath.Join(tmp, "godoc")
if runtime.GOOS == "windows" {
bin += ".exe"
}
cmd := exec.Command("go", "build", "-o", bin)
if err := cmd.Run(); err != nil {
t.Fatalf("Building godoc: %v", err)
}
return bin, func() { os.RemoveAll(tmp) }
}
func serverAddress(t *testing.T) string {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
ln, err = net.Listen("tcp6", "[::1]:0")
}
if err != nil {
t.Fatal(err)
}
defer ln.Close()
return ln.Addr().String()
}
func waitForServerReady(t *testing.T, cmd *exec.Cmd, addr string) {
ch := make(chan error, 1)
go func() { ch <- fmt.Errorf("server exited early: %v", cmd.Wait()) }()
go waitForServer(t, ch,
fmt.Sprintf("http://%v/", addr),
"Go Documentation Server",
15*time.Second,
false)
if err := <-ch; err != nil {
t.Fatal(err)
}
}
func waitForSearchReady(t *testing.T, cmd *exec.Cmd, addr string) {
ch := make(chan error, 1)
go func() { ch <- fmt.Errorf("server exited early: %v", cmd.Wait()) }()
go waitForServer(t, ch,
fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
"The list of tokens.",
2*time.Minute,
false)
if err := <-ch; err != nil {
t.Fatal(err)
}
}
func waitUntilScanComplete(t *testing.T, addr string) {
ch := make(chan error)
go waitForServer(t, ch,
fmt.Sprintf("http://%v/pkg", addr),
"Scan is not yet complete",
2*time.Minute,
// setting reverse as true, which means this waits
// until the string is not returned in the response anymore
true,
)
if err := <-ch; err != nil {
t.Fatal(err)
}
}
const pollInterval = 200 * time.Millisecond
// waitForServer waits for server to meet the required condition.
// It sends a single error value to ch, unless the test has failed.
// The error value is nil if the required condition was met within
// timeout, or non-nil otherwise.
func waitForServer(t *testing.T, ch chan<- error, url, match string, timeout time.Duration, reverse bool) {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
time.Sleep(pollInterval)
if t.Failed() {
return
}
res, err := http.Get(url)
if err != nil {
continue
}
body, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil || res.StatusCode != http.StatusOK {
continue
}
switch {
case !reverse && bytes.Contains(body, []byte(match)),
reverse && !bytes.Contains(body, []byte(match)):
ch <- nil
return
}
}
ch <- fmt.Errorf("server failed to respond in %v", timeout)
}
// hasTag checks whether a given release tag is contained in the current version
// of the go binary.
func hasTag(t string) bool {
for _, v := range build.Default.ReleaseTags {
if t == v {
return true
}
}
return false
}
func killAndWait(cmd *exec.Cmd) {
cmd.Process.Kill()
cmd.Process.Wait()
}
func TestURL(t *testing.T) {
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; fails to start up quickly enough")
}
bin, cleanup := buildGodoc(t)
defer cleanup()
testcase := func(url string, contents string) func(t *testing.T) {
return func(t *testing.T) {
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
args := []string{fmt.Sprintf("-url=%s", url)}
cmd := exec.Command(bin, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Args[0] = "godoc"
// Set GOPATH variable to a non-existing absolute path
// and GOPROXY=off to disable module fetches.
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
// (We don't want the indexer looking at the local workspace during tests.)
cmd.Env = append(os.Environ(),
"GOPATH=/does_not_exist",
"GOPROXY=off",
"GO111MODULE=off")
if err := cmd.Run(); err != nil {
t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s", url, err, stderr)
}
if !strings.Contains(stdout.String(), contents) {
t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s", contents, url, stdout)
}
}
}
t.Run("index", testcase("/", "These packages are part of the Go Project but outside the main Go tree."))
t.Run("fmt", testcase("/pkg/fmt", "Package fmt implements formatted I/O"))
}
// Basic integration test for godoc HTTP interface.
func TestWeb(t *testing.T) {
bin, cleanup := buildGodoc(t)
defer cleanup()
for _, x := range packagestest.All {
t.Run(x.Name(), func(t *testing.T) {
testWeb(t, x, bin, false)
})
}
}
// Basic integration test for godoc HTTP interface.
func TestWebIndex(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in -short mode")
}
bin, cleanup := buildGodoc(t)
defer cleanup()
testWeb(t, packagestest.GOPATH, bin, true)
}
// Basic integration test for godoc HTTP interface.
func testWeb(t *testing.T, x packagestest.Exporter, bin string, withIndex bool) {
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; fails to start up quickly enough")
}
// Write a fake GOROOT/GOPATH with some third party packages.
e := packagestest.Export(t, x, []packagestest.Module{
{
Name: "godoc.test/repo1",
Files: map[string]interface{}{
"a/a.go": `// Package a is a package in godoc.test/repo1.
package a; import _ "godoc.test/repo2/a"; const Name = "repo1a"`,
"b/b.go": `package b; const Name = "repo1b"`,
},
},
{
Name: "godoc.test/repo2",
Files: map[string]interface{}{
"a/a.go": `package a; const Name = "repo2a"`,
"b/b.go": `package b; const Name = "repo2b"`,
},
},
})
defer e.Cleanup()
// Start the server.
addr := serverAddress(t)
args := []string{fmt.Sprintf("-http=%s", addr)}
if withIndex {
args = append(args, "-index", "-index_interval=-1s")
}
cmd := exec.Command(bin, args...)
cmd.Dir = e.Config.Dir
cmd.Env = e.Config.Env
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
cmd.Args[0] = "godoc"
if err := cmd.Start(); err != nil {
t.Fatalf("failed to start godoc: %s", err)
}
defer killAndWait(cmd)
if withIndex {
waitForSearchReady(t, cmd, addr)
} else {
waitForServerReady(t, cmd, addr)
waitUntilScanComplete(t, addr)
}
tests := []struct {
path string
contains []string // substring
match []string // regexp
notContains []string
needIndex bool
releaseTag string // optional release tag that must be in go/build.ReleaseTags
}{
{
path: "/",
contains: []string{
"Go Documentation Server",
"Standard library",
"These packages are part of the Go Project but outside the main Go tree.",
},
},
{
path: "/pkg/fmt/",
contains: []string{"Package fmt implements formatted I/O"},
},
{
path: "/src/fmt/",
contains: []string{"scan_test.go"},
},
{
path: "/src/fmt/print.go",
contains: []string{"// Println formats using"},
},
{
path: "/pkg",
contains: []string{
"Standard library",
"Package fmt implements formatted I/O",
"Third party",
"Package a is a package in godoc.test/repo1.",
},
notContains: []string{
"internal/syscall",
"cmd/gc",
},
},
{
path: "/pkg/?m=all",
contains: []string{
"Standard library",
"Package fmt implements formatted I/O",
"internal/syscall/?m=all",
},
notContains: []string{
"cmd/gc",
},
},
{
path: "/search?q=ListenAndServe",
contains: []string{
"/src",
},
notContains: []string{
"/pkg/bootstrap",
},
needIndex: true,
},
{
path: "/pkg/strings/",
contains: []string{
`href="/src/strings/strings.go"`,
},
},
{
path: "/cmd/compile/internal/amd64/",
contains: []string{
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
},
},
{
path: "/pkg/math/bits/",
contains: []string{
`Added in Go 1.9`,
},
},
{
path: "/pkg/net/",
contains: []string{
`// IPv6 scoped addressing zone; added in Go 1.1`,
},
},
{
path: "/pkg/net/http/httptrace/",
match: []string{
`Got1xxResponse.*// Go 1\.11`,
},
releaseTag: "go1.11",
},
// Verify we don't add version info to a struct field added the same time
// as the struct itself:
{
path: "/pkg/net/http/httptrace/",
match: []string{
`(?m)GotFirstResponseByte func\(\)\s*$`,
},
},
// Remove trailing periods before adding semicolons:
{
path: "/pkg/database/sql/",
contains: []string{
"The number of connections currently in use; added in Go 1.11",
"The number of idle connections; added in Go 1.11",
},
releaseTag: "go1.11",
},
// Third party packages.
{
path: "/pkg/godoc.test/repo1/a",
contains: []string{`const Name = "repo1a"`},
},
{
path: "/pkg/godoc.test/repo2/b",
contains: []string{`const Name = "repo2b"`},
},
}
for _, test := range tests {
if test.needIndex && !withIndex {
continue
}
url := fmt.Sprintf("http://%s%s", addr, test.path)
resp, err := http.Get(url)
if err != nil {
t.Errorf("GET %s failed: %s", url, err)
continue
}
body, err := ioutil.ReadAll(resp.Body)
strBody := string(body)
resp.Body.Close()
if err != nil {
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
}
isErr := false
for _, substr := range test.contains {
if test.releaseTag != "" && !hasTag(test.releaseTag) {
continue
}
if !bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: wanted substring %q in body", url, substr)
isErr = true
}
}
for _, re := range test.match {
if test.releaseTag != "" && !hasTag(test.releaseTag) {
continue
}
if ok, err := regexp.MatchString(re, strBody); !ok || err != nil {
if err != nil {
t.Fatalf("Bad regexp %q: %v", re, err)
}
t.Errorf("GET %s: wanted to match %s in body", url, re)
isErr = true
}
}
for _, substr := range test.notContains {
if bytes.Contains(body, []byte(substr)) {
t.Errorf("GET %s: didn't want substring %q in body", url, substr)
isErr = true
}
}
if isErr {
t.Errorf("GET %s: got:\n%s", url, body)
}
}
}
// Test for golang.org/issue/35476.
func TestNoMainModule(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in -short mode")
}
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; for consistency with other tests that build godoc binary")
}
bin, cleanup := buildGodoc(t)
defer cleanup()
tempDir, err := ioutil.TempDir("", "godoc-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
// Run godoc in an empty directory with module mode explicitly on,
// so that 'go env GOMOD' reports os.DevNull.
cmd := exec.Command(bin, "-url=/")
cmd.Dir = tempDir
cmd.Env = append(os.Environ(), "GO111MODULE=on")
var stderr bytes.Buffer
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
t.Fatalf("godoc command failed: %v\nstderr=%q", err, stderr.String())
}
if strings.Contains(stderr.String(), "go mod download") {
t.Errorf("stderr contains 'go mod download', is that intentional?\nstderr=%q", stderr.String())
}
}
golang-golang-x-tools-0.1.9+ds/cmd/godoc/goroot.go 0000664 0000000 0000000 00000001260 14177515506 0021752 0 ustar 00root root 0000000 0000000 // 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 main
import (
exec "golang.org/x/sys/execabs"
"os"
"path/filepath"
"runtime"
"strings"
)
func findGOROOT() string {
if env := os.Getenv("GOROOT"); env != "" {
return filepath.Clean(env)
}
def := filepath.Clean(runtime.GOROOT())
if runtime.Compiler == "gccgo" {
// gccgo has no real GOROOT, and it certainly doesn't
// depend on the executable's location.
return def
}
out, err := exec.Command("go", "env", "GOROOT").Output()
if err != nil {
return def
}
return strings.TrimSpace(string(out))
}
golang-golang-x-tools-0.1.9+ds/cmd/godoc/handlers.go 0000664 0000000 0000000 00000005203 14177515506 0022242 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"go/format"
"log"
"net/http"
"text/template"
"golang.org/x/tools/godoc"
"golang.org/x/tools/godoc/redirect"
"golang.org/x/tools/godoc/vfs"
_ "golang.org/x/tools/playground" // register "/compile" playground redirect
)
var (
pres *godoc.Presentation
fs = vfs.NameSpace{}
)
func registerHandlers(pres *godoc.Presentation) {
if pres == nil {
panic("nil Presentation")
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/" {
http.Redirect(w, req, "/pkg/", http.StatusFound)
return
}
pres.ServeHTTP(w, req)
})
mux.Handle("/pkg/C/", redirect.Handler("/cmd/cgo/"))
mux.HandleFunc("/fmt", fmtHandler)
redirect.Register(mux)
http.Handle("/", mux)
}
func readTemplate(name string) *template.Template {
if pres == nil {
panic("no global Presentation set yet")
}
path := "lib/godoc/" + name
// use underlying file system fs to read the template file
// (cannot use template ParseFile functions directly)
data, err := vfs.ReadFile(fs, path)
if err != nil {
log.Fatal("readTemplate: ", err)
}
// be explicit with errors (for app engine use)
t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data))
if err != nil {
log.Fatal("readTemplate: ", err)
}
return t
}
func readTemplates(p *godoc.Presentation) {
p.CallGraphHTML = readTemplate("callgraph.html")
p.DirlistHTML = readTemplate("dirlist.html")
p.ErrorHTML = readTemplate("error.html")
p.ExampleHTML = readTemplate("example.html")
p.GodocHTML = readTemplate("godoc.html")
p.ImplementsHTML = readTemplate("implements.html")
p.MethodSetHTML = readTemplate("methodset.html")
p.PackageHTML = readTemplate("package.html")
p.PackageRootHTML = readTemplate("packageroot.html")
p.SearchHTML = readTemplate("search.html")
p.SearchDocHTML = readTemplate("searchdoc.html")
p.SearchCodeHTML = readTemplate("searchcode.html")
p.SearchTxtHTML = readTemplate("searchtxt.html")
}
type fmtResponse struct {
Body string
Error string
}
// fmtHandler takes a Go program in its "body" form value, formats it with
// standard gofmt formatting, and writes a fmtResponse as a JSON object.
func fmtHandler(w http.ResponseWriter, r *http.Request) {
resp := new(fmtResponse)
body, err := format.Source([]byte(r.FormValue("body")))
if err != nil {
resp.Error = err.Error()
} else {
resp.Body = string(body)
}
w.Header().Set("Content-type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(resp)
}
golang-golang-x-tools-0.1.9+ds/cmd/godoc/main.go 0000664 0000000 0000000 00000036245 14177515506 0021400 0 ustar 00root root 0000000 0000000 // Copyright 2009 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.
// godoc: Go Documentation Server
// Web server tree:
//
// http://godoc/ redirect to /pkg/
// http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed
// http://godoc/cmd/ serve documentation about commands
// http://godoc/pkg/ serve documentation about packages
// (idea is if you say import "compress/zlib", you go to
// http://godoc/pkg/compress/zlib)
//
package main
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
_ "expvar" // to serve /debug/vars
"flag"
"fmt"
"go/build"
"io"
"log"
"net/http"
_ "net/http/pprof" // to serve /debug/pprof/*
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
exec "golang.org/x/sys/execabs"
"golang.org/x/tools/godoc"
"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"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/xerrors"
)
const defaultAddr = "localhost:6060" // default webserver address
var (
// file system to serve
// (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i favicon.ico)
zipfile = flag.String("zip", "", "zip file providing the file system to serve; disabled if empty")
// file-based index
writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files")
// network
httpAddr = flag.String("http", defaultAddr, "HTTP service address")
// layout control
urlFlag = flag.String("url", "", "print HTML for named URL")
verbose = flag.Bool("v", false, "verbose mode")
// file system roots
// TODO(gri) consider the invariant that goroot always end in '/'
goroot = flag.String("goroot", findGOROOT(), "Go root directory")
// layout control
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory")
showPlayground = flag.Bool("play", false, "enable playground")
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
// search index
indexEnabled = flag.Bool("index", false, "enable search index")
indexFiles = flag.String("index_files", "", "glob pattern specifying index files; if not empty, the index is read from these files in sorted order")
indexInterval = flag.Duration("index_interval", 0, "interval of indexing; 0 for default (5m), negative to only index once at startup")
maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
// source code notes
notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
)
// An httpResponseRecorder is an http.ResponseWriter
type httpResponseRecorder struct {
body *bytes.Buffer
header http.Header
code int
}
func (w *httpResponseRecorder) Header() http.Header { return w.header }
func (w *httpResponseRecorder) Write(b []byte) (int, error) { return w.body.Write(b) }
func (w *httpResponseRecorder) WriteHeader(code int) { w.code = code }
func usage() {
fmt.Fprintf(os.Stderr, "usage: godoc -http="+defaultAddr+"\n")
flag.PrintDefaults()
os.Exit(2)
}
func loggingHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
log.Printf("%s\t%s", req.RemoteAddr, req.URL)
h.ServeHTTP(w, req)
})
}
func handleURLFlag() {
// Try up to 10 fetches, following redirects.
urlstr := *urlFlag
for i := 0; i < 10; i++ {
// Prepare request.
u, err := url.Parse(urlstr)
if err != nil {
log.Fatal(err)
}
req := &http.Request{
URL: u,
}
// Invoke default HTTP handler to serve request
// to our buffering httpWriter.
w := &httpResponseRecorder{code: 200, header: make(http.Header), body: new(bytes.Buffer)}
http.DefaultServeMux.ServeHTTP(w, req)
// Return data, error, or follow redirect.
switch w.code {
case 200: // ok
os.Stdout.Write(w.body.Bytes())
return
case 301, 302, 303, 307: // redirect
redirect := w.header.Get("Location")
if redirect == "" {
log.Fatalf("HTTP %d without Location header", w.code)
}
urlstr = redirect
default:
log.Fatalf("HTTP error %d", w.code)
}
}
log.Fatalf("too many redirects")
}
func initCorpus(corpus *godoc.Corpus) {
err := corpus.Init()
if err != nil {
log.Fatal(err)
}
}
func main() {
flag.Usage = usage
flag.Parse()
// Check usage.
if flag.NArg() > 0 {
fmt.Fprintln(os.Stderr, `Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf".`)
usage()
}
if *httpAddr == "" && *urlFlag == "" && !*writeIndex {
fmt.Fprintln(os.Stderr, "At least one of -http, -url, or -write_index must be set to a non-zero value.")
usage()
}
// Set the resolved goroot.
vfs.GOROOT = *goroot
fsGate := make(chan bool, 20)
// Determine file system to use.
if *zipfile == "" {
// use file system of underlying OS
rootfs := gatefs.New(vfs.OS(*goroot), fsGate)
fs.Bind("/", rootfs, "/", vfs.BindReplace)
} else {
// use file system specified via .zip file (path separator must be '/')
rc, err := zip.OpenReader(*zipfile)
if err != nil {
log.Fatalf("%s: %s\n", *zipfile, err)
}
defer rc.Close() // be nice (e.g., -writeIndex mode)
fs.Bind("/", zipfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
}
if *templateDir != "" {
fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
fs.Bind("/favicon.ico", vfs.OS(*templateDir), "/favicon.ico", vfs.BindReplace)
} else {
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
fs.Bind("/favicon.ico", mapfs.New(static.Files), "/favicon.ico", vfs.BindReplace)
}
// Get the GOMOD value, use it to determine if godoc is being invoked in module mode.
goModFile, err := goMod()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine go env GOMOD value: %v", err)
goModFile = "" // Fall back to GOPATH mode.
}
if goModFile != "" {
fmt.Printf("using module mode; GOMOD=%s\n", goModFile)
// Detect whether to use vendor mode or not.
mainMod, vendorEnabled, err := gocommand.VendorEnabled(context.Background(), gocommand.Invocation{}, &gocommand.Runner{})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine if vendoring is enabled: %v", err)
os.Exit(1)
}
if vendorEnabled {
// Bind the root directory of the main module.
fs.Bind(path.Join("/src", mainMod.Path), gatefs.New(vfs.OS(mainMod.Dir), fsGate), "/", vfs.BindAfter)
// Bind the vendor directory.
//
// Note that in module mode, vendor directories in locations
// other than the main module's root directory are ignored.
// See https://golang.org/ref/mod#vendoring.
vendorDir := filepath.Join(mainMod.Dir, "vendor")
fs.Bind("/src", gatefs.New(vfs.OS(vendorDir), fsGate), "/", vfs.BindAfter)
} else {
// Try to download dependencies that are not in the module cache in order to
// to show their documentation.
// This may fail if module downloading is disallowed (GOPROXY=off) or due to
// limited connectivity, in which case we print errors to stderr and show
// documentation only for packages that are available.
fillModuleCache(os.Stderr, goModFile)
// Determine modules in the build list.
mods, err := buildList(goModFile)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine the build list of the main module: %v", err)
os.Exit(1)
}
// Bind module trees into Go root.
for _, m := range mods {
if m.Dir == "" {
// Module is not available in the module cache, skip it.
continue
}
dst := path.Join("/src", m.Path)
fs.Bind(dst, gatefs.New(vfs.OS(m.Dir), fsGate), "/", vfs.BindAfter)
}
}
} else {
fmt.Println("using GOPATH mode")
// Bind $GOPATH trees into Go root.
for _, p := range filepath.SplitList(build.Default.GOPATH) {
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
}
}
var corpus *godoc.Corpus
if goModFile != "" {
corpus = godoc.NewCorpus(moduleFS{fs})
} else {
corpus = godoc.NewCorpus(fs)
}
corpus.Verbose = *verbose
corpus.MaxResults = *maxResults
corpus.IndexEnabled = *indexEnabled
if *maxResults == 0 {
corpus.IndexFullText = false
}
corpus.IndexFiles = *indexFiles
corpus.IndexDirectory = func(dir string) bool {
return dir != "/pkg" && !strings.HasPrefix(dir, "/pkg/")
}
corpus.IndexThrottle = *indexThrottle
corpus.IndexInterval = *indexInterval
if *writeIndex || *urlFlag != "" {
corpus.IndexThrottle = 1.0
corpus.IndexEnabled = true
initCorpus(corpus)
} else {
go initCorpus(corpus)
}
// Initialize the version info before readTemplates, which saves
// the map value in a method value.
corpus.InitVersionInfo()
pres = godoc.NewPresentation(corpus)
pres.ShowTimestamps = *showTimestamps
pres.ShowPlayground = *showPlayground
pres.DeclLinks = *declLinks
if *notesRx != "" {
pres.NotesRx = regexp.MustCompile(*notesRx)
}
readTemplates(pres)
registerHandlers(pres)
if *writeIndex {
// Write search index and exit.
if *indexFiles == "" {
log.Fatal("no index file specified")
}
log.Println("initialize file systems")
*verbose = true // want to see what happens
corpus.UpdateIndex()
log.Println("writing index file", *indexFiles)
f, err := os.Create(*indexFiles)
if err != nil {
log.Fatal(err)
}
index, _ := corpus.CurrentIndex()
_, err = index.WriteTo(f)
if err != nil {
log.Fatal(err)
}
log.Println("done")
return
}
// Print content that would be served at the URL *urlFlag.
if *urlFlag != "" {
handleURLFlag()
return
}
var handler http.Handler = http.DefaultServeMux
if *verbose {
log.Printf("Go Documentation Server")
log.Printf("version = %s", runtime.Version())
log.Printf("address = %s", *httpAddr)
log.Printf("goroot = %s", *goroot)
switch {
case !*indexEnabled:
log.Print("search index disabled")
case *maxResults > 0:
log.Printf("full text index enabled (maxresults = %d)", *maxResults)
default:
log.Print("identifier search index enabled")
}
fs.Fprint(os.Stderr)
handler = loggingHandler(handler)
}
// Initialize search index.
if *indexEnabled {
go corpus.RunIndexer()
}
// Start http server.
if *verbose {
log.Println("starting HTTP server")
}
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
}
}
// goMod returns the go env GOMOD value in the current directory
// by invoking the go command.
//
// GOMOD is documented at https://golang.org/cmd/go/#hdr-Environment_variables:
//
// The absolute path to the go.mod of the main module,
// or the empty string if not using modules.
//
func goMod() (string, error) {
out, err := exec.Command("go", "env", "-json", "GOMOD").Output()
if ee := (*exec.ExitError)(nil); xerrors.As(err, &ee) {
return "", fmt.Errorf("go command exited unsuccessfully: %v\n%s", ee.ProcessState.String(), ee.Stderr)
} else if err != nil {
return "", err
}
var env struct {
GoMod string
}
err = json.Unmarshal(out, &env)
if err != nil {
return "", err
}
return env.GoMod, nil
}
// fillModuleCache does a best-effort attempt to fill the module cache
// with all dependencies of the main module in the current directory
// by invoking the go command. Module download logs are streamed to w.
// If there are any problems encountered, they are also written to w.
// It should only be used in module mode, when vendor mode isn't on.
//
// See https://golang.org/cmd/go/#hdr-Download_modules_to_local_cache.
func fillModuleCache(w io.Writer, goMod string) {
if goMod == os.DevNull {
// No module requirements, nothing to do.
return
}
cmd := exec.Command("go", "mod", "download", "-json")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = w
err := cmd.Run()
if ee := (*exec.ExitError)(nil); xerrors.As(err, &ee) && ee.ExitCode() == 1 {
// Exit code 1 from this command means there were some
// non-empty Error values in the output. Print them to w.
fmt.Fprintf(w, "documentation for some packages is not shown:\n")
for dec := json.NewDecoder(&out); ; {
var m struct {
Path string // Module path.
Version string // Module version.
Error string // Error loading module.
}
err := dec.Decode(&m)
if err == io.EOF {
break
} else if err != nil {
fmt.Fprintf(w, "error decoding JSON object from go mod download -json: %v\n", err)
continue
}
if m.Error == "" {
continue
}
fmt.Fprintf(w, "\tmodule %s@%s is not in the module cache and there was a problem downloading it: %s\n", m.Path, m.Version, m.Error)
}
} else if err != nil {
fmt.Fprintf(w, "there was a problem filling module cache: %v\n", err)
}
}
type mod struct {
Path string // Module path.
Dir string // Directory holding files for this module, if any.
}
// buildList determines the build list in the current directory
// by invoking the go command. It should only be used in module mode,
// when vendor mode isn't on.
//
// See https://golang.org/cmd/go/#hdr-The_main_module_and_the_build_list.
func buildList(goMod string) ([]mod, error) {
if goMod == os.DevNull {
// Empty build list.
return nil, nil
}
out, err := exec.Command("go", "list", "-m", "-json", "all").Output()
if ee := (*exec.ExitError)(nil); xerrors.As(err, &ee) {
return nil, fmt.Errorf("go command exited unsuccessfully: %v\n%s", ee.ProcessState.String(), ee.Stderr)
} else if err != nil {
return nil, err
}
var mods []mod
for dec := json.NewDecoder(bytes.NewReader(out)); ; {
var m mod
err := dec.Decode(&m)
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
mods = append(mods, m)
}
return mods, nil
}
// moduleFS is a vfs.FileSystem wrapper used when godoc is running
// in module mode. It's needed so that packages inside modules are
// considered to be third party.
//
// It overrides the RootType method of the underlying filesystem
// and implements it using a heuristic based on the import path.
// If the first element of the import path does not contain a dot,
// that package is considered to be inside GOROOT. If it contains
// a dot, then that package is considered to be third party.
//
// TODO(dmitshur): The RootType abstraction works well when GOPATH
// workspaces are bound at their roots, but scales poorly in the
// general case. It should be replaced by a more direct solution
// for determining whether a package is third party or not.
//
type moduleFS struct{ vfs.FileSystem }
func (moduleFS) RootType(path string) vfs.RootType {
if !strings.HasPrefix(path, "/src/") {
return ""
}
domain := path[len("/src/"):]
if i := strings.Index(domain, "/"); i >= 0 {
domain = domain[:i]
}
if !strings.Contains(domain, ".") {
// No dot in the first element of import path
// suggests this is a package in GOROOT.
return vfs.RootTypeGoRoot
} else {
// A dot in the first element of import path
// suggests this is a third party package.
return vfs.RootTypeGoPath
}
}
func (fs moduleFS) String() string { return "module(" + fs.FileSystem.String() + ")" }
golang-golang-x-tools-0.1.9+ds/cmd/goimports/ 0000775 0000000 0000000 00000000000 14177515506 0021043 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/goimports/doc.go 0000664 0000000 0000000 00000003102 14177515506 0022133 0 ustar 00root root 0000000 0000000 // 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.
/*
Command goimports updates your Go import lines,
adding missing ones and removing unreferenced ones.
$ go install golang.org/x/tools/cmd/goimports@latest
In addition to fixing imports, goimports also formats
your code in the same style as gofmt so it can be used
as a replacement for your editor's gofmt-on-save hook.
For emacs, make sure you have the latest go-mode.el:
https://github.com/dominikh/go-mode.el
Then in your .emacs file:
(setq gofmt-command "goimports")
(add-hook 'before-save-hook 'gofmt-before-save)
For vim, set "gofmt_command" to "goimports":
https://golang.org/change/39c724dd7f252
https://golang.org/wiki/IDEsAndTextEditorPlugins
etc
For GoSublime, follow the steps described here:
http://michaelwhatcott.com/gosublime-goimports/
For other editors, you probably know what to do.
To exclude directories in your $GOPATH from being scanned for Go
files, goimports respects a configuration file at
$GOPATH/src/.goimportsignore which may contain blank lines, comment
lines (beginning with '#'), or lines naming a directory relative to
the configuration file to ignore when scanning. No globbing or regex
patterns are allowed. Use the "-v" verbose flag to verify it's
working and see what goimports is doing.
File bugs or feature requests at:
https://golang.org/issues/new?title=x/tools/cmd/goimports:+
Happy hacking!
*/
package main // import "golang.org/x/tools/cmd/goimports"
golang-golang-x-tools-0.1.9+ds/cmd/goimports/goimports.go 0000664 0000000 0000000 00000023261 14177515506 0023421 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"go/scanner"
exec "golang.org/x/sys/execabs"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/imports"
)
var (
// main operation modes
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
srcdir = flag.String("srcdir", "", "choose imports as if source code is from `dir`. When operating on a single file, dir may instead be the complete file name.")
verbose bool // verbose logging
cpuProfile = flag.String("cpuprofile", "", "CPU profile output")
memProfile = flag.String("memprofile", "", "memory profile output")
memProfileRate = flag.Int("memrate", 0, "if > 0, sets runtime.MemProfileRate")
options = &imports.Options{
TabWidth: 8,
TabIndent: true,
Comments: true,
Fragment: true,
Env: &imports.ProcessEnv{
GocmdRunner: &gocommand.Runner{},
},
}
exitCode = 0
)
func init() {
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
flag.StringVar(&options.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) {
scanner.PrintError(os.Stderr, err)
exitCode = 2
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: goimports [flags] [path ...]\n")
flag.PrintDefaults()
os.Exit(2)
}
func isGoFile(f os.FileInfo) bool {
// ignore non-Go files
name := f.Name()
return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
}
// argumentType is which mode goimports was invoked as.
type argumentType int
const (
// fromStdin means the user is piping their source into goimports.
fromStdin argumentType = iota
// singleArg is the common case from editors, when goimports is run on
// a single file.
singleArg
// multipleArg is when the user ran "goimports file1.go file2.go"
// or ran goimports on a directory tree.
multipleArg
)
func processFile(filename string, in io.Reader, out io.Writer, argType argumentType) error {
opt := options
if argType == fromStdin {
nopt := *options
nopt.Fragment = true
opt = &nopt
}
if in == nil {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
in = f
}
src, err := ioutil.ReadAll(in)
if err != nil {
return err
}
target := filename
if *srcdir != "" {
// Determine whether the provided -srcdirc is a directory or file
// and then use it to override the target.
//
// See https://github.com/dominikh/go-mode.el/issues/146
if isFile(*srcdir) {
if argType == multipleArg {
return errors.New("-srcdir value can't be a file when passing multiple arguments or when walking directories")
}
target = *srcdir
} else if argType == singleArg && strings.HasSuffix(*srcdir, ".go") && !isDir(*srcdir) {
// For a file which doesn't exist on disk yet, but might shortly.
// e.g. user in editor opens $DIR/newfile.go and newfile.go doesn't yet exist on disk.
// The goimports on-save hook writes the buffer to a temp file
// first and runs goimports before the actual save to newfile.go.
// The editor's buffer is named "newfile.go" so that is passed to goimports as:
// goimports -srcdir=/gopath/src/pkg/newfile.go /tmp/gofmtXXXXXXXX.go
// and then the editor reloads the result from the tmp file and writes
// it to newfile.go.
target = *srcdir
} else {
// Pretend that file is from *srcdir in order to decide
// visible imports correctly.
target = filepath.Join(*srcdir, filepath.Base(filename))
}
}
res, err := imports.Process(target, src, opt)
if err != nil {
return err
}
if !bytes.Equal(src, res) {
// formatting has changed
if *list {
fmt.Fprintln(out, filename)
}
if *write {
if argType == fromStdin {
// filename is ""
return errors.New("can't use -w on stdin")
}
// On Windows, we need to re-set the permissions from the file. See golang/go#38225.
var perms os.FileMode
if fi, err := os.Stat(filename); err == nil {
perms = fi.Mode() & os.ModePerm
}
err = ioutil.WriteFile(filename, res, perms)
if err != nil {
return err
}
}
if *doDiff {
if argType == fromStdin {
filename = "stdin.go" // because .orig looks silly
}
data, err := diff(src, res, filename)
if err != nil {
return fmt.Errorf("computing diff: %s", err)
}
fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
out.Write(data)
}
}
if !*list && !*write && !*doDiff {
_, err = out.Write(res)
}
return err
}
func visitFile(path string, f os.FileInfo, err error) error {
if err == nil && isGoFile(f) {
err = processFile(path, nil, os.Stdout, multipleArg)
}
if err != nil {
report(err)
}
return nil
}
func walkDir(path string) {
filepath.Walk(path, visitFile)
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
// call gofmtMain in a separate function
// so that it can use defer and have them
// run before the exit.
gofmtMain()
os.Exit(exitCode)
}
// parseFlags parses command line flags and returns the paths to process.
// It's a var so that custom implementations can replace it in other files.
var parseFlags = func() []string {
flag.BoolVar(&verbose, "v", false, "verbose logging")
flag.Parse()
return flag.Args()
}
func bufferedFileWriter(dest string) (w io.Writer, close func()) {
f, err := os.Create(dest)
if err != nil {
log.Fatal(err)
}
bw := bufio.NewWriter(f)
return bw, func() {
if err := bw.Flush(); err != nil {
log.Fatalf("error flushing %v: %v", dest, err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
}
func gofmtMain() {
flag.Usage = usage
paths := parseFlags()
if *cpuProfile != "" {
bw, flush := bufferedFileWriter(*cpuProfile)
pprof.StartCPUProfile(bw)
defer flush()
defer pprof.StopCPUProfile()
}
// doTrace is a conditionally compiled wrapper around runtime/trace. It is
// used to allow goimports to compile under gccgo, which does not support
// runtime/trace. See https://golang.org/issue/15544.
defer doTrace()()
if *memProfileRate > 0 {
runtime.MemProfileRate = *memProfileRate
bw, flush := bufferedFileWriter(*memProfile)
defer func() {
runtime.GC() // materialize all statistics
if err := pprof.WriteHeapProfile(bw); err != nil {
log.Fatal(err)
}
flush()
}()
}
if verbose {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
options.Env.Logf = log.Printf
}
if options.TabWidth < 0 {
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
exitCode = 2
return
}
if len(paths) == 0 {
if err := processFile("", os.Stdin, os.Stdout, fromStdin); err != nil {
report(err)
}
return
}
argType := singleArg
if len(paths) > 1 {
argType = multipleArg
}
for _, path := range paths {
switch dir, err := os.Stat(path); {
case err != nil:
report(err)
case dir.IsDir():
walkDir(path)
default:
if err := processFile(path, nil, os.Stdout, argType); err != nil {
report(err)
}
}
}
}
func writeTempFile(dir, prefix string, data []byte) (string, error) {
file, err := ioutil.TempFile(dir, prefix)
if err != nil {
return "", err
}
_, err = file.Write(data)
if err1 := file.Close(); err == nil {
err = err1
}
if err != nil {
os.Remove(file.Name())
return "", err
}
return file.Name(), nil
}
func diff(b1, b2 []byte, filename string) (data []byte, err error) {
f1, err := writeTempFile("", "gofmt", b1)
if err != nil {
return
}
defer os.Remove(f1)
f2, err := writeTempFile("", "gofmt", b2)
if err != nil {
return
}
defer os.Remove(f2)
cmd := "diff"
if runtime.GOOS == "plan9" {
cmd = "/bin/ape/diff"
}
data, err = exec.Command(cmd, "-u", f1, f2).CombinedOutput()
if len(data) > 0 {
// diff exits with a non-zero status when the files don't match.
// Ignore that failure as long as we get output.
return replaceTempFilename(data, filename)
}
return
}
// replaceTempFilename replaces temporary filenames in diff with actual one.
//
// --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500
// +++ /tmp/gofmt617882815 2017-02-03 19:13:00.280468375 -0500
// ...
// ->
// --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500
// +++ path/to/file.go 2017-02-03 19:13:00.280468375 -0500
// ...
func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
bs := bytes.SplitN(diff, []byte{'\n'}, 3)
if len(bs) < 3 {
return nil, fmt.Errorf("got unexpected diff for %s", filename)
}
// Preserve timestamps.
var t0, t1 []byte
if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
t0 = bs[0][i:]
}
if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
t1 = bs[1][i:]
}
// Always print filepath with slash separator.
f := filepath.ToSlash(filename)
bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
return bytes.Join(bs, []byte{'\n'}), nil
}
// isFile reports whether name is a file.
func isFile(name string) bool {
fi, err := os.Stat(name)
return err == nil && fi.Mode().IsRegular()
}
// isDir reports whether name is a directory.
func isDir(name string) bool {
fi, err := os.Stat(name)
return err == nil && fi.IsDir()
}
golang-golang-x-tools-0.1.9+ds/cmd/goimports/goimports_gc.go 0000664 0000000 0000000 00000000761 14177515506 0024072 0 ustar 00root root 0000000 0000000 // 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.
//go:build gc
// +build gc
package main
import (
"flag"
"runtime/trace"
)
var traceProfile = flag.String("trace", "", "trace profile output")
func doTrace() func() {
if *traceProfile != "" {
bw, flush := bufferedFileWriter(*traceProfile)
trace.Start(bw)
return func() {
flush()
trace.Stop()
}
}
return func() {}
}
golang-golang-x-tools-0.1.9+ds/cmd/goimports/goimports_not_gc.go 0000664 0000000 0000000 00000000370 14177515506 0024746 0 ustar 00root root 0000000 0000000 // 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.
//go:build !gc
// +build !gc
package main
func doTrace() func() {
return func() {}
}
golang-golang-x-tools-0.1.9+ds/cmd/gomvpkg/ 0000775 0000000 0000000 00000000000 14177515506 0020472 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/gomvpkg/main.go 0000664 0000000 0000000 00000005607 14177515506 0021755 0 ustar 00root root 0000000 0000000 // Copyright 2015 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 gomvpkg command moves go packages, updating import declarations.
// See the -help message or Usage constant for details.
package main
import (
"flag"
"fmt"
"go/build"
"os"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/refactor/rename"
)
var (
fromFlag = flag.String("from", "", "Import path of package to be moved")
toFlag = flag.String("to", "", "Destination import path for package")
vcsMvCmdFlag = flag.String("vcs_mv_cmd", "", `A template for the version control system's "move directory" command, e.g. "git mv {{.Src}} {{.Dst}}"`)
helpFlag = flag.Bool("help", false, "show usage message")
)
func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
}
const Usage = `gomvpkg: moves a package, updating import declarations
Usage:
gomvpkg -from -to [-vcs_mv_cmd ]
Flags:
-from specifies the import path of the package to be moved
-to specifies the destination import path
-vcs_mv_cmd specifies a shell command to inform the version control system of a
directory move. The argument is a template using the syntax of the
text/template package. It has two fields: Src and Dst, the absolute
paths of the directories.
For example: "git mv {{.Src}} {{.Dst}}"
gomvpkg determines the set of packages that might be affected, including all
packages importing the 'from' package and any of its subpackages. It will move
the 'from' package and all its subpackages to the destination path and update all
imports of those packages to point to its new import path.
gomvpkg rejects moves in which a package already exists at the destination import
path, or in which a directory already exists at the location the package would be
moved to.
gomvpkg will not always be able to rename imports when a package's name is changed.
Import statements may want further cleanup.
gomvpkg's behavior is not defined if any of the packages to be moved are
imported using dot imports.
Examples:
% gomvpkg -from myproject/foo -to myproject/bar
Move the package with import path "myproject/foo" to the new path
"myproject/bar".
% gomvpkg -from myproject/foo -to myproject/bar -vcs_mv_cmd "git mv {{.Src}} {{.Dst}}"
Move the package with import path "myproject/foo" to the new path
"myproject/bar" using "git mv" to execute the directory move.
`
func main() {
flag.Parse()
if len(flag.Args()) > 0 {
fmt.Fprintln(os.Stderr, "gomvpkg: surplus arguments.")
os.Exit(1)
}
if *helpFlag || *fromFlag == "" || *toFlag == "" {
fmt.Print(Usage)
return
}
if err := rename.Move(&build.Default, *fromFlag, *toFlag, *vcsMvCmdFlag); err != nil {
fmt.Fprintf(os.Stderr, "gomvpkg: %s.\n", err)
os.Exit(1)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/gorename/ 0000775 0000000 0000000 00000000000 14177515506 0020615 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/gorename/gorename_test.go 0000664 0000000 0000000 00000022544 14177515506 0024007 0 ustar 00root root 0000000 0000000 // Copyright 2017 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_test
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"golang.org/x/tools/internal/testenv"
)
type test struct {
offset, from, to string // specify the arguments
fileSpecified bool // true if the offset or from args specify a specific file
pkgs map[string][]string
wantErr bool
wantOut string // a substring expected to be in the output
packages map[string][]string // a map of the package name to the files contained within, which will be numbered by i.go where i is the index
}
// Test that renaming that would modify cgo files will produce an error and not modify the file.
func TestGeneratedFiles(t *testing.T) {
testenv.NeedsTool(t, "go")
testenv.NeedsTool(t, "cgo")
tmp, bin, cleanup := buildGorename(t)
defer cleanup()
srcDir := filepath.Join(tmp, "src")
err := os.Mkdir(srcDir, os.ModePerm)
if err != nil {
t.Fatal(err)
}
var env = []string{fmt.Sprintf("GOPATH=%s", tmp)}
for _, envVar := range os.Environ() {
if !strings.HasPrefix(envVar, "GOPATH=") {
env = append(env, envVar)
}
}
// gorename currently requires GOPATH mode.
env = append(env, "GO111MODULE=off")
// Testing renaming in packages that include cgo files:
for iter, renameTest := range []test{
{
// Test: variable not used in any cgo file -> no error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include
import "C"
func z() {C.puts(nil)}`},
},
wantErr: false,
wantOut: "Renamed 1 occurrence in 1 file in 1 package.",
}, {
// Test: to name used in cgo file -> rename error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include
import "C"
func g() {C.puts(nil)}`},
},
wantErr: true,
wantOut: "conflicts with func in same block",
},
{
// Test: from name in package in cgo file -> error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest
// #include
import "C"
func f() { C.puts(nil); }
`},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:",
}, {
// Test: from name in cgo file -> error
from: filepath.Join("mytest", "0.go") + `::f`, to: "g",
fileSpecified: true,
packages: map[string][]string{
"mytest": []string{`package mytest
// #include
import "C"
func f() { C.puts(nil); }
`},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:",
}, {
// Test: offset in cgo file -> identifier in cgo error
offset: filepath.Join("main", "0.go") + `:#78`, to: "bar",
fileSpecified: true,
wantErr: true,
packages: map[string][]string{
"main": {`package main
// #include
import "C"
import "fmt"
func main() {
foo := 1
C.close(2)
fmt.Println(foo)
}
`},
},
wantOut: "cannot rename identifiers in generated file containing DO NOT EDIT marker:",
}, {
// Test: from identifier appears in cgo file in another package -> error
from: `"test"::Foo`, to: "Bar",
packages: map[string][]string{
"test": []string{
`package test
func Foo(x int) (int){
return x * 2
}
`,
},
"main": []string{
`package main
import "test"
import "fmt"
// #include
import "C"
func fun() {
x := test.Foo(3)
C.close(3)
fmt.Println(x)
}
`,
},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated file containing DO NOT EDIT marker:",
}, {
// Test: from identifier doesn't appear in cgo file that includes modified package -> rename successful
from: `"test".Foo::x`, to: "y",
packages: map[string][]string{
"test": []string{
`package test
func Foo(x int) (int){
return x * 2
}
`,
},
"main": []string{
`package main
import "test"
import "fmt"
// #include
import "C"
func fun() {
x := test.Foo(3)
C.close(3)
fmt.Println(x)
}
`,
},
},
wantErr: false,
wantOut: "Renamed 2 occurrences in 1 file in 1 package.",
}, {
// Test: from name appears in cgo file in same package -> error
from: `"mytest"::f`, to: "g",
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include
import "C"
func z() {C.puts(nil); f()}`,
`package mytest
// #include
import "C"
func foo() {C.close(3); f()}`,
},
},
wantErr: true,
wantOut: "gorename: refusing to modify generated files containing DO NOT EDIT marker:",
}, {
// Test: from name in file, identifier not used in cgo file -> rename successful
from: filepath.Join("mytest", "0.go") + `::f`, to: "g",
fileSpecified: true,
packages: map[string][]string{
"mytest": []string{`package mytest; func f() {}`,
`package mytest
// #include
import "C"
func z() {C.puts(nil)}`},
},
wantErr: false,
wantOut: "Renamed 1 occurrence in 1 file in 1 package.",
}, {
// Test: from identifier imported to another package but does not modify cgo file -> rename successful
from: `"test".Foo`, to: "Bar",
packages: map[string][]string{
"test": []string{
`package test
func Foo(x int) (int){
return x * 2
}
`,
},
"main": []string{
`package main
// #include
import "C"
func fun() {
C.close(3)
}
`,
`package main
import "test"
import "fmt"
func g() { fmt.Println(test.Foo(3)) }
`,
},
},
wantErr: false,
wantOut: "Renamed 2 occurrences in 2 files in 2 packages.",
},
} {
// Write the test files
testCleanup := setUpPackages(t, srcDir, renameTest.packages)
// Set up arguments
var args []string
var arg, val string
if renameTest.offset != "" {
arg, val = "-offset", renameTest.offset
} else {
arg, val = "-from", renameTest.from
}
prefix := fmt.Sprintf("%d: %s %q -to %q", iter, arg, val, renameTest.to)
if renameTest.fileSpecified {
// add the src dir to the value of the argument
val = filepath.Join(srcDir, val)
}
args = append(args, arg, val, "-to", renameTest.to)
// Run command
cmd := exec.Command(bin, args...)
cmd.Args[0] = "gorename"
cmd.Env = env
// Check the output
out, err := cmd.CombinedOutput()
// errors should result in no changes to files
if err != nil {
if !renameTest.wantErr {
t.Errorf("%s: received unexpected error %s", prefix, err)
}
// Compare output
if ok := strings.Contains(string(out), renameTest.wantOut); !ok {
t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut)
}
// Check that no files were modified
if modified := modifiedFiles(t, srcDir, renameTest.packages); len(modified) != 0 {
t.Errorf("%s: files unexpectedly modified: %s", prefix, modified)
}
} else {
if !renameTest.wantErr {
if ok := strings.Contains(string(out), renameTest.wantOut); !ok {
t.Errorf("%s: unexpected command output: %s (want: %s)", prefix, out, renameTest.wantOut)
}
} else {
t.Errorf("%s: command succeeded unexpectedly, output: %s", prefix, out)
}
}
testCleanup()
}
}
// buildGorename builds the gorename executable.
// It returns its path, and a cleanup function.
func buildGorename(t *testing.T) (tmp, bin string, cleanup func()) {
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
tmp, err := ioutil.TempDir("", "gorename-regtest-")
if err != nil {
t.Fatal(err)
}
defer func() {
if cleanup == nil { // probably, go build failed.
os.RemoveAll(tmp)
}
}()
bin = filepath.Join(tmp, "gorename")
if runtime.GOOS == "windows" {
bin += ".exe"
}
cmd := exec.Command("go", "build", "-o", bin)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("Building gorename: %v\n%s", err, out)
}
return tmp, bin, func() { os.RemoveAll(tmp) }
}
// setUpPackages sets up the files in a temporary directory provided by arguments.
func setUpPackages(t *testing.T, dir string, packages map[string][]string) (cleanup func()) {
var pkgDirs []string
for pkgName, files := range packages {
// Create a directory for the package.
pkgDir := filepath.Join(dir, pkgName)
pkgDirs = append(pkgDirs, pkgDir)
if err := os.Mkdir(pkgDir, os.ModePerm); err != nil {
t.Fatal(err)
}
// Write the packages files
for i, val := range files {
file := filepath.Join(pkgDir, strconv.Itoa(i)+".go")
if err := ioutil.WriteFile(file, []byte(val), os.ModePerm); err != nil {
t.Fatal(err)
}
}
}
return func() {
for _, dir := range pkgDirs {
os.RemoveAll(dir)
}
}
}
// modifiedFiles returns a list of files that were renamed (without the prefix dir).
func modifiedFiles(t *testing.T, dir string, packages map[string][]string) (results []string) {
for pkgName, files := range packages {
pkgDir := filepath.Join(dir, pkgName)
for i, val := range files {
file := filepath.Join(pkgDir, strconv.Itoa(i)+".go")
// read file contents and compare to val
if contents, err := ioutil.ReadFile(file); err != nil {
t.Fatalf("File missing: %s", err)
} else if string(contents) != val {
results = append(results, strings.TrimPrefix(dir, file))
}
}
}
return results
}
golang-golang-x-tools-0.1.9+ds/cmd/gorename/main.go 0000664 0000000 0000000 00000003437 14177515506 0022077 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 gorename command performs precise type-safe renaming of
// identifiers in Go source code.
//
// Run with -help for usage information, or view the Usage constant in
// package golang.org/x/tools/refactor/rename, which contains most of
// the implementation.
//
package main // import "golang.org/x/tools/cmd/gorename"
import (
"flag"
"fmt"
"go/build"
"log"
"os"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/refactor/rename"
)
var (
offsetFlag = flag.String("offset", "", "file and byte offset of identifier to be renamed, e.g. 'file.go:#123'. For use by editors.")
fromFlag = flag.String("from", "", "identifier to be renamed; see -help for formats")
toFlag = flag.String("to", "", "new name for identifier")
helpFlag = flag.Bool("help", false, "show usage message")
)
func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
flag.BoolVar(&rename.Force, "force", false, "proceed, even if conflicts were reported")
flag.BoolVar(&rename.Verbose, "v", false, "print verbose information")
flag.BoolVar(&rename.Diff, "d", false, "display diffs instead of rewriting files")
flag.StringVar(&rename.DiffCmd, "diffcmd", "diff", "diff command invoked when using -d")
}
func main() {
log.SetPrefix("gorename: ")
log.SetFlags(0)
flag.Parse()
if len(flag.Args()) > 0 {
log.Fatal("surplus arguments")
}
if *helpFlag || (*offsetFlag == "" && *fromFlag == "" && *toFlag == "") {
fmt.Print(rename.Usage)
return
}
if err := rename.Main(&build.Default, *offsetFlag, *fromFlag, *toFlag); err != nil {
if err != rename.ConflictError {
log.Fatal(err)
}
os.Exit(1)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/gotype/ 0000775 0000000 0000000 00000000000 14177515506 0020327 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/gotype/gotype.go 0000664 0000000 0000000 00000020525 14177515506 0022171 0 ustar 00root root 0000000 0000000 // 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.
// gotype.go is a copy of the original source maintained
// in $GOROOT/src/go/types/gotype.go, but with the call
// to types.SizesFor factored out so we can provide a local
// implementation when compiling against Go 1.8 and earlier.
//
// This code is here for the sole purpose of satisfying historic
// references to this location, and for making gotype accessible
// via 'go get'.
//
// Do NOT make changes to this version as they will not be maintained
// (and possibly overwritten). Any changes should be made to the original
// and then ported to here.
/*
The gotype command, like the front-end of a Go compiler, parses and
type-checks a single Go package. Errors are reported if the analysis
fails; otherwise gotype is quiet (unless -v is set).
Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.
With a single directory argument, gotype checks the Go files in
that directory, comprising a single package. Use -t to include the
(in-package) _test.go files. Use -x to type check only external
test files.
Otherwise, each path must be the filename of a Go file belonging
to the same package.
Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).
The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.
Usage:
gotype [flags] [path...]
The flags are:
-t
include local test files in a directory (ignored if -x is provided)
-x
consider only external test files in a directory
-e
report all errors (not just the first 10)
-v
verbose mode
-c
compiler used for installed packages (gc, gccgo, or source); default: source
Flags controlling additional output:
-ast
print AST (forces -seq)
-trace
print parse trace (forces -seq)
-comments
parse comments (ignored unless -ast or -trace is provided)
Examples:
To check the files a.go, b.go, and c.go:
gotype a.go b.go c.go
To check an entire package including (in-package) tests in the directory dir and print the processed files:
gotype -t -v dir
To check the external test package (if any) in the current directory, based on installed packages compiled with
cmd/compile:
gotype -c=gc -x .
To verify the output of a pipe:
echo "package foo" | gotype
*/
package main
import (
"flag"
"fmt"
"go/ast"
"go/build"
"go/importer"
"go/parser"
"go/scanner"
"go/token"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
)
var (
// main operation modes
testFiles = flag.Bool("t", false, "include in-package test files in a directory")
xtestFiles = flag.Bool("x", false, "consider only external test files in a directory")
allErrors = flag.Bool("e", false, "report all errors, not just the first 10")
verbose = flag.Bool("v", false, "verbose mode")
compiler = flag.String("c", defaultCompiler, "compiler used for installed packages (gc, gccgo, or source)")
// additional output control
printAST = flag.Bool("ast", false, "print AST (forces -seq)")
printTrace = flag.Bool("trace", false, "print parse trace (forces -seq)")
parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)")
)
var (
fset = token.NewFileSet()
errorCount = 0
sequential = false
parserMode parser.Mode
)
func initParserMode() {
if *allErrors {
parserMode |= parser.AllErrors
}
if *printAST {
sequential = true
}
if *printTrace {
parserMode |= parser.Trace
sequential = true
}
if *parseComments && (*printAST || *printTrace) {
parserMode |= parser.ParseComments
}
}
const usageString = `usage: gotype [flags] [path ...]
The gotype command, like the front-end of a Go compiler, parses and
type-checks a single Go package. Errors are reported if the analysis
fails; otherwise gotype is quiet (unless -v is set).
Without a list of paths, gotype reads from standard input, which
must provide a single Go source file defining a complete package.
With a single directory argument, gotype checks the Go files in
that directory, comprising a single package. Use -t to include the
(in-package) _test.go files. Use -x to type check only external
test files.
Otherwise, each path must be the filename of a Go file belonging
to the same package.
Imports are processed by importing directly from the source of
imported packages (default), or by importing from compiled and
installed packages (by setting -c to the respective compiler).
The -c flag must be set to a compiler ("gc", "gccgo") when type-
checking packages containing imports with relative import paths
(import "./mypkg") because the source importer cannot know which
files to include for such packages.
`
func usage() {
fmt.Fprint(os.Stderr, usageString)
fmt.Fprintln(os.Stderr)
flag.PrintDefaults()
os.Exit(2)
}
func report(err error) {
scanner.PrintError(os.Stderr, err)
if list, ok := err.(scanner.ErrorList); ok {
errorCount += len(list)
return
}
errorCount++
}
// parse may be called concurrently
func parse(filename string, src interface{}) (*ast.File, error) {
if *verbose {
fmt.Println(filename)
}
file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently
if *printAST {
ast.Print(fset, file)
}
return file, err
}
func parseStdin() (*ast.File, error) {
src, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return nil, err
}
return parse("", src)
}
func parseFiles(dir string, filenames []string) ([]*ast.File, error) {
files := make([]*ast.File, len(filenames))
errors := make([]error, len(filenames))
var wg sync.WaitGroup
for i, filename := range filenames {
wg.Add(1)
go func(i int, filepath string) {
defer wg.Done()
files[i], errors[i] = parse(filepath, nil)
}(i, filepath.Join(dir, filename))
if sequential {
wg.Wait()
}
}
wg.Wait()
// if there are errors, return the first one for deterministic results
for _, err := range errors {
if err != nil {
return nil, err
}
}
return files, nil
}
func parseDir(dir string) ([]*ast.File, error) {
ctxt := build.Default
pkginfo, err := ctxt.ImportDir(dir, 0)
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
return nil, err
}
if *xtestFiles {
return parseFiles(dir, pkginfo.XTestGoFiles)
}
filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
if *testFiles {
filenames = append(filenames, pkginfo.TestGoFiles...)
}
return parseFiles(dir, filenames)
}
func getPkgFiles(args []string) ([]*ast.File, error) {
if len(args) == 0 {
// stdin
file, err := parseStdin()
if err != nil {
return nil, err
}
return []*ast.File{file}, nil
}
if len(args) == 1 {
// possibly a directory
path := args[0]
info, err := os.Stat(path)
if err != nil {
return nil, err
}
if info.IsDir() {
return parseDir(path)
}
}
// list of files
return parseFiles("", args)
}
func checkPkgFiles(files []*ast.File) {
type bailout struct{}
// if checkPkgFiles is called multiple times, set up conf only once
conf := types.Config{
FakeImportC: true,
Error: func(err error) {
if !*allErrors && errorCount >= 10 {
panic(bailout{})
}
report(err)
},
Importer: importer.ForCompiler(fset, *compiler, nil),
Sizes: SizesFor(build.Default.Compiler, build.Default.GOARCH),
}
defer func() {
switch p := recover().(type) {
case nil, bailout:
// normal return or early exit
default:
// re-panic
panic(p)
}
}()
const path = "pkg" // any non-empty string will do for now
conf.Check(path, fset, files, nil)
}
func printStats(d time.Duration) {
fileCount := 0
lineCount := 0
fset.Iterate(func(f *token.File) bool {
fileCount++
lineCount += f.LineCount()
return true
})
fmt.Printf(
"%s (%d files, %d lines, %d lines/s)\n",
d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()),
)
}
func main() {
flag.Usage = usage
flag.Parse()
initParserMode()
start := time.Now()
files, err := getPkgFiles(flag.Args())
if err != nil {
report(err)
os.Exit(2)
}
checkPkgFiles(files)
if errorCount > 0 {
os.Exit(2)
}
if *verbose {
printStats(time.Since(start))
}
}
golang-golang-x-tools-0.1.9+ds/cmd/gotype/sizesFor18.go 0000664 0000000 0000000 00000001525 14177515506 0022636 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !go1.9
// +build !go1.9
// This file contains a copy of the implementation of types.SizesFor
// since this function is not available in go/types before Go 1.9.
package main
import "go/types"
const defaultCompiler = "gc"
var gcArchSizes = map[string]*types.StdSizes{
"386": {4, 4},
"arm": {4, 4},
"arm64": {8, 8},
"amd64": {8, 8},
"amd64p32": {4, 8},
"mips": {4, 4},
"mipsle": {4, 4},
"mips64": {8, 8},
"mips64le": {8, 8},
"ppc64": {8, 8},
"ppc64le": {8, 8},
"s390x": {8, 8},
}
func SizesFor(compiler, arch string) types.Sizes {
if compiler != "gc" {
return nil
}
s, ok := gcArchSizes[arch]
if !ok {
return nil
}
return s
}
golang-golang-x-tools-0.1.9+ds/cmd/gotype/sizesFor19.go 0000664 0000000 0000000 00000000541 14177515506 0022634 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build go1.9
// +build go1.9
package main
import "go/types"
const defaultCompiler = "source"
func SizesFor(compiler, arch string) types.Sizes {
return types.SizesFor(compiler, arch)
}
golang-golang-x-tools-0.1.9+ds/cmd/goyacc/ 0000775 0000000 0000000 00000000000 14177515506 0020265 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/goyacc/doc.go 0000664 0000000 0000000 00000004635 14177515506 0021371 0 ustar 00root root 0000000 0000000 // Copyright 2009 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.
/*
Goyacc is a version of yacc for Go.
It is written in Go and generates parsers written in Go.
Usage:
goyacc args...
It is largely transliterated from the Inferno version written in Limbo
which in turn was largely transliterated from the Plan 9 version
written in C and documented at
https://9p.io/magic/man2html/1/yacc
Adepts of the original yacc will have no trouble adapting to this
form of the tool.
The directory $GOPATH/src/golang.org/x/tools/cmd/goyacc/testdata/expr
is a yacc program for a very simple expression parser. See expr.y and
main.go in that directory for examples of how to write and build
goyacc programs.
The generated parser is reentrant. The parsing function yyParse expects
to be given an argument that conforms to the following interface:
type yyLexer interface {
Lex(lval *yySymType) int
Error(e string)
}
Lex should return the token identifier, and place other token
information in lval (which replaces the usual yylval).
Error is equivalent to yyerror in the original yacc.
Code inside the grammar actions may refer to the variable yylex,
which holds the yyLexer passed to yyParse.
Clients that need to understand more about the parser state can
create the parser separately from invoking it. The function yyNewParser
returns a yyParser conforming to the following interface:
type yyParser interface {
Parse(yyLex) int
Lookahead() int
}
Parse runs the parser; the top-level call yyParse(yylex) is equivalent
to yyNewParser().Parse(yylex).
Lookahead can be called during grammar actions to read (but not consume)
the value of the current lookahead token, as returned by yylex.Lex.
If there is no current lookahead token (because the parser has not called Lex
or has consumed the token returned by the most recent call to Lex),
Lookahead returns -1. Calling Lookahead is equivalent to reading
yychar from within in a grammar action.
Multiple grammars compiled into a single program should be placed in
distinct packages. If that is impossible, the "-p prefix" flag to
goyacc sets the prefix, by default yy, that begins the names of
symbols, including types, the parser, and the lexer, generated and
referenced by yacc's generated code. Setting it to distinct values
allows multiple grammars to be placed in a single package.
*/
package main
golang-golang-x-tools-0.1.9+ds/cmd/goyacc/testdata/ 0000775 0000000 0000000 00000000000 14177515506 0022076 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/goyacc/testdata/expr/ 0000775 0000000 0000000 00000000000 14177515506 0023054 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/goyacc/testdata/expr/README 0000664 0000000 0000000 00000000675 14177515506 0023744 0 ustar 00root root 0000000 0000000 This directory contains a simple program demonstrating how to use
the Go version of yacc.
To build it:
$ go generate
$ go build
or
$ go generate
$ go run expr.go
The file main.go contains the "go generate" command to run yacc to
create expr.go from expr.y. It also has the package doc comment,
as godoc will not scan the .y file.
The actual implementation is in expr.y.
The program is not installed in the binary distributions of Go.
golang-golang-x-tools-0.1.9+ds/cmd/goyacc/testdata/expr/expr.y 0000664 0000000 0000000 00000006230 14177515506 0024225 0 ustar 00root root 0000000 0000000 // 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 is an example of a goyacc program.
// To build it:
// goyacc -p "expr" expr.y (produces y.go)
// go build -o expr y.go
// expr
// >
%{
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"math/big"
"os"
"unicode/utf8"
)
%}
%union {
num *big.Rat
}
%type expr expr1 expr2 expr3
%token '+' '-' '*' '/' '(' ')'
%token NUM
%%
top:
expr
{
if $1.IsInt() {
fmt.Println($1.Num().String())
} else {
fmt.Println($1.String())
}
}
expr:
expr1
| '+' expr
{
$$ = $2
}
| '-' expr
{
$$ = $2.Neg($2)
}
expr1:
expr2
| expr1 '+' expr2
{
$$ = $1.Add($1, $3)
}
| expr1 '-' expr2
{
$$ = $1.Sub($1, $3)
}
expr2:
expr3
| expr2 '*' expr3
{
$$ = $1.Mul($1, $3)
}
| expr2 '/' expr3
{
$$ = $1.Quo($1, $3)
}
expr3:
NUM
| '(' expr ')'
{
$$ = $2
}
%%
// The parser expects the lexer to return 0 on EOF. Give it a name
// for clarity.
const eof = 0
// The parser uses the type Lex as a lexer. It must provide
// the methods Lex(*SymType) int and Error(string).
type exprLex struct {
line []byte
peek rune
}
// The parser calls this method to get each new token. This
// implementation returns operators and NUM.
func (x *exprLex) Lex(yylval *exprSymType) int {
for {
c := x.next()
switch c {
case eof:
return eof
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return x.num(c, yylval)
case '+', '-', '*', '/', '(', ')':
return int(c)
// Recognize Unicode multiplication and division
// symbols, returning what the parser expects.
case '×':
return '*'
case '÷':
return '/'
case ' ', '\t', '\n', '\r':
default:
log.Printf("unrecognized character %q", c)
}
}
}
// Lex a number.
func (x *exprLex) num(c rune, yylval *exprSymType) int {
add := func(b *bytes.Buffer, c rune) {
if _, err := b.WriteRune(c); err != nil {
log.Fatalf("WriteRune: %s", err)
}
}
var b bytes.Buffer
add(&b, c)
L: for {
c = x.next()
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', 'E':
add(&b, c)
default:
break L
}
}
if c != eof {
x.peek = c
}
yylval.num = &big.Rat{}
_, ok := yylval.num.SetString(b.String())
if !ok {
log.Printf("bad number %q", b.String())
return eof
}
return NUM
}
// Return the next rune for the lexer.
func (x *exprLex) next() rune {
if x.peek != eof {
r := x.peek
x.peek = eof
return r
}
if len(x.line) == 0 {
return eof
}
c, size := utf8.DecodeRune(x.line)
x.line = x.line[size:]
if c == utf8.RuneError && size == 1 {
log.Print("invalid utf8")
return x.next()
}
return c
}
// The parser calls this method on a parse error.
func (x *exprLex) Error(s string) {
log.Printf("parse error: %s", s)
}
func main() {
in := bufio.NewReader(os.Stdin)
for {
if _, err := os.Stdout.WriteString("> "); err != nil {
log.Fatalf("WriteString: %s", err)
}
line, err := in.ReadBytes('\n')
if err == io.EOF {
return
}
if err != nil {
log.Fatalf("ReadBytes: %s", err)
}
exprParse(&exprLex{line: line})
}
}
golang-golang-x-tools-0.1.9+ds/cmd/goyacc/testdata/expr/main.go 0000664 0000000 0000000 00000000727 14177515506 0024335 0 ustar 00root root 0000000 0000000 // Copyright 2014 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 holds the go generate command to run yacc on the grammar in expr.y.
// To build expr:
// % go generate
// % go build
//go:generate goyacc -o expr.go -p "expr" expr.y
// Expr is a simple expression evaluator that serves as a working example of
// how to use Go's yacc implementation.
package main
golang-golang-x-tools-0.1.9+ds/cmd/goyacc/yacc.go 0000664 0000000 0000000 00000211707 14177515506 0021543 0 ustar 00root root 0000000 0000000 /*
Derived from Inferno's utils/iyacc/yacc.c
http://code.google.com/p/inferno-os/source/browse/utils/iyacc/yacc.c
This copyright NOTICE applies to all files in this directory and
subdirectories, unless another copyright notice appears in a given
file or subdirectory. If you take substantial code from this software to use in
other programs, you must somehow include with it an appropriate
copyright notice that includes the copyright notice and the other
notices below. It is fine (and often tidier) to do that in a separate
file such as NOTICE, LICENCE or COPYING.
Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
Portions Copyright © 1997-1999 Vita Nuova Limited
Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
Portions Copyright © 2004,2006 Bruce Ellis
Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
Portions Copyright © 2009 The Go Authors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package main
// yacc
// major difference is lack of stem ("y" variable)
//
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/format"
"io/ioutil"
"os"
"strconv"
"strings"
"unicode"
)
// the following are adjustable
// according to memory size
const (
ACTSIZE = 240000
NSTATES = 16000
TEMPSIZE = 16000
SYMINC = 50 // increase for non-term or term
RULEINC = 50 // increase for max rule length prodptr[i]
PRODINC = 100 // increase for productions prodptr
WSETINC = 50 // increase for working sets wsets
STATEINC = 200 // increase for states statemem
PRIVATE = 0xE000 // unicode private use
// relationships which must hold:
// TEMPSIZE >= NTERMS + NNONTERM + 1;
// TEMPSIZE >= NSTATES;
//
NTBASE = 010000
ERRCODE = 8190
ACCEPTCODE = 8191
YYLEXUNK = 3
TOKSTART = 4 //index of first defined token
)
// no, left, right, binary assoc.
const (
NOASC = iota
LASC
RASC
BASC
)
// flags for state generation
const (
DONE = iota
MUSTDO
MUSTLOOKAHEAD
)
// flags for a rule having an action, and being reduced
const (
ACTFLAG = 1 << (iota + 2)
REDFLAG
)
// output parser flags
const yyFlag = -1000
// parse tokens
const (
IDENTIFIER = PRIVATE + iota
MARK
TERM
LEFT
RIGHT
BINARY
PREC
LCURLY
IDENTCOLON
NUMBER
START
TYPEDEF
TYPENAME
UNION
ERROR
)
const ENDFILE = 0
const EMPTY = 1
const WHOKNOWS = 0
const OK = 1
const NOMORE = -1000
// macros for getting associativity and precedence levels
func ASSOC(i int) int { return i & 3 }
func PLEVEL(i int) int { return (i >> 4) & 077 }
func TYPE(i int) int { return (i >> 10) & 077 }
// macros for setting associativity and precedence levels
func SETASC(i, j int) int { return i | j }
func SETPLEV(i, j int) int { return i | (j << 4) }
func SETTYPE(i, j int) int { return i | (j << 10) }
// I/O descriptors
var finput *bufio.Reader // input file
var stderr *bufio.Writer
var ftable *bufio.Writer // y.go file
var fcode = &bytes.Buffer{} // saved code
var foutput *bufio.Writer // y.output file
var fmtImported bool // output file has recorded an import of "fmt"
var oflag string // -o [y.go] - y.go file
var vflag string // -v [y.output] - y.output file
var lflag bool // -l - disable line directives
var prefix string // name prefix for identifiers, default yy
func init() {
flag.StringVar(&oflag, "o", "y.go", "parser output")
flag.StringVar(&prefix, "p", "yy", "name prefix to use in generated code")
flag.StringVar(&vflag, "v", "y.output", "create parsing tables")
flag.BoolVar(&lflag, "l", false, "disable line directives")
}
var initialstacksize = 16
// communication variables between various I/O routines
var infile string // input file name
var numbval int // value of an input number
var tokname string // input token name, slop for runes and 0
var tokflag = false
// structure declarations
type Lkset []int
type Pitem struct {
prod []int
off int // offset within the production
first int // first term or non-term in item
prodno int // production number for sorting
}
type Item struct {
pitem Pitem
look Lkset
}
type Symb struct {
name string
noconst bool
value int
}
type Wset struct {
pitem Pitem
flag int
ws Lkset
}
// storage of types
var ntypes int // number of types defined
var typeset = make(map[int]string) // pointers to type tags
// token information
var ntokens = 0 // number of tokens
var tokset []Symb
var toklev []int // vector with the precedence of the terminals
// nonterminal information
var nnonter = -1 // the number of nonterminals
var nontrst []Symb
var start int // start symbol
// state information
var nstate = 0 // number of states
var pstate = make([]int, NSTATES+2) // index into statemem to the descriptions of the states
var statemem []Item
var tystate = make([]int, NSTATES) // contains type information about the states
var tstates []int // states generated by terminal gotos
var ntstates []int // states generated by nonterminal gotos
var mstates = make([]int, NSTATES) // chain of overflows of term/nonterm generation lists
var lastred int // number of last reduction of a state
var defact = make([]int, NSTATES) // default actions of states
// lookahead set information
var nolook = 0 // flag to turn off lookahead computations
var tbitset = 0 // size of lookahead sets
var clset Lkset // temporary storage for lookahead computations
// working set information
var wsets []Wset
var cwp int
// storage for action table
var amem []int // action table storage
var memp int // next free action table position
var indgo = make([]int, NSTATES) // index to the stored goto table
// temporary vector, indexable by states, terms, or ntokens
var temp1 = make([]int, TEMPSIZE) // temporary storage, indexed by terms + ntokens or states
var lineno = 1 // current input line number
var fatfl = 1 // if on, error is fatal
var nerrors = 0 // number of errors
// assigned token type values
var extval = 0
// grammar rule information
var nprod = 1 // number of productions
var prdptr [][]int // pointers to descriptions of productions
var levprd []int // precedence levels for the productions
var rlines []int // line number for this rule
// statistics collection variables
var zzgoent = 0
var zzgobest = 0
var zzacent = 0
var zzexcp = 0
var zzclose = 0
var zzrrconf = 0
var zzsrconf = 0
var zzstate = 0
// optimizer arrays
var yypgo [][]int
var optst [][]int
var ggreed []int
var pgo []int
var maxspr int // maximum spread of any entry
var maxoff int // maximum offset into a array
var maxa int
// storage for information about the nonterminals
var pres [][][]int // vector of pointers to productions yielding each nonterminal
var pfirst []Lkset
var pempty []int // vector of nonterminals nontrivially deriving e
// random stuff picked out from between functions
var indebug = 0 // debugging flag for cpfir
var pidebug = 0 // debugging flag for putitem
var gsdebug = 0 // debugging flag for stagen
var cldebug = 0 // debugging flag for closure
var pkdebug = 0 // debugging flag for apack
var g2debug = 0 // debugging for go2gen
var adb = 0 // debugging for callopt
type Resrv struct {
name string
value int
}
var resrv = []Resrv{
{"binary", BINARY},
{"left", LEFT},
{"nonassoc", BINARY},
{"prec", PREC},
{"right", RIGHT},
{"start", START},
{"term", TERM},
{"token", TERM},
{"type", TYPEDEF},
{"union", UNION},
{"struct", UNION},
{"error", ERROR},
}
type Error struct {
lineno int
tokens []string
msg string
}
var errors []Error
type Row struct {
actions []int
defaultAction int
}
var stateTable []Row
var zznewstate = 0
const EOF = -1
func main() {
setup() // initialize and read productions
tbitset = (ntokens + 32) / 32
cpres() // make table of which productions yield a given nonterminal
cempty() // make a table of which nonterminals can match the empty string
cpfir() // make a table of firsts of nonterminals
stagen() // generate the states
yypgo = make([][]int, nnonter+1)
optst = make([][]int, nstate)
output() // write the states and the tables
go2out()
hideprod()
summary()
callopt()
others()
exit(0)
}
func setup() {
var j, ty int
stderr = bufio.NewWriter(os.Stderr)
foutput = nil
flag.Parse()
if flag.NArg() != 1 {
usage()
}
if initialstacksize < 1 {
// never set so cannot happen
fmt.Fprintf(stderr, "yacc: stack size too small\n")
usage()
}
yaccpar = strings.Replace(yaccpartext, "$$", prefix, -1)
openup()
fmt.Fprintf(ftable, "// Code generated by goyacc %s. DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
defin(0, "$end")
extval = PRIVATE // tokens start in unicode 'private use'
defin(0, "error")
defin(1, "$accept")
defin(0, "$unk")
i := 0
t := gettok()
outer:
for {
switch t {
default:
errorf("syntax error tok=%v", t-PRIVATE)
case MARK, ENDFILE:
break outer
case ';':
// Do nothing.
case START:
t = gettok()
if t != IDENTIFIER {
errorf("bad %%start construction")
}
start = chfind(1, tokname)
case ERROR:
lno := lineno
var tokens []string
for {
t := gettok()
if t == ':' {
break
}
if t != IDENTIFIER && t != IDENTCOLON {
errorf("bad syntax in %%error")
}
tokens = append(tokens, tokname)
if t == IDENTCOLON {
break
}
}
if gettok() != IDENTIFIER {
errorf("bad syntax in %%error")
}
errors = append(errors, Error{lno, tokens, tokname})
case TYPEDEF:
t = gettok()
if t != TYPENAME {
errorf("bad syntax in %%type")
}
ty = numbval
for {
t = gettok()
switch t {
case IDENTIFIER:
t = chfind(1, tokname)
if t < NTBASE {
j = TYPE(toklev[t])
if j != 0 && j != ty {
errorf("type redeclaration of token %s",
tokset[t].name)
} else {
toklev[t] = SETTYPE(toklev[t], ty)
}
} else {
j = nontrst[t-NTBASE].value
if j != 0 && j != ty {
errorf("type redeclaration of nonterminal %v",
nontrst[t-NTBASE].name)
} else {
nontrst[t-NTBASE].value = ty
}
}
continue
case ',':
continue
}
break
}
continue
case UNION:
cpyunion()
case LEFT, BINARY, RIGHT, TERM:
// nonzero means new prec. and assoc.
lev := t - TERM
if lev != 0 {
i++
}
ty = 0
// get identifiers so defined
t = gettok()
// there is a type defined
if t == TYPENAME {
ty = numbval
t = gettok()
}
for {
switch t {
case ',':
t = gettok()
continue
case ';':
// Do nothing.
case IDENTIFIER:
j = chfind(0, tokname)
if j >= NTBASE {
errorf("%v defined earlier as nonterminal", tokname)
}
if lev != 0 {
if ASSOC(toklev[j]) != 0 {
errorf("redeclaration of precedence of %v", tokname)
}
toklev[j] = SETASC(toklev[j], lev)
toklev[j] = SETPLEV(toklev[j], i)
}
if ty != 0 {
if TYPE(toklev[j]) != 0 {
errorf("redeclaration of type of %v", tokname)
}
toklev[j] = SETTYPE(toklev[j], ty)
}
t = gettok()
if t == NUMBER {
tokset[j].value = numbval
t = gettok()
}
continue
}
break
}
continue
case LCURLY:
cpycode()
}
t = gettok()
}
if t == ENDFILE {
errorf("unexpected EOF before %%")
}
fmt.Fprintf(fcode, "switch %snt {\n", prefix)
moreprod()
prdptr[0] = []int{NTBASE, start, 1, 0}
nprod = 1
curprod := make([]int, RULEINC)
t = gettok()
if t != IDENTCOLON {
errorf("bad syntax on first rule")
}
if start == 0 {
prdptr[0][1] = chfind(1, tokname)
}
// read rules
// put into prdptr array in the format
// target
// followed by id's of terminals and non-terminals
// followed by -nprod
for t != MARK && t != ENDFILE {
mem := 0
// process a rule
rlines[nprod] = lineno
ruleline := lineno
if t == '|' {
curprod[mem] = prdptr[nprod-1][0]
mem++
} else if t == IDENTCOLON {
curprod[mem] = chfind(1, tokname)
if curprod[mem] < NTBASE {
lerrorf(ruleline, "token illegal on LHS of grammar rule")
}
mem++
} else {
lerrorf(ruleline, "illegal rule: missing semicolon or | ?")
}
// read rule body
t = gettok()
for {
for t == IDENTIFIER {
curprod[mem] = chfind(1, tokname)
if curprod[mem] < NTBASE {
levprd[nprod] = toklev[curprod[mem]]
}
mem++
if mem >= len(curprod) {
ncurprod := make([]int, mem+RULEINC)
copy(ncurprod, curprod)
curprod = ncurprod
}
t = gettok()
}
if t == PREC {
if gettok() != IDENTIFIER {
lerrorf(ruleline, "illegal %%prec syntax")
}
j = chfind(2, tokname)
if j >= NTBASE {
lerrorf(ruleline, "nonterminal "+nontrst[j-NTBASE].name+" illegal after %%prec")
}
levprd[nprod] = toklev[j]
t = gettok()
}
if t != '=' {
break
}
levprd[nprod] |= ACTFLAG
fmt.Fprintf(fcode, "\n\tcase %v:", nprod)
fmt.Fprintf(fcode, "\n\t\t%sDollar = %sS[%spt-%v:%spt+1]", prefix, prefix, prefix, mem-1, prefix)
cpyact(curprod, mem)
// action within rule...
t = gettok()
if t == IDENTIFIER {
// make it a nonterminal
j = chfind(1, fmt.Sprintf("$$%v", nprod))
//
// the current rule will become rule number nprod+1
// enter null production for action
//
prdptr[nprod] = make([]int, 2)
prdptr[nprod][0] = j
prdptr[nprod][1] = -nprod
// update the production information
nprod++
moreprod()
levprd[nprod] = levprd[nprod-1] & ^ACTFLAG
levprd[nprod-1] = ACTFLAG
rlines[nprod] = lineno
// make the action appear in the original rule
curprod[mem] = j
mem++
if mem >= len(curprod) {
ncurprod := make([]int, mem+RULEINC)
copy(ncurprod, curprod)
curprod = ncurprod
}
}
}
for t == ';' {
t = gettok()
}
curprod[mem] = -nprod
mem++
// check that default action is reasonable
if ntypes != 0 && (levprd[nprod]&ACTFLAG) == 0 &&
nontrst[curprod[0]-NTBASE].value != 0 {
// no explicit action, LHS has value
tempty := curprod[1]
if tempty < 0 {
lerrorf(ruleline, "must return a value, since LHS has a type")
}
if tempty >= NTBASE {
tempty = nontrst[tempty-NTBASE].value
} else {
tempty = TYPE(toklev[tempty])
}
if tempty != nontrst[curprod[0]-NTBASE].value {
lerrorf(ruleline, "default action causes potential type clash")
}
}
moreprod()
prdptr[nprod] = make([]int, mem)
copy(prdptr[nprod], curprod)
nprod++
moreprod()
levprd[nprod] = 0
}
if TEMPSIZE < ntokens+nnonter+1 {
errorf("too many tokens (%d) or non-terminals (%d)", ntokens, nnonter)
}
//
// end of all rules
// dump out the prefix code
//
fmt.Fprintf(fcode, "\n\t}")
// put out non-literal terminals
for i := TOKSTART; i <= ntokens; i++ {
// non-literals
if !tokset[i].noconst {
fmt.Fprintf(ftable, "const %v = %v\n", tokset[i].name, tokset[i].value)
}
}
// put out names of tokens
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "var %sToknames = [...]string{\n", prefix)
for i := 1; i <= ntokens; i++ {
fmt.Fprintf(ftable, "\t%q,\n", tokset[i].name)
}
fmt.Fprintf(ftable, "}\n")
// put out names of states.
// commented out to avoid a huge table just for debugging.
// re-enable to have the names in the binary.
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "var %sStatenames = [...]string{\n", prefix)
// for i:=TOKSTART; i<=ntokens; i++ {
// fmt.Fprintf(ftable, "\t%q,\n", tokset[i].name);
// }
fmt.Fprintf(ftable, "}\n")
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "const %sEofCode = 1\n", prefix)
fmt.Fprintf(ftable, "const %sErrCode = 2\n", prefix)
fmt.Fprintf(ftable, "const %sInitialStackSize = %v\n", prefix, initialstacksize)
//
// copy any postfix code
//
if t == MARK {
if !lflag {
fmt.Fprintf(ftable, "\n//line %v:%v\n", infile, lineno)
}
for {
c := getrune(finput)
if c == EOF {
break
}
ftable.WriteRune(c)
}
}
}
//
// allocate enough room to hold another production
//
func moreprod() {
n := len(prdptr)
if nprod >= n {
nn := n + PRODINC
aprod := make([][]int, nn)
alevprd := make([]int, nn)
arlines := make([]int, nn)
copy(aprod, prdptr)
copy(alevprd, levprd)
copy(arlines, rlines)
prdptr = aprod
levprd = alevprd
rlines = arlines
}
}
//
// define s to be a terminal if nt==0
// or a nonterminal if nt==1
//
func defin(nt int, s string) int {
val := 0
if nt != 0 {
nnonter++
if nnonter >= len(nontrst) {
anontrst := make([]Symb, nnonter+SYMINC)
copy(anontrst, nontrst)
nontrst = anontrst
}
nontrst[nnonter] = Symb{name: s}
return NTBASE + nnonter
}
// must be a token
ntokens++
if ntokens >= len(tokset) {
nn := ntokens + SYMINC
atokset := make([]Symb, nn)
atoklev := make([]int, nn)
copy(atoklev, toklev)
copy(atokset, tokset)
tokset = atokset
toklev = atoklev
}
tokset[ntokens].name = s
toklev[ntokens] = 0
// establish value for token
// single character literal
if s[0] == '\'' || s[0] == '"' {
q, err := strconv.Unquote(s)
if err != nil {
errorf("invalid token: %s", err)
}
rq := []rune(q)
if len(rq) != 1 {
errorf("character token too long: %s", s)
}
val = int(rq[0])
if val == 0 {
errorf("token value 0 is illegal")
}
tokset[ntokens].noconst = true
} else {
val = extval
extval++
if s[0] == '$' {
tokset[ntokens].noconst = true
}
}
tokset[ntokens].value = val
return ntokens
}
var peekline = 0
func gettok() int {
var i int
var match, c rune
tokname = ""
for {
lineno += peekline
peekline = 0
c = getrune(finput)
for c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\r' {
if c == '\n' {
lineno++
}
c = getrune(finput)
}
// skip comment -- fix
if c != '/' {
break
}
lineno += skipcom()
}
switch c {
case EOF:
if tokflag {
fmt.Printf(">>> ENDFILE %v\n", lineno)
}
return ENDFILE
case '{':
ungetrune(finput, c)
if tokflag {
fmt.Printf(">>> ={ %v\n", lineno)
}
return '='
case '<':
// get, and look up, a type name (union member name)
c = getrune(finput)
for c != '>' && c != EOF && c != '\n' {
tokname += string(c)
c = getrune(finput)
}
if c != '>' {
errorf("unterminated < ... > clause")
}
for i = 1; i <= ntypes; i++ {
if typeset[i] == tokname {
numbval = i
if tokflag {
fmt.Printf(">>> TYPENAME old <%v> %v\n", tokname, lineno)
}
return TYPENAME
}
}
ntypes++
numbval = ntypes
typeset[numbval] = tokname
if tokflag {
fmt.Printf(">>> TYPENAME new <%v> %v\n", tokname, lineno)
}
return TYPENAME
case '"', '\'':
match = c
tokname = string(c)
for {
c = getrune(finput)
if c == '\n' || c == EOF {
errorf("illegal or missing ' or \"")
}
if c == '\\' {
tokname += string('\\')
c = getrune(finput)
} else if c == match {
if tokflag {
fmt.Printf(">>> IDENTIFIER \"%v\" %v\n", tokname, lineno)
}
tokname += string(c)
return IDENTIFIER
}
tokname += string(c)
}
case '%':
c = getrune(finput)
switch c {
case '%':
if tokflag {
fmt.Printf(">>> MARK %%%% %v\n", lineno)
}
return MARK
case '=':
if tokflag {
fmt.Printf(">>> PREC %%= %v\n", lineno)
}
return PREC
case '{':
if tokflag {
fmt.Printf(">>> LCURLY %%{ %v\n", lineno)
}
return LCURLY
}
getword(c)
// find a reserved word
for i := range resrv {
if tokname == resrv[i].name {
if tokflag {
fmt.Printf(">>> %%%v %v %v\n", tokname,
resrv[i].value-PRIVATE, lineno)
}
return resrv[i].value
}
}
errorf("invalid escape, or illegal reserved word: %v", tokname)
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
numbval = int(c - '0')
for {
c = getrune(finput)
if !isdigit(c) {
break
}
numbval = numbval*10 + int(c-'0')
}
ungetrune(finput, c)
if tokflag {
fmt.Printf(">>> NUMBER %v %v\n", numbval, lineno)
}
return NUMBER
default:
if isword(c) || c == '.' || c == '$' {
getword(c)
break
}
if tokflag {
fmt.Printf(">>> OPERATOR %v %v\n", string(c), lineno)
}
return int(c)
}
// look ahead to distinguish IDENTIFIER from IDENTCOLON
c = getrune(finput)
for c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\r' || c == '/' {
if c == '\n' {
peekline++
}
// look for comments
if c == '/' {
peekline += skipcom()
}
c = getrune(finput)
}
if c == ':' {
if tokflag {
fmt.Printf(">>> IDENTCOLON %v: %v\n", tokname, lineno)
}
return IDENTCOLON
}
ungetrune(finput, c)
if tokflag {
fmt.Printf(">>> IDENTIFIER %v %v\n", tokname, lineno)
}
return IDENTIFIER
}
func getword(c rune) {
tokname = ""
for isword(c) || isdigit(c) || c == '.' || c == '$' {
tokname += string(c)
c = getrune(finput)
}
ungetrune(finput, c)
}
//
// determine the type of a symbol
//
func fdtype(t int) int {
var v int
var s string
if t >= NTBASE {
v = nontrst[t-NTBASE].value
s = nontrst[t-NTBASE].name
} else {
v = TYPE(toklev[t])
s = tokset[t].name
}
if v <= 0 {
errorf("must specify type for %v", s)
}
return v
}
func chfind(t int, s string) int {
if s[0] == '"' || s[0] == '\'' {
t = 0
}
for i := 0; i <= ntokens; i++ {
if s == tokset[i].name {
return i
}
}
for i := 0; i <= nnonter; i++ {
if s == nontrst[i].name {
return NTBASE + i
}
}
// cannot find name
if t > 1 {
errorf("%v should have been defined earlier", s)
}
return defin(t, s)
}
//
// copy the union declaration to the output, and the define file if present
//
func cpyunion() {
if !lflag {
fmt.Fprintf(ftable, "\n//line %v:%v\n", infile, lineno)
}
fmt.Fprintf(ftable, "type %sSymType struct", prefix)
level := 0
out:
for {
c := getrune(finput)
if c == EOF {
errorf("EOF encountered while processing %%union")
}
ftable.WriteRune(c)
switch c {
case '\n':
lineno++
case '{':
if level == 0 {
fmt.Fprintf(ftable, "\n\tyys int")
}
level++
case '}':
level--
if level == 0 {
break out
}
}
}
fmt.Fprintf(ftable, "\n\n")
}
//
// saves code between %{ and %}
// adds an import for __fmt__ the first time
//
func cpycode() {
lno := lineno
c := getrune(finput)
if c == '\n' {
c = getrune(finput)
lineno++
}
if !lflag {
fmt.Fprintf(ftable, "\n//line %v:%v\n", infile, lineno)
}
// accumulate until %}
code := make([]rune, 0, 1024)
for c != EOF {
if c == '%' {
c = getrune(finput)
if c == '}' {
emitcode(code, lno+1)
return
}
code = append(code, '%')
}
code = append(code, c)
if c == '\n' {
lineno++
}
c = getrune(finput)
}
lineno = lno
errorf("eof before %%}")
}
//
// emits code saved up from between %{ and %}
// called by cpycode
// adds an import for __yyfmt__ after the package clause
//
func emitcode(code []rune, lineno int) {
for i, line := range lines(code) {
writecode(line)
if !fmtImported && isPackageClause(line) {
fmt.Fprintln(ftable, `import __yyfmt__ "fmt"`)
if !lflag {
fmt.Fprintf(ftable, "//line %v:%v\n\t\t", infile, lineno+i)
}
fmtImported = true
}
}
}
//
// does this line look like a package clause? not perfect: might be confused by early comments.
//
func isPackageClause(line []rune) bool {
line = skipspace(line)
// must be big enough.
if len(line) < len("package X\n") {
return false
}
// must start with "package"
for i, r := range []rune("package") {
if line[i] != r {
return false
}
}
line = skipspace(line[len("package"):])
// must have another identifier.
if len(line) == 0 || (!unicode.IsLetter(line[0]) && line[0] != '_') {
return false
}
for len(line) > 0 {
if !unicode.IsLetter(line[0]) && !unicode.IsDigit(line[0]) && line[0] != '_' {
break
}
line = line[1:]
}
line = skipspace(line)
// eol, newline, or comment must follow
if len(line) == 0 {
return true
}
if line[0] == '\r' || line[0] == '\n' {
return true
}
if len(line) >= 2 {
return line[0] == '/' && (line[1] == '/' || line[1] == '*')
}
return false
}
//
// skip initial spaces
//
func skipspace(line []rune) []rune {
for len(line) > 0 {
if line[0] != ' ' && line[0] != '\t' {
break
}
line = line[1:]
}
return line
}
//
// break code into lines
//
func lines(code []rune) [][]rune {
l := make([][]rune, 0, 100)
for len(code) > 0 {
// one line per loop
var i int
for i = range code {
if code[i] == '\n' {
break
}
}
l = append(l, code[:i+1])
code = code[i+1:]
}
return l
}
//
// writes code to ftable
//
func writecode(code []rune) {
for _, r := range code {
ftable.WriteRune(r)
}
}
//
// skip over comments
// skipcom is called after reading a '/'
//
func skipcom() int {
c := getrune(finput)
if c == '/' {
for c != EOF {
if c == '\n' {
return 1
}
c = getrune(finput)
}
errorf("EOF inside comment")
return 0
}
if c != '*' {
errorf("illegal comment")
}
nl := 0 // lines skipped
c = getrune(finput)
l1:
switch c {
case '*':
c = getrune(finput)
if c == '/' {
break
}
goto l1
case '\n':
nl++
fallthrough
default:
c = getrune(finput)
goto l1
}
return nl
}
//
// copy action to the next ; or closing }
//
func cpyact(curprod []int, max int) {
if !lflag {
fmt.Fprintf(fcode, "\n//line %v:%v", infile, lineno)
}
fmt.Fprint(fcode, "\n\t\t")
lno := lineno
brac := 0
loop:
for {
c := getrune(finput)
swt:
switch c {
case ';':
if brac == 0 {
fcode.WriteRune(c)
return
}
case '{':
brac++
case '$':
s := 1
tok := -1
c = getrune(finput)
// type description
if c == '<' {
ungetrune(finput, c)
if gettok() != TYPENAME {
errorf("bad syntax on $ clause")
}
tok = numbval
c = getrune(finput)
}
if c == '$' {
fmt.Fprintf(fcode, "%sVAL", prefix)
// put out the proper tag...
if ntypes != 0 {
if tok < 0 {
tok = fdtype(curprod[0])
}
fmt.Fprintf(fcode, ".%v", typeset[tok])
}
continue loop
}
if c == '-' {
s = -s
c = getrune(finput)
}
j := 0
if isdigit(c) {
for isdigit(c) {
j = j*10 + int(c-'0')
c = getrune(finput)
}
ungetrune(finput, c)
j = j * s
if j >= max {
errorf("Illegal use of $%v", j)
}
} else if isword(c) || c == '.' {
// look for $name
ungetrune(finput, c)
if gettok() != IDENTIFIER {
errorf("$ must be followed by an identifier")
}
tokn := chfind(2, tokname)
fnd := -1
c = getrune(finput)
if c != '@' {
ungetrune(finput, c)
} else if gettok() != NUMBER {
errorf("@ must be followed by number")
} else {
fnd = numbval
}
for j = 1; j < max; j++ {
if tokn == curprod[j] {
fnd--
if fnd <= 0 {
break
}
}
}
if j >= max {
errorf("$name or $name@number not found")
}
} else {
fcode.WriteRune('$')
if s < 0 {
fcode.WriteRune('-')
}
ungetrune(finput, c)
continue loop
}
fmt.Fprintf(fcode, "%sDollar[%v]", prefix, j)
// put out the proper tag
if ntypes != 0 {
if j <= 0 && tok < 0 {
errorf("must specify type of $%v", j)
}
if tok < 0 {
tok = fdtype(curprod[j])
}
fmt.Fprintf(fcode, ".%v", typeset[tok])
}
continue loop
case '}':
brac--
if brac != 0 {
break
}
fcode.WriteRune(c)
return
case '/':
nc := getrune(finput)
if nc != '/' && nc != '*' {
ungetrune(finput, nc)
break
}
// a comment
fcode.WriteRune(c)
fcode.WriteRune(nc)
c = getrune(finput)
for c != EOF {
switch {
case c == '\n':
lineno++
if nc == '/' { // end of // comment
break swt
}
case c == '*' && nc == '*': // end of /* comment?
nnc := getrune(finput)
if nnc == '/' {
fcode.WriteRune('*')
fcode.WriteRune('/')
continue loop
}
ungetrune(finput, nnc)
}
fcode.WriteRune(c)
c = getrune(finput)
}
errorf("EOF inside comment")
case '\'', '"':
// character string or constant
match := c
fcode.WriteRune(c)
c = getrune(finput)
for c != EOF {
if c == '\\' {
fcode.WriteRune(c)
c = getrune(finput)
if c == '\n' {
lineno++
}
} else if c == match {
break swt
}
if c == '\n' {
errorf("newline in string or char const")
}
fcode.WriteRune(c)
c = getrune(finput)
}
errorf("EOF in string or character constant")
case EOF:
lineno = lno
errorf("action does not terminate")
case '\n':
fmt.Fprint(fcode, "\n\t")
lineno++
continue loop
}
fcode.WriteRune(c)
}
}
func openup() {
infile = flag.Arg(0)
finput = open(infile)
if finput == nil {
errorf("cannot open %v", infile)
}
foutput = nil
if vflag != "" {
foutput = create(vflag)
if foutput == nil {
errorf("can't create file %v", vflag)
}
}
ftable = nil
if oflag == "" {
oflag = "y.go"
}
ftable = create(oflag)
if ftable == nil {
errorf("can't create file %v", oflag)
}
}
//
// return a pointer to the name of symbol i
//
func symnam(i int) string {
var s string
if i >= NTBASE {
s = nontrst[i-NTBASE].name
} else {
s = tokset[i].name
}
return s
}
//
// set elements 0 through n-1 to c
//
func aryfil(v []int, n, c int) {
for i := 0; i < n; i++ {
v[i] = c
}
}
//
// compute an array with the beginnings of productions yielding given nonterminals
// The array pres points to these lists
// the array pyield has the lists: the total size is only NPROD+1
//
func cpres() {
pres = make([][][]int, nnonter+1)
curres := make([][]int, nprod)
if false {
for j := 0; j <= nnonter; j++ {
fmt.Printf("nnonter[%v] = %v\n", j, nontrst[j].name)
}
for j := 0; j < nprod; j++ {
fmt.Printf("prdptr[%v][0] = %v+NTBASE\n", j, prdptr[j][0]-NTBASE)
}
}
fatfl = 0 // make undefined symbols nonfatal
for i := 0; i <= nnonter; i++ {
n := 0
c := i + NTBASE
for j := 0; j < nprod; j++ {
if prdptr[j][0] == c {
curres[n] = prdptr[j][1:]
n++
}
}
if n == 0 {
errorf("nonterminal %v not defined", nontrst[i].name)
continue
}
pres[i] = make([][]int, n)
copy(pres[i], curres)
}
fatfl = 1
if nerrors != 0 {
summary()
exit(1)
}
}
//
// mark nonterminals which derive the empty string
// also, look for nonterminals which don't derive any token strings
//
func cempty() {
var i, p, np int
var prd []int
pempty = make([]int, nnonter+1)
// first, use the array pempty to detect productions that can never be reduced
// set pempty to WHONOWS
aryfil(pempty, nnonter+1, WHOKNOWS)
// now, look at productions, marking nonterminals which derive something
more:
for {
for i = 0; i < nprod; i++ {
prd = prdptr[i]
if pempty[prd[0]-NTBASE] != 0 {
continue
}
np = len(prd) - 1
for p = 1; p < np; p++ {
if prd[p] >= NTBASE && pempty[prd[p]-NTBASE] == WHOKNOWS {
break
}
}
// production can be derived
if p == np {
pempty[prd[0]-NTBASE] = OK
continue more
}
}
break
}
// now, look at the nonterminals, to see if they are all OK
for i = 0; i <= nnonter; i++ {
// the added production rises or falls as the start symbol ...
if i == 0 {
continue
}
if pempty[i] != OK {
fatfl = 0
errorf("nonterminal " + nontrst[i].name + " never derives any token string")
}
}
if nerrors != 0 {
summary()
exit(1)
}
// now, compute the pempty array, to see which nonterminals derive the empty string
// set pempty to WHOKNOWS
aryfil(pempty, nnonter+1, WHOKNOWS)
// loop as long as we keep finding empty nonterminals
again:
for {
next:
for i = 1; i < nprod; i++ {
// not known to be empty
prd = prdptr[i]
if pempty[prd[0]-NTBASE] != WHOKNOWS {
continue
}
np = len(prd) - 1
for p = 1; p < np; p++ {
if prd[p] < NTBASE || pempty[prd[p]-NTBASE] != EMPTY {
continue next
}
}
// we have a nontrivially empty nonterminal
pempty[prd[0]-NTBASE] = EMPTY
// got one ... try for another
continue again
}
return
}
}
//
// compute an array with the first of nonterminals
//
func cpfir() {
var s, n, p, np, ch, i int
var curres [][]int
var prd []int
wsets = make([]Wset, nnonter+WSETINC)
pfirst = make([]Lkset, nnonter+1)
for i = 0; i <= nnonter; i++ {
wsets[i].ws = mkset()
pfirst[i] = mkset()
curres = pres[i]
n = len(curres)
// initially fill the sets
for s = 0; s < n; s++ {
prd = curres[s]
np = len(prd) - 1
for p = 0; p < np; p++ {
ch = prd[p]
if ch < NTBASE {
setbit(pfirst[i], ch)
break
}
if pempty[ch-NTBASE] == 0 {
break
}
}
}
}
// now, reflect transitivity
changes := 1
for changes != 0 {
changes = 0
for i = 0; i <= nnonter; i++ {
curres = pres[i]
n = len(curres)
for s = 0; s < n; s++ {
prd = curres[s]
np = len(prd) - 1
for p = 0; p < np; p++ {
ch = prd[p] - NTBASE
if ch < 0 {
break
}
changes |= setunion(pfirst[i], pfirst[ch])
if pempty[ch] == 0 {
break
}
}
}
}
}
if indebug == 0 {
return
}
if foutput != nil {
for i = 0; i <= nnonter; i++ {
fmt.Fprintf(foutput, "\n%v: %v %v\n",
nontrst[i].name, pfirst[i], pempty[i])
}
}
}
//
// generate the states
//
func stagen() {
// initialize
nstate = 0
tstates = make([]int, ntokens+1) // states generated by terminal gotos
ntstates = make([]int, nnonter+1) // states generated by nonterminal gotos
amem = make([]int, ACTSIZE)
memp = 0
clset = mkset()
pstate[0] = 0
pstate[1] = 0
aryfil(clset, tbitset, 0)
putitem(Pitem{prdptr[0], 0, 0, 0}, clset)
tystate[0] = MUSTDO
nstate = 1
pstate[2] = pstate[1]
//
// now, the main state generation loop
// first pass generates all of the states
// later passes fix up lookahead
// could be sped up a lot by remembering
// results of the first pass rather than recomputing
//
first := 1
for more := 1; more != 0; first = 0 {
more = 0
for i := 0; i < nstate; i++ {
if tystate[i] != MUSTDO {
continue
}
tystate[i] = DONE
aryfil(temp1, nnonter+1, 0)
// take state i, close it, and do gotos
closure(i)
// generate goto's
for p := 0; p < cwp; p++ {
pi := wsets[p]
if pi.flag != 0 {
continue
}
wsets[p].flag = 1
c := pi.pitem.first
if c <= 1 {
if pstate[i+1]-pstate[i] <= p {
tystate[i] = MUSTLOOKAHEAD
}
continue
}
// do a goto on c
putitem(wsets[p].pitem, wsets[p].ws)
for q := p + 1; q < cwp; q++ {
// this item contributes to the goto
if c == wsets[q].pitem.first {
putitem(wsets[q].pitem, wsets[q].ws)
wsets[q].flag = 1
}
}
if c < NTBASE {
state(c) // register new state
} else {
temp1[c-NTBASE] = state(c)
}
}
if gsdebug != 0 && foutput != nil {
fmt.Fprintf(foutput, "%v: ", i)
for j := 0; j <= nnonter; j++ {
if temp1[j] != 0 {
fmt.Fprintf(foutput, "%v %v,", nontrst[j].name, temp1[j])
}
}
fmt.Fprintf(foutput, "\n")
}
if first != 0 {
indgo[i] = apack(temp1[1:], nnonter-1) - 1
}
more++
}
}
}
//
// generate the closure of state i
//
func closure(i int) {
zzclose++
// first, copy kernel of state i to wsets
cwp = 0
q := pstate[i+1]
for p := pstate[i]; p < q; p++ {
wsets[cwp].pitem = statemem[p].pitem
wsets[cwp].flag = 1 // this item must get closed
copy(wsets[cwp].ws, statemem[p].look)
cwp++
}
// now, go through the loop, closing each item
work := 1
for work != 0 {
work = 0
for u := 0; u < cwp; u++ {
if wsets[u].flag == 0 {
continue
}
// dot is before c
c := wsets[u].pitem.first
if c < NTBASE {
wsets[u].flag = 0
// only interesting case is where . is before nonterminal
continue
}
// compute the lookahead
aryfil(clset, tbitset, 0)
// find items involving c
for v := u; v < cwp; v++ {
if wsets[v].flag != 1 || wsets[v].pitem.first != c {
continue
}
pi := wsets[v].pitem.prod
ipi := wsets[v].pitem.off + 1
wsets[v].flag = 0
if nolook != 0 {
continue
}
ch := pi[ipi]
ipi++
for ch > 0 {
// terminal symbol
if ch < NTBASE {
setbit(clset, ch)
break
}
// nonterminal symbol
setunion(clset, pfirst[ch-NTBASE])
if pempty[ch-NTBASE] == 0 {
break
}
ch = pi[ipi]
ipi++
}
if ch <= 0 {
setunion(clset, wsets[v].ws)
}
}
//
// now loop over productions derived from c
//
curres := pres[c-NTBASE]
n := len(curres)
nexts:
// initially fill the sets
for s := 0; s < n; s++ {
prd := curres[s]
//
// put these items into the closure
// is the item there
//
for v := 0; v < cwp; v++ {
// yes, it is there
if wsets[v].pitem.off == 0 &&
aryeq(wsets[v].pitem.prod, prd) != 0 {
if nolook == 0 &&
setunion(wsets[v].ws, clset) != 0 {
wsets[v].flag = 1
work = 1
}
continue nexts
}
}
// not there; make a new entry
if cwp >= len(wsets) {
awsets := make([]Wset, cwp+WSETINC)
copy(awsets, wsets)
wsets = awsets
}
wsets[cwp].pitem = Pitem{prd, 0, prd[0], -prd[len(prd)-1]}
wsets[cwp].flag = 1
wsets[cwp].ws = mkset()
if nolook == 0 {
work = 1
copy(wsets[cwp].ws, clset)
}
cwp++
}
}
}
// have computed closure; flags are reset; return
if cldebug != 0 && foutput != nil {
fmt.Fprintf(foutput, "\nState %v, nolook = %v\n", i, nolook)
for u := 0; u < cwp; u++ {
if wsets[u].flag != 0 {
fmt.Fprintf(foutput, "flag set\n")
}
wsets[u].flag = 0
fmt.Fprintf(foutput, "\t%v", writem(wsets[u].pitem))
prlook(wsets[u].ws)
fmt.Fprintf(foutput, "\n")
}
}
}
//
// sorts last state,and sees if it equals earlier ones. returns state number
//
func state(c int) int {
zzstate++
p1 := pstate[nstate]
p2 := pstate[nstate+1]
if p1 == p2 {
return 0 // null state
}
// sort the items
var k, l int
for k = p1 + 1; k < p2; k++ { // make k the biggest
for l = k; l > p1; l-- {
if statemem[l].pitem.prodno < statemem[l-1].pitem.prodno ||
statemem[l].pitem.prodno == statemem[l-1].pitem.prodno &&
statemem[l].pitem.off < statemem[l-1].pitem.off {
s := statemem[l]
statemem[l] = statemem[l-1]
statemem[l-1] = s
} else {
break
}
}
}
size1 := p2 - p1 // size of state
var i int
if c >= NTBASE {
i = ntstates[c-NTBASE]
} else {
i = tstates[c]
}
look:
for ; i != 0; i = mstates[i] {
// get ith state
q1 := pstate[i]
q2 := pstate[i+1]
size2 := q2 - q1
if size1 != size2 {
continue
}
k = p1
for l = q1; l < q2; l++ {
if aryeq(statemem[l].pitem.prod, statemem[k].pitem.prod) == 0 ||
statemem[l].pitem.off != statemem[k].pitem.off {
continue look
}
k++
}
// found it
pstate[nstate+1] = pstate[nstate] // delete last state
// fix up lookaheads
if nolook != 0 {
return i
}
k = p1
for l = q1; l < q2; l++ {
if setunion(statemem[l].look, statemem[k].look) != 0 {
tystate[i] = MUSTDO
}
k++
}
return i
}
// state is new
zznewstate++
if nolook != 0 {
errorf("yacc state/nolook error")
}
pstate[nstate+2] = p2
if nstate+1 >= NSTATES {
errorf("too many states")
}
if c >= NTBASE {
mstates[nstate] = ntstates[c-NTBASE]
ntstates[c-NTBASE] = nstate
} else {
mstates[nstate] = tstates[c]
tstates[c] = nstate
}
tystate[nstate] = MUSTDO
nstate++
return nstate - 1
}
func putitem(p Pitem, set Lkset) {
p.off++
p.first = p.prod[p.off]
if pidebug != 0 && foutput != nil {
fmt.Fprintf(foutput, "putitem(%v), state %v\n", writem(p), nstate)
}
j := pstate[nstate+1]
if j >= len(statemem) {
asm := make([]Item, j+STATEINC)
copy(asm, statemem)
statemem = asm
}
statemem[j].pitem = p
if nolook == 0 {
s := mkset()
copy(s, set)
statemem[j].look = s
}
j++
pstate[nstate+1] = j
}
//
// creates output string for item pointed to by pp
//
func writem(pp Pitem) string {
var i int
p := pp.prod
q := chcopy(nontrst[prdptr[pp.prodno][0]-NTBASE].name) + ": "
npi := pp.off
pi := aryeq(p, prdptr[pp.prodno])
for {
c := ' '
if pi == npi {
c = '.'
}
q += string(c)
i = p[pi]
pi++
if i <= 0 {
break
}
q += chcopy(symnam(i))
}
// an item calling for a reduction
i = p[npi]
if i < 0 {
q += fmt.Sprintf(" (%v)", -i)
}
return q
}
//
// pack state i from temp1 into amem
//
func apack(p []int, n int) int {
//
// we don't need to worry about checking because
// we will only look at entries known to be there...
// eliminate leading and trailing 0's
//
off := 0
pp := 0
for ; pp <= n && p[pp] == 0; pp++ {
off--
}
// no actions
if pp > n {
return 0
}
for ; n > pp && p[n] == 0; n-- {
}
p = p[pp : n+1]
// now, find a place for the elements from p to q, inclusive
r := len(amem) - len(p)
nextk:
for rr := 0; rr <= r; rr++ {
qq := rr
for pp = 0; pp < len(p); pp++ {
if p[pp] != 0 {
if p[pp] != amem[qq] && amem[qq] != 0 {
continue nextk
}
}
qq++
}
// we have found an acceptable k
if pkdebug != 0 && foutput != nil {
fmt.Fprintf(foutput, "off = %v, k = %v\n", off+rr, rr)
}
qq = rr
for pp = 0; pp < len(p); pp++ {
if p[pp] != 0 {
if qq > memp {
memp = qq
}
amem[qq] = p[pp]
}
qq++
}
if pkdebug != 0 && foutput != nil {
for pp = 0; pp <= memp; pp += 10 {
fmt.Fprintf(foutput, "\n")
for qq = pp; qq <= pp+9; qq++ {
fmt.Fprintf(foutput, "%v ", amem[qq])
}
fmt.Fprintf(foutput, "\n")
}
}
return off + rr
}
errorf("no space in action table")
return 0
}
//
// print the output for the states
//
func output() {
var c, u, v int
if !lflag {
fmt.Fprintf(ftable, "\n//line yacctab:1")
}
fmt.Fprintf(ftable, "\nvar %sExca = [...]int{\n", prefix)
if len(errors) > 0 {
stateTable = make([]Row, nstate)
}
noset := mkset()
// output the stuff for state i
for i := 0; i < nstate; i++ {
nolook = 0
if tystate[i] != MUSTLOOKAHEAD {
nolook = 1
}
closure(i)
// output actions
nolook = 1
aryfil(temp1, ntokens+nnonter+1, 0)
for u = 0; u < cwp; u++ {
c = wsets[u].pitem.first
if c > 1 && c < NTBASE && temp1[c] == 0 {
for v = u; v < cwp; v++ {
if c == wsets[v].pitem.first {
putitem(wsets[v].pitem, noset)
}
}
temp1[c] = state(c)
} else if c > NTBASE {
c -= NTBASE
if temp1[c+ntokens] == 0 {
temp1[c+ntokens] = amem[indgo[i]+c]
}
}
}
if i == 1 {
temp1[1] = ACCEPTCODE
}
// now, we have the shifts; look at the reductions
lastred = 0
for u = 0; u < cwp; u++ {
c = wsets[u].pitem.first
// reduction
if c > 0 {
continue
}
lastred = -c
us := wsets[u].ws
for k := 0; k <= ntokens; k++ {
if bitset(us, k) == 0 {
continue
}
if temp1[k] == 0 {
temp1[k] = c
} else if temp1[k] < 0 { // reduce/reduce conflict
if foutput != nil {
fmt.Fprintf(foutput,
"\n %v: reduce/reduce conflict (red'ns "+
"%v and %v) on %v",
i, -temp1[k], lastred, symnam(k))
}
if -temp1[k] > lastred {
temp1[k] = -lastred
}
zzrrconf++
} else {
// potential shift/reduce conflict
precftn(lastred, k, i)
}
}
}
wract(i)
}
fmt.Fprintf(ftable, "}\n")
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "const %sPrivate = %v\n", prefix, PRIVATE)
}
//
// decide a shift/reduce conflict by precedence.
// r is a rule number, t a token number
// the conflict is in state s
// temp1[t] is changed to reflect the action
//
func precftn(r, t, s int) {
action := NOASC
lp := levprd[r]
lt := toklev[t]
if PLEVEL(lt) == 0 || PLEVEL(lp) == 0 {
// conflict
if foutput != nil {
fmt.Fprintf(foutput,
"\n%v: shift/reduce conflict (shift %v(%v), red'n %v(%v)) on %v",
s, temp1[t], PLEVEL(lt), r, PLEVEL(lp), symnam(t))
}
zzsrconf++
return
}
if PLEVEL(lt) == PLEVEL(lp) {
action = ASSOC(lt)
} else if PLEVEL(lt) > PLEVEL(lp) {
action = RASC // shift
} else {
action = LASC
} // reduce
switch action {
case BASC: // error action
temp1[t] = ERRCODE
case LASC: // reduce
temp1[t] = -r
}
}
//
// output state i
// temp1 has the actions, lastred the default
//
func wract(i int) {
var p, p1 int
// find the best choice for lastred
lastred = 0
ntimes := 0
for j := 0; j <= ntokens; j++ {
if temp1[j] >= 0 {
continue
}
if temp1[j]+lastred == 0 {
continue
}
// count the number of appearances of temp1[j]
count := 0
tred := -temp1[j]
levprd[tred] |= REDFLAG
for p = 0; p <= ntokens; p++ {
if temp1[p]+tred == 0 {
count++
}
}
if count > ntimes {
lastred = tred
ntimes = count
}
}
//
// for error recovery, arrange that, if there is a shift on the
// error recovery token, `error', that the default be the error action
//
if temp1[2] > 0 {
lastred = 0
}
// clear out entries in temp1 which equal lastred
// count entries in optst table
n := 0
for p = 0; p <= ntokens; p++ {
p1 = temp1[p]
if p1+lastred == 0 {
temp1[p] = 0
p1 = 0
}
if p1 > 0 && p1 != ACCEPTCODE && p1 != ERRCODE {
n++
}
}
wrstate(i)
defact[i] = lastred
flag := 0
os := make([]int, n*2)
n = 0
for p = 0; p <= ntokens; p++ {
p1 = temp1[p]
if p1 != 0 {
if p1 < 0 {
p1 = -p1
} else if p1 == ACCEPTCODE {
p1 = -1
} else if p1 == ERRCODE {
p1 = 0
} else {
os[n] = p
n++
os[n] = p1
n++
zzacent++
continue
}
if flag == 0 {
fmt.Fprintf(ftable, "\t-1, %v,\n", i)
}
flag++
fmt.Fprintf(ftable, "\t%v, %v,\n", p, p1)
zzexcp++
}
}
if flag != 0 {
defact[i] = -2
fmt.Fprintf(ftable, "\t-2, %v,\n", lastred)
}
optst[i] = os
}
//
// writes state i
//
func wrstate(i int) {
var j0, j1, u int
var pp, qq int
if len(errors) > 0 {
actions := append([]int(nil), temp1...)
defaultAction := ERRCODE
if lastred != 0 {
defaultAction = -lastred
}
stateTable[i] = Row{actions, defaultAction}
}
if foutput == nil {
return
}
fmt.Fprintf(foutput, "\nstate %v\n", i)
qq = pstate[i+1]
for pp = pstate[i]; pp < qq; pp++ {
fmt.Fprintf(foutput, "\t%v\n", writem(statemem[pp].pitem))
}
if tystate[i] == MUSTLOOKAHEAD {
// print out empty productions in closure
for u = pstate[i+1] - pstate[i]; u < cwp; u++ {
if wsets[u].pitem.first < 0 {
fmt.Fprintf(foutput, "\t%v\n", writem(wsets[u].pitem))
}
}
}
// check for state equal to another
for j0 = 0; j0 <= ntokens; j0++ {
j1 = temp1[j0]
if j1 != 0 {
fmt.Fprintf(foutput, "\n\t%v ", symnam(j0))
// shift, error, or accept
if j1 > 0 {
if j1 == ACCEPTCODE {
fmt.Fprintf(foutput, "accept")
} else if j1 == ERRCODE {
fmt.Fprintf(foutput, "error")
} else {
fmt.Fprintf(foutput, "shift %v", j1)
}
} else {
fmt.Fprintf(foutput, "reduce %v (src line %v)", -j1, rlines[-j1])
}
}
}
// output the final production
if lastred != 0 {
fmt.Fprintf(foutput, "\n\t. reduce %v (src line %v)\n\n",
lastred, rlines[lastred])
} else {
fmt.Fprintf(foutput, "\n\t. error\n\n")
}
// now, output nonterminal actions
j1 = ntokens
for j0 = 1; j0 <= nnonter; j0++ {
j1++
if temp1[j1] != 0 {
fmt.Fprintf(foutput, "\t%v goto %v\n", symnam(j0+NTBASE), temp1[j1])
}
}
}
//
// output the gotos for the nontermninals
//
func go2out() {
for i := 1; i <= nnonter; i++ {
go2gen(i)
// find the best one to make default
best := -1
times := 0
// is j the most frequent
for j := 0; j < nstate; j++ {
if tystate[j] == 0 {
continue
}
if tystate[j] == best {
continue
}
// is tystate[j] the most frequent
count := 0
cbest := tystate[j]
for k := j; k < nstate; k++ {
if tystate[k] == cbest {
count++
}
}
if count > times {
best = cbest
times = count
}
}
// best is now the default entry
zzgobest += times - 1
n := 0
for j := 0; j < nstate; j++ {
if tystate[j] != 0 && tystate[j] != best {
n++
}
}
goent := make([]int, 2*n+1)
n = 0
for j := 0; j < nstate; j++ {
if tystate[j] != 0 && tystate[j] != best {
goent[n] = j
n++
goent[n] = tystate[j]
n++
zzgoent++
}
}
// now, the default
if best == -1 {
best = 0
}
zzgoent++
goent[n] = best
yypgo[i] = goent
}
}
//
// output the gotos for nonterminal c
//
func go2gen(c int) {
var i, cc, p, q int
// first, find nonterminals with gotos on c
aryfil(temp1, nnonter+1, 0)
temp1[c] = 1
work := 1
for work != 0 {
work = 0
for i = 0; i < nprod; i++ {
// cc is a nonterminal with a goto on c
cc = prdptr[i][1] - NTBASE
if cc >= 0 && temp1[cc] != 0 {
// thus, the left side of production i does too
cc = prdptr[i][0] - NTBASE
if temp1[cc] == 0 {
work = 1
temp1[cc] = 1
}
}
}
}
// now, we have temp1[c] = 1 if a goto on c in closure of cc
if g2debug != 0 && foutput != nil {
fmt.Fprintf(foutput, "%v: gotos on ", nontrst[c].name)
for i = 0; i <= nnonter; i++ {
if temp1[i] != 0 {
fmt.Fprintf(foutput, "%v ", nontrst[i].name)
}
}
fmt.Fprintf(foutput, "\n")
}
// now, go through and put gotos into tystate
aryfil(tystate, nstate, 0)
for i = 0; i < nstate; i++ {
q = pstate[i+1]
for p = pstate[i]; p < q; p++ {
cc = statemem[p].pitem.first
if cc >= NTBASE {
// goto on c is possible
if temp1[cc-NTBASE] != 0 {
tystate[i] = amem[indgo[i]+c]
break
}
}
}
}
}
//
// in order to free up the mem and amem arrays for the optimizer,
// and still be able to output yyr1, etc., after the sizes of
// the action array is known, we hide the nonterminals
// derived by productions in levprd.
//
func hideprod() {
nred := 0
levprd[0] = 0
for i := 1; i < nprod; i++ {
if (levprd[i] & REDFLAG) == 0 {
if foutput != nil {
fmt.Fprintf(foutput, "Rule not reduced: %v\n",
writem(Pitem{prdptr[i], 0, 0, i}))
}
fmt.Printf("rule %v never reduced\n", writem(Pitem{prdptr[i], 0, 0, i}))
nred++
}
levprd[i] = prdptr[i][0] - NTBASE
}
if nred != 0 {
fmt.Printf("%v rules never reduced\n", nred)
}
}
func callopt() {
var j, k, p, q, i int
var v []int
pgo = make([]int, nnonter+1)
pgo[0] = 0
maxoff = 0
maxspr = 0
for i = 0; i < nstate; i++ {
k = 32000
j = 0
v = optst[i]
q = len(v)
for p = 0; p < q; p += 2 {
if v[p] > j {
j = v[p]
}
if v[p] < k {
k = v[p]
}
}
// nontrivial situation
if k <= j {
// j is now the range
// j -= k; // call scj
if k > maxoff {
maxoff = k
}
}
tystate[i] = q + 2*j
if j > maxspr {
maxspr = j
}
}
// initialize ggreed table
ggreed = make([]int, nnonter+1)
for i = 1; i <= nnonter; i++ {
ggreed[i] = 1
j = 0
// minimum entry index is always 0
v = yypgo[i]
q = len(v) - 1
for p = 0; p < q; p += 2 {
ggreed[i] += 2
if v[p] > j {
j = v[p]
}
}
ggreed[i] = ggreed[i] + 2*j
if j > maxoff {
maxoff = j
}
}
// now, prepare to put the shift actions into the amem array
for i = 0; i < ACTSIZE; i++ {
amem[i] = 0
}
maxa = 0
for i = 0; i < nstate; i++ {
if tystate[i] == 0 && adb > 1 {
fmt.Fprintf(ftable, "State %v: null\n", i)
}
indgo[i] = yyFlag
}
i = nxti()
for i != NOMORE {
if i >= 0 {
stin(i)
} else {
gin(-i)
}
i = nxti()
}
// print amem array
if adb > 2 {
for p = 0; p <= maxa; p += 10 {
fmt.Fprintf(ftable, "%v ", p)
for i = 0; i < 10; i++ {
fmt.Fprintf(ftable, "%v ", amem[p+i])
}
ftable.WriteRune('\n')
}
}
aoutput()
osummary()
}
//
// finds the next i
//
func nxti() int {
max := 0
maxi := 0
for i := 1; i <= nnonter; i++ {
if ggreed[i] >= max {
max = ggreed[i]
maxi = -i
}
}
for i := 0; i < nstate; i++ {
if tystate[i] >= max {
max = tystate[i]
maxi = i
}
}
if max == 0 {
return NOMORE
}
return maxi
}
func gin(i int) {
var s int
// enter gotos on nonterminal i into array amem
ggreed[i] = 0
q := yypgo[i]
nq := len(q) - 1
// now, find amem place for it
nextgp:
for p := 0; p < ACTSIZE; p++ {
if amem[p] != 0 {
continue
}
for r := 0; r < nq; r += 2 {
s = p + q[r] + 1
if s > maxa {
maxa = s
if maxa >= ACTSIZE {
errorf("a array overflow")
}
}
if amem[s] != 0 {
continue nextgp
}
}
// we have found amem spot
amem[p] = q[nq]
if p > maxa {
maxa = p
}
for r := 0; r < nq; r += 2 {
s = p + q[r] + 1
amem[s] = q[r+1]
}
pgo[i] = p
if adb > 1 {
fmt.Fprintf(ftable, "Nonterminal %v, entry at %v\n", i, pgo[i])
}
return
}
errorf("cannot place goto %v\n", i)
}
func stin(i int) {
var s int
tystate[i] = 0
// enter state i into the amem array
q := optst[i]
nq := len(q)
nextn:
// find an acceptable place
for n := -maxoff; n < ACTSIZE; n++ {
flag := 0
for r := 0; r < nq; r += 2 {
s = q[r] + n
if s < 0 || s > ACTSIZE {
continue nextn
}
if amem[s] == 0 {
flag++
} else if amem[s] != q[r+1] {
continue nextn
}
}
// check the position equals another only if the states are identical
for j := 0; j < nstate; j++ {
if indgo[j] == n {
// we have some disagreement
if flag != 0 {
continue nextn
}
if nq == len(optst[j]) {
// states are equal
indgo[i] = n
if adb > 1 {
fmt.Fprintf(ftable, "State %v: entry at"+
"%v equals state %v\n",
i, n, j)
}
return
}
// we have some disagreement
continue nextn
}
}
for r := 0; r < nq; r += 2 {
s = q[r] + n
if s > maxa {
maxa = s
}
if amem[s] != 0 && amem[s] != q[r+1] {
errorf("clobber of a array, pos'n %v, by %v", s, q[r+1])
}
amem[s] = q[r+1]
}
indgo[i] = n
if adb > 1 {
fmt.Fprintf(ftable, "State %v: entry at %v\n", i, indgo[i])
}
return
}
errorf("Error; failure to place state %v", i)
}
//
// this version is for limbo
// write out the optimized parser
//
func aoutput() {
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "const %sLast = %v\n", prefix, maxa+1)
arout("Act", amem, maxa+1)
arout("Pact", indgo, nstate)
arout("Pgo", pgo, nnonter+1)
}
//
// put out other arrays, copy the parsers
//
func others() {
var i, j int
arout("R1", levprd, nprod)
aryfil(temp1, nprod, 0)
//
//yyr2 is the number of rules for each production
//
for i = 1; i < nprod; i++ {
temp1[i] = len(prdptr[i]) - 2
}
arout("R2", temp1, nprod)
aryfil(temp1, nstate, -1000)
for i = 0; i <= ntokens; i++ {
for j := tstates[i]; j != 0; j = mstates[j] {
temp1[j] = i
}
}
for i = 0; i <= nnonter; i++ {
for j = ntstates[i]; j != 0; j = mstates[j] {
temp1[j] = -i
}
}
arout("Chk", temp1, nstate)
arout("Def", defact, nstate)
// put out token translation tables
// table 1 has 0-256
aryfil(temp1, 256, 0)
c := 0
for i = 1; i <= ntokens; i++ {
j = tokset[i].value
if j >= 0 && j < 256 {
if temp1[j] != 0 {
fmt.Print("yacc bug -- cannot have 2 different Ts with same value\n")
fmt.Printf(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name)
nerrors++
}
temp1[j] = i
if j > c {
c = j
}
}
}
for i = 0; i <= c; i++ {
if temp1[i] == 0 {
temp1[i] = YYLEXUNK
}
}
arout("Tok1", temp1, c+1)
// table 2 has PRIVATE-PRIVATE+256
aryfil(temp1, 256, 0)
c = 0
for i = 1; i <= ntokens; i++ {
j = tokset[i].value - PRIVATE
if j >= 0 && j < 256 {
if temp1[j] != 0 {
fmt.Print("yacc bug -- cannot have 2 different Ts with same value\n")
fmt.Printf(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name)
nerrors++
}
temp1[j] = i
if j > c {
c = j
}
}
}
arout("Tok2", temp1, c+1)
// table 3 has everything else
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "var %sTok3 = [...]int{\n\t", prefix)
c = 0
for i = 1; i <= ntokens; i++ {
j = tokset[i].value
if j >= 0 && j < 256 {
continue
}
if j >= PRIVATE && j < 256+PRIVATE {
continue
}
if c%5 != 0 {
ftable.WriteRune(' ')
}
fmt.Fprintf(ftable, "%d, %d,", j, i)
c++
if c%5 == 0 {
fmt.Fprint(ftable, "\n\t")
}
}
if c%5 != 0 {
ftable.WriteRune(' ')
}
fmt.Fprintf(ftable, "%d,\n}\n", 0)
// Custom error messages.
fmt.Fprintf(ftable, "\n")
fmt.Fprintf(ftable, "var %sErrorMessages = [...]struct {\n", prefix)
fmt.Fprintf(ftable, "\tstate int\n")
fmt.Fprintf(ftable, "\ttoken int\n")
fmt.Fprintf(ftable, "\tmsg string\n")
fmt.Fprintf(ftable, "}{\n")
for _, error := range errors {
lineno = error.lineno
state, token := runMachine(error.tokens)
fmt.Fprintf(ftable, "\t{%v, %v, %s},\n", state, token, error.msg)
}
fmt.Fprintf(ftable, "}\n")
// copy parser text
ch := getrune(finput)
for ch != EOF {
ftable.WriteRune(ch)
ch = getrune(finput)
}
// copy yaccpar
if !lflag {
fmt.Fprintf(ftable, "\n//line yaccpar:1\n")
}
parts := strings.SplitN(yaccpar, prefix+"run()", 2)
fmt.Fprintf(ftable, "%v", parts[0])
ftable.Write(fcode.Bytes())
fmt.Fprintf(ftable, "%v", parts[1])
}
func runMachine(tokens []string) (state, token int) {
var stack []int
i := 0
token = -1
Loop:
if token < 0 {
token = chfind(2, tokens[i])
i++
}
row := stateTable[state]
c := token
if token >= NTBASE {
c = token - NTBASE + ntokens
}
action := row.actions[c]
if action == 0 {
action = row.defaultAction
}
switch {
case action == ACCEPTCODE:
errorf("tokens are accepted")
return
case action == ERRCODE:
if token >= NTBASE {
errorf("error at non-terminal token %s", symnam(token))
}
return
case action > 0:
// Shift to state action.
stack = append(stack, state)
state = action
token = -1
goto Loop
default:
// Reduce by production -action.
prod := prdptr[-action]
if rhsLen := len(prod) - 2; rhsLen > 0 {
n := len(stack) - rhsLen
state = stack[n]
stack = stack[:n]
}
if token >= 0 {
i--
}
token = prod[0]
goto Loop
}
}
func arout(s string, v []int, n int) {
s = prefix + s
ftable.WriteRune('\n')
fmt.Fprintf(ftable, "var %v = [...]int{", s)
for i := 0; i < n; i++ {
if i%10 == 0 {
fmt.Fprintf(ftable, "\n\t")
} else {
ftable.WriteRune(' ')
}
fmt.Fprintf(ftable, "%d,", v[i])
}
fmt.Fprintf(ftable, "\n}\n")
}
//
// output the summary on y.output
//
func summary() {
if foutput != nil {
fmt.Fprintf(foutput, "\n%v terminals, %v nonterminals\n", ntokens, nnonter+1)
fmt.Fprintf(foutput, "%v grammar rules, %v/%v states\n", nprod, nstate, NSTATES)
fmt.Fprintf(foutput, "%v shift/reduce, %v reduce/reduce conflicts reported\n", zzsrconf, zzrrconf)
fmt.Fprintf(foutput, "%v working sets used\n", len(wsets))
fmt.Fprintf(foutput, "memory: parser %v/%v\n", memp, ACTSIZE)
fmt.Fprintf(foutput, "%v extra closures\n", zzclose-2*nstate)
fmt.Fprintf(foutput, "%v shift entries, %v exceptions\n", zzacent, zzexcp)
fmt.Fprintf(foutput, "%v goto entries\n", zzgoent)
fmt.Fprintf(foutput, "%v entries saved by goto default\n", zzgobest)
}
if zzsrconf != 0 || zzrrconf != 0 {
fmt.Printf("\nconflicts: ")
if zzsrconf != 0 {
fmt.Printf("%v shift/reduce", zzsrconf)
}
if zzsrconf != 0 && zzrrconf != 0 {
fmt.Printf(", ")
}
if zzrrconf != 0 {
fmt.Printf("%v reduce/reduce", zzrrconf)
}
fmt.Printf("\n")
}
}
//
// write optimizer summary
//
func osummary() {
if foutput == nil {
return
}
i := 0
for p := maxa; p >= 0; p-- {
if amem[p] == 0 {
i++
}
}
fmt.Fprintf(foutput, "Optimizer space used: output %v/%v\n", maxa+1, ACTSIZE)
fmt.Fprintf(foutput, "%v table entries, %v zero\n", maxa+1, i)
fmt.Fprintf(foutput, "maximum spread: %v, maximum offset: %v\n", maxspr, maxoff)
}
//
// copies and protects "'s in q
//
func chcopy(q string) string {
s := ""
i := 0
j := 0
for i = 0; i < len(q); i++ {
if q[i] == '"' {
s += q[j:i] + "\\"
j = i
}
}
return s + q[j:i]
}
func usage() {
fmt.Fprintf(stderr, "usage: yacc [-o output] [-v parsetable] input\n")
exit(1)
}
func bitset(set Lkset, bit int) int { return set[bit>>5] & (1 << uint(bit&31)) }
func setbit(set Lkset, bit int) { set[bit>>5] |= (1 << uint(bit&31)) }
func mkset() Lkset { return make([]int, tbitset) }
//
// set a to the union of a and b
// return 1 if b is not a subset of a, 0 otherwise
//
func setunion(a, b []int) int {
sub := 0
for i := 0; i < tbitset; i++ {
x := a[i]
y := x | b[i]
a[i] = y
if y != x {
sub = 1
}
}
return sub
}
func prlook(p Lkset) {
if p == nil {
fmt.Fprintf(foutput, "\tNULL")
return
}
fmt.Fprintf(foutput, " { ")
for j := 0; j <= ntokens; j++ {
if bitset(p, j) != 0 {
fmt.Fprintf(foutput, "%v ", symnam(j))
}
}
fmt.Fprintf(foutput, "}")
}
//
// utility routines
//
var peekrune rune
func isdigit(c rune) bool { return c >= '0' && c <= '9' }
func isword(c rune) bool {
return c >= 0xa0 || c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
//
// return 1 if 2 arrays are equal
// return 0 if not equal
//
func aryeq(a []int, b []int) int {
n := len(a)
if len(b) != n {
return 0
}
for ll := 0; ll < n; ll++ {
if a[ll] != b[ll] {
return 0
}
}
return 1
}
func getrune(f *bufio.Reader) rune {
var r rune
if peekrune != 0 {
if peekrune == EOF {
return EOF
}
r = peekrune
peekrune = 0
return r
}
c, n, err := f.ReadRune()
if n == 0 {
return EOF
}
if err != nil {
errorf("read error: %v", err)
}
//fmt.Printf("rune = %v n=%v\n", string(c), n);
return c
}
func ungetrune(f *bufio.Reader, c rune) {
if f != finput {
panic("ungetc - not finput")
}
if peekrune != 0 {
panic("ungetc - 2nd unget")
}
peekrune = c
}
func open(s string) *bufio.Reader {
fi, err := os.Open(s)
if err != nil {
errorf("error opening %v: %v", s, err)
}
//fmt.Printf("open %v\n", s);
return bufio.NewReader(fi)
}
func create(s string) *bufio.Writer {
fo, err := os.Create(s)
if err != nil {
errorf("error creating %v: %v", s, err)
}
//fmt.Printf("create %v mode %v\n", s);
return bufio.NewWriter(fo)
}
//
// write out error comment
//
func lerrorf(lineno int, s string, v ...interface{}) {
nerrors++
fmt.Fprintf(stderr, s, v...)
fmt.Fprintf(stderr, ": %v:%v\n", infile, lineno)
if fatfl != 0 {
summary()
exit(1)
}
}
func errorf(s string, v ...interface{}) {
lerrorf(lineno, s, v...)
}
func exit(status int) {
if ftable != nil {
ftable.Flush()
ftable = nil
gofmt()
}
if foutput != nil {
foutput.Flush()
foutput = nil
}
if stderr != nil {
stderr.Flush()
stderr = nil
}
os.Exit(status)
}
func gofmt() {
src, err := ioutil.ReadFile(oflag)
if err != nil {
return
}
src, err = format.Source(src)
if err != nil {
return
}
ioutil.WriteFile(oflag, src, 0666)
}
var yaccpar string // will be processed version of yaccpartext: s/$$/prefix/g
var yaccpartext = `
/* parser for yacc output */
var (
$$Debug = 0
$$ErrorVerbose = false
)
type $$Lexer interface {
Lex(lval *$$SymType) int
Error(s string)
}
type $$Parser interface {
Parse($$Lexer) int
Lookahead() int
}
type $$ParserImpl struct {
lval $$SymType
stack [$$InitialStackSize]$$SymType
char int
}
func (p *$$ParserImpl) Lookahead() int {
return p.char
}
func $$NewParser() $$Parser {
return &$$ParserImpl{}
}
const $$Flag = -1000
func $$Tokname(c int) string {
if c >= 1 && c-1 < len($$Toknames) {
if $$Toknames[c-1] != "" {
return $$Toknames[c-1]
}
}
return __yyfmt__.Sprintf("tok-%v", c)
}
func $$Statname(s int) string {
if s >= 0 && s < len($$Statenames) {
if $$Statenames[s] != "" {
return $$Statenames[s]
}
}
return __yyfmt__.Sprintf("state-%v", s)
}
func $$ErrorMessage(state, lookAhead int) string {
const TOKSTART = 4
if !$$ErrorVerbose {
return "syntax error"
}
for _, e := range $$ErrorMessages {
if e.state == state && e.token == lookAhead {
return "syntax error: " + e.msg
}
}
res := "syntax error: unexpected " + $$Tokname(lookAhead)
// To match Bison, suggest at most four expected tokens.
expected := make([]int, 0, 4)
// Look for shiftable tokens.
base := $$Pact[state]
for tok := TOKSTART; tok-1 < len($$Toknames); tok++ {
if n := base + tok; n >= 0 && n < $$Last && $$Chk[$$Act[n]] == tok {
if len(expected) == cap(expected) {
return res
}
expected = append(expected, tok)
}
}
if $$Def[state] == -2 {
i := 0
for $$Exca[i] != -1 || $$Exca[i+1] != state {
i += 2
}
// Look for tokens that we accept or reduce.
for i += 2; $$Exca[i] >= 0; i += 2 {
tok := $$Exca[i]
if tok < TOKSTART || $$Exca[i+1] == 0 {
continue
}
if len(expected) == cap(expected) {
return res
}
expected = append(expected, tok)
}
// If the default action is to accept or reduce, give up.
if $$Exca[i+1] != 0 {
return res
}
}
for i, tok := range expected {
if i == 0 {
res += ", expecting "
} else {
res += " or "
}
res += $$Tokname(tok)
}
return res
}
func $$lex1(lex $$Lexer, lval *$$SymType) (char, token int) {
token = 0
char = lex.Lex(lval)
if char <= 0 {
token = $$Tok1[0]
goto out
}
if char < len($$Tok1) {
token = $$Tok1[char]
goto out
}
if char >= $$Private {
if char < $$Private+len($$Tok2) {
token = $$Tok2[char-$$Private]
goto out
}
}
for i := 0; i < len($$Tok3); i += 2 {
token = $$Tok3[i+0]
if token == char {
token = $$Tok3[i+1]
goto out
}
}
out:
if token == 0 {
token = $$Tok2[1] /* unknown char */
}
if $$Debug >= 3 {
__yyfmt__.Printf("lex %s(%d)\n", $$Tokname(token), uint(char))
}
return char, token
}
func $$Parse($$lex $$Lexer) int {
return $$NewParser().Parse($$lex)
}
func ($$rcvr *$$ParserImpl) Parse($$lex $$Lexer) int {
var $$n int
var $$VAL $$SymType
var $$Dollar []$$SymType
_ = $$Dollar // silence set and not used
$$S := $$rcvr.stack[:]
Nerrs := 0 /* number of errors */
Errflag := 0 /* error recovery flag */
$$state := 0
$$rcvr.char = -1
$$token := -1 // $$rcvr.char translated into internal numbering
defer func() {
// Make sure we report no lookahead when not parsing.
$$state = -1
$$rcvr.char = -1
$$token = -1
}()
$$p := -1
goto $$stack
ret0:
return 0
ret1:
return 1
$$stack:
/* put a state and value onto the stack */
if $$Debug >= 4 {
__yyfmt__.Printf("char %v in %v\n", $$Tokname($$token), $$Statname($$state))
}
$$p++
if $$p >= len($$S) {
nyys := make([]$$SymType, len($$S)*2)
copy(nyys, $$S)
$$S = nyys
}
$$S[$$p] = $$VAL
$$S[$$p].yys = $$state
$$newstate:
$$n = $$Pact[$$state]
if $$n <= $$Flag {
goto $$default /* simple state */
}
if $$rcvr.char < 0 {
$$rcvr.char, $$token = $$lex1($$lex, &$$rcvr.lval)
}
$$n += $$token
if $$n < 0 || $$n >= $$Last {
goto $$default
}
$$n = $$Act[$$n]
if $$Chk[$$n] == $$token { /* valid shift */
$$rcvr.char = -1
$$token = -1
$$VAL = $$rcvr.lval
$$state = $$n
if Errflag > 0 {
Errflag--
}
goto $$stack
}
$$default:
/* default state action */
$$n = $$Def[$$state]
if $$n == -2 {
if $$rcvr.char < 0 {
$$rcvr.char, $$token = $$lex1($$lex, &$$rcvr.lval)
}
/* look through exception table */
xi := 0
for {
if $$Exca[xi+0] == -1 && $$Exca[xi+1] == $$state {
break
}
xi += 2
}
for xi += 2; ; xi += 2 {
$$n = $$Exca[xi+0]
if $$n < 0 || $$n == $$token {
break
}
}
$$n = $$Exca[xi+1]
if $$n < 0 {
goto ret0
}
}
if $$n == 0 {
/* error ... attempt to resume parsing */
switch Errflag {
case 0: /* brand new error */
$$lex.Error($$ErrorMessage($$state, $$token))
Nerrs++
if $$Debug >= 1 {
__yyfmt__.Printf("%s", $$Statname($$state))
__yyfmt__.Printf(" saw %s\n", $$Tokname($$token))
}
fallthrough
case 1, 2: /* incompletely recovered error ... try again */
Errflag = 3
/* find a state where "error" is a legal shift action */
for $$p >= 0 {
$$n = $$Pact[$$S[$$p].yys] + $$ErrCode
if $$n >= 0 && $$n < $$Last {
$$state = $$Act[$$n] /* simulate a shift of "error" */
if $$Chk[$$state] == $$ErrCode {
goto $$stack
}
}
/* the current p has no shift on "error", pop stack */
if $$Debug >= 2 {
__yyfmt__.Printf("error recovery pops state %d\n", $$S[$$p].yys)
}
$$p--
}
/* there is no state on the stack with an error shift ... abort */
goto ret1
case 3: /* no shift yet; clobber input char */
if $$Debug >= 2 {
__yyfmt__.Printf("error recovery discards %s\n", $$Tokname($$token))
}
if $$token == $$EofCode {
goto ret1
}
$$rcvr.char = -1
$$token = -1
goto $$newstate /* try again in the same state */
}
}
/* reduction by production $$n */
if $$Debug >= 2 {
__yyfmt__.Printf("reduce %v in:\n\t%v\n", $$n, $$Statname($$state))
}
$$nt := $$n
$$pt := $$p
_ = $$pt // guard against "declared and not used"
$$p -= $$R2[$$n]
// $$p is now the index of $0. Perform the default action. Iff the
// reduced production is ε, $1 is possibly out of range.
if $$p+1 >= len($$S) {
nyys := make([]$$SymType, len($$S)*2)
copy(nyys, $$S)
$$S = nyys
}
$$VAL = $$S[$$p+1]
/* consult goto table to find next state */
$$n = $$R1[$$n]
$$g := $$Pgo[$$n]
$$j := $$g + $$S[$$p].yys + 1
if $$j >= $$Last {
$$state = $$Act[$$g]
} else {
$$state = $$Act[$$j]
if $$Chk[$$state] != -$$n {
$$state = $$Act[$$g]
}
}
// dummy call; replaced with literal code
$$run()
goto $$stack /* stack new state and value */
}
`
golang-golang-x-tools-0.1.9+ds/cmd/guru/ 0000775 0000000 0000000 00000000000 14177515506 0020002 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/callees.go 0000664 0000000 0000000 00000015272 14177515506 0021750 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// The callees function reports the possible callees of the function call site
// identified by the specified source location.
func callees(q *Query) error {
lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil {
return err
}
// Determine the enclosing call for the specified position.
var e *ast.CallExpr
for _, n := range qpos.path {
if e, _ = n.(*ast.CallExpr); e != nil {
break
}
}
if e == nil {
return fmt.Errorf("there is no function call here")
}
// TODO(adonovan): issue an error if the call is "too far
// away" from the current selection, as this most likely is
// not what the user intended.
// Reject type conversions.
if qpos.info.Types[e.Fun].IsType() {
return fmt.Errorf("this is a type conversion, not a function call")
}
// Deal with obviously static calls before constructing SSA form.
// Some static calls may yet require SSA construction,
// e.g. f := func(){}; f().
switch funexpr := unparen(e.Fun).(type) {
case *ast.Ident:
switch obj := qpos.info.Uses[funexpr].(type) {
case *types.Builtin:
// Reject calls to built-ins.
return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name())
case *types.Func:
// This is a static function call
q.Output(lprog.Fset, &calleesTypesResult{
site: e,
callee: obj,
})
return nil
}
case *ast.SelectorExpr:
sel := qpos.info.Selections[funexpr]
if sel == nil {
// qualified identifier.
// May refer to top level function variable
// or to top level function.
callee := qpos.info.Uses[funexpr.Sel]
if obj, ok := callee.(*types.Func); ok {
q.Output(lprog.Fset, &calleesTypesResult{
site: e,
callee: obj,
})
return nil
}
} else if sel.Kind() == types.MethodVal {
// Inspect the receiver type of the selected method.
// If it is concrete, the call is statically dispatched.
// (Due to implicit field selections, it is not enough to look
// at sel.Recv(), the type of the actual receiver expression.)
method := sel.Obj().(*types.Func)
recvtype := method.Type().(*types.Signature).Recv().Type()
if !types.IsInterface(recvtype) {
// static method call
q.Output(lprog.Fset, &calleesTypesResult{
site: e,
callee: method,
})
return nil
}
}
}
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil {
return fmt.Errorf("no SSA package")
}
// Defer SSA construction till after errors are reported.
prog.Build()
// Ascertain calling function and call site.
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
if callerFn == nil {
return fmt.Errorf("no SSA function built for this location (dead code?)")
}
// Find the call site.
site, err := findCallSite(callerFn, e)
if err != nil {
return err
}
funcs, err := findCallees(ptaConfig, site)
if err != nil {
return err
}
q.Output(lprog.Fset, &calleesSSAResult{
site: site,
funcs: funcs,
})
return nil
}
func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) {
instr, _ := fn.ValueForExpr(call)
callInstr, _ := instr.(ssa.CallInstruction)
if instr == nil {
return nil, fmt.Errorf("this call site is unreachable in this analysis")
}
return callInstr, nil
}
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
// Avoid running the pointer analysis for static calls.
if callee := site.Common().StaticCallee(); callee != nil {
switch callee.String() {
case "runtime.SetFinalizer", "(reflect.Value).Call":
// The PTA treats calls to these intrinsics as dynamic.
// TODO(adonovan): avoid reliance on PTA internals.
default:
return []*ssa.Function{callee}, nil // singleton
}
}
// Dynamic call: use pointer analysis.
conf.BuildCallGraph = true
cg := ptrAnalysis(conf).CallGraph
cg.DeleteSyntheticNodes()
// Find all call edges from the site.
n := cg.Nodes[site.Parent()]
if n == nil {
return nil, fmt.Errorf("this call site is unreachable in this analysis")
}
calleesMap := make(map[*ssa.Function]bool)
for _, edge := range n.Out {
if edge.Site == site {
calleesMap[edge.Callee.Func] = true
}
}
// De-duplicate and sort.
funcs := make([]*ssa.Function, 0, len(calleesMap))
for f := range calleesMap {
funcs = append(funcs, f)
}
sort.Sort(byFuncPos(funcs))
return funcs, nil
}
type calleesSSAResult struct {
site ssa.CallInstruction
funcs []*ssa.Function
}
type calleesTypesResult struct {
site *ast.CallExpr
callee *types.Func
}
func (r *calleesSSAResult) PrintPlain(printf printfFunc) {
if len(r.funcs) == 0 {
// dynamic call on a provably nil func/interface
printf(r.site, "%s on nil value", r.site.Common().Description())
} else {
printf(r.site, "this %s dispatches to:", r.site.Common().Description())
for _, callee := range r.funcs {
printf(callee, "\t%s", callee)
}
}
}
func (r *calleesSSAResult) JSON(fset *token.FileSet) []byte {
j := &serial.Callees{
Pos: fset.Position(r.site.Pos()).String(),
Desc: r.site.Common().Description(),
}
for _, callee := range r.funcs {
j.Callees = append(j.Callees, &serial.Callee{
Name: callee.String(),
Pos: fset.Position(callee.Pos()).String(),
})
}
return toJSON(j)
}
func (r *calleesTypesResult) PrintPlain(printf printfFunc) {
printf(r.site, "this static function call dispatches to:")
printf(r.callee, "\t%s", r.callee.FullName())
}
func (r *calleesTypesResult) JSON(fset *token.FileSet) []byte {
j := &serial.Callees{
Pos: fset.Position(r.site.Pos()).String(),
Desc: "static function call",
}
j.Callees = []*serial.Callee{
{
Name: r.callee.FullName(),
Pos: fset.Position(r.callee.Pos()).String(),
},
}
return toJSON(j)
}
// NB: byFuncPos is not deterministic across packages since it depends on load order.
// Use lessPos if the tests need it.
type byFuncPos []*ssa.Function
func (a byFuncPos) Len() int { return len(a) }
func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
golang-golang-x-tools-0.1.9+ds/cmd/guru/callers.go 0000664 0000000 0000000 00000012362 14177515506 0021762 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// The callers function reports the possible callers of the function
// immediately enclosing the specified source location.
//
func callers(q *Query) error {
lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
prog := ssautil.CreateProgram(lprog, 0)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil {
return fmt.Errorf("no SSA package")
}
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
return fmt.Errorf("this position is not inside a function")
}
// Defer SSA construction till after errors are reported.
prog.Build()
target := ssa.EnclosingFunction(pkg, qpos.path)
if target == nil {
return fmt.Errorf("no SSA function built for this location (dead code?)")
}
// If the function is never address-taken, all calls are direct
// and can be found quickly by inspecting the whole SSA program.
cg := directCallsTo(target, entryPoints(ptaConfig.Mains))
if cg == nil {
// Run the pointer analysis, recording each
// call found to originate from target.
// (Pointer analysis may return fewer results than
// directCallsTo because it ignores dead code.)
ptaConfig.BuildCallGraph = true
cg = ptrAnalysis(ptaConfig).CallGraph
}
cg.DeleteSyntheticNodes()
edges := cg.CreateNode(target).In
// TODO(adonovan): sort + dedup calls to ensure test determinism.
q.Output(lprog.Fset, &callersResult{
target: target,
callgraph: cg,
edges: edges,
})
return nil
}
// directCallsTo inspects the whole program and returns a callgraph
// containing edges for all direct calls to the target function.
// directCallsTo returns nil if the function is ever address-taken.
func directCallsTo(target *ssa.Function, entrypoints []*ssa.Function) *callgraph.Graph {
cg := callgraph.New(nil) // use nil as root *Function
targetNode := cg.CreateNode(target)
// Is the function a program entry point?
// If so, add edge from callgraph root.
for _, f := range entrypoints {
if f == target {
callgraph.AddEdge(cg.Root, nil, targetNode)
}
}
// Find receiver type (for methods).
var recvType types.Type
if recv := target.Signature.Recv(); recv != nil {
recvType = recv.Type()
}
// Find all direct calls to function,
// or a place where its address is taken.
var space [32]*ssa.Value // preallocate
for fn := range ssautil.AllFunctions(target.Prog) {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
// Is this a method (T).f of a concrete type T
// whose runtime type descriptor is address-taken?
// (To be fully sound, we would have to check that
// the type doesn't make it to reflection as a
// subelement of some other address-taken type.)
if recvType != nil {
if mi, ok := instr.(*ssa.MakeInterface); ok {
if types.Identical(mi.X.Type(), recvType) {
return nil // T is address-taken
}
if ptr, ok := mi.X.Type().(*types.Pointer); ok &&
types.Identical(ptr.Elem(), recvType) {
return nil // *T is address-taken
}
}
}
// Direct call to target?
rands := instr.Operands(space[:0])
if site, ok := instr.(ssa.CallInstruction); ok &&
site.Common().Value == target {
callgraph.AddEdge(cg.CreateNode(fn), site, targetNode)
rands = rands[1:] // skip .Value (rands[0])
}
// Address-taken?
for _, rand := range rands {
if rand != nil && *rand == target {
return nil
}
}
}
}
}
return cg
}
func entryPoints(mains []*ssa.Package) []*ssa.Function {
var entrypoints []*ssa.Function
for _, pkg := range mains {
entrypoints = append(entrypoints, pkg.Func("init"))
if main := pkg.Func("main"); main != nil && pkg.Pkg.Name() == "main" {
entrypoints = append(entrypoints, main)
}
}
return entrypoints
}
type callersResult struct {
target *ssa.Function
callgraph *callgraph.Graph
edges []*callgraph.Edge
}
func (r *callersResult) PrintPlain(printf printfFunc) {
root := r.callgraph.Root
if r.edges == nil {
printf(r.target, "%s is not reachable in this program.", r.target)
} else {
printf(r.target, "%s is called from these %d sites:", r.target, len(r.edges))
for _, edge := range r.edges {
if edge.Caller == root {
printf(r.target, "the root of the call graph")
} else {
printf(edge, "\t%s from %s", edge.Description(), edge.Caller.Func)
}
}
}
}
func (r *callersResult) JSON(fset *token.FileSet) []byte {
var callers []serial.Caller
for _, edge := range r.edges {
callers = append(callers, serial.Caller{
Caller: edge.Caller.Func.String(),
Pos: fset.Position(edge.Pos()).String(),
Desc: edge.Description(),
})
}
return toJSON(callers)
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/callstack.go 0000664 0000000 0000000 00000007330 14177515506 0022275 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/token"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// The callstack function displays an arbitrary path from a root of the callgraph
// to the function at the current position.
//
// The information may be misleading in a context-insensitive
// analysis. e.g. the call path X->Y->Z might be infeasible if Y never
// calls Z when it is called from X. TODO(adonovan): think about UI.
//
// TODO(adonovan): permit user to specify a starting point other than
// the analysis root.
//
func callstack(q *Query) error {
fset := token.NewFileSet()
lconf := loader.Config{Fset: fset, Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
prog := ssautil.CreateProgram(lprog, 0)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
pkg := prog.Package(qpos.info.Pkg)
if pkg == nil {
return fmt.Errorf("no SSA package")
}
if !ssa.HasEnclosingFunction(pkg, qpos.path) {
return fmt.Errorf("this position is not inside a function")
}
// Defer SSA construction till after errors are reported.
prog.Build()
target := ssa.EnclosingFunction(pkg, qpos.path)
if target == nil {
return fmt.Errorf("no SSA function built for this location (dead code?)")
}
var callpath []*callgraph.Edge
isEnd := func(n *callgraph.Node) bool { return n.Func == target }
// First, build a callgraph containing only static call edges,
// and search for an arbitrary path from a root to the target function.
// This is quick, and the user wants a static path if one exists.
cg := static.CallGraph(prog)
cg.DeleteSyntheticNodes()
for _, ep := range entryPoints(ptaConfig.Mains) {
callpath = callgraph.PathSearch(cg.CreateNode(ep), isEnd)
if callpath != nil {
break
}
}
// No fully static path found.
// Run the pointer analysis and build a complete call graph.
if callpath == nil {
ptaConfig.BuildCallGraph = true
cg := ptrAnalysis(ptaConfig).CallGraph
cg.DeleteSyntheticNodes()
callpath = callgraph.PathSearch(cg.Root, isEnd)
if callpath != nil {
callpath = callpath[1:] // remove synthetic edge from
}
}
q.Output(fset, &callstackResult{
qpos: qpos,
target: target,
callpath: callpath,
})
return nil
}
type callstackResult struct {
qpos *queryPos
target *ssa.Function
callpath []*callgraph.Edge
}
func (r *callstackResult) PrintPlain(printf printfFunc) {
if r.callpath != nil {
printf(r.qpos, "Found a call path from root to %s", r.target)
printf(r.target, "%s", r.target)
for i := len(r.callpath) - 1; i >= 0; i-- {
edge := r.callpath[i]
printf(edge, "%s from %s", edge.Description(), edge.Caller.Func)
}
} else {
printf(r.target, "%s is unreachable in this analysis scope", r.target)
}
}
func (r *callstackResult) JSON(fset *token.FileSet) []byte {
var callers []serial.Caller
for i := len(r.callpath) - 1; i >= 0; i-- { // (innermost first)
edge := r.callpath[i]
callers = append(callers, serial.Caller{
Pos: fset.Position(edge.Pos()).String(),
Caller: edge.Caller.Func.String(),
Desc: edge.Description(),
})
}
return toJSON(&serial.CallStack{
Pos: fset.Position(r.target.Pos()).String(),
Target: r.target.String(),
Callers: callers,
})
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/definition.go 0000664 0000000 0000000 00000012757 14177515506 0022475 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
pathpkg "path"
"path/filepath"
"strconv"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/loader"
)
// definition reports the location of the definition of an identifier.
func definition(q *Query) error {
// First try the simple resolution done by parser.
// It only works for intra-file references but it is very fast.
// (Extending this approach to all the files of the package,
// resolved using ast.NewPackage, was not worth the effort.)
{
qpos, err := fastQueryPos(q.Build, q.Pos)
if err != nil {
return err
}
id, _ := qpos.path[0].(*ast.Ident)
if id == nil {
return fmt.Errorf("no identifier here")
}
// Did the parser resolve it to a local object?
if obj := id.Obj; obj != nil && obj.Pos().IsValid() {
q.Output(qpos.fset, &definitionResult{
pos: obj.Pos(),
descr: fmt.Sprintf("%s %s", obj.Kind, obj.Name),
})
return nil // success
}
// Qualified identifier?
if pkg := packageForQualIdent(qpos.path, id); pkg != "" {
srcdir := filepath.Dir(qpos.fset.File(qpos.start).Name())
tok, pos, err := findPackageMember(q.Build, qpos.fset, srcdir, pkg, id.Name)
if err != nil {
return err
}
q.Output(qpos.fset, &definitionResult{
pos: pos,
descr: fmt.Sprintf("%s %s.%s", tok, pkg, id.Name),
})
return nil // success
}
// Fall back on the type checker.
}
// Run the type checker.
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
id, _ := qpos.path[0].(*ast.Ident)
if id == nil {
return fmt.Errorf("no identifier here")
}
// Look up the declaration of this identifier.
// If id is an anonymous field declaration,
// it is both a use of a type and a def of a field;
// prefer the use in that case.
obj := qpos.info.Uses[id]
if obj == nil {
obj = qpos.info.Defs[id]
if obj == nil {
// Happens for y in "switch y := x.(type)",
// and the package declaration,
// but I think that's all.
return fmt.Errorf("no object for identifier")
}
}
if !obj.Pos().IsValid() {
return fmt.Errorf("%s is built in", obj.Name())
}
q.Output(lprog.Fset, &definitionResult{
pos: obj.Pos(),
descr: qpos.objectString(obj),
})
return nil
}
// packageForQualIdent returns the package p if id is X in a qualified
// identifier p.X; it returns "" otherwise.
//
// Precondition: id is path[0], and the parser did not resolve id to a
// local object. For speed, packageForQualIdent assumes that p is a
// package iff it is the basename of an import path (and not, say, a
// package-level decl in another file or a predeclared identifier).
func packageForQualIdent(path []ast.Node, id *ast.Ident) string {
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == id && ast.IsExported(id.Name) {
if pkgid, ok := sel.X.(*ast.Ident); ok && pkgid.Obj == nil {
f := path[len(path)-1].(*ast.File)
for _, imp := range f.Imports {
path, _ := strconv.Unquote(imp.Path.Value)
if imp.Name != nil {
if imp.Name.Name == pkgid.Name {
return path // renaming import
}
} else if pathpkg.Base(path) == pkgid.Name {
return path // ordinary import
}
}
}
}
return ""
}
// findPackageMember returns the type and position of the declaration of
// pkg.member by loading and parsing the files of that package.
// srcdir is the directory in which the import appears.
func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, member string) (token.Token, token.Pos, error) {
bp, err := ctxt.Import(pkg, srcdir, 0)
if err != nil {
return 0, token.NoPos, err // no files for package
}
// TODO(adonovan): opt: parallelize.
for _, fname := range bp.GoFiles {
filename := filepath.Join(bp.Dir, fname)
// Parse the file, opening it the file via the build.Context
// so that we observe the effects of the -modified flag.
f, _ := buildutil.ParseFile(fset, ctxt, nil, ".", filename, parser.Mode(0))
if f == nil {
continue
}
// Find a package-level decl called 'member'.
for _, decl := range f.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec:
// const or var
for _, id := range spec.Names {
if id.Name == member {
return decl.Tok, id.Pos(), nil
}
}
case *ast.TypeSpec:
if spec.Name.Name == member {
return token.TYPE, spec.Name.Pos(), nil
}
}
}
case *ast.FuncDecl:
if decl.Recv == nil && decl.Name.Name == member {
return token.FUNC, decl.Name.Pos(), nil
}
}
}
}
return 0, token.NoPos, fmt.Errorf("couldn't find declaration of %s in %q", member, pkg)
}
type definitionResult struct {
pos token.Pos // (nonzero) location of definition
descr string // description of object it denotes
}
func (r *definitionResult) PrintPlain(printf printfFunc) {
printf(r.pos, "defined here as %s", r.descr)
}
func (r *definitionResult) JSON(fset *token.FileSet) []byte {
return toJSON(&serial.Definition{
Desc: r.descr,
ObjPos: fset.Position(r.pos).String(),
})
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/describe.go 0000664 0000000 0000000 00000061766 14177515506 0022131 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"os"
"strings"
"unicode/utf8"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types/typeutil"
)
// describe describes the syntax node denoted by the query position,
// including:
// - its syntactic category
// - the definition of its referent (for identifiers) [now redundant]
// - its type, fields, and methods (for an expression or type expression)
//
func describe(q *Query) error {
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
if err != nil {
return err
}
if false { // debugging
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
}
var qr QueryResult
path, action := findInterestingNode(qpos.info, qpos.path)
switch action {
case actionExpr:
qr, err = describeValue(qpos, path)
case actionType:
qr, err = describeType(qpos, path)
case actionPackage:
qr, err = describePackage(qpos, path)
case actionStmt:
qr, err = describeStmt(qpos, path)
case actionUnknown:
qr = &describeUnknownResult{path[0]}
default:
panic(action) // unreachable
}
if err != nil {
return err
}
q.Output(lprog.Fset, qr)
return nil
}
type describeUnknownResult struct {
node ast.Node
}
func (r *describeUnknownResult) PrintPlain(printf printfFunc) {
// Nothing much to say about misc syntax.
printf(r.node, "%s", astutil.NodeDescription(r.node))
}
func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte {
return toJSON(&serial.Describe{
Desc: astutil.NodeDescription(r.node),
Pos: fset.Position(r.node.Pos()).String(),
})
}
type action int
const (
actionUnknown action = iota // None of the below
actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
actionType // type Expr or Ident(types.TypeName).
actionStmt // Stmt or Ident(types.Label)
actionPackage // Ident(types.Package) or ImportSpec
)
// findInterestingNode classifies the syntax node denoted by path as one of:
// - an expression, part of an expression or a reference to a constant
// or variable;
// - a type, part of a type, or a reference to a named type;
// - a statement, part of a statement, or a label referring to a statement;
// - part of a package declaration or import spec.
// - none of the above.
// and returns the most "interesting" associated node, which may be
// the same node, an ancestor or a descendent.
//
func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
// TODO(adonovan): integrate with go/types/stdlib_test.go and
// apply this to every AST node we can find to make sure it
// doesn't crash.
// TODO(adonovan): audit for ParenExpr safety, esp. since we
// traverse up and down.
// TODO(adonovan): if the users selects the "." in
// "fmt.Fprintf()", they'll get an ambiguous selection error;
// we won't even reach here. Can we do better?
// TODO(adonovan): describing a field within 'type T struct {...}'
// describes the (anonymous) struct type and concludes "no methods".
// We should ascend to the enclosing type decl, if any.
for len(path) > 0 {
switch n := path[0].(type) {
case *ast.GenDecl:
if len(n.Specs) == 1 {
// Descend to sole {Import,Type,Value}Spec child.
path = append([]ast.Node{n.Specs[0]}, path...)
continue
}
return path, actionUnknown // uninteresting
case *ast.FuncDecl:
// Descend to function name.
path = append([]ast.Node{n.Name}, path...)
continue
case *ast.ImportSpec:
return path, actionPackage
case *ast.ValueSpec:
if len(n.Names) == 1 {
// Descend to sole Ident child.
path = append([]ast.Node{n.Names[0]}, path...)
continue
}
return path, actionUnknown // uninteresting
case *ast.TypeSpec:
// Descend to type name.
path = append([]ast.Node{n.Name}, path...)
continue
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
return path, actionUnknown // uninteresting
case ast.Stmt:
return path, actionStmt
case *ast.ArrayType,
*ast.StructType,
*ast.FuncType,
*ast.InterfaceType,
*ast.MapType,
*ast.ChanType:
return path, actionType
case *ast.Ellipsis:
// Continue to enclosing node.
// e.g. [...]T in ArrayType
// f(x...) in CallExpr
// f(x...T) in FuncType
case *ast.Field:
// TODO(adonovan): this needs more thought,
// since fields can be so many things.
if len(n.Names) == 1 {
// Descend to sole Ident child.
path = append([]ast.Node{n.Names[0]}, path...)
continue
}
// Zero names (e.g. anon field in struct)
// or multiple field or param names:
// continue to enclosing field list.
case *ast.FieldList:
// Continue to enclosing node:
// {Struct,Func,Interface}Type or FuncDecl.
case *ast.BasicLit:
if _, ok := path[1].(*ast.ImportSpec); ok {
return path[1:], actionPackage
}
return path, actionExpr
case *ast.SelectorExpr:
// TODO(adonovan): use Selections info directly.
if pkginfo.Uses[n.Sel] == nil {
// TODO(adonovan): is this reachable?
return path, actionUnknown
}
// Descend to .Sel child.
path = append([]ast.Node{n.Sel}, path...)
continue
case *ast.Ident:
switch pkginfo.ObjectOf(n).(type) {
case *types.PkgName:
return path, actionPackage
case *types.Const:
return path, actionExpr
case *types.Label:
return path, actionStmt
case *types.TypeName:
return path, actionType
case *types.Var:
// For x in 'struct {x T}', return struct type, for now.
if _, ok := path[1].(*ast.Field); ok {
_ = path[2].(*ast.FieldList) // assertion
if _, ok := path[3].(*ast.StructType); ok {
return path[3:], actionType
}
}
return path, actionExpr
case *types.Func:
return path, actionExpr
case *types.Builtin:
// For reference to built-in function, return enclosing call.
path = path[1:] // ascend to enclosing function call
continue
case *types.Nil:
return path, actionExpr
}
// No object.
switch path[1].(type) {
case *ast.SelectorExpr:
// Return enclosing selector expression.
return path[1:], actionExpr
case *ast.Field:
// TODO(adonovan): test this.
// e.g. all f in:
// struct { f, g int }
// interface { f() }
// func (f T) method(f, g int) (f, g bool)
//
// switch path[3].(type) {
// case *ast.FuncDecl:
// case *ast.StructType:
// case *ast.InterfaceType:
// }
//
// return path[1:], actionExpr
//
// Unclear what to do with these.
// Struct.Fields -- field
// Interface.Methods -- field
// FuncType.{Params.Results} -- actionExpr
// FuncDecl.Recv -- actionExpr
case *ast.File:
// 'package foo'
return path, actionPackage
case *ast.ImportSpec:
return path[1:], actionPackage
default:
// e.g. blank identifier
// or y in "switch y := x.(type)"
// or code in a _test.go file that's not part of the package.
return path, actionUnknown
}
case *ast.StarExpr:
if pkginfo.Types[n].IsType() {
return path, actionType
}
return path, actionExpr
case ast.Expr:
// All Expr but {BasicLit,Ident,StarExpr} are
// "true" expressions that evaluate to a value.
return path, actionExpr
}
// Ascend to parent.
path = path[1:]
}
return nil, actionUnknown // unreachable
}
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
var expr ast.Expr
var obj types.Object
switch n := path[0].(type) {
case *ast.ValueSpec:
// ambiguous ValueSpec containing multiple names
return nil, fmt.Errorf("multiple value specification")
case *ast.Ident:
obj = qpos.info.ObjectOf(n)
expr = n
case ast.Expr:
expr = n
default:
// TODO(adonovan): is this reachable?
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
}
typ := qpos.info.TypeOf(expr)
if typ == nil {
typ = types.Typ[types.Invalid]
}
constVal := qpos.info.Types[expr].Value
if c, ok := obj.(*types.Const); ok {
constVal = c.Val()
}
return &describeValueResult{
qpos: qpos,
expr: expr,
typ: typ,
names: appendNames(nil, typ),
constVal: constVal,
obj: obj,
methods: accessibleMethods(typ, qpos.info.Pkg),
fields: accessibleFields(typ, qpos.info.Pkg),
}, nil
}
// appendNames returns named types found within the Type by
// removing map, pointer, channel, slice, and array constructors.
// It does not descend into structs or interfaces.
func appendNames(names []*types.Named, typ types.Type) []*types.Named {
// elemType specifies type that has some element in it
// such as array, slice, chan, pointer
type elemType interface {
Elem() types.Type
}
switch t := typ.(type) {
case *types.Named:
names = append(names, t)
case *types.Map:
names = appendNames(names, t.Key())
names = appendNames(names, t.Elem())
case elemType:
names = appendNames(names, t.Elem())
}
return names
}
type describeValueResult struct {
qpos *queryPos
expr ast.Expr // query node
typ types.Type // type of expression
names []*types.Named // named types within typ
constVal constant.Value // value of expression, if constant
obj types.Object // var/func/const object, if expr was Ident
methods []*types.Selection
fields []describeField
}
func (r *describeValueResult) PrintPlain(printf printfFunc) {
var prefix, suffix string
if r.constVal != nil {
suffix = fmt.Sprintf(" of value %s", r.constVal)
}
switch obj := r.obj.(type) {
case *types.Func:
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
prefix = "interface method "
} else {
prefix = "method "
}
}
}
// Describe the expression.
if r.obj != nil {
if r.obj.Pos() == r.expr.Pos() {
// defining ident
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
} else {
// referring ident
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
if def := r.obj.Pos(); def != token.NoPos {
printf(def, "defined here")
}
}
} else {
desc := astutil.NodeDescription(r.expr)
if suffix != "" {
// constant expression
printf(r.expr, "%s%s", desc, suffix)
} else {
// non-constant expression
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
}
}
printMethods(printf, r.expr, r.methods)
printFields(printf, r.expr, r.fields)
printNamedTypes(printf, r.expr, r.names)
}
func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
var value, objpos string
if r.constVal != nil {
value = r.constVal.String()
}
if r.obj != nil {
objpos = fset.Position(r.obj.Pos()).String()
}
typesPos := make([]serial.Definition, len(r.names))
for i, t := range r.names {
typesPos[i] = serial.Definition{
ObjPos: fset.Position(t.Obj().Pos()).String(),
Desc: r.qpos.typeString(t),
}
}
return toJSON(&serial.Describe{
Desc: astutil.NodeDescription(r.expr),
Pos: fset.Position(r.expr.Pos()).String(),
Detail: "value",
Value: &serial.DescribeValue{
Type: r.qpos.typeString(r.typ),
TypesPos: typesPos,
Value: value,
ObjPos: objpos,
},
})
}
// ---- TYPE ------------------------------------------------------------
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
var description string
var typ types.Type
switch n := path[0].(type) {
case *ast.Ident:
obj := qpos.info.ObjectOf(n).(*types.TypeName)
typ = obj.Type()
if isAlias(obj) {
description = "alias of "
} else if obj.Pos() == n.Pos() {
description = "definition of " // (Named type)
} else if _, ok := typ.(*types.Basic); ok {
description = "reference to built-in "
} else {
description = "reference to " // (Named type)
}
case ast.Expr:
typ = qpos.info.TypeOf(n)
default:
// Unreachable?
return nil, fmt.Errorf("unexpected AST for type: %T", n)
}
description = description + "type " + qpos.typeString(typ)
// Show sizes for structs and named types (it's fairly obvious for others).
switch typ.(type) {
case *types.Named, *types.Struct:
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
description = fmt.Sprintf("%s (size %d, align %d)", description,
szs.Sizeof(typ), szs.Alignof(typ))
}
return &describeTypeResult{
qpos: qpos,
node: path[0],
description: description,
typ: typ,
methods: accessibleMethods(typ, qpos.info.Pkg),
fields: accessibleFields(typ, qpos.info.Pkg),
}, nil
}
type describeTypeResult struct {
qpos *queryPos
node ast.Node
description string
typ types.Type
methods []*types.Selection
fields []describeField
}
type describeField struct {
implicits []*types.Named
field *types.Var
}
func printMethods(printf printfFunc, node ast.Node, methods []*types.Selection) {
if len(methods) > 0 {
printf(node, "Methods:")
}
for _, meth := range methods {
// Print the method type relative to the package
// in which it was defined, not the query package,
printf(meth.Obj(), "\t%s",
types.SelectionString(meth, types.RelativeTo(meth.Obj().Pkg())))
}
}
func printFields(printf printfFunc, node ast.Node, fields []describeField) {
if len(fields) > 0 {
printf(node, "Fields:")
}
// Align the names and the types (requires two passes).
var width int
var names []string
for _, f := range fields {
var buf bytes.Buffer
for _, fld := range f.implicits {
buf.WriteString(fld.Obj().Name())
buf.WriteByte('.')
}
buf.WriteString(f.field.Name())
name := buf.String()
if n := utf8.RuneCountInString(name); n > width {
width = n
}
names = append(names, name)
}
for i, f := range fields {
// Print the field type relative to the package
// in which it was defined, not the query package,
printf(f.field, "\t%*s %s", -width, names[i],
types.TypeString(f.field.Type(), types.RelativeTo(f.field.Pkg())))
}
}
func printNamedTypes(printf printfFunc, node ast.Node, names []*types.Named) {
if len(names) > 0 {
printf(node, "Named types:")
}
for _, t := range names {
// Print the type relative to the package
// in which it was defined, not the query package,
printf(t.Obj(), "\ttype %s defined here",
types.TypeString(t.Obj().Type(), types.RelativeTo(t.Obj().Pkg())))
}
}
func (r *describeTypeResult) PrintPlain(printf printfFunc) {
printf(r.node, "%s", r.description)
// Show the underlying type for a reference to a named type.
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
// TODO(adonovan): improve display of complex struct/interface types.
printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
}
printMethods(printf, r.node, r.methods)
if len(r.methods) == 0 {
// Only report null result for type kinds
// capable of bearing methods.
switch r.typ.(type) {
case *types.Interface, *types.Struct, *types.Named:
printf(r.node, "No methods.")
}
}
printFields(printf, r.node, r.fields)
}
func (r *describeTypeResult) JSON(fset *token.FileSet) []byte {
var namePos, nameDef string
if nt, ok := r.typ.(*types.Named); ok {
namePos = fset.Position(nt.Obj().Pos()).String()
nameDef = nt.Underlying().String()
}
return toJSON(&serial.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "type",
Type: &serial.DescribeType{
Type: r.qpos.typeString(r.typ),
NamePos: namePos,
NameDef: nameDef,
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
},
})
}
// ---- PACKAGE ------------------------------------------------------------
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
var description string
var pkg *types.Package
switch n := path[0].(type) {
case *ast.ImportSpec:
var obj types.Object
if n.Name != nil {
obj = qpos.info.Defs[n.Name]
} else {
obj = qpos.info.Implicits[n]
}
pkgname, _ := obj.(*types.PkgName)
if pkgname == nil {
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
}
pkg = pkgname.Imported()
description = fmt.Sprintf("import of package %q", pkg.Path())
case *ast.Ident:
if _, isDef := path[1].(*ast.File); isDef {
// e.g. package id
pkg = qpos.info.Pkg
description = fmt.Sprintf("definition of package %q", pkg.Path())
} else {
// e.g. import id "..."
// or id.F()
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
description = fmt.Sprintf("reference to package %q", pkg.Path())
}
default:
// Unreachable?
return nil, fmt.Errorf("unexpected AST for package: %T", n)
}
var members []*describeMember
// NB: "unsafe" has no types.Package
if pkg != nil {
// Enumerate the accessible package members
// in lexicographic order.
for _, name := range pkg.Scope().Names() {
if pkg == qpos.info.Pkg || ast.IsExported(name) {
mem := pkg.Scope().Lookup(name)
var methods []*types.Selection
if mem, ok := mem.(*types.TypeName); ok {
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
}
members = append(members, &describeMember{
mem,
methods,
})
}
}
}
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
}
type describePackageResult struct {
fset *token.FileSet
node ast.Node
description string
pkg *types.Package
members []*describeMember // in lexicographic name order
}
type describeMember struct {
obj types.Object
methods []*types.Selection // in types.MethodSet order
}
func (r *describePackageResult) PrintPlain(printf printfFunc) {
printf(r.node, "%s", r.description)
// Compute max width of name "column".
maxname := 0
for _, mem := range r.members {
if l := len(mem.obj.Name()); l > maxname {
maxname = l
}
}
for _, mem := range r.members {
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
for _, meth := range mem.methods {
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
}
}
}
func formatMember(obj types.Object, maxname int) string {
qualifier := types.RelativeTo(obj.Pkg())
var buf bytes.Buffer
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
switch obj := obj.(type) {
case *types.Const:
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val())
case *types.Func:
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
case *types.TypeName:
typ := obj.Type()
if isAlias(obj) {
buf.WriteString(" = ")
} else {
buf.WriteByte(' ')
typ = typ.Underlying()
}
var typestr string
// Abbreviate long aggregate type names.
switch typ := typ.(type) {
case *types.Interface:
if typ.NumMethods() > 1 {
typestr = "interface{...}"
}
case *types.Struct:
if typ.NumFields() > 1 {
typestr = "struct{...}"
}
}
if typestr == "" {
// The fix for #44515 changed the printing of unsafe.Pointer
// such that it uses a qualifier if one is provided. Using
// the types.RelativeTo qualifier provided here, the output
// is just "Pointer" rather than "unsafe.Pointer". This is
// consistent with the printing of non-type objects but it
// breaks an existing test which needs to work with older
// versions of Go. Re-establish the original output by not
// using a qualifier at all if we're printing a type from
// package unsafe - there's only unsafe.Pointer (#44596).
// NOTE: This correction can be removed (and the test's
// golden file adjusted) once we only run against go1.17
// or bigger.
qualifier := qualifier
if obj.Pkg() == types.Unsafe {
qualifier = nil
}
typestr = types.TypeString(typ, qualifier)
}
buf.WriteString(typestr)
case *types.Var:
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
}
return buf.String()
}
func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
var members []*serial.DescribeMember
for _, mem := range r.members {
obj := mem.obj
typ := obj.Type()
var val string
var alias string
switch obj := obj.(type) {
case *types.Const:
val = obj.Val().String()
case *types.TypeName:
if isAlias(obj) {
alias = "= " // kludgy
} else {
typ = typ.Underlying()
}
}
members = append(members, &serial.DescribeMember{
Name: obj.Name(),
Type: alias + typ.String(),
Value: val,
Pos: fset.Position(obj.Pos()).String(),
Kind: tokenOf(obj),
Methods: methodsToSerial(r.pkg, mem.methods, fset),
})
}
return toJSON(&serial.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "package",
Package: &serial.DescribePackage{
Path: r.pkg.Path(),
Members: members,
},
})
}
func tokenOf(o types.Object) string {
switch o.(type) {
case *types.Func:
return "func"
case *types.Var:
return "var"
case *types.TypeName:
return "type"
case *types.Const:
return "const"
case *types.PkgName:
return "package"
case *types.Builtin:
return "builtin" // e.g. when describing package "unsafe"
case *types.Nil:
return "nil"
case *types.Label:
return "label"
}
panic(o)
}
// ---- STATEMENT ------------------------------------------------------------
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
var description string
switch n := path[0].(type) {
case *ast.Ident:
if qpos.info.Defs[n] != nil {
description = "labelled statement"
} else {
description = "reference to labelled statement"
}
default:
// Nothing much to say about statements.
description = astutil.NodeDescription(n)
}
return &describeStmtResult{qpos.fset, path[0], description}, nil
}
type describeStmtResult struct {
fset *token.FileSet
node ast.Node
description string
}
func (r *describeStmtResult) PrintPlain(printf printfFunc) {
printf(r.node, "%s", r.description)
}
func (r *describeStmtResult) JSON(fset *token.FileSet) []byte {
return toJSON(&serial.Describe{
Desc: r.description,
Pos: fset.Position(r.node.Pos()).String(),
Detail: "unknown",
})
}
// ------------------- Utilities -------------------
// pathToString returns a string containing the concrete types of the
// nodes in path.
func pathToString(path []ast.Node) string {
var buf bytes.Buffer
fmt.Fprint(&buf, "[")
for i, n := range path {
if i > 0 {
fmt.Fprint(&buf, " ")
}
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
}
fmt.Fprint(&buf, "]")
return buf.String()
}
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
var methods []*types.Selection
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
if isAccessibleFrom(meth.Obj(), from) {
methods = append(methods, meth)
}
}
return methods
}
// accessibleFields returns the set of accessible
// field selections on a value of type recv.
func accessibleFields(recv types.Type, from *types.Package) []describeField {
wantField := func(f *types.Var) bool {
if !isAccessibleFrom(f, from) {
return false
}
// Check that the field is not shadowed.
obj, _, _ := types.LookupFieldOrMethod(recv, true, f.Pkg(), f.Name())
return obj == f
}
var fields []describeField
var visit func(t types.Type, stack []*types.Named)
visit = func(t types.Type, stack []*types.Named) {
tStruct, ok := deref(t).Underlying().(*types.Struct)
if !ok {
return
}
fieldloop:
for i := 0; i < tStruct.NumFields(); i++ {
f := tStruct.Field(i)
// Handle recursion through anonymous fields.
if f.Anonymous() {
tf := f.Type()
if ptr, ok := tf.(*types.Pointer); ok {
tf = ptr.Elem()
}
if named, ok := tf.(*types.Named); ok { // (be defensive)
// If we've already visited this named type
// on this path, break the cycle.
for _, x := range stack {
if x == named {
continue fieldloop
}
}
visit(f.Type(), append(stack, named))
}
}
// Save accessible fields.
if wantField(f) {
fields = append(fields, describeField{
implicits: append([]*types.Named(nil), stack...),
field: f,
})
}
}
}
visit(recv, nil)
return fields
}
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
}
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
qualifier := types.RelativeTo(this)
var jmethods []serial.DescribeMethod
for _, meth := range methods {
var ser serial.DescribeMethod
if meth != nil { // may contain nils when called by implements (on a method)
ser = serial.DescribeMethod{
Name: types.SelectionString(meth, qualifier),
Pos: fset.Position(meth.Obj().Pos()).String(),
}
}
jmethods = append(jmethods, ser)
}
return jmethods
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/freevars.go 0000664 0000000 0000000 00000012300 14177515506 0022142 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"bytes"
"go/ast"
"go/printer"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/loader"
)
// freevars displays the lexical (not package-level) free variables of
// the selection.
//
// It treats A.B.C as a separate variable from A to reveal the parts
// of an aggregate type that are actually needed.
// This aids refactoring.
//
// TODO(adonovan): optionally display the free references to
// file/package scope objects, and to objects from other packages.
// Depending on where the resulting function abstraction will go,
// these might be interesting. Perhaps group the results into three
// bands.
//
func freevars(q *Query) error {
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
file := qpos.path[len(qpos.path)-1] // the enclosing file
fileScope := qpos.info.Scopes[file]
pkgScope := fileScope.Parent()
// The id and sel functions return non-nil if they denote an
// object o or selection o.x.y that is referenced by the
// selection but defined neither within the selection nor at
// file scope, i.e. it is in the lexical environment.
var id func(n *ast.Ident) types.Object
var sel func(n *ast.SelectorExpr) types.Object
sel = func(n *ast.SelectorExpr) types.Object {
switch x := unparen(n.X).(type) {
case *ast.SelectorExpr:
return sel(x)
case *ast.Ident:
return id(x)
}
return nil
}
id = func(n *ast.Ident) types.Object {
obj := qpos.info.Uses[n]
if obj == nil {
return nil // not a reference
}
if _, ok := obj.(*types.PkgName); ok {
return nil // imported package
}
if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
return nil // not defined in this file
}
scope := obj.Parent()
if scope == nil {
return nil // e.g. interface method, struct field
}
if scope == fileScope || scope == pkgScope {
return nil // defined at file or package scope
}
if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end {
return nil // defined within selection => not free
}
return obj
}
// Maps each reference that is free in the selection
// to the object it refers to.
// The map de-duplicates repeated references.
refsMap := make(map[string]freevarsRef)
// Visit all the identifiers in the selected ASTs.
ast.Inspect(qpos.path[0], func(n ast.Node) bool {
if n == nil {
return true // popping DFS stack
}
// Is this node contained within the selection?
// (freevars permits inexact selections,
// like two stmts in a block.)
if qpos.start <= n.Pos() && n.End() <= qpos.end {
var obj types.Object
var prune bool
switch n := n.(type) {
case *ast.Ident:
obj = id(n)
case *ast.SelectorExpr:
obj = sel(n)
prune = true
}
if obj != nil {
var kind string
switch obj.(type) {
case *types.Var:
kind = "var"
case *types.Func:
kind = "func"
case *types.TypeName:
kind = "type"
case *types.Const:
kind = "const"
case *types.Label:
kind = "label"
default:
panic(obj)
}
typ := qpos.info.TypeOf(n.(ast.Expr))
ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj}
refsMap[ref.ref] = ref
if prune {
return false // don't descend
}
}
}
return true // descend
})
refs := make([]freevarsRef, 0, len(refsMap))
for _, ref := range refsMap {
refs = append(refs, ref)
}
sort.Sort(byRef(refs))
q.Output(lprog.Fset, &freevarsResult{
qpos: qpos,
refs: refs,
})
return nil
}
type freevarsResult struct {
qpos *queryPos
refs []freevarsRef
}
type freevarsRef struct {
kind string
ref string
typ types.Type
obj types.Object
}
func (r *freevarsResult) PrintPlain(printf printfFunc) {
if len(r.refs) == 0 {
printf(r.qpos, "No free identifiers.")
} else {
printf(r.qpos, "Free identifiers:")
qualifier := types.RelativeTo(r.qpos.info.Pkg)
for _, ref := range r.refs {
// Avoid printing "type T T".
var typstr string
if ref.kind != "type" && ref.kind != "label" {
typstr = " " + types.TypeString(ref.typ, qualifier)
}
printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr)
}
}
}
func (r *freevarsResult) JSON(fset *token.FileSet) []byte {
var buf bytes.Buffer
for i, ref := range r.refs {
if i > 0 {
buf.WriteByte('\n')
}
buf.Write(toJSON(serial.FreeVar{
Pos: fset.Position(ref.obj.Pos()).String(),
Kind: ref.kind,
Ref: ref.ref,
Type: ref.typ.String(),
}))
}
return buf.Bytes()
}
// -------- utils --------
type byRef []freevarsRef
func (p byRef) Len() int { return len(p) }
func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref }
func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// printNode returns the pretty-printed syntax of n.
func printNode(fset *token.FileSet, n ast.Node) string {
var buf bytes.Buffer
printer.Fprint(&buf, fset, n)
return buf.String()
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/guru.go 0000664 0000000 0000000 00000027417 14177515506 0021326 0 ustar 00root root 0000000 0000000 // Copyright 2014 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
// TODO(adonovan): new queries
// - show all statements that may update the selected lvalue
// (local, global, field, etc).
// - show all places where an object of type T is created
// (&T{}, var t T, new(T), new(struct{array [3]T}), etc.
import (
"encoding/json"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"io"
"log"
"path/filepath"
"strings"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
)
type printfFunc func(pos interface{}, format string, args ...interface{})
// A QueryResult is an item of output. Each query produces a stream of
// query results, calling Query.Output for each one.
type QueryResult interface {
// JSON returns the QueryResult in JSON form.
JSON(fset *token.FileSet) []byte
// PrintPlain prints the QueryResult in plain text form.
// The implementation calls printfFunc to print each line of output.
PrintPlain(printf printfFunc)
}
// A QueryPos represents the position provided as input to a query:
// a textual extent in the program's source code, the AST node it
// corresponds to, and the package to which it belongs.
// Instances are created by parseQueryPos.
type queryPos struct {
fset *token.FileSet
start, end token.Pos // source extent of query
path []ast.Node // AST path from query node to root of ast.File
exact bool // 2nd result of PathEnclosingInterval
info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos)
}
// TypeString prints type T relative to the query position.
func (qpos *queryPos) typeString(T types.Type) string {
return types.TypeString(T, types.RelativeTo(qpos.info.Pkg))
}
// ObjectString prints object obj relative to the query position.
func (qpos *queryPos) objectString(obj types.Object) string {
return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg))
}
// A Query specifies a single guru query.
type Query struct {
Pos string // query position
Build *build.Context // package loading configuration
// pointer analysis options
Scope []string // main packages in (*loader.Config).FromArgs syntax
PTALog io.Writer // (optional) pointer-analysis log file
Reflection bool // model reflection soundly (currently slow).
// result-printing function, safe for concurrent use
Output func(*token.FileSet, QueryResult)
}
// Run runs an guru query and populates its Fset and Result.
func Run(mode string, q *Query) error {
switch mode {
case "callees":
return callees(q)
case "callers":
return callers(q)
case "callstack":
return callstack(q)
case "peers":
return peers(q)
case "pointsto":
return pointsto(q)
case "whicherrs":
return whicherrs(q)
case "definition":
return definition(q)
case "describe":
return describe(q)
case "freevars":
return freevars(q)
case "implements":
return implements(q)
case "referrers":
return referrers(q)
case "what":
return what(q)
default:
return fmt.Errorf("invalid mode: %q", mode)
}
}
func setPTAScope(lconf *loader.Config, scope []string) error {
pkgs := buildutil.ExpandPatterns(lconf.Build, scope)
if len(pkgs) == 0 {
return fmt.Errorf("no packages specified for pointer analysis scope")
}
// The value of each entry in pkgs is true,
// giving ImportWithTests (not Import) semantics.
lconf.ImportPkgs = pkgs
return nil
}
// Create a pointer.Config whose scope is the initial packages of lprog
// and their dependencies.
func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) {
// For each initial package (specified on the command line),
// analyze the package if it has a main function.
var mains []*ssa.Package
for _, info := range lprog.InitialPackages() {
p := prog.Package(info.Pkg)
// Add package to the pointer analysis scope.
if p.Pkg.Name() == "main" && p.Func("main") != nil {
mains = append(mains, p)
}
}
if mains == nil {
return nil, fmt.Errorf("analysis scope has no main and no tests")
}
return &pointer.Config{
Log: ptaLog,
Reflection: reflection,
Mains: mains,
}, nil
}
// importQueryPackage finds the package P containing the
// query position and tells conf to import it.
// It returns the package's path.
func importQueryPackage(pos string, conf *loader.Config) (string, error) {
fqpos, err := fastQueryPos(conf.Build, pos)
if err != nil {
return "", err // bad query
}
filename := fqpos.fset.File(fqpos.start).Name()
_, importPath, err := guessImportPath(filename, conf.Build)
if err != nil {
// Can't find GOPATH dir.
// Treat the query file as its own package.
importPath = "command-line-arguments"
conf.CreateFromFilenames(importPath, filename)
} else {
// Check that it's possible to load the queried package.
// (e.g. guru tests contain different 'package' decls in same dir.)
// Keep consistent with logic in loader/util.go!
cfg2 := *conf.Build
cfg2.CgoEnabled = false
bp, err := cfg2.Import(importPath, "", 0)
if err != nil {
return "", err // no files for package
}
switch pkgContainsFile(bp, filename) {
case 'T':
conf.ImportWithTests(importPath)
case 'X':
conf.ImportWithTests(importPath)
importPath += "_test" // for TypeCheckFuncBodies
case 'G':
conf.Import(importPath)
default:
// This happens for ad-hoc packages like
// $GOROOT/src/net/http/triv.go.
return "", fmt.Errorf("package %q doesn't contain file %s",
importPath, filename)
}
}
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
return importPath, nil
}
// pkgContainsFile reports whether file was among the packages Go
// files, Test files, eXternal test files, or not found.
func pkgContainsFile(bp *build.Package, filename string) byte {
for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} {
for _, file := range files {
if sameFile(filepath.Join(bp.Dir, file), filename) {
return "GTX"[i]
}
}
}
return 0 // not found
}
// ParseQueryPos parses the source query position pos and returns the
// AST node of the loaded program lprog that it identifies.
// If needExact, it must identify a single AST subtree;
// this is appropriate for queries that allow fairly arbitrary syntax,
// e.g. "describe".
//
func parseQueryPos(lprog *loader.Program, pos string, needExact bool) (*queryPos, error) {
filename, startOffset, endOffset, err := parsePos(pos)
if err != nil {
return nil, err
}
// Find the named file among those in the loaded program.
var file *token.File
lprog.Fset.Iterate(func(f *token.File) bool {
if sameFile(filename, f.Name()) {
file = f
return false // done
}
return true // continue
})
if file == nil {
return nil, fmt.Errorf("file %s not found in loaded program", filename)
}
start, end, err := fileOffsetToPos(file, startOffset, endOffset)
if err != nil {
return nil, err
}
info, path, exact := lprog.PathEnclosingInterval(start, end)
if path == nil {
return nil, fmt.Errorf("no syntax here")
}
if needExact && !exact {
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
}
return &queryPos{lprog.Fset, start, end, path, exact, info}, nil
}
// ---------- Utilities ----------
// loadWithSoftErrors calls lconf.Load, suppressing "soft" errors. (See Go issue 16530.)
// TODO(adonovan): Once the loader has an option to allow soft errors,
// replace calls to loadWithSoftErrors with loader calls with that parameter.
func loadWithSoftErrors(lconf *loader.Config) (*loader.Program, error) {
lconf.AllowErrors = true
// Ideally we would just return conf.Load() here, but go/types
// reports certain "soft" errors that gc does not (Go issue 14596).
// As a workaround, we set AllowErrors=true and then duplicate
// the loader's error checking but allow soft errors.
// It would be nice if the loader API permitted "AllowErrors: soft".
prog, err := lconf.Load()
if err != nil {
return nil, err
}
var errpkgs []string
// Report hard errors in indirectly imported packages.
for _, info := range prog.AllPackages {
if containsHardErrors(info.Errors) {
errpkgs = append(errpkgs, info.Pkg.Path())
} else {
// Enable SSA construction for packages containing only soft errors.
info.TransitivelyErrorFree = true
}
}
if errpkgs != nil {
var more string
if len(errpkgs) > 3 {
more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
errpkgs = errpkgs[:3]
}
return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
strings.Join(errpkgs, ", "), more)
}
return prog, err
}
func containsHardErrors(errors []error) bool {
for _, err := range errors {
if err, ok := err.(types.Error); ok && err.Soft {
continue
}
return true
}
return false
}
// allowErrors causes type errors to be silently ignored.
// (Not suitable if SSA construction follows.)
func allowErrors(lconf *loader.Config) {
ctxt := *lconf.Build // copy
ctxt.CgoEnabled = false
lconf.Build = &ctxt
lconf.AllowErrors = true
// AllErrors makes the parser always return an AST instead of
// bailing out after 10 errors and returning an empty ast.File.
lconf.ParserMode = parser.AllErrors
lconf.TypeChecker.Error = func(err error) {}
}
// ptrAnalysis runs the pointer analysis and returns its result.
func ptrAnalysis(conf *pointer.Config) *pointer.Result {
result, err := pointer.Analyze(conf)
if err != nil {
panic(err) // pointer analysis internal error
}
return result
}
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
// deref returns a pointer's element type; otherwise it returns typ.
func deref(typ types.Type) types.Type {
if p, ok := typ.Underlying().(*types.Pointer); ok {
return p.Elem()
}
return typ
}
// fprintf prints to w a message of the form "location: message\n"
// where location is derived from pos.
//
// pos must be one of:
// - a token.Pos, denoting a position
// - an ast.Node, denoting an interval
// - anything with a Pos() method:
// ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc.
// - a QueryPos, denoting the extent of the user's query.
// - nil, meaning no position at all.
//
// The output format is is compatible with the 'gnu'
// compilation-error-regexp in Emacs' compilation mode.
//
func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) {
var start, end token.Pos
switch pos := pos.(type) {
case ast.Node:
start = pos.Pos()
end = pos.End()
case token.Pos:
start = pos
end = start
case *types.PkgName:
// The Pos of most PkgName objects does not coincide with an identifier,
// so we suppress the usual start+len(name) heuristic for types.Objects.
start = pos.Pos()
end = start
case types.Object:
start = pos.Pos()
end = start + token.Pos(len(pos.Name())) // heuristic
case interface {
Pos() token.Pos
}:
start = pos.Pos()
end = start
case *queryPos:
start = pos.start
end = pos.end
case nil:
// no-op
default:
panic(fmt.Sprintf("invalid pos: %T", pos))
}
if sp := fset.Position(start); start == end {
// (prints "-: " for token.NoPos)
fmt.Fprintf(w, "%s: ", sp)
} else {
ep := fset.Position(end)
// The -1 below is a concession to Emacs's broken use of
// inclusive (not half-open) intervals.
// Other editors may not want it.
// TODO(adonovan): add an -editor=vim|emacs|acme|auto
// flag; auto uses EMACS=t / VIM=... / etc env vars.
fmt.Fprintf(w, "%s:%d.%d-%d.%d: ",
sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1)
}
fmt.Fprintf(w, format, args...)
io.WriteString(w, "\n")
}
func toJSON(x interface{}) []byte {
b, err := json.MarshalIndent(x, "", "\t")
if err != nil {
log.Fatalf("JSON error: %v", err)
}
return b
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/guru_test.go 0000664 0000000 0000000 00000022023 14177515506 0022351 0 ustar 00root root 0000000 0000000 // 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.
package main_test
// This file defines a test framework for guru queries.
//
// The files beneath testdata/src contain Go programs containing
// query annotations of the form:
//
// @verb id "select"
//
// where verb is the query mode (e.g. "callers"), id is a unique name
// for this query, and "select" is a regular expression matching the
// substring of the current line that is the query's input selection.
//
// The expected output for each query is provided in the accompanying
// .golden file.
//
// (Location information is not included because it's too fragile to
// display as text. TODO(adonovan): think about how we can test its
// correctness, since it is critical information.)
//
// Run this test with:
// % go test golang.org/x/tools/cmd/guru -update
// to update the golden files.
import (
"bytes"
"flag"
"fmt"
"go/build"
"go/parser"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"testing"
guru "golang.org/x/tools/cmd/guru"
"golang.org/x/tools/internal/testenv"
)
func init() {
// This test currently requires GOPATH mode.
// Explicitly disabling module mode should suffix, but
// we'll also turn off GOPROXY just for good measure.
if err := os.Setenv("GO111MODULE", "off"); err != nil {
log.Fatal(err)
}
if err := os.Setenv("GOPROXY", "off"); err != nil {
log.Fatal(err)
}
}
var updateFlag = flag.Bool("update", false, "Update the golden files.")
type query struct {
id string // unique id
verb string // query mode, e.g. "callees"
posn token.Position // query position
filename string
queryPos string // query position in command-line syntax
}
func parseRegexp(text string) (*regexp.Regexp, error) {
pattern, err := strconv.Unquote(text)
if err != nil {
return nil, fmt.Errorf("can't unquote %s", text)
}
return regexp.Compile(pattern)
}
// parseQueries parses and returns the queries in the named file.
func parseQueries(t *testing.T, filename string) []*query {
filedata, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatal(err)
}
// Parse the file once to discover the test queries.
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, filedata, parser.ParseComments)
if err != nil {
t.Fatal(err)
}
lines := bytes.Split(filedata, []byte("\n"))
var queries []*query
queriesById := make(map[string]*query)
// Find all annotations of these forms:
expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp"
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if text == "" || text[0] != '@' {
continue
}
posn := fset.Position(c.Pos())
// @verb id "regexp"
match := expectRe.FindStringSubmatch(text)
if match == nil {
t.Errorf("%s: ill-formed query: %s", posn, text)
continue
}
id := match[2]
if prev, ok := queriesById[id]; ok {
t.Errorf("%s: duplicate id %s", posn, id)
t.Errorf("%s: previously used here", prev.posn)
continue
}
q := &query{
id: id,
verb: match[1],
filename: filename,
posn: posn,
}
if match[3] != `"nopos"` {
selectRe, err := parseRegexp(match[3])
if err != nil {
t.Errorf("%s: %s", posn, err)
continue
}
// Find text of the current line, sans query.
// (Queries must be // not /**/ comments.)
line := lines[posn.Line-1][:posn.Column-1]
// Apply regexp to current line to find input selection.
loc := selectRe.FindIndex(line)
if loc == nil {
t.Errorf("%s: selection pattern %s doesn't match line %q",
posn, match[3], string(line))
continue
}
// Assumes ASCII. TODO(adonovan): test on UTF-8.
linestart := posn.Offset - (posn.Column - 1)
// Compute the file offsets.
q.queryPos = fmt.Sprintf("%s:#%d,#%d",
filename, linestart+loc[0], linestart+loc[1])
}
queries = append(queries, q)
queriesById[id] = q
}
// Return the slice, not map, for deterministic iteration.
return queries
}
// doQuery poses query q to the guru and writes its response and
// error (if any) to out.
func doQuery(out io.Writer, q *query, json bool) {
fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)
var buildContext = build.Default
buildContext.GOPATH = "testdata"
pkg := filepath.Dir(strings.TrimPrefix(q.filename, "testdata/src/"))
gopathAbs, _ := filepath.Abs(buildContext.GOPATH)
var outputMu sync.Mutex // guards outputs
var outputs []string // JSON objects or lines of text
outputFn := func(fset *token.FileSet, qr guru.QueryResult) {
outputMu.Lock()
defer outputMu.Unlock()
if json {
jsonstr := string(qr.JSON(fset))
// Sanitize any absolute filenames that creep in.
jsonstr = strings.Replace(jsonstr, gopathAbs, "$GOPATH", -1)
outputs = append(outputs, jsonstr)
} else {
// suppress position information
qr.PrintPlain(func(_ interface{}, format string, args ...interface{}) {
outputs = append(outputs, fmt.Sprintf(format, args...))
})
}
}
query := guru.Query{
Pos: q.queryPos,
Build: &buildContext,
Scope: []string{pkg},
Reflection: true,
Output: outputFn,
}
if err := guru.Run(q.verb, &query); err != nil {
fmt.Fprintf(out, "\nError: %s\n", err)
return
}
// In a "referrers" query, references are sorted within each
// package but packages are visited in arbitrary order,
// so for determinism we sort them. Line 0 is a caption.
if q.verb == "referrers" {
sort.Strings(outputs[1:])
}
for _, output := range outputs {
// Replace occurrences of interface{} with any, for consistent output
// across go 1.18 and earlier.
output = strings.ReplaceAll(output, "interface{}", "any")
fmt.Fprintf(out, "%s\n", output)
}
if !json {
io.WriteString(out, "\n")
}
}
func TestGuru(t *testing.T) {
if testing.Short() {
// These tests are super slow.
// TODO: make a lighter version of the tests for short mode?
t.Skipf("skipping in short mode")
}
switch runtime.GOOS {
case "android":
t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS)
case "windows":
t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS)
}
for _, filename := range []string{
"testdata/src/alias/alias.go",
"testdata/src/calls/main.go",
"testdata/src/describe/main.go",
"testdata/src/freevars/main.go",
"testdata/src/implements/main.go",
"testdata/src/implements-methods/main.go",
"testdata/src/imports/main.go",
"testdata/src/peers/main.go",
"testdata/src/pointsto/main.go",
"testdata/src/referrers/main.go",
"testdata/src/reflection/main.go",
"testdata/src/what/main.go",
"testdata/src/whicherrs/main.go",
"testdata/src/softerrs/main.go",
// JSON:
// TODO(adonovan): most of these are very similar; combine them.
"testdata/src/calls-json/main.go",
"testdata/src/peers-json/main.go",
"testdata/src/definition-json/main.go",
"testdata/src/describe-json/main.go",
"testdata/src/implements-json/main.go",
"testdata/src/implements-methods-json/main.go",
"testdata/src/pointsto-json/main.go",
"testdata/src/referrers-json/main.go",
"testdata/src/what-json/main.go",
} {
filename := filename
name := strings.Split(filename, "/")[2]
t.Run(name, func(t *testing.T) {
t.Parallel()
if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
// Disable this test on plan9 since it expects a particular
// wording for a "no such file or directory" error.
t.Skip()
}
json := strings.Contains(filename, "-json/")
queries := parseQueries(t, filename)
golden := filename + "lden"
gotfh, err := ioutil.TempFile("", filepath.Base(filename)+"t")
if err != nil {
t.Fatal(err)
}
got := gotfh.Name()
defer func() {
gotfh.Close()
os.Remove(got)
}()
// Run the guru on each query, redirecting its output
// and error (if any) to the foo.got file.
for _, q := range queries {
doQuery(gotfh, q, json)
}
// Compare foo.got with foo.golden.
var cmd *exec.Cmd
switch runtime.GOOS {
case "plan9":
cmd = exec.Command("/bin/diff", "-c", golden, got)
default:
cmd = exec.Command("/usr/bin/diff", "-u", golden, got)
}
testenv.NeedsTool(t, cmd.Path)
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
t.Errorf("Guru tests for %s failed: %s.\n%s\n",
filename, err, buf)
if *updateFlag {
t.Logf("Updating %s...", golden)
if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
t.Errorf("Update failed: %s", err)
}
}
}
})
}
}
func TestIssue14684(t *testing.T) {
var buildContext = build.Default
buildContext.GOPATH = "testdata"
query := guru.Query{
Pos: "testdata/src/README.txt:#1",
Build: &buildContext,
}
err := guru.Run("freevars", &query)
if err == nil {
t.Fatal("guru query succeeded unexpectedly")
}
if got, want := err.Error(), "testdata/src/README.txt is not a Go source file"; got != want {
t.Errorf("query error was %q, want %q", got, want)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/implements.go 0000664 0000000 0000000 00000023706 14177515506 0022516 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
"sort"
"strings"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/refactor/importgraph"
)
// The implements function displays the "implements" relation as it pertains to the
// selected type.
// If the selection is a method, 'implements' displays
// the corresponding methods of the types that would have been reported
// by an implements query on the receiver type.
//
func implements(q *Query) error {
lconf := loader.Config{Build: q.Build}
allowErrors(&lconf)
qpkg, err := importQueryPackage(q.Pos, &lconf)
if err != nil {
return err
}
// Set the packages to search.
if len(q.Scope) > 0 {
// Inspect all packages in the analysis scope, if specified.
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
} else {
// Otherwise inspect the forward and reverse
// transitive closure of the selected package.
// (In theory even this is incomplete.)
_, rev, _ := importgraph.Build(q.Build)
for path := range rev.Search(qpkg) {
lconf.ImportWithTests(path)
}
// TODO(adonovan): for completeness, we should also
// type-check and inspect function bodies in all
// imported packages. This would be expensive, but we
// could optimize by skipping functions that do not
// contain type declarations. This would require
// changing the loader's TypeCheckFuncBodies hook to
// provide the []*ast.File.
}
// Load/parse/type-check the program.
lprog, err := lconf.Load()
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
// Find the selected type.
path, action := findInterestingNode(qpos.info, qpos.path)
var method *types.Func
var T types.Type // selected type (receiver if method != nil)
switch action {
case actionExpr:
// method?
if id, ok := path[0].(*ast.Ident); ok {
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
recv := obj.Type().(*types.Signature).Recv()
if recv == nil {
return fmt.Errorf("this function is not a method")
}
method = obj
T = recv.Type()
}
}
// If not a method, use the expression's type.
if T == nil {
T = qpos.info.TypeOf(path[0].(ast.Expr))
}
case actionType:
T = qpos.info.TypeOf(path[0].(ast.Expr))
}
if T == nil {
return fmt.Errorf("not a type, method, or value")
}
// Find all named types, even local types (which can have
// methods due to promotion) and the built-in "error".
// We ignore aliases 'type M = N' to avoid duplicate
// reporting of the Named type N.
var allNamed []*types.Named
for _, info := range lprog.AllPackages {
for _, obj := range info.Defs {
if obj, ok := obj.(*types.TypeName); ok && !isAlias(obj) {
if named, ok := obj.Type().(*types.Named); ok {
allNamed = append(allNamed, named)
}
}
}
}
allNamed = append(allNamed, types.Universe.Lookup("error").Type().(*types.Named))
var msets typeutil.MethodSetCache
// Test each named type.
var to, from, fromPtr []types.Type
for _, U := range allNamed {
if isInterface(T) {
if msets.MethodSet(T).Len() == 0 {
continue // empty interface
}
if isInterface(U) {
if msets.MethodSet(U).Len() == 0 {
continue // empty interface
}
// T interface, U interface
if !types.Identical(T, U) {
if types.AssignableTo(U, T) {
to = append(to, U)
}
if types.AssignableTo(T, U) {
from = append(from, U)
}
}
} else {
// T interface, U concrete
if types.AssignableTo(U, T) {
to = append(to, U)
} else if pU := types.NewPointer(U); types.AssignableTo(pU, T) {
to = append(to, pU)
}
}
} else if isInterface(U) {
if msets.MethodSet(U).Len() == 0 {
continue // empty interface
}
// T concrete, U interface
if types.AssignableTo(T, U) {
from = append(from, U)
} else if pT := types.NewPointer(T); types.AssignableTo(pT, U) {
fromPtr = append(fromPtr, U)
}
}
}
var pos interface{} = qpos
if nt, ok := deref(T).(*types.Named); ok {
pos = nt.Obj()
}
// Sort types (arbitrarily) to ensure test determinism.
sort.Sort(typesByString(to))
sort.Sort(typesByString(from))
sort.Sort(typesByString(fromPtr))
var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
if method != nil {
for _, t := range to {
toMethod = append(toMethod,
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
}
for _, t := range from {
fromMethod = append(fromMethod,
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
}
for _, t := range fromPtr {
fromPtrMethod = append(fromPtrMethod,
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
}
}
q.Output(lprog.Fset, &implementsResult{
qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod,
})
return nil
}
type implementsResult struct {
qpos *queryPos
t types.Type // queried type (not necessarily named)
pos interface{} // pos of t (*types.Name or *QueryPos)
to []types.Type // named or ptr-to-named types assignable to interface T
from []types.Type // named interfaces assignable from T
fromPtr []types.Type // named interfaces assignable only from *T
// if a method was queried:
method *types.Func // queried method
toMethod []*types.Selection // method of type to[i], if any
fromMethod []*types.Selection // method of type from[i], if any
fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
}
func (r *implementsResult) PrintPlain(printf printfFunc) {
relation := "is implemented by"
meth := func(sel *types.Selection) {
if sel != nil {
printf(sel.Obj(), "\t%s method (%s).%s",
relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name())
}
}
if isInterface(r.t) {
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t))
return
}
if r.method == nil {
printf(r.pos, "interface type %s", r.qpos.typeString(r.t))
} else {
printf(r.method, "abstract method %s", r.qpos.objectString(r.method))
}
// Show concrete types (or methods) first; use two passes.
for i, sub := range r.to {
if !isInterface(sub) {
if r.method == nil {
printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s",
relation, typeKind(sub), r.qpos.typeString(sub))
} else {
meth(r.toMethod[i])
}
}
}
for i, sub := range r.to {
if isInterface(sub) {
if r.method == nil {
printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
relation, typeKind(sub), r.qpos.typeString(sub))
} else {
meth(r.toMethod[i])
}
}
}
relation = "implements"
for i, super := range r.from {
if r.method == nil {
printf(super.(*types.Named).Obj(), "\t%s %s",
relation, r.qpos.typeString(super))
} else {
meth(r.fromMethod[i])
}
}
} else {
relation = "implements"
if r.from != nil {
if r.method == nil {
printf(r.pos, "%s type %s",
typeKind(r.t), r.qpos.typeString(r.t))
} else {
printf(r.method, "concrete method %s",
r.qpos.objectString(r.method))
}
for i, super := range r.from {
if r.method == nil {
printf(super.(*types.Named).Obj(), "\t%s %s",
relation, r.qpos.typeString(super))
} else {
meth(r.fromMethod[i])
}
}
}
if r.fromPtr != nil {
if r.method == nil {
printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t))
} else {
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
printf(r.method, "concrete method %s",
r.qpos.objectString(r.method))
}
for i, psuper := range r.fromPtr {
if r.method == nil {
printf(psuper.(*types.Named).Obj(), "\t%s %s",
relation, r.qpos.typeString(psuper))
} else {
meth(r.fromPtrMethod[i])
}
}
} else if r.from == nil {
printf(r.pos, "%s type %s implements only interface{}",
typeKind(r.t), r.qpos.typeString(r.t))
}
}
}
func (r *implementsResult) JSON(fset *token.FileSet) []byte {
var method *serial.DescribeMethod
if r.method != nil {
method = &serial.DescribeMethod{
Name: r.qpos.objectString(r.method),
Pos: fset.Position(r.method.Pos()).String(),
}
}
return toJSON(&serial.Implements{
T: makeImplementsType(r.t, fset),
AssignableTo: makeImplementsTypes(r.to, fset),
AssignableFrom: makeImplementsTypes(r.from, fset),
AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
Method: method,
})
}
func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType {
var r []serial.ImplementsType
for _, t := range tt {
r = append(r, makeImplementsType(t, fset))
}
return r
}
func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType {
var pos token.Pos
if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named
pos = nt.Obj().Pos()
}
return serial.ImplementsType{
Name: T.String(),
Pos: fset.Position(pos).String(),
Kind: typeKind(T),
}
}
// typeKind returns a string describing the underlying kind of type,
// e.g. "slice", "array", "struct".
func typeKind(T types.Type) string {
s := reflect.TypeOf(T.Underlying()).String()
return strings.ToLower(strings.TrimPrefix(s, "*types."))
}
func isInterface(T types.Type) bool { return types.IsInterface(T) }
type typesByString []types.Type
func (p typesByString) Len() int { return len(p) }
func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
golang-golang-x-tools-0.1.9+ds/cmd/guru/isAlias18.go 0000664 0000000 0000000 00000000541 14177515506 0022067 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build !go1.9
// +build !go1.9
package main
import "go/types"
func isAlias(obj *types.TypeName) bool {
return false // there are no type aliases before Go 1.9
}
const HasAlias = false
golang-golang-x-tools-0.1.9+ds/cmd/guru/isAlias19.go 0000664 0000000 0000000 00000000473 14177515506 0022074 0 ustar 00root root 0000000 0000000 // Copyright 2017 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.
//go:build go1.9
// +build go1.9
package main
import "go/types"
func isAlias(obj *types.TypeName) bool {
return obj.IsAlias()
}
const HasAlias = true
golang-golang-x-tools-0.1.9+ds/cmd/guru/main.go 0000664 0000000 0000000 00000014441 14177515506 0021261 0 ustar 00root root 0000000 0000000 // 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.
// guru: a tool for answering questions about Go source code.
//
// http://golang.org/s/using-guru
//
// Run with -help flag or help subcommand for usage information.
//
package main // import "golang.org/x/tools/cmd/guru"
import (
"bufio"
"flag"
"fmt"
"go/build"
"go/token"
"io"
"log"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"sync"
"golang.org/x/tools/go/buildutil"
)
// flags
var (
modifiedFlag = flag.Bool("modified", false, "read archive of modified files from standard input")
scopeFlag = flag.String("scope", "", "comma-separated list of `packages` the analysis should be limited to")
ptalogFlag = flag.String("ptalog", "", "write points-to analysis log to `file`")
jsonFlag = flag.Bool("json", false, "emit output in JSON format")
reflectFlag = flag.Bool("reflect", false, "analyze reflection soundly (slow)")
cpuprofileFlag = flag.String("cpuprofile", "", "write CPU profile to `file`")
)
func init() {
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
// gccgo does not provide a GOROOT with standard library sources.
// If we have one in the environment, force gc mode.
if build.Default.Compiler == "gccgo" {
if _, err := os.Stat(filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime.go")); err == nil {
build.Default.Compiler = "gc"
}
}
}
const useHelp = "Run 'guru -help' for more information.\n"
const helpMessage = `Go source code guru.
Usage: guru [flags]
The mode argument determines the query to perform:
callees show possible targets of selected function call
callers show possible callers of selected function
callstack show path from callgraph root to selected function
definition show declaration of selected identifier
describe describe selected syntax: definition, methods, etc
freevars show free variables of selection
implements show 'implements' relation for selected type or method
peers show send/receive corresponding to selected channel op
pointsto show variables the selected pointer may point to
referrers show all refs to entity denoted by selected identifier
what show basic information about the selected syntax node
whicherrs show possible values of the selected error variable
The position argument specifies the filename and byte offset (or range)
of the syntax element to query. For example:
foo.go:#123,#128
bar.go:#123
The -json flag causes guru to emit output in JSON format;
golang.org/x/tools/cmd/guru/serial defines its schema.
Otherwise, the output is in an editor-friendly format in which
every line has the form "pos: text", where pos is "-" if unknown.
The -modified flag causes guru to read an archive from standard input.
Files in this archive will be used in preference to those in
the file system. In this way, a text editor may supply guru
with the contents of its unsaved buffers. Each archive entry
consists of the file name, a newline, the decimal file size,
another newline, and the contents of the file.
The -scope flag restricts analysis to the specified packages.
Its value is a comma-separated list of patterns of these forms:
golang.org/x/tools/cmd/guru # a single package
golang.org/x/tools/... # all packages beneath dir
... # the entire workspace.
A pattern preceded by '-' is negative, so the scope
encoding/...,-encoding/xml
matches all encoding packages except encoding/xml.
User manual: http://golang.org/s/using-guru
Example: describe syntax at offset 530 in this file (an import spec):
$ guru describe src/golang.org/x/tools/cmd/guru/main.go:#530
`
func printHelp() {
fmt.Fprint(os.Stderr, helpMessage)
fmt.Fprintln(os.Stderr, "\nFlags:")
flag.PrintDefaults()
}
func main() {
log.SetPrefix("guru: ")
log.SetFlags(0)
// Don't print full help unless -help was requested.
// Just gently remind users that it's there.
flag.Usage = func() { fmt.Fprint(os.Stderr, useHelp) }
flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) // hack
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
// (err has already been printed)
if err == flag.ErrHelp {
printHelp()
}
os.Exit(2)
}
args := flag.Args()
if len(args) != 2 {
flag.Usage()
os.Exit(2)
}
mode, posn := args[0], args[1]
if mode == "help" {
printHelp()
os.Exit(2)
}
// Set up points-to analysis log file.
var ptalog io.Writer
if *ptalogFlag != "" {
if f, err := os.Create(*ptalogFlag); err != nil {
log.Fatalf("Failed to create PTA log file: %s", err)
} else {
buf := bufio.NewWriter(f)
ptalog = buf
defer func() {
if err := buf.Flush(); err != nil {
log.Printf("flush: %s", err)
}
if err := f.Close(); err != nil {
log.Printf("close: %s", err)
}
}()
}
}
// Profiling support.
if *cpuprofileFlag != "" {
f, err := os.Create(*cpuprofileFlag)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
ctxt := &build.Default
// If there were modified files,
// read them from the standard input and
// overlay them on the build context.
if *modifiedFlag {
modified, err := buildutil.ParseOverlayArchive(os.Stdin)
if err != nil {
log.Fatal(err)
}
// All I/O done by guru needs to consult the modified map.
// The ReadFile done by referrers does,
// but the loader's cgo preprocessing currently does not.
if len(modified) > 0 {
ctxt = buildutil.OverlayContext(ctxt, modified)
}
}
var outputMu sync.Mutex
output := func(fset *token.FileSet, qr QueryResult) {
outputMu.Lock()
defer outputMu.Unlock()
if *jsonFlag {
// JSON output
fmt.Printf("%s\n", qr.JSON(fset))
} else {
// plain output
printf := func(pos interface{}, format string, args ...interface{}) {
fprintf(os.Stdout, fset, pos, format, args...)
}
qr.PrintPlain(printf)
}
}
// Avoid corner case of split("").
var scope []string
if *scopeFlag != "" {
scope = strings.Split(*scopeFlag, ",")
}
// Ask the guru.
query := Query{
Pos: posn,
Build: ctxt,
Scope: scope,
PTALog: ptalog,
Reflection: *reflectFlag,
Output: output,
}
if err := Run(mode, &query); err != nil {
log.Fatal(err)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/peers.go 0000664 0000000 0000000 00000016044 14177515506 0021454 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// peers enumerates, for a given channel send (or receive) operation,
// the set of possible receives (or sends) that correspond to it.
//
// TODO(adonovan): support reflect.{Select,Recv,Send,Close}.
// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv),
// or the implicit receive in "for v := range ch".
func peers(q *Query) error {
lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
opPos := findOp(qpos)
if opPos == token.NoPos {
return fmt.Errorf("there is no channel operation here")
}
// Defer SSA construction till after errors are reported.
prog.Build()
var queryOp chanOp // the originating send or receive operation
var ops []chanOp // all sends/receives of opposite direction
// Look at all channel operations in the whole ssa.Program.
// Build a list of those of same type as the query.
allFuncs := ssautil.AllFunctions(prog)
for fn := range allFuncs {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
for _, op := range chanOps(instr) {
ops = append(ops, op)
if op.pos == opPos {
queryOp = op // we found the query op
}
}
}
}
}
if queryOp.ch == nil {
return fmt.Errorf("ssa.Instruction for send/receive not found")
}
// Discard operations of wrong channel element type.
// Build set of channel ssa.Values as query to pointer analysis.
// We compare channels by element types, not channel types, to
// ignore both directionality and type names.
queryType := queryOp.ch.Type()
queryElemType := queryType.Underlying().(*types.Chan).Elem()
ptaConfig.AddQuery(queryOp.ch)
i := 0
for _, op := range ops {
if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) {
ptaConfig.AddQuery(op.ch)
ops[i] = op
i++
}
}
ops = ops[:i]
// Run the pointer analysis.
ptares := ptrAnalysis(ptaConfig)
// Find the points-to set.
queryChanPtr := ptares.Queries[queryOp.ch]
// Ascertain which make(chan) labels the query's channel can alias.
var makes []token.Pos
for _, label := range queryChanPtr.PointsTo().Labels() {
makes = append(makes, label.Pos())
}
sort.Sort(byPos(makes))
// Ascertain which channel operations can alias the same make(chan) labels.
var sends, receives, closes []token.Pos
for _, op := range ops {
if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) {
switch op.dir {
case types.SendOnly:
sends = append(sends, op.pos)
case types.RecvOnly:
receives = append(receives, op.pos)
case types.SendRecv:
closes = append(closes, op.pos)
}
}
}
sort.Sort(byPos(sends))
sort.Sort(byPos(receives))
sort.Sort(byPos(closes))
q.Output(lprog.Fset, &peersResult{
queryPos: opPos,
queryType: queryType,
makes: makes,
sends: sends,
receives: receives,
closes: closes,
})
return nil
}
// findOp returns the position of the enclosing send/receive/close op.
// For send and receive operations, this is the position of the <- token;
// for close operations, it's the Lparen of the function call.
//
// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements.
func findOp(qpos *queryPos) token.Pos {
for _, n := range qpos.path {
switch n := n.(type) {
case *ast.UnaryExpr:
if n.Op == token.ARROW {
return n.OpPos
}
case *ast.SendStmt:
return n.Arrow
case *ast.CallExpr:
// close function call can only exist as a direct identifier
if close, ok := unparen(n.Fun).(*ast.Ident); ok {
if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" {
return n.Lparen
}
}
}
}
return token.NoPos
}
// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState.
type chanOp struct {
ch ssa.Value
dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close
pos token.Pos
}
// chanOps returns a slice of all the channel operations in the instruction.
func chanOps(instr ssa.Instruction) []chanOp {
// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too.
var ops []chanOp
switch instr := instr.(type) {
case *ssa.UnOp:
if instr.Op == token.ARROW {
ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()})
}
case *ssa.Send:
ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()})
case *ssa.Select:
for _, st := range instr.States {
ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos})
}
case ssa.CallInstruction:
cc := instr.Common()
if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" {
ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()})
}
}
return ops
}
// TODO(adonovan): show the line of text for each pos, like "referrers" does.
type peersResult struct {
queryPos token.Pos // of queried channel op
queryType types.Type // type of queried channel
makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs
}
func (r *peersResult) PrintPlain(printf printfFunc) {
if len(r.makes) == 0 {
printf(r.queryPos, "This channel can't point to anything.")
return
}
printf(r.queryPos, "This channel of type %s may be:", r.queryType)
for _, alloc := range r.makes {
printf(alloc, "\tallocated here")
}
for _, send := range r.sends {
printf(send, "\tsent to, here")
}
for _, receive := range r.receives {
printf(receive, "\treceived from, here")
}
for _, clos := range r.closes {
printf(clos, "\tclosed, here")
}
}
func (r *peersResult) JSON(fset *token.FileSet) []byte {
peers := &serial.Peers{
Pos: fset.Position(r.queryPos).String(),
Type: r.queryType.String(),
}
for _, alloc := range r.makes {
peers.Allocs = append(peers.Allocs, fset.Position(alloc).String())
}
for _, send := range r.sends {
peers.Sends = append(peers.Sends, fset.Position(send).String())
}
for _, receive := range r.receives {
peers.Receives = append(peers.Receives, fset.Position(receive).String())
}
for _, clos := range r.closes {
peers.Closes = append(peers.Closes, fset.Position(clos).String())
}
return toJSON(peers)
}
// -------- utils --------
// NB: byPos is not deterministic across packages since it depends on load order.
// Use lessPos if the tests need it.
type byPos []token.Pos
func (p byPos) Len() int { return len(p) }
func (p byPos) Less(i, j int) bool { return p[i] < p[j] }
func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
golang-golang-x-tools-0.1.9+ds/cmd/guru/pointsto.go 0000664 0000000 0000000 00000020146 14177515506 0022213 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
// pointsto runs the pointer analysis on the selected expression,
// and reports its points-to set (for a pointer-like expression)
// or its dynamic types (for an interface, reflect.Value, or
// reflect.Type expression) and their points-to sets.
//
// All printed sets are sorted to ensure determinism.
//
func pointsto(q *Query) error {
lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil {
return err
}
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
path, action := findInterestingNode(qpos.info, qpos.path)
if action != actionExpr {
return fmt.Errorf("pointer analysis wants an expression; got %s",
astutil.NodeDescription(qpos.path[0]))
}
var expr ast.Expr
var obj types.Object
switch n := path[0].(type) {
case *ast.ValueSpec:
// ambiguous ValueSpec containing multiple names
return fmt.Errorf("multiple value specification")
case *ast.Ident:
obj = qpos.info.ObjectOf(n)
expr = n
case ast.Expr:
expr = n
default:
// TODO(adonovan): is this reachable?
return fmt.Errorf("unexpected AST for expr: %T", n)
}
// Reject non-pointerlike types (includes all constants---except nil).
// TODO(adonovan): reject nil too.
typ := qpos.info.TypeOf(expr)
if !pointer.CanPoint(typ) {
return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
}
// Determine the ssa.Value for the expression.
var value ssa.Value
var isAddr bool
if obj != nil {
// def/ref of func/var object
value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path)
} else {
value, isAddr, err = ssaValueForExpr(prog, qpos.info, path)
}
if err != nil {
return err // e.g. trivially dead code
}
// Defer SSA construction till after errors are reported.
prog.Build()
// Run the pointer analysis.
ptrs, err := runPTA(ptaConfig, value, isAddr)
if err != nil {
return err // e.g. analytically unreachable
}
q.Output(lprog.Fset, &pointstoResult{
qpos: qpos,
typ: typ,
ptrs: ptrs,
})
return nil
}
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
// to the root of the AST is path. isAddr reports whether the
// ssa.Value is the address denoted by the ast.Ident, not its value.
//
func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
switch obj := obj.(type) {
case *types.Var:
pkg := prog.Package(qinfo.Pkg)
pkg.Build()
if v, addr := prog.VarValue(obj, pkg, path); v != nil {
return v, addr, nil
}
return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
case *types.Func:
fn := prog.FuncValue(obj)
if fn == nil {
return nil, false, fmt.Errorf("%s is an interface method", obj)
}
// TODO(adonovan): there's no point running PTA on a *Func ident.
// Eliminate this feature.
return fn, false, nil
}
panic(obj)
}
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
// expression whose path to the root of the AST is path.
//
func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
pkg := prog.Package(qinfo.Pkg)
pkg.SetDebugMode(true)
pkg.Build()
fn := ssa.EnclosingFunction(pkg, path)
if fn == nil {
return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)")
}
if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
return v, addr, nil
}
return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
}
// runPTA runs the pointer analysis of the selected SSA value or address.
func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
T := v.Type()
if isAddr {
conf.AddIndirectQuery(v)
T = deref(T)
} else {
conf.AddQuery(v)
}
ptares := ptrAnalysis(conf)
var ptr pointer.Pointer
if isAddr {
ptr = ptares.IndirectQueries[v]
} else {
ptr = ptares.Queries[v]
}
if ptr == (pointer.Pointer{}) {
return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)")
}
pts := ptr.PointsTo()
if pointer.CanHaveDynamicTypes(T) {
// Show concrete types for interface/reflect.Value expression.
if concs := pts.DynamicTypes(); concs.Len() > 0 {
concs.Iterate(func(conc types.Type, pta interface{}) {
labels := pta.(pointer.PointsToSet).Labels()
sort.Sort(byPosAndString(labels)) // to ensure determinism
ptrs = append(ptrs, pointerResult{conc, labels})
})
}
} else {
// Show labels for other expressions.
labels := pts.Labels()
sort.Sort(byPosAndString(labels)) // to ensure determinism
ptrs = append(ptrs, pointerResult{T, labels})
}
sort.Sort(byTypeString(ptrs)) // to ensure determinism
return ptrs, nil
}
type pointerResult struct {
typ types.Type // type of the pointer (always concrete)
labels []*pointer.Label // set of labels
}
type pointstoResult struct {
qpos *queryPos
typ types.Type // type of expression
ptrs []pointerResult // pointer info (typ is concrete => len==1)
}
func (r *pointstoResult) PrintPlain(printf printfFunc) {
if pointer.CanHaveDynamicTypes(r.typ) {
// Show concrete types for interface, reflect.Type or
// reflect.Value expression.
if len(r.ptrs) > 0 {
printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ))
for _, ptr := range r.ptrs {
var obj types.Object
if nt, ok := deref(ptr.typ).(*types.Named); ok {
obj = nt.Obj()
}
if len(ptr.labels) > 0 {
printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ))
printLabels(printf, ptr.labels, "\t\t")
} else {
printf(obj, "\t%s", r.qpos.typeString(ptr.typ))
}
}
} else {
printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ)
}
} else {
// Show labels for other expressions.
if ptr := r.ptrs[0]; len(ptr.labels) > 0 {
printf(r.qpos, "this %s may point to these objects:",
r.qpos.typeString(r.typ))
printLabels(printf, ptr.labels, "\t")
} else {
printf(r.qpos, "this %s may not point to anything.",
r.qpos.typeString(r.typ))
}
}
}
func (r *pointstoResult) JSON(fset *token.FileSet) []byte {
var pts []serial.PointsTo
for _, ptr := range r.ptrs {
var namePos string
if nt, ok := deref(ptr.typ).(*types.Named); ok {
namePos = fset.Position(nt.Obj().Pos()).String()
}
var labels []serial.PointsToLabel
for _, l := range ptr.labels {
labels = append(labels, serial.PointsToLabel{
Pos: fset.Position(l.Pos()).String(),
Desc: l.String(),
})
}
pts = append(pts, serial.PointsTo{
Type: r.qpos.typeString(ptr.typ),
NamePos: namePos,
Labels: labels,
})
}
return toJSON(pts)
}
type byTypeString []pointerResult
func (a byTypeString) Len() int { return len(a) }
func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() }
func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type byPosAndString []*pointer.Label
func (a byPosAndString) Len() int { return len(a) }
func (a byPosAndString) Less(i, j int) bool {
cmp := a[i].Pos() - a[j].Pos()
return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String())
}
func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) {
// TODO(adonovan): due to context-sensitivity, many of these
// labels may differ only by context, which isn't apparent.
for _, label := range labels {
printf(label, "%s%s", prefix, label)
}
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/pos.go 0000664 0000000 0000000 00000007406 14177515506 0021141 0 ustar 00root root 0000000 0000000 // 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.
package main
// This file defines utilities for working with file positions.
import (
"fmt"
"go/build"
"go/parser"
"go/token"
"os"
"path/filepath"
"strconv"
"strings"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/buildutil"
)
// parseOctothorpDecimal returns the numeric value if s matches "#%d",
// otherwise -1.
func parseOctothorpDecimal(s string) int {
if s != "" && s[0] == '#' {
if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
return int(s)
}
}
return -1
}
// parsePos parses a string of the form "file:pos" or
// file:start,end" where pos, start, end match #%d and represent byte
// offsets, and returns its components.
//
// (Numbers without a '#' prefix are reserved for future use,
// e.g. to indicate line/column positions.)
//
func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
if pos == "" {
err = fmt.Errorf("no source position specified")
return
}
colon := strings.LastIndex(pos, ":")
if colon < 0 {
err = fmt.Errorf("bad position syntax %q", pos)
return
}
filename, offset := pos[:colon], pos[colon+1:]
startOffset = -1
endOffset = -1
if comma := strings.Index(offset, ","); comma < 0 {
// e.g. "foo.go:#123"
startOffset = parseOctothorpDecimal(offset)
endOffset = startOffset
} else {
// e.g. "foo.go:#123,#456"
startOffset = parseOctothorpDecimal(offset[:comma])
endOffset = parseOctothorpDecimal(offset[comma+1:])
}
if startOffset < 0 || endOffset < 0 {
err = fmt.Errorf("invalid offset %q in query position", offset)
return
}
return
}
// fileOffsetToPos translates the specified file-relative byte offsets
// into token.Pos form. It returns an error if the file was not found
// or the offsets were out of bounds.
//
func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
// Range check [start..end], inclusive of both end-points.
if 0 <= startOffset && startOffset <= file.Size() {
start = file.Pos(int(startOffset))
} else {
err = fmt.Errorf("start position is beyond end of file")
return
}
if 0 <= endOffset && endOffset <= file.Size() {
end = file.Pos(int(endOffset))
} else {
err = fmt.Errorf("end position is beyond end of file")
return
}
return
}
// sameFile returns true if x and y have the same basename and denote
// the same file.
//
func sameFile(x, y string) bool {
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
if xi, err := os.Stat(x); err == nil {
if yi, err := os.Stat(y); err == nil {
return os.SameFile(xi, yi)
}
}
}
return false
}
// fastQueryPos parses the position string and returns a queryPos.
// It parses only a single file and does not run the type checker.
func fastQueryPos(ctxt *build.Context, pos string) (*queryPos, error) {
filename, startOffset, endOffset, err := parsePos(pos)
if err != nil {
return nil, err
}
// Parse the file, opening it the file via the build.Context
// so that we observe the effects of the -modified flag.
fset := token.NewFileSet()
cwd, _ := os.Getwd()
f, err := buildutil.ParseFile(fset, ctxt, nil, cwd, filename, parser.Mode(0))
// ParseFile usually returns a partial file along with an error.
// Only fail if there is no file.
if f == nil {
return nil, err
}
if !f.Pos().IsValid() {
return nil, fmt.Errorf("%s is not a Go source file", filename)
}
start, end, err := fileOffsetToPos(fset.File(f.Pos()), startOffset, endOffset)
if err != nil {
return nil, err
}
path, exact := astutil.PathEnclosingInterval(f, start, end)
if path == nil {
return nil, fmt.Errorf("no syntax here")
}
return &queryPos{fset, start, end, path, exact, nil}, nil
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/referrers.go 0000664 0000000 0000000 00000060560 14177515506 0022337 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"io"
"log"
"os"
"sort"
"strconv"
"strings"
"sync"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/imports"
"golang.org/x/tools/refactor/importgraph"
)
// The referrers function reports all identifiers that resolve to the same object
// as the queried identifier, within any package in the workspace.
func referrers(q *Query) error {
fset := token.NewFileSet()
lconf := loader.Config{Fset: fset, Build: q.Build}
allowErrors(&lconf)
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
return err
}
// Load tests of the query package
// even if the query location is not in the tests.
for path := range lconf.ImportPkgs {
lconf.ImportPkgs[path] = true
}
// Load/parse/type-check the query package.
lprog, err := lconf.Load()
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, false)
if err != nil {
return err
}
id, _ := qpos.path[0].(*ast.Ident)
if id == nil {
return fmt.Errorf("no identifier here")
}
obj := qpos.info.ObjectOf(id)
if obj == nil {
// Happens for y in "switch y := x.(type)",
// the package declaration,
// and unresolved identifiers.
if _, ok := qpos.path[1].(*ast.File); ok { // package decl?
return packageReferrers(q, qpos.info.Pkg.Path())
}
return fmt.Errorf("no object for identifier: %T", qpos.path[1])
}
// Imported package name?
if pkgname, ok := obj.(*types.PkgName); ok {
return packageReferrers(q, pkgname.Imported().Path())
}
if obj.Pkg() == nil {
return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name())
}
q.Output(fset, &referrersInitialResult{
qinfo: qpos.info,
obj: obj,
})
// For a globally accessible object defined in package P, we
// must load packages that depend on P. Specifically, for a
// package-level object, we need load only direct importers
// of P, but for a field or method, we must load
// any package that transitively imports P.
if global, pkglevel := classify(obj); global {
if pkglevel {
return globalReferrersPkgLevel(q, obj, fset)
}
// We'll use the object's position to identify it in the larger program.
objposn := fset.Position(obj.Pos())
defpkg := obj.Pkg().Path() // defining package
return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn)
}
outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg())
return nil // success
}
// classify classifies objects by how far
// we have to look to find references to them.
func classify(obj types.Object) (global, pkglevel bool) {
if obj.Exported() {
if obj.Parent() == nil {
// selectable object (field or method)
return true, false
}
if obj.Parent() == obj.Pkg().Scope() {
// lexical object (package-level var/const/func/type)
return true, true
}
}
// object with unexported named or defined in local scope
return false, false
}
// packageReferrers reports all references to the specified package
// throughout the workspace.
func packageReferrers(q *Query, path string) error {
// Scan the workspace and build the import graph.
// Ignore broken packages.
_, rev, _ := importgraph.Build(q.Build)
// Find the set of packages that directly import the query package.
// Only those packages need typechecking of function bodies.
users := rev[path]
// Load the larger program.
fset := token.NewFileSet()
lconf := loader.Config{
Fset: fset,
Build: q.Build,
TypeCheckFuncBodies: func(p string) bool {
return users[strings.TrimSuffix(p, "_test")]
},
}
allowErrors(&lconf)
// The importgraph doesn't treat external test packages
// as separate nodes, so we must use ImportWithTests.
for path := range users {
lconf.ImportWithTests(path)
}
// Subtle! AfterTypeCheck needs no mutex for qpkg because the
// topological import order gives us the necessary happens-before edges.
// TODO(adonovan): what about import cycles?
var qpkg *types.Package
// For efficiency, we scan each package for references
// just after it has been type-checked. The loader calls
// AfterTypeCheck (concurrently), providing us with a stream of
// packages.
lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) {
// AfterTypeCheck may be called twice for the same package due to augmentation.
if info.Pkg.Path() == path && qpkg == nil {
// Found the package of interest.
qpkg = info.Pkg
fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg)
q.Output(fset, &referrersInitialResult{
qinfo: info,
obj: fakepkgname, // bogus
})
}
// Only inspect packages that directly import the
// declaring package (and thus were type-checked).
if lconf.TypeCheckFuncBodies(info.Pkg.Path()) {
// Find PkgNames that refer to qpkg.
// TODO(adonovan): perhaps more useful would be to show imports
// of the package instead of qualified identifiers.
var refs []*ast.Ident
for id, obj := range info.Uses {
if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg {
refs = append(refs, id)
}
}
outputUses(q, fset, refs, info.Pkg)
}
clearInfoFields(info) // save memory
}
lconf.Load() // ignore error
if qpkg == nil {
log.Fatalf("query package %q not found during reloading", path)
}
return nil
}
func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident {
var refs []*ast.Ident
for id, obj := range info.Uses {
if sameObj(queryObj, obj) {
refs = append(refs, id)
}
}
return refs
}
// outputUses outputs a result describing refs, which appear in the package denoted by info.
func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) {
if len(refs) > 0 {
sort.Sort(byNamePos{fset, refs})
q.Output(fset, &referrersPackageResult{
pkg: pkg,
build: q.Build,
fset: fset,
refs: refs,
})
}
}
// globalReferrers reports references throughout the entire workspace to the
// object (a field or method) at the specified source position.
// Its defining package is defpkg, and the query package is qpkg.
func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position) error {
// Scan the workspace and build the import graph.
// Ignore broken packages.
_, rev, _ := importgraph.Build(q.Build)
// Find the set of packages that depend on defpkg.
// Only function bodies in those packages need type-checking.
users := rev.Search(defpkg) // transitive importers
// Prepare to load the larger program.
fset := token.NewFileSet()
lconf := loader.Config{
Fset: fset,
Build: q.Build,
TypeCheckFuncBodies: func(p string) bool {
return users[strings.TrimSuffix(p, "_test")]
},
}
allowErrors(&lconf)
// The importgraph doesn't treat external test packages
// as separate nodes, so we must use ImportWithTests.
for path := range users {
lconf.ImportWithTests(path)
}
// The remainder of this function is somewhat tricky because it
// operates on the concurrent stream of packages observed by the
// loader's AfterTypeCheck hook. Most of guru's helper
// functions assume the entire program has already been loaded,
// so we can't use them here.
// TODO(adonovan): smooth things out once the other changes have landed.
// Results are reported concurrently from within the
// AfterTypeCheck hook. The program may provide a useful stream
// of information even if the user doesn't let the program run
// to completion.
var (
mu sync.Mutex
qobj types.Object
)
// For efficiency, we scan each package for references
// just after it has been type-checked. The loader calls
// AfterTypeCheck (concurrently), providing us with a stream of
// packages.
lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) {
// AfterTypeCheck may be called twice for the same package due to augmentation.
// Only inspect packages that depend on the declaring package
// (and thus were type-checked).
if lconf.TypeCheckFuncBodies(info.Pkg.Path()) {
// Record the query object and its package when we see it.
mu.Lock()
if qobj == nil && info.Pkg.Path() == defpkg {
// Find the object by its position (slightly ugly).
qobj = findObject(fset, &info.Info, objposn)
if qobj == nil {
// It really ought to be there;
// we found it once already.
log.Fatalf("object at %s not found in package %s",
objposn, defpkg)
}
}
obj := qobj
mu.Unlock()
// Look for references to the query object.
if obj != nil {
outputUses(q, fset, usesOf(obj, info), info.Pkg)
}
}
clearInfoFields(info) // save memory
}
lconf.Load() // ignore error
if qobj == nil {
log.Fatal("query object not found during reloading")
}
return nil // success
}
// globalReferrersPkgLevel reports references throughout the entire workspace to the package-level object obj.
// It assumes that the query object itself has already been reported.
func globalReferrersPkgLevel(q *Query, obj types.Object, fset *token.FileSet) error {
// globalReferrersPkgLevel uses go/ast and friends instead of go/types.
// This affords a considerable performance benefit.
// It comes at the cost of some code complexity.
//
// Here's a high level summary.
//
// The goal is to find references to the query object p.Q.
// There are several possible scenarios, each handled differently.
//
// 1. We are looking in a package other than p, and p is not dot-imported.
// This is the simplest case. Q must be referred to as n.Q,
// where n is the name under which p is imported.
// We look at all imports of p to gather all names under which it is imported.
// (In the typical case, it is imported only once, under its default name.)
// Then we look at all selector expressions and report any matches.
//
// 2. We are looking in a package other than p, and p is dot-imported.
// In this case, Q will be referred to just as Q.
// Furthermore, go/ast's object resolution will not be able to resolve
// Q to any other object, unlike any local (file- or function- or block-scoped) object.
// So we look at all matching identifiers and report all unresolvable ones.
//
// 3. We are looking in package p.
// (Care must be taken to separate p and p_test (an xtest package),
// and make sure that they are treated as separate packages.)
// In this case, we give go/ast the entire package for object resolution,
// instead of going file by file.
// We then iterate over all identifiers that resolve to the query object.
// (The query object itself has already been reported, so we don't re-report it.)
//
// We always skip all files that don't contain the string Q, as they cannot be
// relevant to finding references to Q.
//
// We parse all files leniently. In the presence of parsing errors, results are best-effort.
// Scan the workspace and build the import graph.
// Ignore broken packages.
_, rev, _ := importgraph.Build(q.Build)
// Find the set of packages that directly import defpkg.
defpkg := obj.Pkg().Path()
defpkg = strings.TrimSuffix(defpkg, "_test") // package x_test actually has package name x
defpkg = imports.VendorlessPath(defpkg) // remove vendor goop
users := rev[defpkg]
if len(users) == 0 {
users = make(map[string]bool)
}
// We also need to check defpkg itself, and its xtests.
// For the reverse graph packages, we process xtests with the main package.
// defpkg gets special handling; we must distinguish between in-package vs out-of-package.
// To make the control flow below simpler, add defpkg and defpkg xtest placeholders.
// Use "!test" instead of "_test" because "!" is not a valid character in an import path.
// (More precisely, it is not guaranteed to be a valid character in an import path,
// so it is unlikely that it will be in use. See https://golang.org/ref/spec#Import_declarations.)
users[defpkg] = true
users[defpkg+"!test"] = true
cwd, err := os.Getwd()
if err != nil {
return err
}
defname := obj.Pkg().Name() // name of defining package, used for imports using import path only
isxtest := strings.HasSuffix(defname, "_test") // indicates whether the query object is defined in an xtest package
name := obj.Name()
namebytes := []byte(name) // byte slice version of query object name, for early filtering
objpos := fset.Position(obj.Pos()) // position of query object, used to prevent re-emitting original decl
sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
var wg sync.WaitGroup
for u := range users {
u := u
wg.Add(1)
go func() {
defer wg.Done()
uIsXTest := strings.HasSuffix(u, "!test") // indicates whether this package is the special defpkg xtest package
u = strings.TrimSuffix(u, "!test")
// Resolve package.
sema <- struct{}{} // acquire token
pkg, err := q.Build.Import(u, cwd, build.IgnoreVendor)
<-sema // release token
if err != nil {
return
}
// If we're not in the query package,
// the object is in another package regardless,
// so we want to process all files.
// If we are in the query package,
// we want to only process the files that are
// part of that query package;
// that set depends on whether the query package itself is an xtest.
inQueryPkg := u == defpkg && isxtest == uIsXTest
var files []string
if !inQueryPkg || !isxtest {
files = append(files, pkg.GoFiles...)
files = append(files, pkg.TestGoFiles...)
files = append(files, pkg.CgoFiles...) // use raw cgo files, as we're only parsing
}
if !inQueryPkg || isxtest {
files = append(files, pkg.XTestGoFiles...)
}
if len(files) == 0 {
return
}
var deffiles map[string]*ast.File
if inQueryPkg {
deffiles = make(map[string]*ast.File)
}
buf := new(bytes.Buffer) // reusable buffer for reading files
for _, file := range files {
if !buildutil.IsAbsPath(q.Build, file) {
file = buildutil.JoinPath(q.Build, pkg.Dir, file)
}
buf.Reset()
sema <- struct{}{} // acquire token
src, err := readFile(q.Build, file, buf)
<-sema // release token
if err != nil {
continue
}
// Fast path: If the object's name isn't present anywhere in the source, ignore the file.
if !bytes.Contains(src, namebytes) {
continue
}
if inQueryPkg {
// If we're in the query package, we defer final processing until we have
// parsed all of the candidate files in the package.
// Best effort; allow errors and use what we can from what remains.
f, _ := parser.ParseFile(fset, file, src, parser.AllErrors)
if f != nil {
deffiles[file] = f
}
continue
}
// We aren't in the query package. Go file by file.
// Parse out only the imports, to check whether the defining package
// was imported, and if so, under what names.
// Best effort; allow errors and use what we can from what remains.
f, _ := parser.ParseFile(fset, file, src, parser.ImportsOnly|parser.AllErrors)
if f == nil {
continue
}
// pkgnames is the set of names by which defpkg is imported in this file.
// (Multiple imports in the same file are legal but vanishingly rare.)
pkgnames := make([]string, 0, 1)
var isdotimport bool
for _, imp := range f.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil || path != defpkg {
continue
}
switch {
case imp.Name == nil:
pkgnames = append(pkgnames, defname)
case imp.Name.Name == ".":
isdotimport = true
default:
pkgnames = append(pkgnames, imp.Name.Name)
}
}
if len(pkgnames) == 0 && !isdotimport {
// Defining package not imported, bail.
continue
}
// Re-parse the entire file.
// Parse errors are ok; we'll do the best we can with a partial AST, if we have one.
f, _ = parser.ParseFile(fset, file, src, parser.AllErrors)
if f == nil {
continue
}
// Walk the AST looking for references.
var refs []*ast.Ident
ast.Inspect(f, func(n ast.Node) bool {
// Check selector expressions.
// If the selector matches the target name,
// and the expression is one of the names
// that the defining package was imported under,
// then we have a match.
if sel, ok := n.(*ast.SelectorExpr); ok && sel.Sel.Name == name {
if id, ok := sel.X.(*ast.Ident); ok {
for _, n := range pkgnames {
if n == id.Name {
refs = append(refs, sel.Sel)
// Don't recurse further, to avoid duplicate entries
// from the dot import check below.
return false
}
}
}
}
// Dot imports are special.
// Objects imported from the defining package are placed in the package scope.
// go/ast does not resolve them to an object.
// At all other scopes (file, local), go/ast can do the resolution.
// So we're looking for object-free idents with the right name.
// The only other way to get something with the right name at the package scope
// is to *be* the defining package. We handle that case separately (inQueryPkg).
if isdotimport {
if id, ok := n.(*ast.Ident); ok && id.Obj == nil && id.Name == name {
refs = append(refs, id)
return false
}
}
return true
})
// Emit any references we found.
if len(refs) > 0 {
q.Output(fset, &referrersPackageResult{
pkg: types.NewPackage(pkg.ImportPath, pkg.Name),
build: q.Build,
fset: fset,
refs: refs,
})
}
}
// If we're in the query package, we've now collected all the files in the package.
// (Or at least the ones that might contain references to the object.)
// Find and emit refs.
if inQueryPkg {
// Bundle the files together into a package.
// This does package-level object resolution.
qpkg, _ := ast.NewPackage(fset, deffiles, nil, nil)
// Look up the query object; we know that it is defined in the package scope.
pkgobj := qpkg.Scope.Objects[name]
if pkgobj == nil {
panic("missing defpkg object for " + defpkg + "." + name)
}
// Find all references to the query object.
var refs []*ast.Ident
ast.Inspect(qpkg, func(n ast.Node) bool {
if id, ok := n.(*ast.Ident); ok {
// Check both that this is a reference to the query object
// and that it is not the query object itself;
// the query object itself was already emitted.
if id.Obj == pkgobj && objpos != fset.Position(id.Pos()) {
refs = append(refs, id)
return false
}
}
return true
})
if len(refs) > 0 {
q.Output(fset, &referrersPackageResult{
pkg: types.NewPackage(pkg.ImportPath, pkg.Name),
build: q.Build,
fset: fset,
refs: refs,
})
}
deffiles = nil // allow GC
}
}()
}
wg.Wait()
return nil
}
// findObject returns the object defined at the specified position.
func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object {
good := func(obj types.Object) bool {
if obj == nil {
return false
}
posn := fset.Position(obj.Pos())
return posn.Filename == objposn.Filename && posn.Offset == objposn.Offset
}
for _, obj := range info.Defs {
if good(obj) {
return obj
}
}
for _, obj := range info.Implicits {
if good(obj) {
return obj
}
}
return nil
}
// same reports whether x and y are identical, or both are PkgNames
// that import the same Package.
//
func sameObj(x, y types.Object) bool {
if x == y {
return true
}
if x, ok := x.(*types.PkgName); ok {
if y, ok := y.(*types.PkgName); ok {
return x.Imported() == y.Imported()
}
}
return false
}
func clearInfoFields(info *loader.PackageInfo) {
// TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects.
// (Requires go/types change for Go 1.7.)
// info.Pkg.Scope().ClearChildren()
// Discard the file ASTs and their accumulated type
// information to save memory.
info.Files = nil
info.Defs = make(map[*ast.Ident]types.Object)
info.Uses = make(map[*ast.Ident]types.Object)
info.Implicits = make(map[ast.Node]types.Object)
// Also, disable future collection of wholly unneeded
// type information for the package in case there is
// more type-checking to do (augmentation).
info.Types = nil
info.Scopes = nil
info.Selections = nil
}
// -------- utils --------
// An deterministic ordering for token.Pos that doesn't
// depend on the order in which packages were loaded.
func lessPos(fset *token.FileSet, x, y token.Pos) bool {
fx := fset.File(x)
fy := fset.File(y)
if fx != fy {
return fx.Name() < fy.Name()
}
return x < y
}
type byNamePos struct {
fset *token.FileSet
ids []*ast.Ident
}
func (p byNamePos) Len() int { return len(p.ids) }
func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] }
func (p byNamePos) Less(i, j int) bool {
return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos)
}
// referrersInitialResult is the initial result of a "referrers" query.
type referrersInitialResult struct {
qinfo *loader.PackageInfo
obj types.Object // object it denotes
}
func (r *referrersInitialResult) PrintPlain(printf printfFunc) {
printf(r.obj, "references to %s",
types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg)))
}
func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte {
var objpos string
if pos := r.obj.Pos(); pos.IsValid() {
objpos = fset.Position(pos).String()
}
return toJSON(&serial.ReferrersInitial{
Desc: r.obj.String(),
ObjPos: objpos,
})
}
// referrersPackageResult is the streaming result for one package of a "referrers" query.
type referrersPackageResult struct {
pkg *types.Package
build *build.Context
fset *token.FileSet
refs []*ast.Ident // set of all other references to it
}
// forEachRef calls f(id, text) for id in r.refs, in order.
// Text is the text of the line on which id appears.
func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) {
// Show referring lines, like grep.
type fileinfo struct {
refs []*ast.Ident
linenums []int // line number of refs[i]
data chan interface{} // file contents or error
}
var fileinfos []*fileinfo
fileinfosByName := make(map[string]*fileinfo)
// First pass: start the file reads concurrently.
sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency
for _, ref := range r.refs {
posn := r.fset.Position(ref.Pos())
fi := fileinfosByName[posn.Filename]
if fi == nil {
fi = &fileinfo{data: make(chan interface{})}
fileinfosByName[posn.Filename] = fi
fileinfos = append(fileinfos, fi)
// First request for this file:
// start asynchronous read.
go func() {
sema <- struct{}{} // acquire token
content, err := readFile(r.build, posn.Filename, nil)
<-sema // release token
if err != nil {
fi.data <- err
} else {
fi.data <- content
}
}()
}
fi.refs = append(fi.refs, ref)
fi.linenums = append(fi.linenums, posn.Line)
}
// Second pass: print refs in original order.
// One line may have several refs at different columns.
for _, fi := range fileinfos {
v := <-fi.data // wait for I/O completion
// Print one item for all refs in a file that could not
// be loaded (perhaps due to //line directives).
if err, ok := v.(error); ok {
var suffix string
if more := len(fi.refs) - 1; more > 0 {
suffix = fmt.Sprintf(" (+ %d more refs in this file)", more)
}
f(fi.refs[0], err.Error()+suffix)
continue
}
lines := bytes.Split(v.([]byte), []byte("\n"))
for i, ref := range fi.refs {
f(ref, string(lines[fi.linenums[i]-1]))
}
}
}
// readFile is like ioutil.ReadFile, but
// it goes through the virtualized build.Context.
// If non-nil, buf must have been reset.
func readFile(ctxt *build.Context, filename string, buf *bytes.Buffer) ([]byte, error) {
rc, err := buildutil.OpenFile(ctxt, filename)
if err != nil {
return nil, err
}
defer rc.Close()
if buf == nil {
buf = new(bytes.Buffer)
}
if _, err := io.Copy(buf, rc); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (r *referrersPackageResult) PrintPlain(printf printfFunc) {
r.foreachRef(func(id *ast.Ident, text string) {
printf(id, "%s", text)
})
}
func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte {
refs := serial.ReferrersPackage{Package: r.pkg.Path()}
r.foreachRef(func(id *ast.Ident, text string) {
refs.Refs = append(refs.Refs, serial.Ref{
Pos: fset.Position(id.NamePos).String(),
Text: text,
})
})
return toJSON(refs)
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/serial/ 0000775 0000000 0000000 00000000000 14177515506 0021261 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/serial/serial.go 0000664 0000000 0000000 00000026554 14177515506 0023103 0 ustar 00root root 0000000 0000000 // 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.
// Package serial defines the guru's schema for -json output.
//
// The output of a guru query is a stream of one or more JSON objects.
// This table shows the types of objects in the result stream for each
// query type.
//
// Query Result stream
// ----- -------------
// callees Callees
// callers Caller ...
// callstack CallStack
// definition Definition
// describe Describe
// freevars FreeVar ...
// implements Implements
// peers Peers
// pointsto PointsTo ...
// referrers ReferrersInitial ReferrersPackage ...
// what What
// whicherrs WhichErrs
//
// All 'pos' strings in the output are of the form "file:line:col",
// where line is the 1-based line number and col is the 1-based byte index.
package serial
// A Peers is the result of a 'peers' query.
// If Allocs is empty, the selected channel can't point to anything.
type Peers struct {
Pos string `json:"pos"` // location of the selected channel op (<-)
Type string `json:"type"` // type of the selected channel
Allocs []string `json:"allocs,omitempty"` // locations of aliased make(chan) ops
Sends []string `json:"sends,omitempty"` // locations of aliased ch<-x ops
Receives []string `json:"receives,omitempty"` // locations of aliased <-ch ops
Closes []string `json:"closes,omitempty"` // locations of aliased close(ch) ops
}
// A "referrers" query emits a ReferrersInitial object followed by zero or
// more ReferrersPackage objects, one per package that contains a reference.
type (
ReferrersInitial struct {
ObjPos string `json:"objpos,omitempty"` // location of the definition
Desc string `json:"desc"` // description of the denoted object
}
ReferrersPackage struct {
Package string `json:"package"`
Refs []Ref `json:"refs"` // non-empty list of references within this package
}
Ref struct {
Pos string `json:"pos"` // location of all references
Text string `json:"text"` // text of the referring line
}
)
// A Definition is the result of a 'definition' query.
type Definition struct {
ObjPos string `json:"objpos,omitempty"` // location of the definition
Desc string `json:"desc"` // description of the denoted object
}
// A Callees is the result of a 'callees' query.
//
// Callees is nonempty unless the call was a dynamic call on a
// provably nil func or interface value.
type (
Callees struct {
Pos string `json:"pos"` // location of selected call site
Desc string `json:"desc"` // description of call site
Callees []*Callee `json:"callees"`
}
Callee struct {
Name string `json:"name"` // full name of called function
Pos string `json:"pos"` // location of called function
}
)
// A Caller is one element of the slice returned by a 'callers' query.
// (Callstack also contains a similar slice.)
//
// The root of the callgraph has an unspecified "Caller" string.
type Caller struct {
Pos string `json:"pos,omitempty"` // location of the calling function
Desc string `json:"desc"` // description of call site
Caller string `json:"caller"` // full name of calling function
}
// A CallStack is the result of a 'callstack' query.
// It indicates an arbitrary path from the root of the callgraph to
// the query function.
//
// If the Callers slice is empty, the function was unreachable in this
// analysis scope.
type CallStack struct {
Pos string `json:"pos"` // location of the selected function
Target string `json:"target"` // the selected function
Callers []Caller `json:"callers"` // enclosing calls, innermost first.
}
// A FreeVar is one element of the slice returned by a 'freevars'
// query. Each one identifies an expression referencing a local
// identifier defined outside the selected region.
type FreeVar struct {
Pos string `json:"pos"` // location of the identifier's definition
Kind string `json:"kind"` // one of {var,func,type,const,label}
Ref string `json:"ref"` // referring expression (e.g. "x" or "x.y.z")
Type string `json:"type"` // type of the expression
}
// An Implements contains the result of an 'implements' query.
// It describes the queried type, the set of named non-empty interface
// types to which it is assignable, and the set of named/*named types
// (concrete or non-empty interface) which may be assigned to it.
//
type Implements struct {
T ImplementsType `json:"type,omitempty"` // the queried type
AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T
AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T
AssignableFromPtr []ImplementsType `json:"fromptr,omitempty"` // interface types assignable only from *T
// The following fields are set only if the query was a method.
// Assignable{To,From,FromPtr}Method[i] is the corresponding
// method of type Assignable{To,From,FromPtr}[i], or blank
// {"",""} if that type lacks the method.
Method *DescribeMethod `json:"method,omitempty"` // the queried method
AssignableToMethod []DescribeMethod `json:"to_method,omitempty"`
AssignableFromMethod []DescribeMethod `json:"from_method,omitempty"`
AssignableFromPtrMethod []DescribeMethod `json:"fromptr_method,omitempty"`
}
// An ImplementsType describes a single type as part of an 'implements' query.
type ImplementsType struct {
Name string `json:"name"` // full name of the type
Pos string `json:"pos"` // location of its definition
Kind string `json:"kind"` // "basic", "array", etc
}
// A SyntaxNode is one element of a stack of enclosing syntax nodes in
// a "what" query.
type SyntaxNode struct {
Description string `json:"desc"` // description of syntax tree
Start int `json:"start"` // start byte offset, 0-based
End int `json:"end"` // end byte offset
}
// A What is the result of the "what" query, which quickly identifies
// the selection, parsing only a single file. It is intended for use
// in low-latency GUIs.
type What struct {
Enclosing []SyntaxNode `json:"enclosing"` // enclosing nodes of syntax tree
Modes []string `json:"modes"` // query modes enabled for this selection.
SrcDir string `json:"srcdir,omitempty"` // $GOROOT src directory containing queried package
ImportPath string `json:"importpath,omitempty"` // import path of queried package
Object string `json:"object,omitempty"` // name of identified object, if any
SameIDs []string `json:"sameids,omitempty"` // locations of references to same object
}
// A PointsToLabel describes a pointer analysis label.
//
// A "label" is an object that may be pointed to by a pointer, map,
// channel, 'func', slice or interface. Labels include:
// - functions
// - globals
// - arrays created by literals (e.g. []byte("foo")) and conversions ([]byte(s))
// - stack- and heap-allocated variables (including composite literals)
// - arrays allocated by append()
// - channels, maps and arrays created by make()
// - and their subelements, e.g. "alloc.y[*].z"
//
type PointsToLabel struct {
Pos string `json:"pos"` // location of syntax that allocated the object
Desc string `json:"desc"` // description of the label
}
// A PointsTo is one element of the result of a 'pointsto' query on an
// expression. It describes a single pointer: its type and the set of
// "labels" it points to.
//
// If the pointer is of interface type, it will have one PTS entry
// describing each concrete type that it may contain. For each
// concrete type that is a pointer, the PTS entry describes the labels
// it may point to. The same is true for reflect.Values, except the
// dynamic types needn't be concrete.
//
type PointsTo struct {
Type string `json:"type"` // (concrete) type of the pointer
NamePos string `json:"namepos,omitempty"` // location of type defn, if Named
Labels []PointsToLabel `json:"labels,omitempty"` // pointed-to objects
}
// A DescribeValue is the additional result of a 'describe' query
// if the selection indicates a value or expression.
type DescribeValue struct {
Type string `json:"type"` // type of the expression
Value string `json:"value,omitempty"` // value of the expression, if constant
ObjPos string `json:"objpos,omitempty"` // location of the definition, if an Ident
TypesPos []Definition `json:"typespos,omitempty"` // location of the named types, that type consist of
}
type DescribeMethod struct {
Name string `json:"name"` // method name, as defined by types.Selection.String()
Pos string `json:"pos"` // location of the method's definition
}
// A DescribeType is the additional result of a 'describe' query
// if the selection indicates a type.
type DescribeType struct {
Type string `json:"type"` // the string form of the type
NamePos string `json:"namepos,omitempty"` // location of definition of type, if named
NameDef string `json:"namedef,omitempty"` // underlying definition of type, if named
Methods []DescribeMethod `json:"methods,omitempty"` // methods of the type
}
type DescribeMember struct {
Name string `json:"name"` // name of member
Type string `json:"type,omitempty"` // type of member (underlying, if 'type')
Value string `json:"value,omitempty"` // value of member (if 'const')
Pos string `json:"pos"` // location of definition of member
Kind string `json:"kind"` // one of {var,const,func,type}
Methods []DescribeMethod `json:"methods,omitempty"` // methods (if member is a type)
}
// A DescribePackage is the additional result of a 'describe' if
// the selection indicates a package.
type DescribePackage struct {
Path string `json:"path"` // import path of the package
Members []*DescribeMember `json:"members,omitempty"` // accessible members of the package
}
// A Describe is the result of a 'describe' query.
// It may contain an element describing the selected semantic entity
// in detail.
type Describe struct {
Desc string `json:"desc"` // description of the selected syntax node
Pos string `json:"pos"` // location of the selected syntax node
Detail string `json:"detail,omitempty"` // one of {package, type, value}, or "".
// At most one of the following fields is populated:
// the one specified by 'detail'.
Package *DescribePackage `json:"package,omitempty"`
Type *DescribeType `json:"type,omitempty"`
Value *DescribeValue `json:"value,omitempty"`
}
// A WhichErrs is the result of a 'whicherrs' query.
// It contains the position of the queried error and the possible globals,
// constants, and types it may point to.
type WhichErrs struct {
ErrPos string `json:"errpos,omitempty"` // location of queried error
Globals []string `json:"globals,omitempty"` // locations of globals
Constants []string `json:"constants,omitempty"` // locations of constants
Types []WhichErrsType `json:"types,omitempty"` // Types
}
type WhichErrsType struct {
Type string `json:"type,omitempty"`
Position string `json:"position,omitempty"`
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/ 0000775 0000000 0000000 00000000000 14177515506 0021613 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/ 0000775 0000000 0000000 00000000000 14177515506 0022402 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/README.txt 0000664 0000000 0000000 00000000066 14177515506 0024102 0 ustar 00root root 0000000 0000000 This is not a Go source file.
Used by TestIssue14684.
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/alias/ 0000775 0000000 0000000 00000000000 14177515506 0023473 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/alias/alias.go 0000664 0000000 0000000 00000001053 14177515506 0025112 0 ustar 00root root 0000000 0000000 // Tests of Go 1.9 type aliases.
// See go.tools/guru/guru_test.go for explanation.
// See alias.golden for expected query results.
package alias // @describe describe-pkg "alias"
type I interface { // @implements implements-I "I"
f()
}
type N int
func (N) f() {}
type M = N // @describe describe-def-M "M"
var m M // @describe describe-ref-M "M"
type O N // @describe describe-O "O"
type P = struct{ N } // @describe describe-P "N"
type U = undefined // @describe describe-U "U"
type _ = undefined // @describe describe-undefined "undefined"
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/alias/alias.golden 0000664 0000000 0000000 00000001644 14177515506 0025763 0 ustar 00root root 0000000 0000000 -------- @describe describe-pkg --------
definition of package "alias"
type I interface{f()}
method (I) f()
type M = N
method (N) f()
type N int
method (N) f()
type O int
type P = struct{N}
method (struct{N}) f()
type U = invalid type
var m N
-------- @implements implements-I --------
interface type I
is implemented by basic type N
-------- @describe describe-def-M --------
alias of type N (size 8, align 8)
defined as int
Methods:
method (N) f()
-------- @describe describe-ref-M --------
alias of type N (size 8, align 8)
defined as int
Methods:
method (N) f()
-------- @describe describe-O --------
definition of type O (size 8, align 8)
No methods.
-------- @describe describe-P --------
type struct{N} (size 8, align 8)
Methods:
method (struct{N}) f()
Fields:
N N
-------- @describe describe-U --------
alias of type invalid type
-------- @describe describe-undefined --------
identifier
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/calls-json/ 0000775 0000000 0000000 00000000000 14177515506 0024447 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/calls-json/main.go 0000664 0000000 0000000 00000000515 14177515506 0025723 0 ustar 00root root 0000000 0000000 package main
// Tests of call-graph queries, -format=json.
// See go.tools/guru/guru_test.go for explanation.
// See calls-json.golden for expected query results.
func call(f func()) {
f() // @callees @callees-f "f"
}
func main() {
call(func() {
// @callers callers-main.anon "^"
// @callstack callstack-main.anon "^"
})
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/calls-json/main.golden 0000664 0000000 0000000 00000001164 14177515506 0026567 0 ustar 00root root 0000000 0000000 -------- @callees @callees-f --------
{
"pos": "testdata/src/calls-json/main.go:8:3",
"desc": "dynamic function call",
"callees": [
{
"name": "calls-json.main$1",
"pos": "testdata/src/calls-json/main.go:12:7"
}
]
}
-------- @callstack callstack-main.anon --------
{
"pos": "testdata/src/calls-json/main.go:12:7",
"target": "calls-json.main$1",
"callers": [
{
"pos": "testdata/src/calls-json/main.go:8:3",
"desc": "dynamic function call",
"caller": "calls-json.call"
},
{
"pos": "testdata/src/calls-json/main.go:12:6",
"desc": "static function call",
"caller": "calls-json.main"
}
]
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/calls/ 0000775 0000000 0000000 00000000000 14177515506 0023500 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/calls/main.go 0000664 0000000 0000000 00000005700 14177515506 0024755 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
)
// Tests of call-graph queries.
// See go.tools/guru/guru_test.go for explanation.
// See calls.golden for expected query results.
func A(x *int) { // @pointsto pointsto-A-x "x"
// @callers callers-A "^"
// @callstack callstack-A "^"
}
func B(x *int) { // @pointsto pointsto-B-x "x"
// @callers callers-B "^"
}
func foo() {
}
// apply is not (yet) treated context-sensitively.
func apply(f func(x *int), x *int) {
f(x) // @callees callees-apply "f"
// @callers callers-apply "^"
}
// store *is* treated context-sensitively,
// so the points-to sets for pc, pd are precise.
func store(ptr **int, value *int) {
*ptr = value
// @callers callers-store "^"
}
func call(f func() *int) {
// Result points to anon function.
f() // @pointsto pointsto-result-f "f"
// Target of call is anon function.
f() // @callees callees-main.call-f "f"
// @callers callers-main.call "^"
}
func main() {
var a, b int
go apply(A, &a) // @callees callees-main-apply1 "app"
defer apply(B, &b)
var c, d int
var pc, pd *int // @pointsto pointsto-pc "pc"
store(&pc, &c)
store(&pd, &d)
_ = pd // @pointsto pointsto-pd "pd"
call(func() *int {
// We are called twice from main.call
// @callers callers-main.anon "^"
return &a
})
// Errors
_ = "no function call here" // @callees callees-err-no-call "no"
print("builtin") // @callees callees-err-builtin "builtin"
_ = string("type conversion") // @callees callees-err-conversion "str"
call(nil) // @callees callees-err-bad-selection "call\\(nil"
if false {
main() // @callees callees-err-deadcode1 "main"
}
var nilFunc func()
nilFunc() // @callees callees-err-nil-func "nilFunc"
var i interface {
f()
}
i.f() // @callees callees-err-nil-interface "i.f"
i = new(myint)
i.f() // @callees callees-not-a-wrapper "f"
// statically dispatched calls. Handled specially by callees, so test that they work.
foo() // @callees callees-static-call "foo"
fmt.Println() // @callees callees-qualified-call "Println"
m := new(method)
m.f() // @callees callees-static-method-call "f"
g := new(embeddedIface)
g.iface = m
g.f() // @callees callees-implicit-selection-method-call "f"
}
type myint int
func (myint) f() {
// @callers callers-not-a-wrapper "^"
}
type method int
func (method) f() {
}
type embeddedIface struct {
iface
}
type iface interface {
f()
}
var dynamic = func() {}
func deadcode() {
main() // @callees callees-err-deadcode2 "main"
// @callers callers-err-deadcode "^"
// @callstack callstack-err-deadcode "^"
// Within dead code, dynamic calls have no callees.
dynamic() // @callees callees-err-deadcode3 "dynamic"
}
// This code belongs to init.
var global = 123 // @callers callers-global "global"
// The package initializer may be called by other packages' inits, or
// in this case, the root of the callgraph. The source-level init functions
// are in turn called by it.
func init() {
// @callstack callstack-init "^"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/calls/main.golden 0000664 0000000 0000000 00000006626 14177515506 0025630 0 ustar 00root root 0000000 0000000 -------- @pointsto pointsto-A-x --------
this *int may point to these objects:
a
b
-------- @callstack callstack-A --------
Found a call path from root to calls.A
calls.A
dynamic function call from calls.apply
concurrent static function call from calls.main
-------- @pointsto pointsto-B-x --------
this *int may point to these objects:
a
b
-------- @callers callers-B --------
calls.B is called from these 1 sites:
dynamic function call from calls.apply
-------- @callees callees-apply --------
this dynamic function call dispatches to:
calls.A
calls.B
-------- @callers callers-apply --------
calls.apply is called from these 2 sites:
concurrent static function call from calls.main
deferred static function call from calls.main
-------- @callers callers-store --------
calls.store is called from these 2 sites:
static function call from calls.main
static function call from calls.main
-------- @pointsto pointsto-result-f --------
this func() *int may point to these objects:
calls.main$1
-------- @callees callees-main.call-f --------
this dynamic function call dispatches to:
calls.main$1
-------- @callers callers-main.call --------
calls.call is called from these 2 sites:
static function call from calls.main
static function call from calls.main
-------- @callees callees-main-apply1 --------
this static function call dispatches to:
calls.apply
-------- @pointsto pointsto-pc --------
this *int may point to these objects:
c
-------- @pointsto pointsto-pd --------
this *int may point to these objects:
d
-------- @callees callees-err-no-call --------
Error: there is no function call here
-------- @callees callees-err-builtin --------
Error: this is a call to the built-in 'print' operator
-------- @callees callees-err-conversion --------
Error: this is a type conversion, not a function call
-------- @callees callees-err-bad-selection --------
Error: ambiguous selection within function call (or conversion)
-------- @callees callees-err-deadcode1 --------
this static function call dispatches to:
calls.main
-------- @callees callees-err-nil-func --------
dynamic function call on nil value
-------- @callees callees-err-nil-interface --------
dynamic method call on nil value
-------- @callees callees-not-a-wrapper --------
this dynamic method call dispatches to:
(calls.myint).f
-------- @callees callees-static-call --------
this static function call dispatches to:
calls.foo
-------- @callees callees-qualified-call --------
this static function call dispatches to:
fmt.Println
-------- @callees callees-static-method-call --------
this static function call dispatches to:
(calls.method).f
-------- @callees callees-implicit-selection-method-call --------
this dynamic method call dispatches to:
(calls.method).f
-------- @callers callers-not-a-wrapper --------
(calls.myint).f is called from these 1 sites:
dynamic method call from calls.main
-------- @callees callees-err-deadcode2 --------
this static function call dispatches to:
calls.main
-------- @callstack callstack-err-deadcode --------
calls.deadcode is unreachable in this analysis scope
-------- @callees callees-err-deadcode3 --------
Error: this call site is unreachable in this analysis
-------- @callers callers-global --------
calls.init is called from these 1 sites:
the root of the call graph
-------- @callstack callstack-init --------
Found a call path from root to calls.init#1
calls.init#1
static function call from calls.init
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/definition-json/ 0000775 0000000 0000000 00000000000 14177515506 0025501 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/definition-json/main.go 0000664 0000000 0000000 00000003207 14177515506 0026756 0 ustar 00root root 0000000 0000000 package definition
// Tests of 'definition' query, -json output.
// See golang.org/x/tools/cmd/guru/guru_test.go for explanation.
// See main.golden for expected query results.
// TODO(adonovan): test: selection of member of same package defined in another file.
import (
"lib"
lib2 "lib"
"nosuchpkg"
)
func main() {
var _ int // @definition builtin "int"
var _ undef // @definition lexical-undef "undef"
var x lib.T // @definition lexical-pkgname "lib"
f() // @definition lexical-func "f"
print(x) // @definition lexical-var "x"
if x := ""; x == "" { // @definition lexical-shadowing "x"
}
var _ lib.Type // @definition qualified-type "Type"
var _ lib.Func // @definition qualified-func "Func"
var _ lib.Var // @definition qualified-var "Var"
var _ lib.Const // @definition qualified-const "Const"
var _ lib2.Type // @definition qualified-type-renaming "Type"
var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch"
var _ nosuchpkg.T // @definition qualified-nopkg "nosuchpkg"
var u U
print(u.field) // @definition select-field "field"
u.method() // @definition select-method "method"
}
func f()
type T struct{ field int }
func (T) method()
type U struct{ T }
type V1 struct {
W // @definition embedded-other-file "W"
}
type V2 struct {
*W // @definition embedded-other-file-pointer "W"
}
type V3 struct {
int // @definition embedded-basic "int"
}
type V4 struct {
*int // @definition embedded-basic-pointer "int"
}
type V5 struct {
lib.Type // @definition embedded-other-pkg "Type"
}
type V6 struct {
T // @definition embedded-same-file "T"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/definition-json/main.golden 0000664 0000000 0000000 00000004523 14177515506 0027623 0 ustar 00root root 0000000 0000000 -------- @definition builtin --------
Error: int is built in
-------- @definition lexical-undef --------
Error: no object for identifier
-------- @definition lexical-pkgname --------
{
"objpos": "testdata/src/definition-json/main.go:10:2",
"desc": "package lib"
}
-------- @definition lexical-func --------
{
"objpos": "$GOPATH/src/definition-json/main.go:38:6",
"desc": "func f"
}
-------- @definition lexical-var --------
{
"objpos": "$GOPATH/src/definition-json/main.go:19:6",
"desc": "var x"
}
-------- @definition lexical-shadowing --------
{
"objpos": "$GOPATH/src/definition-json/main.go:22:5",
"desc": "var x"
}
-------- @definition qualified-type --------
{
"objpos": "testdata/src/lib/lib.go:3:6",
"desc": "type lib.Type"
}
-------- @definition qualified-func --------
{
"objpos": "testdata/src/lib/lib.go:9:6",
"desc": "func lib.Func"
}
-------- @definition qualified-var --------
{
"objpos": "testdata/src/lib/lib.go:14:5",
"desc": "var lib.Var"
}
-------- @definition qualified-const --------
{
"objpos": "testdata/src/lib/lib.go:12:7",
"desc": "const lib.Const"
}
-------- @definition qualified-type-renaming --------
{
"objpos": "testdata/src/lib/lib.go:3:6",
"desc": "type lib.Type"
}
-------- @definition qualified-nomember --------
Error: couldn't find declaration of Nonesuch in "lib"
-------- @definition qualified-nopkg --------
{
"objpos": "testdata/src/definition-json/main.go:12:2",
"desc": "package nosuchpkg"
}
-------- @definition select-field --------
{
"objpos": "testdata/src/definition-json/main.go:40:16",
"desc": "field field int"
}
-------- @definition select-method --------
{
"objpos": "testdata/src/definition-json/main.go:42:10",
"desc": "func (T).method()"
}
-------- @definition embedded-other-file --------
{
"objpos": "testdata/src/definition-json/type.go:3:6",
"desc": "type W int"
}
-------- @definition embedded-other-file-pointer --------
{
"objpos": "testdata/src/definition-json/type.go:3:6",
"desc": "type W int"
}
-------- @definition embedded-basic --------
Error: int is built in
-------- @definition embedded-basic-pointer --------
Error: int is built in
-------- @definition embedded-other-pkg --------
{
"objpos": "testdata/src/lib/lib.go:3:6",
"desc": "type lib.Type"
}
-------- @definition embedded-same-file --------
{
"objpos": "$GOPATH/src/definition-json/main.go:40:6",
"desc": "type T"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/definition-json/type.go 0000664 0000000 0000000 00000000037 14177515506 0027011 0 ustar 00root root 0000000 0000000 package definition
type W int
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/describe-json/ 0000775 0000000 0000000 00000000000 14177515506 0025131 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/describe-json/main.go 0000664 0000000 0000000 00000001154 14177515506 0026405 0 ustar 00root root 0000000 0000000 package describe // @describe pkgdecl "describe"
// Tests of 'describe' query, -format=json.
// See go.tools/guru/guru_test.go for explanation.
// See describe-json.golden for expected query results.
func main() {
var s struct{ x [3]int }
p := &s.x[0] // @describe desc-val-p "p"
_ = p
var i I = C(0)
if i == nil {
i = new(D)
}
print(i) // @describe desc-val-i "\\bi\\b"
go main() // @describe desc-stmt "go"
}
type I interface {
f()
}
type C int // @describe desc-type-C "C"
type D struct{}
func (c C) f() {} // @describe desc-param-c "\\bc\\b"
func (d *D) f() {} // @describe desc-param-d "\\bd\\b"
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/describe-json/main.golden 0000664 0000000 0000000 00000005415 14177515506 0027254 0 ustar 00root root 0000000 0000000 -------- @describe pkgdecl --------
{
"desc": "definition of package \"describe-json\"",
"pos": "testdata/src/describe-json/main.go:1:9",
"detail": "package",
"package": {
"path": "describe-json",
"members": [
{
"name": "C",
"type": "int",
"pos": "testdata/src/describe-json/main.go:25:6",
"kind": "type",
"methods": [
{
"name": "method (C) f()",
"pos": "testdata/src/describe-json/main.go:28:12"
}
]
},
{
"name": "D",
"type": "struct{}",
"pos": "testdata/src/describe-json/main.go:26:6",
"kind": "type",
"methods": [
{
"name": "method (*D) f()",
"pos": "testdata/src/describe-json/main.go:29:13"
}
]
},
{
"name": "I",
"type": "interface{f()}",
"pos": "testdata/src/describe-json/main.go:21:6",
"kind": "type",
"methods": [
{
"name": "method (I) f()",
"pos": "testdata/src/describe-json/main.go:22:2"
}
]
},
{
"name": "main",
"type": "func()",
"pos": "testdata/src/describe-json/main.go:7:6",
"kind": "func"
}
]
}
}
-------- @describe desc-val-p --------
{
"desc": "identifier",
"pos": "testdata/src/describe-json/main.go:9:2",
"detail": "value",
"value": {
"type": "*int",
"objpos": "testdata/src/describe-json/main.go:9:2"
}
}
-------- @describe desc-val-i --------
{
"desc": "identifier",
"pos": "testdata/src/describe-json/main.go:16:8",
"detail": "value",
"value": {
"type": "I",
"objpos": "testdata/src/describe-json/main.go:12:6",
"typespos": [
{
"objpos": "testdata/src/describe-json/main.go:21:6",
"desc": "I"
}
]
}
}
-------- @describe desc-stmt --------
{
"desc": "go statement",
"pos": "testdata/src/describe-json/main.go:18:2",
"detail": "unknown"
}
-------- @describe desc-type-C --------
{
"desc": "definition of type C (size 8, align 8)",
"pos": "testdata/src/describe-json/main.go:25:6",
"detail": "type",
"type": {
"type": "C",
"namepos": "testdata/src/describe-json/main.go:25:6",
"namedef": "int",
"methods": [
{
"name": "method (C) f()",
"pos": "testdata/src/describe-json/main.go:28:12"
}
]
}
}
-------- @describe desc-param-c --------
{
"desc": "identifier",
"pos": "testdata/src/describe-json/main.go:28:7",
"detail": "value",
"value": {
"type": "C",
"objpos": "testdata/src/describe-json/main.go:28:7",
"typespos": [
{
"objpos": "testdata/src/describe-json/main.go:25:6",
"desc": "C"
}
]
}
}
-------- @describe desc-param-d --------
{
"desc": "identifier",
"pos": "testdata/src/describe-json/main.go:29:7",
"detail": "value",
"value": {
"type": "*D",
"objpos": "testdata/src/describe-json/main.go:29:7",
"typespos": [
{
"objpos": "testdata/src/describe-json/main.go:26:6",
"desc": "D"
}
]
}
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/describe/ 0000775 0000000 0000000 00000000000 14177515506 0024162 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/describe/main.go 0000664 0000000 0000000 00000006613 14177515506 0025443 0 ustar 00root root 0000000 0000000 package describe // @describe pkgdecl "describe"
// Tests of 'describe' query.
// See go.tools/guru/guru_test.go for explanation.
// See describe.golden for expected query results.
// TODO(adonovan): more coverage of the (extensive) logic.
import (
"lib"
"nosuchpkg" // @describe badimport1 "nosuchpkg"
nosuchpkg2 "nosuchpkg" // @describe badimport2 "nosuchpkg2"
// The unsafe package changed in Go 1.17 with the addition of
// unsafe.Add and unsafe.Slice. While we still support older versions
// of Go, the test case below cannot be enabled.
// _ "unsafe" // @describe unsafe "unsafe"
)
var _ nosuchpkg.T
var _ nosuchpkg2.T
type cake float64 // @describe type-ref-builtin "float64"
const c = iota // @describe const-ref-iota "iota"
const pi = 3.141 // @describe const-def-pi "pi"
const pie = cake(pi) // @describe const-def-pie "pie"
const _ = pi // @describe const-ref-pi "pi"
var global = new(string) // NB: ssa.Global is indirect, i.e. **string
func main() { // @describe func-def-main "main"
// func objects
_ = main // @describe func-ref-main "main"
_ = (*C).f // @describe func-ref-*C.f "..C..f"
_ = D.f // @describe func-ref-D.f "D.f"
_ = I.f // @describe func-ref-I.f "I.f"
var d D // @describe type-D "D"
var i I // @describe type-I "I"
_ = d.f // @describe func-ref-d.f "d.f"
_ = i.f // @describe func-ref-i.f "i.f"
var slice []D // @describe slice-of-D "slice"
var dptr *D // @describe ptr-with-nonptr-methods "dptr"
_ = dptr
// var objects
anon := func() {
_ = d // @describe ref-lexical-d "d"
}
_ = anon // @describe ref-anon "anon"
_ = global // @describe ref-global "global"
// SSA affords some local flow sensitivity.
var a, b int
var x = &a // @describe var-def-x-1 "x"
_ = x // @describe var-ref-x-1 "x"
x = &b // @describe var-def-x-2 "x"
_ = x // @describe var-ref-x-2 "x"
i = new(C) // @describe var-ref-i-C "i"
if i != nil {
i = D{} // @describe var-ref-i-D "i"
}
print(i) // @describe var-ref-i "\\bi\\b"
// const objects
const localpi = 3.141 // @describe const-local-pi "localpi"
const localpie = cake(pi) // @describe const-local-pie "localpie"
const _ = localpi // @describe const-ref-localpi "localpi"
// type objects
type T int // @describe type-def-T "T"
var three T = 3 // @describe type-ref-T "T"
_ = three
print(1 + 2*3) // @describe const-expr " 2.3"
print(real(1+2i) - 3) // @describe const-expr2 "real.*3"
m := map[string]*int{"a": &a}
mapval, _ := m["a"] // @describe map-lookup,ok "m..a.."
_ = mapval // @describe mapval "mapval"
_ = m // @describe m "m"
defer main() // @describe defer-stmt "defer"
go main() // @describe go-stmt "go"
panic(3) // @describe builtin-ref-panic "panic"
var a2 int // @describe var-decl-stmt "var a2 int"
_ = a2
var _ int // @describe var-decl-stmt2 "var _ int"
var _ int // @describe var-def-blank "_"
var _ lib.Outer // @describe lib-outer "Outer"
var mmm map[C]D // @describe var-map-of-C-D "mmm"
d := newD().ThirdField // @describe field-access "ThirdField"
astCopy := ast
unknown() // @describe call-unknown "\\("
}
type I interface { // @describe def-iface-I "I"
f() // @describe def-imethod-I.f "f"
}
type C int
type D struct {
Field int
AnotherField string
ThirdField C
}
func (c *C) f() {}
func (d D) f() {}
func newD() D { return D{} }
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/describe/main.golden 0000664 0000000 0000000 00000012475 14177515506 0026311 0 ustar 00root root 0000000 0000000 -------- @describe pkgdecl --------
definition of package "describe"
type C int
method (*C) f()
type D struct{...}
method (D) f()
type I interface{f()}
method (I) f()
const c untyped int = 0
type cake float64
var global *string
func main func()
func newD func() D
const pi untyped float = 3.141
const pie cake = 3.141
-------- @describe badimport1 --------
import of package "nosuchpkg"
-------- @describe badimport2 --------
reference to package "nosuchpkg"
-------- @describe type-ref-builtin --------
reference to built-in type float64
-------- @describe const-ref-iota --------
reference to const iota untyped int of value 0
-------- @describe const-def-pi --------
definition of const pi untyped float of value 3.141
-------- @describe const-def-pie --------
definition of const pie cake of value 3.141
Named types:
type cake defined here
-------- @describe const-ref-pi --------
reference to const pi untyped float of value 3.141
defined here
-------- @describe func-def-main --------
definition of func main()
-------- @describe func-ref-main --------
reference to func main()
defined here
-------- @describe func-ref-*C.f --------
reference to method func (*C).f()
defined here
-------- @describe func-ref-D.f --------
reference to method func (D).f()
defined here
-------- @describe func-ref-I.f --------
reference to interface method func (I).f()
defined here
-------- @describe type-D --------
reference to type D (size 32, align 8)
defined as struct{Field int; AnotherField string; ThirdField C}
Methods:
method (D) f()
Fields:
Field int
AnotherField string
ThirdField C
-------- @describe type-I --------
reference to type I (size 16, align 8)
defined as interface{f()}
Methods:
method (I) f()
-------- @describe func-ref-d.f --------
reference to method func (D).f()
defined here
-------- @describe func-ref-i.f --------
reference to interface method func (I).f()
defined here
-------- @describe slice-of-D --------
definition of var slice []D
Named types:
type D defined here
-------- @describe ptr-with-nonptr-methods --------
definition of var dptr *D
Methods:
method (*D) f()
Fields:
Field int
AnotherField string
ThirdField C
Named types:
type D defined here
-------- @describe ref-lexical-d --------
reference to var d D
defined here
Methods:
method (D) f()
Fields:
Field int
AnotherField string
ThirdField C
Named types:
type D defined here
-------- @describe ref-anon --------
reference to var anon func()
defined here
-------- @describe ref-global --------
reference to var global *string
defined here
-------- @describe var-def-x-1 --------
definition of var x *int
-------- @describe var-ref-x-1 --------
reference to var x *int
defined here
-------- @describe var-def-x-2 --------
reference to var x *int
defined here
-------- @describe var-ref-x-2 --------
reference to var x *int
defined here
-------- @describe var-ref-i-C --------
reference to var i I
defined here
Methods:
method (I) f()
Named types:
type I defined here
-------- @describe var-ref-i-D --------
reference to var i I
defined here
Methods:
method (I) f()
Named types:
type I defined here
-------- @describe var-ref-i --------
reference to var i I
defined here
Methods:
method (I) f()
Named types:
type I defined here
-------- @describe const-local-pi --------
definition of const localpi untyped float of value 3.141
-------- @describe const-local-pie --------
definition of const localpie cake of value 3.141
Named types:
type cake defined here
-------- @describe const-ref-localpi --------
reference to const localpi untyped float of value 3.141
defined here
-------- @describe type-def-T --------
definition of type T (size 8, align 8)
No methods.
-------- @describe type-ref-T --------
reference to type T (size 8, align 8)
defined as int
No methods.
-------- @describe const-expr --------
binary * operation of value 6
-------- @describe const-expr2 --------
binary - operation of value -2
-------- @describe map-lookup,ok --------
index expression of type (*int, bool)
-------- @describe mapval --------
reference to var mapval *int
defined here
-------- @describe m --------
reference to var m map[string]*int
defined here
-------- @describe defer-stmt --------
defer statement
-------- @describe go-stmt --------
go statement
-------- @describe builtin-ref-panic --------
function call (or conversion) of type ()
-------- @describe var-decl-stmt --------
definition of var a2 int
-------- @describe var-decl-stmt2 --------
definition of var _ int
-------- @describe var-def-blank --------
definition of var _ int
-------- @describe lib-outer --------
reference to type lib.Outer (size 56, align 8)
defined as struct{A int; b int; lib.inner}
No methods.
Fields:
A int
inner.C bool
inner.recursive.E bool
-------- @describe var-map-of-C-D --------
definition of var mmm map[C]D
Named types:
type C defined here
type D defined here
-------- @describe field-access --------
reference to field ThirdField C
defined here
Methods:
method (*C) f()
Named types:
type C defined here
-------- @describe call-unknown --------
function call of type invalid type
-------- @describe def-iface-I --------
definition of type I (size 16, align 8)
Methods:
method (I) f()
-------- @describe def-imethod-I.f --------
definition of interface method func (I).f()
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/freevars/ 0000775 0000000 0000000 00000000000 14177515506 0024217 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/freevars/main.go 0000664 0000000 0000000 00000001262 14177515506 0025473 0 ustar 00root root 0000000 0000000 package main
// Tests of 'freevars' query.
// See go.tools/guru/guru_test.go for explanation.
// See freevars.golden for expected query results.
// TODO(adonovan): it's hard to test this query in a single line of gofmt'd code.
type T struct {
a, b int
}
type S struct {
x int
t T
}
func f(int) {}
func main() {
type C int
x := 1
const exp = 6
if y := 2; x+y+int(C(3)) != exp { // @freevars fv1 "if.*{"
panic("expected 6")
}
var s S
for x, y := range "foo" {
println(s.x + s.t.a + s.t.b + x + int(y)) // @freevars fv2 "print.*y."
}
f(x) // @freevars fv3 "f.x."
loop: // @freevars fv-def-label "loop:"
for {
break loop // @freevars fv-ref-label "break loop"
}
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/freevars/main.golden 0000664 0000000 0000000 00000000605 14177515506 0026336 0 ustar 00root root 0000000 0000000 -------- @freevars fv1 --------
Free identifiers:
type C
const exp int
var x int
-------- @freevars fv2 --------
Free identifiers:
var s.t.a int
var s.t.b int
var s.x int
var x int
var y rune
-------- @freevars fv3 --------
Free identifiers:
var x int
-------- @freevars fv-def-label --------
No free identifiers.
-------- @freevars fv-ref-label --------
Free identifiers:
label loop
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-json/ 0000775 0000000 0000000 00000000000 14177515506 0025526 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-json/main.go 0000664 0000000 0000000 00000001060 14177515506 0026776 0 ustar 00root root 0000000 0000000 package main
// Tests of 'implements' query, -output=json.
// See go.tools/guru/guru_test.go for explanation.
// See implements.golden for expected query results.
func main() {
}
type E interface{} // @implements E "E"
type F interface { // @implements F "F"
f()
}
type FG interface { // @implements FG "FG"
f()
g() []int // @implements slice "..int"
}
type C int // @implements C "C"
type D struct{}
func (c *C) f() {} // @implements starC ".C"
func (d D) f() {} // @implements D "D"
func (d *D) g() []int { return nil } // @implements starD ".D"
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-json/main.golden 0000664 0000000 0000000 00000005102 14177515506 0027642 0 ustar 00root root 0000000 0000000 -------- @implements E --------
{
"type": {
"name": "implements-json.E",
"pos": "testdata/src/implements-json/main.go:10:6",
"kind": "interface"
}
}
-------- @implements F --------
{
"type": {
"name": "implements-json.F",
"pos": "testdata/src/implements-json/main.go:12:6",
"kind": "interface"
},
"to": [
{
"name": "*implements-json.C",
"pos": "testdata/src/implements-json/main.go:21:6",
"kind": "pointer"
},
{
"name": "implements-json.D",
"pos": "testdata/src/implements-json/main.go:22:6",
"kind": "struct"
},
{
"name": "implements-json.FG",
"pos": "testdata/src/implements-json/main.go:16:6",
"kind": "interface"
}
]
}
-------- @implements FG --------
{
"type": {
"name": "implements-json.FG",
"pos": "testdata/src/implements-json/main.go:16:6",
"kind": "interface"
},
"to": [
{
"name": "*implements-json.D",
"pos": "testdata/src/implements-json/main.go:22:6",
"kind": "pointer"
}
],
"from": [
{
"name": "implements-json.F",
"pos": "testdata/src/implements-json/main.go:12:6",
"kind": "interface"
}
]
}
-------- @implements slice --------
{
"type": {
"name": "[]int",
"pos": "-",
"kind": "slice"
}
}
-------- @implements C --------
{
"type": {
"name": "implements-json.C",
"pos": "testdata/src/implements-json/main.go:21:6",
"kind": "basic"
},
"fromptr": [
{
"name": "implements-json.F",
"pos": "testdata/src/implements-json/main.go:12:6",
"kind": "interface"
}
]
}
-------- @implements starC --------
{
"type": {
"name": "*implements-json.C",
"pos": "testdata/src/implements-json/main.go:21:6",
"kind": "pointer"
},
"from": [
{
"name": "implements-json.F",
"pos": "testdata/src/implements-json/main.go:12:6",
"kind": "interface"
}
]
}
-------- @implements D --------
{
"type": {
"name": "implements-json.D",
"pos": "testdata/src/implements-json/main.go:22:6",
"kind": "struct"
},
"from": [
{
"name": "implements-json.F",
"pos": "testdata/src/implements-json/main.go:12:6",
"kind": "interface"
}
],
"fromptr": [
{
"name": "implements-json.FG",
"pos": "testdata/src/implements-json/main.go:16:6",
"kind": "interface"
}
]
}
-------- @implements starD --------
{
"type": {
"name": "*implements-json.D",
"pos": "testdata/src/implements-json/main.go:22:6",
"kind": "pointer"
},
"from": [
{
"name": "implements-json.F",
"pos": "testdata/src/implements-json/main.go:12:6",
"kind": "interface"
},
{
"name": "implements-json.FG",
"pos": "testdata/src/implements-json/main.go:16:6",
"kind": "interface"
}
]
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-methods-json/ 0000775 0000000 0000000 00000000000 14177515506 0027167 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-methods-json/main.go 0000664 0000000 0000000 00000001435 14177515506 0030445 0 ustar 00root root 0000000 0000000 package main
// Tests of 'implements' query applied to methods, -output=json.
// See go.tools/guru/guru_test.go for explanation.
// See implements-methods.golden for expected query results.
import _ "lib"
func main() {
}
type F interface {
f() // @implements F.f "f"
}
type FG interface {
f() // @implements FG.f "f"
g() []int // @implements FG.g "g"
}
type C int
type D struct{}
func (c *C) f() {} // @implements *C.f "f"
func (d D) f() {} // @implements D.f "f"
func (d *D) g() []int { return nil } // @implements *D.g "g"
type sorter []int
func (sorter) Len() int { return 0 } // @implements Len "Len"
func (sorter) Less(i, j int) bool { return false }
func (sorter) Swap(i, j int) {}
type I interface {
Method(*int) *int // @implements I.Method "Method"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-methods-json/main.golden 0000664 0000000 0000000 00000013177 14177515506 0031316 0 ustar 00root root 0000000 0000000 -------- @implements F.f --------
{
"type": {
"name": "implements-methods-json.F",
"pos": "testdata/src/implements-methods-json/main.go:12:6",
"kind": "interface"
},
"to": [
{
"name": "*implements-methods-json.C",
"pos": "testdata/src/implements-methods-json/main.go:21:6",
"kind": "pointer"
},
{
"name": "implements-methods-json.D",
"pos": "testdata/src/implements-methods-json/main.go:22:6",
"kind": "struct"
},
{
"name": "implements-methods-json.FG",
"pos": "testdata/src/implements-methods-json/main.go:16:6",
"kind": "interface"
}
],
"method": {
"name": "func (F).f()",
"pos": "testdata/src/implements-methods-json/main.go:13:2"
},
"to_method": [
{
"name": "method (*C) f()",
"pos": "testdata/src/implements-methods-json/main.go:24:13"
},
{
"name": "method (D) f()",
"pos": "testdata/src/implements-methods-json/main.go:25:12"
},
{
"name": "method (FG) f()",
"pos": "testdata/src/implements-methods-json/main.go:17:2"
}
]
}
-------- @implements FG.f --------
{
"type": {
"name": "implements-methods-json.FG",
"pos": "testdata/src/implements-methods-json/main.go:16:6",
"kind": "interface"
},
"to": [
{
"name": "*implements-methods-json.D",
"pos": "testdata/src/implements-methods-json/main.go:22:6",
"kind": "pointer"
}
],
"from": [
{
"name": "implements-methods-json.F",
"pos": "testdata/src/implements-methods-json/main.go:12:6",
"kind": "interface"
}
],
"method": {
"name": "func (FG).f()",
"pos": "testdata/src/implements-methods-json/main.go:17:2"
},
"to_method": [
{
"name": "method (*D) f()",
"pos": "testdata/src/implements-methods-json/main.go:25:12"
}
],
"from_method": [
{
"name": "method (F) f()",
"pos": "testdata/src/implements-methods-json/main.go:13:2"
}
]
}
-------- @implements FG.g --------
{
"type": {
"name": "implements-methods-json.FG",
"pos": "testdata/src/implements-methods-json/main.go:16:6",
"kind": "interface"
},
"to": [
{
"name": "*implements-methods-json.D",
"pos": "testdata/src/implements-methods-json/main.go:22:6",
"kind": "pointer"
}
],
"from": [
{
"name": "implements-methods-json.F",
"pos": "testdata/src/implements-methods-json/main.go:12:6",
"kind": "interface"
}
],
"method": {
"name": "func (FG).g() []int",
"pos": "testdata/src/implements-methods-json/main.go:18:2"
},
"to_method": [
{
"name": "method (*D) g() []int",
"pos": "testdata/src/implements-methods-json/main.go:27:13"
}
],
"from_method": [
{
"name": "",
"pos": ""
}
]
}
-------- @implements *C.f --------
{
"type": {
"name": "*implements-methods-json.C",
"pos": "testdata/src/implements-methods-json/main.go:21:6",
"kind": "pointer"
},
"from": [
{
"name": "implements-methods-json.F",
"pos": "testdata/src/implements-methods-json/main.go:12:6",
"kind": "interface"
}
],
"method": {
"name": "func (*C).f()",
"pos": "testdata/src/implements-methods-json/main.go:24:13"
},
"from_method": [
{
"name": "method (F) f()",
"pos": "testdata/src/implements-methods-json/main.go:13:2"
}
]
}
-------- @implements D.f --------
{
"type": {
"name": "implements-methods-json.D",
"pos": "testdata/src/implements-methods-json/main.go:22:6",
"kind": "struct"
},
"from": [
{
"name": "implements-methods-json.F",
"pos": "testdata/src/implements-methods-json/main.go:12:6",
"kind": "interface"
}
],
"fromptr": [
{
"name": "implements-methods-json.FG",
"pos": "testdata/src/implements-methods-json/main.go:16:6",
"kind": "interface"
}
],
"method": {
"name": "func (D).f()",
"pos": "testdata/src/implements-methods-json/main.go:25:12"
},
"from_method": [
{
"name": "method (F) f()",
"pos": "testdata/src/implements-methods-json/main.go:13:2"
}
],
"fromptr_method": [
{
"name": "method (FG) f()",
"pos": "testdata/src/implements-methods-json/main.go:17:2"
}
]
}
-------- @implements *D.g --------
{
"type": {
"name": "*implements-methods-json.D",
"pos": "testdata/src/implements-methods-json/main.go:22:6",
"kind": "pointer"
},
"from": [
{
"name": "implements-methods-json.F",
"pos": "testdata/src/implements-methods-json/main.go:12:6",
"kind": "interface"
},
{
"name": "implements-methods-json.FG",
"pos": "testdata/src/implements-methods-json/main.go:16:6",
"kind": "interface"
}
],
"method": {
"name": "func (*D).g() []int",
"pos": "testdata/src/implements-methods-json/main.go:27:13"
},
"from_method": [
{
"name": "",
"pos": ""
},
{
"name": "method (FG) g() []int",
"pos": "testdata/src/implements-methods-json/main.go:18:2"
}
]
}
-------- @implements Len --------
{
"type": {
"name": "implements-methods-json.sorter",
"pos": "testdata/src/implements-methods-json/main.go:29:6",
"kind": "slice"
},
"from": [
{
"name": "lib.Sorter",
"pos": "testdata/src/lib/lib.go:16:6",
"kind": "interface"
}
],
"method": {
"name": "func (sorter).Len() int",
"pos": "testdata/src/implements-methods-json/main.go:31:15"
},
"from_method": [
{
"name": "method (lib.Sorter) Len() int",
"pos": "testdata/src/lib/lib.go:17:2"
}
]
}
-------- @implements I.Method --------
{
"type": {
"name": "implements-methods-json.I",
"pos": "testdata/src/implements-methods-json/main.go:35:6",
"kind": "interface"
},
"to": [
{
"name": "lib.Type",
"pos": "testdata/src/lib/lib.go:3:6",
"kind": "basic"
}
],
"method": {
"name": "func (I).Method(*int) *int",
"pos": "testdata/src/implements-methods-json/main.go:36:2"
},
"to_method": [
{
"name": "method (lib.Type) Method(x *int) *int",
"pos": "testdata/src/lib/lib.go:5:13"
}
]
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-methods/ 0000775 0000000 0000000 00000000000 14177515506 0026220 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-methods/main.go 0000664 0000000 0000000 00000001417 14177515506 0027476 0 ustar 00root root 0000000 0000000 package main
// Tests of 'implements' query applied to methods.
// See go.tools/guru/guru_test.go for explanation.
// See implements-methods.golden for expected query results.
import _ "lib"
func main() {
}
type F interface {
f() // @implements F.f "f"
}
type FG interface {
f() // @implements FG.f "f"
g() []int // @implements FG.g "g"
}
type C int
type D struct{}
func (c *C) f() {} // @implements *C.f "f"
func (d D) f() {} // @implements D.f "f"
func (d *D) g() []int { return nil } // @implements *D.g "g"
type sorter []int
func (sorter) Len() int { return 0 } // @implements Len "Len"
func (sorter) Less(i, j int) bool { return false }
func (sorter) Swap(i, j int) {}
type I interface {
Method(*int) *int // @implements I.Method "Method"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements-methods/main.golden 0000664 0000000 0000000 00000001702 14177515506 0030336 0 ustar 00root root 0000000 0000000 -------- @implements F.f --------
abstract method func (F).f()
is implemented by method (*C).f
is implemented by method (D).f
is implemented by method (FG).f
-------- @implements FG.f --------
abstract method func (FG).f()
is implemented by method (*D).f
implements method (F).f
-------- @implements FG.g --------
abstract method func (FG).g() []int
is implemented by method (*D).g
-------- @implements *C.f --------
concrete method func (*C).f()
implements method (F).f
-------- @implements D.f --------
concrete method func (D).f()
implements method (F).f
concrete method func (D).f()
implements method (FG).f
-------- @implements *D.g --------
concrete method func (*D).g() []int
implements method (FG).g
-------- @implements Len --------
concrete method func (sorter).Len() int
implements method (lib.Sorter).Len
-------- @implements I.Method --------
abstract method func (I).Method(*int) *int
is implemented by method (lib.Type).Method
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements/ 0000775 0000000 0000000 00000000000 14177515506 0024557 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements/main.go 0000664 0000000 0000000 00000001541 14177515506 0026033 0 ustar 00root root 0000000 0000000 package main
// Tests of 'implements' query.
// See go.tools/guru/guru_test.go for explanation.
// See implements.golden for expected query results.
import _ "lib"
func main() {
}
type E interface{} // @implements E "E"
type F interface { // @implements F "F"
f()
}
type FG interface { // @implements FG "FG"
f()
g() []int // @implements slice "..int"
}
type C int // @implements C "C"
type D struct{}
func (c *C) f() {} // @implements starC ".C"
func (d D) f() {} // @implements D "D"
func (d *D) g() []int { return nil } // @implements starD ".D"
type sorter []int // @implements sorter "sorter"
func (sorter) Len() int { return 0 }
func (sorter) Less(i, j int) bool { return false }
func (sorter) Swap(i, j int) {}
type I interface { // @implements I "I"
Method(*int) *int
}
func _() {
var d D
_ = d // @implements var_d "d"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/implements/main.golden 0000664 0000000 0000000 00000001672 14177515506 0026703 0 ustar 00root root 0000000 0000000 -------- @implements E --------
empty interface type E
-------- @implements F --------
interface type F
is implemented by pointer type *C
is implemented by struct type D
is implemented by interface type FG
-------- @implements FG --------
interface type FG
is implemented by pointer type *D
implements F
-------- @implements slice --------
slice type []int implements only any
-------- @implements C --------
pointer type *C
implements F
-------- @implements starC --------
pointer type *C
implements F
-------- @implements D --------
struct type D
implements F
pointer type *D
implements FG
-------- @implements starD --------
pointer type *D
implements F
implements FG
-------- @implements sorter --------
slice type sorter
implements lib.Sorter
-------- @implements I --------
interface type I
is implemented by basic type lib.Type
-------- @implements var_d --------
struct type D
implements F
pointer type *D
implements FG
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/imports/ 0000775 0000000 0000000 00000000000 14177515506 0024077 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/imports/main.go 0000664 0000000 0000000 00000001450 14177515506 0025352 0 ustar 00root root 0000000 0000000 package main
import (
"lib" // @describe ref-pkg-import "lib"
"lib/sublib" // @describe ref-pkg-import2 "sublib"
)
// Tests that import another package. (To make the tests run quickly,
// we avoid using imports in all the other tests. Remember, each
// query causes parsing and typechecking of the whole program.)
//
// See go.tools/guru/guru_test.go for explanation.
// See imports.golden for expected query results.
var a int
func main() {
const c = lib.Const // @describe ref-const "Const"
lib.Func() // @describe ref-func "Func"
lib.Var++ // @describe ref-var "Var"
var t lib.Type // @describe ref-type "Type"
p := t.Method(&a) // @describe ref-method "Method"
print(*p + 1) // @pointsto p "p "
var _ lib.Type // @describe ref-pkg "lib"
_ = sublib.C
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/imports/main.golden 0000664 0000000 0000000 00000002565 14177515506 0026225 0 ustar 00root root 0000000 0000000 -------- @describe ref-pkg-import --------
import of package "lib"
const Const untyped int = 3
func Func func()
type Outer struct{...}
type Sorter interface{...}
method (Sorter) Len() int
method (Sorter) Less(i int, j int) bool
method (Sorter) Swap(i int, j int)
type Type int
method (Type) Method(x *int) *int
var Var int
-------- @describe ref-pkg-import2 --------
import of package "lib/sublib"
const C untyped int = 0
-------- @describe ref-const --------
reference to const lib.Const untyped int of value 3
defined here
-------- @describe ref-func --------
reference to func lib.Func()
defined here
-------- @describe ref-var --------
reference to var lib.Var int
defined here
-------- @describe ref-type --------
reference to type lib.Type (size 8, align 8)
defined as int
Methods:
method (Type) Method(x *int) *int
-------- @describe ref-method --------
reference to method func (lib.Type).Method(x *int) *int
defined here
-------- @pointsto p --------
this *int may point to these objects:
imports.a
-------- @describe ref-pkg --------
reference to package "lib"
const Const untyped int = 3
func Func func()
type Outer struct{...}
type Sorter interface{...}
method (Sorter) Len() int
method (Sorter) Less(i int, j int) bool
method (Sorter) Swap(i int, j int)
type Type int
method (Type) Method(x *int) *int
var Var int
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/lib/ 0000775 0000000 0000000 00000000000 14177515506 0023150 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/lib/lib.go 0000664 0000000 0000000 00000000517 14177515506 0024250 0 ustar 00root root 0000000 0000000 package lib
type Type int
func (Type) Method(x *int) *int {
return x
}
func Func() {
}
const Const = 3
var Var = 0
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
type Outer struct {
A int
b int
inner
}
type inner struct {
C bool
d string
recursive
}
type recursive struct {
E bool
*inner
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/lib/sublib/ 0000775 0000000 0000000 00000000000 14177515506 0024430 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/lib/sublib/sublib.go 0000664 0000000 0000000 00000000034 14177515506 0026234 0 ustar 00root root 0000000 0000000 package sublib
const C = 0
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/main/ 0000775 0000000 0000000 00000000000 14177515506 0023326 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/main/multi.go 0000664 0000000 0000000 00000000202 14177515506 0025001 0 ustar 00root root 0000000 0000000 package main
func g(x int) {
}
func f() {
x := 1
g(x) // "g(x)" is the selection for multiple queries
}
func main() {
f()
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/peers-json/ 0000775 0000000 0000000 00000000000 14177515506 0024467 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/peers-json/main.go 0000664 0000000 0000000 00000000416 14177515506 0025743 0 ustar 00root root 0000000 0000000 package main
// Tests of channel 'peers' query, -format=json.
// See go.tools/guru/guru_test.go for explanation.
// See peers-json.golden for expected query results.
func main() {
chA := make(chan *int)
<-chA
select {
case <-chA: // @peers peer-recv-chA "<-"
}
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/peers-json/main.golden 0000664 0000000 0000000 00000000417 14177515506 0026607 0 ustar 00root root 0000000 0000000 -------- @peers peer-recv-chA --------
{
"pos": "testdata/src/peers-json/main.go:11:7",
"type": "chan *int",
"allocs": [
"testdata/src/peers-json/main.go:8:13"
],
"receives": [
"testdata/src/peers-json/main.go:9:2",
"testdata/src/peers-json/main.go:11:7"
]
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/peers/ 0000775 0000000 0000000 00000000000 14177515506 0023520 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/peers/main.go 0000664 0000000 0000000 00000001773 14177515506 0025003 0 ustar 00root root 0000000 0000000 package main
// Tests of channel 'peers' query.
// See go.tools/guru/guru_test.go for explanation.
// See peers.golden for expected query results.
var a2 int
func main() {
chA := make(chan *int)
a1 := 1
chA <- &a1
chA2 := make(chan *int, 2)
if a2 == 0 {
chA = chA2
}
chB := make(chan *int)
b := 3
chB <- &b
<-chA // @pointsto pointsto-chA "chA"
<-chA2 // @pointsto pointsto-chA2 "chA2"
<-chB // @pointsto pointsto-chB "chB"
select {
case rA := <-chA: // @peers peer-recv-chA "<-"
_ = rA // @pointsto pointsto-rA "rA"
case rB := <-chB: // @peers peer-recv-chB "<-"
_ = rB // @pointsto pointsto-rB "rB"
case <-chA: // @peers peer-recv-chA' "<-"
case chA2 <- &a2: // @peers peer-send-chA' "<-"
}
for range chA {
}
close(chA) // @peers peer-close-chA "chA"
chC := make(chan *int)
(close)(chC) // @peers peer-close-chC "chC"
close := func(ch chan *int) chan *int {
return ch
}
close(chC) <- &b // @peers peer-send-chC "chC"
<-close(chC) // @peers peer-recv-chC "chC"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/peers/main.golden 0000664 0000000 0000000 00000004027 14177515506 0025641 0 ustar 00root root 0000000 0000000 -------- @pointsto pointsto-chA --------
this chan *int may point to these objects:
makechan
makechan
-------- @pointsto pointsto-chA2 --------
this chan *int may point to these objects:
makechan
-------- @pointsto pointsto-chB --------
this chan *int may point to these objects:
makechan
-------- @peers peer-recv-chA --------
This channel of type chan *int may be:
allocated here
allocated here
sent to, here
sent to, here
received from, here
received from, here
received from, here
received from, here
received from, here
closed, here
-------- @pointsto pointsto-rA --------
this *int may point to these objects:
peers.a2
a1
-------- @peers peer-recv-chB --------
This channel of type chan *int may be:
allocated here
sent to, here
received from, here
received from, here
-------- @pointsto pointsto-rB --------
this *int may point to these objects:
b
-------- @peers peer-recv-chA' --------
This channel of type chan *int may be:
allocated here
allocated here
sent to, here
sent to, here
received from, here
received from, here
received from, here
received from, here
received from, here
closed, here
-------- @peers peer-send-chA' --------
This channel of type chan *int may be:
allocated here
sent to, here
received from, here
received from, here
received from, here
received from, here
received from, here
closed, here
-------- @peers peer-close-chA --------
This channel of type chan *int may be:
allocated here
allocated here
sent to, here
sent to, here
received from, here
received from, here
received from, here
received from, here
received from, here
closed, here
-------- @peers peer-close-chC --------
This channel of type chan *int may be:
allocated here
sent to, here
received from, here
closed, here
-------- @peers peer-send-chC --------
This channel of type chan *int may be:
allocated here
sent to, here
received from, here
closed, here
-------- @peers peer-recv-chC --------
This channel of type chan *int may be:
allocated here
sent to, here
received from, here
closed, here
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/pointsto-json/ 0000775 0000000 0000000 00000000000 14177515506 0025230 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/pointsto-json/main.go 0000664 0000000 0000000 00000000666 14177515506 0026513 0 ustar 00root root 0000000 0000000 package main
// Tests of 'pointsto' queries, -format=json.
// See go.tools/guru/guru_test.go for explanation.
// See pointsto-json.golden for expected query results.
func main() { //
var s struct{ x [3]int }
p := &s.x[0] // @pointsto val-p "p"
_ = p
var i I = C(0)
if i == nil {
i = new(D)
}
print(i) // @pointsto val-i "\\bi\\b"
}
type I interface {
f()
}
type C int
type D struct{}
func (c C) f() {}
func (d *D) f() {}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/pointsto-json/main.golden 0000664 0000000 0000000 00000000712 14177515506 0027346 0 ustar 00root root 0000000 0000000 -------- @pointsto val-p --------
[
{
"type": "*int",
"labels": [
{
"pos": "testdata/src/pointsto-json/main.go:8:6",
"desc": "s.x[*]"
}
]
}
]
-------- @pointsto val-i --------
[
{
"type": "*D",
"namepos": "testdata/src/pointsto-json/main.go:24:6",
"labels": [
{
"pos": "testdata/src/pointsto-json/main.go:14:10",
"desc": "new"
}
]
},
{
"type": "C",
"namepos": "testdata/src/pointsto-json/main.go:23:6"
}
]
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/pointsto/ 0000775 0000000 0000000 00000000000 14177515506 0024261 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/pointsto/main.go 0000664 0000000 0000000 00000003506 14177515506 0025540 0 ustar 00root root 0000000 0000000 package main
// Tests of 'pointsto' query.
// See go.tools/guru/guru_test.go for explanation.
// See pointsto.golden for expected query results.
const pi = 3.141 // @pointsto const "pi"
var global = new(string) // NB: ssa.Global is indirect, i.e. **string
func main() {
livecode()
// func objects
_ = main // @pointsto func-ref-main "main"
_ = (*C).f // @pointsto func-ref-*C.f "..C..f"
_ = D.f // @pointsto func-ref-D.f "D.f"
_ = I.f // @pointsto func-ref-I.f "I.f"
var d D
var i I
_ = d.f // @pointsto func-ref-d.f "d.f"
_ = i.f // @pointsto func-ref-i.f "i.f"
// var objects
anon := func() {
_ = d.f // @pointsto ref-lexical-d.f "d.f"
}
_ = anon // @pointsto ref-anon "anon"
_ = global // @pointsto ref-global "global"
// SSA affords some local flow sensitivity.
var a, b int
var x = &a // @pointsto var-def-x-1 "x"
_ = x // @pointsto var-ref-x-1 "x"
x = &b // @pointsto var-def-x-2 "x"
_ = x // @pointsto var-ref-x-2 "x"
i = new(C) // @pointsto var-ref-i-C "i"
if i != nil {
i = D{} // @pointsto var-ref-i-D "i"
}
print(i) // @pointsto var-ref-i "\\bi\\b"
m := map[string]*int{"a": &a}
mapval, _ := m["a"] // @pointsto map-lookup,ok "m..a.."
_ = mapval // @pointsto mapval "mapval"
_ = m // @pointsto m "m"
if false {
panic(3) // @pointsto builtin-panic "panic"
}
// NB: s.f is addressable per (*ssa.Program).VarValue,
// but our query concerns the object, not its address.
s := struct{ f interface{} }{f: make(chan bool)}
print(s.f) // @pointsto var-ref-s-f "s.f"
}
func livecode() {} // @pointsto func-live "livecode"
func deadcode() { // @pointsto func-dead "deadcode"
// Pointer analysis can't run on dead code.
var b = new(int) // @pointsto b "b"
_ = b
}
type I interface {
f()
}
type C int
type D struct{}
func (c *C) f() {}
func (d D) f() {}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/pointsto/main.golden 0000664 0000000 0000000 00000004563 14177515506 0026407 0 ustar 00root root 0000000 0000000 -------- @pointsto const --------
Error: pointer analysis wants an expression of reference type; got untyped float
-------- @pointsto func-ref-main --------
this func() may point to these objects:
pointsto.main
-------- @pointsto func-ref-*C.f --------
this func() may point to these objects:
(*pointsto.C).f
-------- @pointsto func-ref-D.f --------
this func() may point to these objects:
(pointsto.D).f
-------- @pointsto func-ref-I.f --------
Error: func (pointsto.I).f() is an interface method
-------- @pointsto func-ref-d.f --------
this func() may point to these objects:
(pointsto.D).f
-------- @pointsto func-ref-i.f --------
Error: func (pointsto.I).f() is an interface method
-------- @pointsto ref-lexical-d.f --------
this func() may point to these objects:
(pointsto.D).f
-------- @pointsto ref-anon --------
this func() may point to these objects:
pointsto.main$1
-------- @pointsto ref-global --------
this *string may point to these objects:
new
-------- @pointsto var-def-x-1 --------
this *int may point to these objects:
a
-------- @pointsto var-ref-x-1 --------
this *int may point to these objects:
a
-------- @pointsto var-def-x-2 --------
this *int may point to these objects:
b
-------- @pointsto var-ref-x-2 --------
this *int may point to these objects:
b
-------- @pointsto var-ref-i-C --------
this I may contain these dynamic types:
*C, may point to:
new
-------- @pointsto var-ref-i-D --------
this I may contain these dynamic types:
D
-------- @pointsto var-ref-i --------
this I may contain these dynamic types:
*C, may point to:
new
D
-------- @pointsto map-lookup,ok --------
Error: pointer analysis wants an expression of reference type; got (*int, bool)
-------- @pointsto mapval --------
this *int may point to these objects:
a
-------- @pointsto m --------
this map[string]*int may point to these objects:
makemap
-------- @pointsto builtin-panic --------
Error: pointer analysis wants an expression of reference type; got ()
-------- @pointsto var-ref-s-f --------
this any may contain these dynamic types:
chan bool, may point to:
makechan
-------- @pointsto func-live --------
Error: pointer analysis did not find expression (dead code?)
-------- @pointsto func-dead --------
Error: pointer analysis did not find expression (dead code?)
-------- @pointsto b --------
Error: pointer analysis did not find expression (dead code?)
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers-json/ 0000775 0000000 0000000 00000000000 14177515506 0025350 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers-json/main.go 0000664 0000000 0000000 00000000667 14177515506 0026634 0 ustar 00root root 0000000 0000000 package main
// Tests of 'referrers' query.
// See go.tools/guru/guru_test.go for explanation.
// See referrers.golden for expected query results.
import "lib"
type s struct {
f int
}
func main() {
var v lib.Type = lib.Const // @referrers ref-package "lib"
_ = v.Method // @referrers ref-method "Method"
_ = v.Method
v++ //@referrers ref-local "v"
v++
_ = s{}.f // @referrers ref-field "f"
var s2 s
s2.f = 1
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers-json/main.golden 0000664 0000000 0000000 00000012465 14177515506 0027476 0 ustar 00root root 0000000 0000000 -------- @referrers ref-package --------
{
"desc": "package lib"
}
{
"package": "definition-json",
"refs": [
{
"pos": "testdata/src/definition-json/main.go:19:8",
"text": "\tvar x lib.T // @definition lexical-pkgname \"lib\""
},
{
"pos": "testdata/src/definition-json/main.go:25:8",
"text": "\tvar _ lib.Type // @definition qualified-type \"Type\""
},
{
"pos": "testdata/src/definition-json/main.go:26:8",
"text": "\tvar _ lib.Func // @definition qualified-func \"Func\""
},
{
"pos": "testdata/src/definition-json/main.go:27:8",
"text": "\tvar _ lib.Var // @definition qualified-var \"Var\""
},
{
"pos": "testdata/src/definition-json/main.go:28:8",
"text": "\tvar _ lib.Const // @definition qualified-const \"Const\""
},
{
"pos": "testdata/src/definition-json/main.go:29:8",
"text": "\tvar _ lib2.Type // @definition qualified-type-renaming \"Type\""
},
{
"pos": "testdata/src/definition-json/main.go:30:8",
"text": "\tvar _ lib.Nonesuch // @definition qualified-nomember \"Nonesuch\""
},
{
"pos": "testdata/src/definition-json/main.go:63:2",
"text": "\tlib.Type // @definition embedded-other-pkg \"Type\""
}
]
}
{
"package": "describe",
"refs": [
{
"pos": "testdata/src/describe/main.go:95:8",
"text": "\tvar _ lib.Outer // @describe lib-outer \"Outer\""
}
]
}
{
"package": "imports",
"refs": [
{
"pos": "testdata/src/imports/main.go:18:12",
"text": "\tconst c = lib.Const // @describe ref-const \"Const\""
},
{
"pos": "testdata/src/imports/main.go:19:2",
"text": "\tlib.Func() // @describe ref-func \"Func\""
},
{
"pos": "testdata/src/imports/main.go:20:2",
"text": "\tlib.Var++ // @describe ref-var \"Var\""
},
{
"pos": "testdata/src/imports/main.go:21:8",
"text": "\tvar t lib.Type // @describe ref-type \"Type\""
},
{
"pos": "testdata/src/imports/main.go:26:8",
"text": "\tvar _ lib.Type // @describe ref-pkg \"lib\""
}
]
}
{
"package": "referrers",
"refs": [
{
"pos": "testdata/src/referrers/int_test.go:7:7",
"text": "\t_ = (lib.Type).Method // ref from internal test package"
}
]
}
{
"package": "referrers",
"refs": [
{
"pos": "testdata/src/referrers/main.go:16:8",
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
},
{
"pos": "testdata/src/referrers/main.go:16:19",
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
}
]
}
{
"package": "referrers-json",
"refs": [
{
"pos": "testdata/src/referrers-json/main.go:14:8",
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
},
{
"pos": "testdata/src/referrers-json/main.go:14:19",
"text": "\tvar v lib.Type = lib.Const // @referrers ref-package \"lib\""
}
]
}
{
"package": "referrers_test",
"refs": [
{
"pos": "testdata/src/referrers/ext_test.go:10:7",
"text": "\t_ = (lib.Type).Method // ref from external test package"
}
]
}
{
"package": "what-json",
"refs": [
{
"pos": "testdata/src/what-json/main.go:13:7",
"text": "var _ lib.Var // @what pkg \"lib\""
},
{
"pos": "testdata/src/what-json/main.go:14:8",
"text": "type _ lib.T"
}
]
}
-------- @referrers ref-method --------
{
"objpos": "testdata/src/lib/lib.go:5:13",
"desc": "func (lib.Type).Method(x *int) *int"
}
{
"package": "imports",
"refs": [
{
"pos": "testdata/src/imports/main.go:22:9",
"text": "\tp := t.Method(\u0026a) // @describe ref-method \"Method\""
}
]
}
{
"package": "referrers",
"refs": [
{
"pos": "testdata/src/referrers/int_test.go:7:17",
"text": "\t_ = (lib.Type).Method // ref from internal test package"
}
]
}
{
"package": "referrers",
"refs": [
{
"pos": "testdata/src/referrers/main.go:17:8",
"text": "\t_ = v.Method // @referrers ref-method \"Method\""
},
{
"pos": "testdata/src/referrers/main.go:18:8",
"text": "\t_ = v.Method"
}
]
}
{
"package": "referrers-json",
"refs": [
{
"pos": "testdata/src/referrers-json/main.go:15:8",
"text": "\t_ = v.Method // @referrers ref-method \"Method\""
},
{
"pos": "testdata/src/referrers-json/main.go:16:8",
"text": "\t_ = v.Method"
}
]
}
{
"package": "referrers_test",
"refs": [
{
"pos": "testdata/src/referrers/ext_test.go:10:17",
"text": "\t_ = (lib.Type).Method // ref from external test package"
}
]
}
-------- @referrers ref-local --------
{
"objpos": "testdata/src/referrers-json/main.go:14:6",
"desc": "var v lib.Type"
}
{
"package": "referrers-json",
"refs": [
{
"pos": "testdata/src/referrers-json/main.go:15:6",
"text": "\t_ = v.Method // @referrers ref-method \"Method\""
},
{
"pos": "testdata/src/referrers-json/main.go:16:6",
"text": "\t_ = v.Method"
},
{
"pos": "testdata/src/referrers-json/main.go:17:2",
"text": "\tv++ //@referrers ref-local \"v\""
},
{
"pos": "testdata/src/referrers-json/main.go:18:2",
"text": "\tv++"
}
]
}
-------- @referrers ref-field --------
{
"objpos": "testdata/src/referrers-json/main.go:10:2",
"desc": "field f int"
}
{
"package": "referrers-json",
"refs": [
{
"pos": "testdata/src/referrers-json/main.go:20:10",
"text": "\t_ = s{}.f // @referrers ref-field \"f\""
},
{
"pos": "testdata/src/referrers-json/main.go:23:5",
"text": "\ts2.f = 1"
}
]
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers/ 0000775 0000000 0000000 00000000000 14177515506 0024401 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers/ext_test.go 0000664 0000000 0000000 00000000421 14177515506 0026564 0 ustar 00root root 0000000 0000000 package main_test
import (
"lib"
renamed "referrers" // package has name "main", path "referrers", local name "renamed"
)
func _() {
// This reference should be found by the ref-method query.
_ = (lib.Type).Method // ref from external test package
var _ renamed.T
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers/int_test.go 0000664 0000000 0000000 00000000260 14177515506 0026557 0 ustar 00root root 0000000 0000000 package main
import "lib"
func _() {
// This reference should be found by the ref-method query.
_ = (lib.Type).Method // ref from internal test package
_ = notexported
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers/main.go 0000664 0000000 0000000 00000001264 14177515506 0025657 0 ustar 00root root 0000000 0000000 package main // @referrers package-decl "main"
// Tests of 'referrers' query.
// See go.tools/guru/guru_test.go for explanation.
// See referrers.golden for expected query results.
import "lib"
type s struct { // @referrers type " s "
f int
}
type T int
func main() {
var v lib.Type = lib.Const // @referrers ref-package "lib"
_ = v.Method // @referrers ref-method "Method"
_ = v.Method
v++ //@referrers ref-local "v"
v++
_ = s{}.f // @referrers ref-field "f"
var s2 s
s2.f = 1
}
var notexported int // @referrers unexported-from-test "notexported"
// Test //line directives:
type U int // @referrers ref-type-U "U"
//line nosuchfile.y:123
var u1 U
var u2 U
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/referrers/main.golden 0000664 0000000 0000000 00000004566 14177515506 0026532 0 ustar 00root root 0000000 0000000 -------- @referrers package-decl --------
references to package main ("referrers")
var _ renamed.T
-------- @referrers type --------
references to type s struct{f int}
_ = s{}.f // @referrers ref-field "f"
var s2 s
-------- @referrers ref-package --------
references to package lib
_ = (lib.Type).Method // ref from external test package
_ = (lib.Type).Method // ref from internal test package
const c = lib.Const // @describe ref-const "Const"
lib.Func() // @describe ref-func "Func"
lib.Type // @definition embedded-other-pkg "Type"
lib.Var++ // @describe ref-var "Var"
var _ lib.Const // @definition qualified-const "Const"
var _ lib.Func // @definition qualified-func "Func"
var _ lib.Nonesuch // @definition qualified-nomember "Nonesuch"
var _ lib.Outer // @describe lib-outer "Outer"
var _ lib.Type // @definition qualified-type "Type"
var _ lib.Type // @describe ref-pkg "lib"
var _ lib.Var // @definition qualified-var "Var"
var _ lib2.Type // @definition qualified-type-renaming "Type"
var t lib.Type // @describe ref-type "Type"
var v lib.Type = lib.Const // @referrers ref-package "lib"
var v lib.Type = lib.Const // @referrers ref-package "lib"
var v lib.Type = lib.Const // @referrers ref-package "lib"
var v lib.Type = lib.Const // @referrers ref-package "lib"
var x lib.T // @definition lexical-pkgname "lib"
type _ lib.T
var _ lib.Var // @what pkg "lib"
-------- @referrers ref-method --------
references to func (lib.Type).Method(x *int) *int
_ = (lib.Type).Method // ref from external test package
_ = (lib.Type).Method // ref from internal test package
_ = v.Method
_ = v.Method
_ = v.Method // @referrers ref-method "Method"
_ = v.Method // @referrers ref-method "Method"
p := t.Method(&a) // @describe ref-method "Method"
-------- @referrers ref-local --------
references to var v lib.Type
_ = v.Method
_ = v.Method // @referrers ref-method "Method"
v++
v++ //@referrers ref-local "v"
-------- @referrers ref-field --------
references to field f int
_ = s{}.f // @referrers ref-field "f"
s2.f = 1
-------- @referrers unexported-from-test --------
references to var notexported int
_ = notexported
-------- @referrers ref-type-U --------
references to type U int
open testdata/src/referrers/nosuchfile.y: no such file or directory (+ 1 more refs in this file)
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/reflection/ 0000775 0000000 0000000 00000000000 14177515506 0024534 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/reflection/main.go 0000664 0000000 0000000 00000001165 14177515506 0026012 0 ustar 00root root 0000000 0000000 package main
// This is a test of 'pointsto', but we split it into a separate file
// so that pointsto.go doesn't have to import "reflect" each time.
import "reflect"
var a int
var b bool
func main() {
m := make(map[*int]*bool)
m[&a] = &b
mrv := reflect.ValueOf(m)
if a > 0 {
mrv = reflect.ValueOf(&b)
}
if a > 0 {
mrv = reflect.ValueOf(&a)
}
_ = mrv // @pointsto mrv "mrv"
p1 := mrv.Interface() // @pointsto p1 "p1"
p2 := mrv.MapKeys() // @pointsto p2 "p2"
p3 := p2[0] // @pointsto p3 "p3"
p4 := reflect.TypeOf(p1) // @pointsto p4 "p4"
_, _, _, _ = p1, p2, p3, p4
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/reflection/main.golden 0000664 0000000 0000000 00000001402 14177515506 0026647 0 ustar 00root root 0000000 0000000 -------- @pointsto mrv --------
this reflect.Value may contain these dynamic types:
*bool, may point to:
reflection.b
*int, may point to:
reflection.a
map[*int]*bool, may point to:
makemap
-------- @pointsto p1 --------
this any may contain these dynamic types:
*bool, may point to:
reflection.b
*int, may point to:
reflection.a
map[*int]*bool, may point to:
makemap
-------- @pointsto p2 --------
this []reflect.Value may point to these objects:
-------- @pointsto p3 --------
this reflect.Value may contain these dynamic types:
*int, may point to:
reflection.a
-------- @pointsto p4 --------
this reflect.Type may contain these dynamic types:
*reflect.rtype, may point to:
*bool
*int
map[*int]*bool
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/softerrs/ 0000775 0000000 0000000 00000000000 14177515506 0024251 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/softerrs/main.go 0000664 0000000 0000000 00000000534 14177515506 0025526 0 ustar 00root root 0000000 0000000 package main
// Tests of various queries on a program containing only "soft" errors.
// See go.tools/guru/guru_test.go for explanation.
// See main.golden for expected query results.
func _() {
var i int // "unused var" is a soft error
}
func f() {} // @callers softerrs-callers-f "f"
func main() {
f() // @describe softerrs-describe-f "f"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/softerrs/main.golden 0000664 0000000 0000000 00000000325 14177515506 0026367 0 ustar 00root root 0000000 0000000 -------- @callers softerrs-callers-f --------
softerrs.f is called from these 1 sites:
static function call from softerrs.main
-------- @describe softerrs-describe-f --------
reference to func f()
defined here
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/what-json/ 0000775 0000000 0000000 00000000000 14177515506 0024314 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/what-json/main.go 0000664 0000000 0000000 00000000404 14177515506 0025565 0 ustar 00root root 0000000 0000000 package main
import "lib"
// Tests of 'what' queries, -format=json.
// See go.tools/guru/guru_test.go for explanation.
// See what-json.golden for expected query results.
func main() {
f() // @what call "f"
}
var _ lib.Var // @what pkg "lib"
type _ lib.T
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/what-json/main.golden 0000664 0000000 0000000 00000002511 14177515506 0026431 0 ustar 00root root 0000000 0000000 -------- @what call --------
{
"enclosing": [
{
"desc": "identifier",
"start": 189,
"end": 190
},
{
"desc": "function call",
"start": 189,
"end": 192
},
{
"desc": "expression statement",
"start": 189,
"end": 192
},
{
"desc": "block",
"start": 186,
"end": 212
},
{
"desc": "function declaration",
"start": 174,
"end": 212
},
{
"desc": "source file",
"start": 0,
"end": 259
}
],
"modes": [
"callees",
"callers",
"callstack",
"definition",
"describe",
"freevars",
"implements",
"pointsto",
"referrers",
"whicherrs"
],
"srcdir": "testdata/src",
"importpath": "what-json"
}
-------- @what pkg --------
{
"enclosing": [
{
"desc": "identifier",
"start": 220,
"end": 223
},
{
"desc": "selector",
"start": 220,
"end": 227
},
{
"desc": "value specification",
"start": 218,
"end": 227
},
{
"desc": "variable declaration",
"start": 214,
"end": 227
},
{
"desc": "source file",
"start": 0,
"end": 259
}
],
"modes": [
"definition",
"describe",
"freevars",
"implements",
"pointsto",
"referrers",
"whicherrs"
],
"srcdir": "testdata/src",
"importpath": "what-json",
"object": "lib",
"sameids": [
"$GOPATH/src/what-json/main.go:13:7",
"$GOPATH/src/what-json/main.go:14:8"
]
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/what/ 0000775 0000000 0000000 00000000000 14177515506 0023345 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/what/main.go 0000664 0000000 0000000 00000000440 14177515506 0024616 0 ustar 00root root 0000000 0000000 package main // @what pkgdecl "main"
// Tests of 'what' queries.
// See go.tools/guru/guru_test.go for explanation.
// See what.golden for expected query results.
func main() {
f() // @what call "f"
var ch chan int // @what var "var"
<-ch // @what recv "ch"
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/what/main.golden 0000664 0000000 0000000 00000001631 14177515506 0025464 0 ustar 00root root 0000000 0000000 -------- @what pkgdecl --------
identifier
source file
modes: [definition describe freevars implements pointsto referrers whicherrs]
srcdir: testdata/src
import path: what
-------- @what call --------
identifier
function call
expression statement
block
function declaration
source file
modes: [callees callers callstack definition describe freevars implements pointsto referrers whicherrs]
srcdir: testdata/src
import path: what
-------- @what var --------
variable declaration
variable declaration statement
block
function declaration
source file
modes: [callers callstack describe freevars pointsto whicherrs]
srcdir: testdata/src
import path: what
-------- @what recv --------
identifier
unary <- operation
expression statement
block
function declaration
source file
modes: [callers callstack definition describe freevars implements peers pointsto referrers whicherrs]
srcdir: testdata/src
import path: what
ch
ch
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/whicherrs/ 0000775 0000000 0000000 00000000000 14177515506 0024400 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/whicherrs/main.go 0000664 0000000 0000000 00000000657 14177515506 0025663 0 ustar 00root root 0000000 0000000 package main
type errType string
const constErr errType = "blah"
func (et errType) Error() string {
return string(et)
}
var errVar error = errType("foo")
func genErr(i int) error {
switch i {
case 0:
return constErr
case 1:
return errVar
default:
return nil
}
}
func unreachable() {
err := errVar // @whicherrs func-dead "err"
_ = err
}
func main() {
err := genErr(0) // @whicherrs localerrs "err"
_ = err
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/testdata/src/whicherrs/main.golden 0000664 0000000 0000000 00000000443 14177515506 0026517 0 ustar 00root root 0000000 0000000 -------- @whicherrs func-dead --------
Error: pointer analysis did not find expression (dead code?)
-------- @whicherrs localerrs --------
this error may point to these globals:
errVar
this error may contain these constants:
constErr
this error may contain these dynamic types:
errType
golang-golang-x-tools-0.1.9+ds/cmd/guru/unit_test.go 0000664 0000000 0000000 00000006705 14177515506 0022357 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/build"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
// Unit tests for internal guru functions
func TestIssue17515(t *testing.T) {
// Tests handling of symlinks in function guessImportPath
// If we have Go code inside $HOME/go/src and create a symlink $HOME/src to it
// there are 4 possible cases that need to be tested:
// (1) absolute & absolute: GOPATH=$HOME/go/src file=$HOME/go/src/test/test.go
// (2) absolute & symlink: GOPATH=$HOME/go/src file=$HOME/src/test/test.go
// (3) symlink & symlink: GOPATH=$HOME/src file=$HOME/src/test/test.go
// (4) symlink & absolute: GOPATH=$HOME/src file= $HOME/go/src/test/test.go
// Create a temporary home directory under /tmp
home, err := ioutil.TempDir(os.TempDir(), "home")
if err != nil {
t.Errorf("Unable to create a temporary directory in %s", os.TempDir())
}
defer os.RemoveAll(home)
// create filepath /tmp/home/go/src/test/test.go
if err = os.MkdirAll(home+"/go/src/test", 0755); err != nil {
t.Fatal(err)
}
var buildContext = build.Default
// Success test cases
type SuccessTest struct {
gopath, filename, wantSrcdir string
}
successTests := []SuccessTest{
{home + "/go", home + "/go/src/test/test.go", filepath.FromSlash(home + "/go/src")},
}
// Add symlink cases if not on Windows, Plan 9
if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
// symlink between /tmp/home/go/src and /tmp/home/src
if err := os.Symlink(home+"/go/src", home+"/src"); err != nil {
t.Fatal(err)
}
successTests = append(successTests, []SuccessTest{
{home + "/go", home + "/src/test/test.go", filepath.FromSlash(home + "/go/src")},
{home, home + "/go/src/test/test.go", filepath.FromSlash(home + "/src")},
{home, home + "/src/test/test.go", filepath.FromSlash(home + "/src")},
}...)
}
for _, test := range successTests {
buildContext.GOPATH = test.gopath
srcdir, importPath, err := guessImportPath(test.filename, &buildContext)
if srcdir != test.wantSrcdir || importPath != "test" || err != nil {
t.Errorf("guessImportPath(%q, %q) = %q, %q, %q; want %q, %q, %q",
test.filename, test.gopath, srcdir, importPath, err, test.wantSrcdir, "test", "nil")
}
}
// Function to format expected error message
errFormat := func(fpath string) string {
return fmt.Sprintf("can't evaluate symlinks of %s", fpath)
}
// Failure test cases
type FailTest struct {
gopath, filename, wantErr string
}
failTests := []FailTest{
{home + "/go", home + "/go/src/fake/test.go", errFormat(filepath.FromSlash(home + "/go/src/fake"))},
}
if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
failTests = append(failTests, []FailTest{
{home + "/go", home + "/src/fake/test.go", errFormat(filepath.FromSlash(home + "/src/fake"))},
{home, home + "/src/fake/test.go", errFormat(filepath.FromSlash(home + "/src/fake"))},
{home, home + "/go/src/fake/test.go", errFormat(filepath.FromSlash(home + "/go/src/fake"))},
}...)
}
for _, test := range failTests {
buildContext.GOPATH = test.gopath
srcdir, importPath, err := guessImportPath(test.filename, &buildContext)
if !strings.HasPrefix(fmt.Sprint(err), test.wantErr) {
t.Errorf("guessImportPath(%q, %q) = %q, %q, %q; want %q, %q, %q",
test.filename, test.gopath, srcdir, importPath, err, "", "", test.wantErr)
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/what.go 0000664 0000000 0000000 00000016361 14177515506 0021303 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"fmt"
"go/ast"
"go/build"
"go/token"
"os"
"path"
"path/filepath"
"sort"
"strings"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/ast/astutil"
)
// what reports all the information about the query selection that can be
// obtained from parsing only its containing source file.
// It is intended to be a very low-latency query callable from GUI
// tools, e.g. to populate a menu of options of slower queries about
// the selected location.
//
func what(q *Query) error {
qpos, err := fastQueryPos(q.Build, q.Pos)
if err != nil {
return err
}
// (ignore errors)
srcdir, importPath, _ := guessImportPath(qpos.fset.File(qpos.start).Name(), q.Build)
// Determine which query modes are applicable to the selection.
enable := map[string]bool{
"describe": true, // any syntax; always enabled
}
if qpos.end > qpos.start {
enable["freevars"] = true // nonempty selection?
}
for _, n := range qpos.path {
switch n := n.(type) {
case *ast.Ident:
enable["definition"] = true
enable["referrers"] = true
enable["implements"] = true
case *ast.CallExpr:
enable["callees"] = true
case *ast.FuncDecl:
enable["callers"] = true
enable["callstack"] = true
case *ast.SendStmt:
enable["peers"] = true
case *ast.UnaryExpr:
if n.Op == token.ARROW {
enable["peers"] = true
}
}
// For implements, we approximate findInterestingNode.
if _, ok := enable["implements"]; !ok {
switch n.(type) {
case *ast.ArrayType,
*ast.StructType,
*ast.FuncType,
*ast.InterfaceType,
*ast.MapType,
*ast.ChanType:
enable["implements"] = true
}
}
// For pointsto and whicherrs, we approximate findInterestingNode.
if _, ok := enable["pointsto"]; !ok {
switch n.(type) {
case ast.Stmt,
*ast.ArrayType,
*ast.StructType,
*ast.FuncType,
*ast.InterfaceType,
*ast.MapType,
*ast.ChanType:
// not an expression
enable["pointsto"] = false
enable["whicherrs"] = false
case ast.Expr, ast.Decl, *ast.ValueSpec:
// an expression, maybe
enable["pointsto"] = true
enable["whicherrs"] = true
default:
// Comment, Field, KeyValueExpr, etc: ascend.
}
}
}
// If we don't have an exact selection, disable modes that need one.
if !qpos.exact {
enable["callees"] = false
enable["pointsto"] = false
enable["whicherrs"] = false
enable["describe"] = false
}
var modes []string
for mode := range enable {
modes = append(modes, mode)
}
sort.Strings(modes)
// Find the object referred to by the selection (if it's an
// identifier) and report the position of each identifier
// that refers to the same object.
//
// This may return spurious matches (e.g. struct fields) because
// it uses the best-effort name resolution done by go/parser.
var sameids []token.Pos
var object string
if id, ok := qpos.path[0].(*ast.Ident); ok {
if id.Obj == nil {
// An unresolved identifier is potentially a package name.
// Resolve them with a simple importer (adds ~100µs).
importer := func(imports map[string]*ast.Object, path string) (*ast.Object, error) {
pkg, ok := imports[path]
if !ok {
pkg = &ast.Object{
Kind: ast.Pkg,
Name: filepath.Base(path), // a guess
}
imports[path] = pkg
}
return pkg, nil
}
f := qpos.path[len(qpos.path)-1].(*ast.File)
ast.NewPackage(qpos.fset, map[string]*ast.File{"": f}, importer, nil)
}
if id.Obj != nil {
object = id.Obj.Name
decl := qpos.path[len(qpos.path)-1]
ast.Inspect(decl, func(n ast.Node) bool {
if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
sameids = append(sameids, n.Pos())
}
return true
})
}
}
q.Output(qpos.fset, &whatResult{
path: qpos.path,
srcdir: srcdir,
importPath: importPath,
modes: modes,
object: object,
sameids: sameids,
})
return nil
}
// guessImportPath finds the package containing filename, and returns
// its source directory (an element of $GOPATH) and its import path
// relative to it.
//
// TODO(adonovan): what about _test.go files that are not part of the
// package?
//
func guessImportPath(filename string, buildContext *build.Context) (srcdir, importPath string, err error) {
absFile, err := filepath.Abs(filename)
if err != nil {
return "", "", fmt.Errorf("can't form absolute path of %s: %v", filename, err)
}
absFileDir := filepath.Dir(absFile)
resolvedAbsFileDir, err := filepath.EvalSymlinks(absFileDir)
if err != nil {
return "", "", fmt.Errorf("can't evaluate symlinks of %s: %v", absFileDir, err)
}
segmentedAbsFileDir := segments(resolvedAbsFileDir)
// Find the innermost directory in $GOPATH that encloses filename.
minD := 1024
for _, gopathDir := range buildContext.SrcDirs() {
absDir, err := filepath.Abs(gopathDir)
if err != nil {
continue // e.g. non-existent dir on $GOPATH
}
resolvedAbsDir, err := filepath.EvalSymlinks(absDir)
if err != nil {
continue // e.g. non-existent dir on $GOPATH
}
d := prefixLen(segments(resolvedAbsDir), segmentedAbsFileDir)
// If there are multiple matches,
// prefer the innermost enclosing directory
// (smallest d).
if d >= 0 && d < minD {
minD = d
srcdir = gopathDir
importPath = path.Join(segmentedAbsFileDir[len(segmentedAbsFileDir)-minD:]...)
}
}
if srcdir == "" {
return "", "", fmt.Errorf("directory %s is not beneath any of these GOROOT/GOPATH directories: %s",
filepath.Dir(absFile), strings.Join(buildContext.SrcDirs(), ", "))
}
if importPath == "" {
// This happens for e.g. $GOPATH/src/a.go, but
// "" is not a valid path for (*go/build).Import.
return "", "", fmt.Errorf("cannot load package in root of source directory %s", srcdir)
}
return srcdir, importPath, nil
}
func segments(path string) []string {
return strings.Split(path, string(os.PathSeparator))
}
// prefixLen returns the length of the remainder of y if x is a prefix
// of y, a negative number otherwise.
func prefixLen(x, y []string) int {
d := len(y) - len(x)
if d >= 0 {
for i := range x {
if y[i] != x[i] {
return -1 // not a prefix
}
}
}
return d
}
type whatResult struct {
path []ast.Node
modes []string
srcdir string
importPath string
object string
sameids []token.Pos
}
func (r *whatResult) PrintPlain(printf printfFunc) {
for _, n := range r.path {
printf(n, "%s", astutil.NodeDescription(n))
}
printf(nil, "modes: %s", r.modes)
printf(nil, "srcdir: %s", r.srcdir)
printf(nil, "import path: %s", r.importPath)
for _, pos := range r.sameids {
printf(pos, "%s", r.object)
}
}
func (r *whatResult) JSON(fset *token.FileSet) []byte {
var enclosing []serial.SyntaxNode
for _, n := range r.path {
enclosing = append(enclosing, serial.SyntaxNode{
Description: astutil.NodeDescription(n),
Start: fset.Position(n.Pos()).Offset,
End: fset.Position(n.End()).Offset,
})
}
var sameids []string
for _, pos := range r.sameids {
sameids = append(sameids, fset.Position(pos).String())
}
return toJSON(&serial.What{
Modes: r.modes,
SrcDir: r.srcdir,
ImportPath: r.importPath,
Enclosing: enclosing,
Object: r.object,
SameIDs: sameids,
})
}
golang-golang-x-tools-0.1.9+ds/cmd/guru/whicherrs.go 0000664 0000000 0000000 00000021200 14177515506 0022322 0 ustar 00root root 0000000 0000000 // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
var builtinErrorType = types.Universe.Lookup("error").Type()
// whicherrs takes an position to an error and tries to find all types, constants
// and global value which a given error can point to and which can be checked from the
// scope where the error lives.
// In short, it returns a list of things that can be checked against in order to handle
// an error properly.
//
// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
// can be queried recursively somehow.
func whicherrs(q *Query) error {
lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil {
return err
}
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf)
if err != nil {
return err
}
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil {
return err
}
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
if err != nil {
return err
}
path, action := findInterestingNode(qpos.info, qpos.path)
if action != actionExpr {
return fmt.Errorf("whicherrs wants an expression; got %s",
astutil.NodeDescription(qpos.path[0]))
}
var expr ast.Expr
var obj types.Object
switch n := path[0].(type) {
case *ast.ValueSpec:
// ambiguous ValueSpec containing multiple names
return fmt.Errorf("multiple value specification")
case *ast.Ident:
obj = qpos.info.ObjectOf(n)
expr = n
case ast.Expr:
expr = n
default:
return fmt.Errorf("unexpected AST for expr: %T", n)
}
typ := qpos.info.TypeOf(expr)
if !types.Identical(typ, builtinErrorType) {
return fmt.Errorf("selection is not an expression of type 'error'")
}
// Determine the ssa.Value for the expression.
var value ssa.Value
if obj != nil {
// def/ref of func/var object
value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
} else {
value, _, err = ssaValueForExpr(prog, qpos.info, path)
}
if err != nil {
return err // e.g. trivially dead code
}
// Defer SSA construction till after errors are reported.
prog.Build()
globals := findVisibleErrs(prog, qpos)
constants := findVisibleConsts(prog, qpos)
res := &whicherrsResult{
qpos: qpos,
errpos: expr.Pos(),
}
// TODO(adonovan): the following code is heavily duplicated
// w.r.t. "pointsto". Refactor?
// Find the instruction which initialized the
// global error. If more than one instruction has stored to the global
// remove the global from the set of values that we want to query.
allFuncs := ssautil.AllFunctions(prog)
for fn := range allFuncs {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
store, ok := instr.(*ssa.Store)
if !ok {
continue
}
gval, ok := store.Addr.(*ssa.Global)
if !ok {
continue
}
gbl, ok := globals[gval]
if !ok {
continue
}
// we already found a store to this global
// The normal error define is just one store in the init
// so we just remove this global from the set we want to query
if gbl != nil {
delete(globals, gval)
}
globals[gval] = store.Val
}
}
}
ptaConfig.AddQuery(value)
for _, v := range globals {
ptaConfig.AddQuery(v)
}
ptares := ptrAnalysis(ptaConfig)
valueptr := ptares.Queries[value]
if valueptr == (pointer.Pointer{}) {
return fmt.Errorf("pointer analysis did not find expression (dead code?)")
}
for g, v := range globals {
ptr, ok := ptares.Queries[v]
if !ok {
continue
}
if !ptr.MayAlias(valueptr) {
continue
}
res.globals = append(res.globals, g)
}
pts := valueptr.PointsTo()
dedup := make(map[*ssa.NamedConst]bool)
for _, label := range pts.Labels() {
// These values are either MakeInterfaces or reflect
// generated interfaces. For the purposes of this
// analysis, we don't care about reflect generated ones
makeiface, ok := label.Value().(*ssa.MakeInterface)
if !ok {
continue
}
constval, ok := makeiface.X.(*ssa.Const)
if !ok {
continue
}
c := constants[*constval]
if c != nil && !dedup[c] {
dedup[c] = true
res.consts = append(res.consts, c)
}
}
concs := pts.DynamicTypes()
concs.Iterate(func(conc types.Type, _ interface{}) {
// go/types is a bit annoying here.
// We want to find all the types that we can
// typeswitch or assert to. This means finding out
// if the type pointed to can be seen by us.
//
// For the purposes of this analysis, we care only about
// TypeNames of Named or pointer-to-Named types.
// We ignore other types (e.g. structs) that implement error.
var name *types.TypeName
switch t := conc.(type) {
case *types.Pointer:
named, ok := t.Elem().(*types.Named)
if !ok {
return
}
name = named.Obj()
case *types.Named:
name = t.Obj()
default:
return
}
if !isAccessibleFrom(name, qpos.info.Pkg) {
return
}
res.types = append(res.types, &errorType{conc, name})
})
sort.Sort(membersByPosAndString(res.globals))
sort.Sort(membersByPosAndString(res.consts))
sort.Sort(sorterrorType(res.types))
q.Output(lprog.Fset, res)
return nil
}
// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value {
globals := make(map[*ssa.Global]ssa.Value)
for _, pkg := range prog.AllPackages() {
for _, mem := range pkg.Members {
gbl, ok := mem.(*ssa.Global)
if !ok {
continue
}
gbltype := gbl.Type()
// globals are always pointers
if !types.Identical(deref(gbltype), builtinErrorType) {
continue
}
if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) {
continue
}
globals[gbl] = nil
}
}
return globals
}
// findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst {
constants := make(map[ssa.Const]*ssa.NamedConst)
for _, pkg := range prog.AllPackages() {
for _, mem := range pkg.Members {
obj, ok := mem.(*ssa.NamedConst)
if !ok {
continue
}
consttype := obj.Type()
if !types.AssignableTo(consttype, builtinErrorType) {
continue
}
if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) {
continue
}
constants[*obj.Value] = obj
}
}
return constants
}
type membersByPosAndString []ssa.Member
func (a membersByPosAndString) Len() int { return len(a) }
func (a membersByPosAndString) Less(i, j int) bool {
cmp := a[i].Pos() - a[j].Pos()
return cmp < 0 || cmp == 0 && a[i].String() < a[j].String()
}
func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type sorterrorType []*errorType
func (a sorterrorType) Len() int { return len(a) }
func (a sorterrorType) Less(i, j int) bool {
cmp := a[i].obj.Pos() - a[j].obj.Pos()
return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String()
}
func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type errorType struct {
typ types.Type // concrete type N or *N that implements error
obj *types.TypeName // the named type N
}
type whicherrsResult struct {
qpos *queryPos
errpos token.Pos
globals []ssa.Member
consts []ssa.Member
types []*errorType
}
func (r *whicherrsResult) PrintPlain(printf printfFunc) {
if len(r.globals) > 0 {
printf(r.qpos, "this error may point to these globals:")
for _, g := range r.globals {
printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg))
}
}
if len(r.consts) > 0 {
printf(r.qpos, "this error may contain these constants:")
for _, c := range r.consts {
printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg))
}
}
if len(r.types) > 0 {
printf(r.qpos, "this error may contain these dynamic types:")
for _, t := range r.types {
printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ))
}
}
}
func (r *whicherrsResult) JSON(fset *token.FileSet) []byte {
we := &serial.WhichErrs{}
we.ErrPos = fset.Position(r.errpos).String()
for _, g := range r.globals {
we.Globals = append(we.Globals, fset.Position(g.Pos()).String())
}
for _, c := range r.consts {
we.Constants = append(we.Constants, fset.Position(c.Pos()).String())
}
for _, t := range r.types {
var et serial.WhichErrsType
et.Type = r.qpos.typeString(t.typ)
et.Position = fset.Position(t.obj.Pos()).String()
we.Types = append(we.Types, et)
}
return toJSON(we)
}
golang-golang-x-tools-0.1.9+ds/cmd/html2article/ 0000775 0000000 0000000 00000000000 14177515506 0021412 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/html2article/conv.go 0000664 0000000 0000000 00000015262 14177515506 0022714 0 ustar 00root root 0000000 0000000 // 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 program takes an HTML file and outputs a corresponding article file in
// present format. See: golang.org/x/tools/present
package main // import "golang.org/x/tools/cmd/html2article"
import (
"bytes"
"errors"
"flag"
"fmt"
"io"
"log"
"net/url"
"os"
"regexp"
"strings"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
func main() {
flag.Parse()
err := convert(os.Stdout, os.Stdin)
if err != nil {
log.Fatal(err)
}
}
func convert(w io.Writer, r io.Reader) error {
root, err := html.Parse(r)
if err != nil {
return err
}
style := find(root, isTag(atom.Style))
if err := parseStyles(style); err != nil {
log.Printf("couldn't parse all styles: %v", err)
}
body := find(root, isTag(atom.Body))
if body == nil {
return errors.New("couldn't find body")
}
article := limitNewlineRuns(makeHeadings(strings.TrimSpace(text(body))))
_, err = fmt.Fprintf(w, "Title\n\n%s", article)
return err
}
type Style string
const (
Bold Style = "*"
Italic Style = "_"
Code Style = "`"
)
var cssRules = make(map[string]Style)
func parseStyles(style *html.Node) error {
if style == nil || style.FirstChild == nil {
return errors.New("couldn't find styles")
}
styles := style.FirstChild.Data
readUntil := func(end rune) (string, bool) {
i := strings.IndexRune(styles, end)
if i < 0 {
return "", false
}
s := styles[:i]
styles = styles[i:]
return s, true
}
for {
sel, ok := readUntil('{')
if !ok && sel == "" {
break
} else if !ok {
return fmt.Errorf("could not parse selector %q", styles)
}
value, ok := readUntil('}')
if !ok {
return fmt.Errorf("couldn't parse style body for %s", sel)
}
switch {
case strings.Contains(value, "italic"):
cssRules[sel] = Italic
case strings.Contains(value, "bold"):
cssRules[sel] = Bold
case strings.Contains(value, "Consolas") || strings.Contains(value, "Courier New"):
cssRules[sel] = Code
}
}
return nil
}
var newlineRun = regexp.MustCompile(`\n\n+`)
func limitNewlineRuns(s string) string {
return newlineRun.ReplaceAllString(s, "\n\n")
}
func makeHeadings(body string) string {
buf := new(bytes.Buffer)
lines := strings.Split(body, "\n")
for i, s := range lines {
if i == 0 && !isBoldTitle(s) {
buf.WriteString("* Introduction\n\n")
}
if isBoldTitle(s) {
s = strings.TrimSpace(strings.Replace(s, "*", " ", -1))
s = "* " + s
}
buf.WriteString(s)
buf.WriteByte('\n')
}
return buf.String()
}
func isBoldTitle(s string) bool {
return !strings.Contains(s, " ") &&
strings.HasPrefix(s, "*") &&
strings.HasSuffix(s, "*")
}
func indent(buf *bytes.Buffer, s string) {
for _, l := range strings.Split(s, "\n") {
if l != "" {
buf.WriteByte('\t')
buf.WriteString(l)
}
buf.WriteByte('\n')
}
}
func unwrap(buf *bytes.Buffer, s string) {
var cont bool
for _, l := range strings.Split(s, "\n") {
l = strings.TrimSpace(l)
if len(l) == 0 {
if cont {
buf.WriteByte('\n')
buf.WriteByte('\n')
}
cont = false
} else {
if cont {
buf.WriteByte(' ')
}
buf.WriteString(l)
cont = true
}
}
}
func text(n *html.Node) string {
var buf bytes.Buffer
walk(n, func(n *html.Node) bool {
switch n.Type {
case html.TextNode:
buf.WriteString(n.Data)
return false
case html.ElementNode:
// no-op
default:
return true
}
a := n.DataAtom
if a == atom.Span {
switch {
case hasStyle(Code)(n):
a = atom.Code
case hasStyle(Bold)(n):
a = atom.B
case hasStyle(Italic)(n):
a = atom.I
}
}
switch a {
case atom.Br:
buf.WriteByte('\n')
case atom.P:
unwrap(&buf, childText(n))
buf.WriteString("\n\n")
case atom.Li:
buf.WriteString("- ")
unwrap(&buf, childText(n))
buf.WriteByte('\n')
case atom.Pre:
indent(&buf, childText(n))
buf.WriteByte('\n')
case atom.A:
href, text := attr(n, "href"), childText(n)
// Skip links with no text.
if strings.TrimSpace(text) == "" {
break
}
// Don't emit empty links.
if strings.TrimSpace(href) == "" {
buf.WriteString(text)
break
}
// Use original url for Google Docs redirections.
if u, err := url.Parse(href); err != nil {
log.Printf("parsing url %q: %v", href, err)
} else if u.Host == "www.google.com" && u.Path == "/url" {
href = u.Query().Get("q")
}
fmt.Fprintf(&buf, "[[%s][%s]]", href, text)
case atom.Code:
buf.WriteString(highlight(n, "`"))
case atom.B:
buf.WriteString(highlight(n, "*"))
case atom.I:
buf.WriteString(highlight(n, "_"))
case atom.Img:
src := attr(n, "src")
fmt.Fprintf(&buf, ".image %s\n", src)
case atom.Iframe:
src, w, h := attr(n, "src"), attr(n, "width"), attr(n, "height")
fmt.Fprintf(&buf, "\n.iframe %s %s %s\n", src, h, w)
case atom.Param:
if attr(n, "name") == "movie" {
// Old style YouTube embed.
u := attr(n, "value")
u = strings.Replace(u, "/v/", "/embed/", 1)
if i := strings.Index(u, "&"); i >= 0 {
u = u[:i]
}
fmt.Fprintf(&buf, "\n.iframe %s 540 304\n", u)
}
case atom.Title:
default:
return true
}
return false
})
return buf.String()
}
func childText(node *html.Node) string {
var buf bytes.Buffer
for n := node.FirstChild; n != nil; n = n.NextSibling {
fmt.Fprint(&buf, text(n))
}
return buf.String()
}
func highlight(node *html.Node, char string) string {
t := strings.Replace(childText(node), " ", char, -1)
return fmt.Sprintf("%s%s%s", char, t, char)
}
type selector func(*html.Node) bool
func isTag(a atom.Atom) selector {
return func(n *html.Node) bool {
return n.DataAtom == a
}
}
func hasClass(name string) selector {
return func(n *html.Node) bool {
for _, a := range n.Attr {
if a.Key == "class" {
for _, c := range strings.Fields(a.Val) {
if c == name {
return true
}
}
}
}
return false
}
}
func hasStyle(s Style) selector {
return func(n *html.Node) bool {
for rule, s2 := range cssRules {
if s2 != s {
continue
}
if strings.HasPrefix(rule, ".") && hasClass(rule[1:])(n) {
return true
}
if n.DataAtom.String() == rule {
return true
}
}
return false
}
}
func attr(node *html.Node, key string) (value string) {
for _, attr := range node.Attr {
if attr.Key == key {
return attr.Val
}
}
return ""
}
func find(n *html.Node, fn selector) *html.Node {
var result *html.Node
walk(n, func(n *html.Node) bool {
if result != nil {
return false
}
if fn(n) {
result = n
return false
}
return true
})
return result
}
func walk(n *html.Node, fn selector) {
if fn(n) {
for c := n.FirstChild; c != nil; c = c.NextSibling {
walk(c, fn)
}
}
}
golang-golang-x-tools-0.1.9+ds/cmd/present/ 0000775 0000000 0000000 00000000000 14177515506 0020500 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/present/dir.go 0000664 0000000 0000000 00000012404 14177515506 0021606 0 ustar 00root root 0000000 0000000 // Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"html/template"
"io"
"log"
"net"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"golang.org/x/tools/present"
)
func init() {
http.HandleFunc("/", dirHandler)
}
// dirHandler serves a directory listing for the requested path, rooted at *contentPath.
func dirHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/favicon.ico" {
http.NotFound(w, r)
return
}
name := filepath.Join(*contentPath, r.URL.Path)
if isDoc(name) {
err := renderDoc(w, name)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
if isDir, err := dirList(w, name); err != nil {
addr, _, e := net.SplitHostPort(r.RemoteAddr)
if e != nil {
addr = r.RemoteAddr
}
log.Printf("request from %s: %s", addr, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} else if isDir {
return
}
http.FileServer(http.Dir(*contentPath)).ServeHTTP(w, r)
}
func isDoc(path string) bool {
_, ok := contentTemplate[filepath.Ext(path)]
return ok
}
var (
// dirListTemplate holds the front page template.
dirListTemplate *template.Template
// contentTemplate maps the presentable file extensions to the
// template to be executed.
contentTemplate map[string]*template.Template
)
func initTemplates(base string) error {
// Locate the template file.
actionTmpl := filepath.Join(base, "templates/action.tmpl")
contentTemplate = make(map[string]*template.Template)
for ext, contentTmpl := range map[string]string{
".slide": "slides.tmpl",
".article": "article.tmpl",
} {
contentTmpl = filepath.Join(base, "templates", contentTmpl)
// Read and parse the input.
tmpl := present.Template()
tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
if _, err := tmpl.ParseFiles(actionTmpl, contentTmpl); err != nil {
return err
}
contentTemplate[ext] = tmpl
}
var err error
dirListTemplate, err = template.ParseFiles(filepath.Join(base, "templates/dir.tmpl"))
return err
}
// renderDoc reads the present file, gets its template representation,
// and executes the template, sending output to w.
func renderDoc(w io.Writer, docFile string) error {
// Read the input and build the doc structure.
doc, err := parse(docFile, 0)
if err != nil {
return err
}
// Find which template should be executed.
tmpl := contentTemplate[filepath.Ext(docFile)]
// Execute the template.
return doc.Render(w, tmpl)
}
func parse(name string, mode present.ParseMode) (*present.Doc, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return present.Parse(f, name, mode)
}
// dirList scans the given path and writes a directory listing to w.
// It parses the first part of each .slide file it encounters to display the
// presentation title in the listing.
// If the given path is not a directory, it returns (isDir == false, err == nil)
// and writes nothing to w.
func dirList(w io.Writer, name string) (isDir bool, err error) {
f, err := os.Open(name)
if err != nil {
return false, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return false, err
}
if isDir = fi.IsDir(); !isDir {
return false, nil
}
fis, err := f.Readdir(0)
if err != nil {
return false, err
}
strippedPath := strings.TrimPrefix(name, filepath.Clean(*contentPath))
strippedPath = strings.TrimPrefix(strippedPath, "/")
d := &dirListData{Path: strippedPath}
for _, fi := range fis {
// skip the golang.org directory
if name == "." && fi.Name() == "golang.org" {
continue
}
e := dirEntry{
Name: fi.Name(),
Path: filepath.ToSlash(filepath.Join(strippedPath, fi.Name())),
}
if fi.IsDir() && showDir(e.Name) {
d.Dirs = append(d.Dirs, e)
continue
}
if isDoc(e.Name) {
fn := filepath.ToSlash(filepath.Join(name, fi.Name()))
if p, err := parse(fn, present.TitlesOnly); err != nil {
log.Printf("parse(%q, present.TitlesOnly): %v", fn, err)
} else {
e.Title = p.Title
}
switch filepath.Ext(e.Path) {
case ".article":
d.Articles = append(d.Articles, e)
case ".slide":
d.Slides = append(d.Slides, e)
}
} else if showFile(e.Name) {
d.Other = append(d.Other, e)
}
}
if d.Path == "." {
d.Path = ""
}
sort.Sort(d.Dirs)
sort.Sort(d.Slides)
sort.Sort(d.Articles)
sort.Sort(d.Other)
return true, dirListTemplate.Execute(w, d)
}
// showFile reports whether the given file should be displayed in the list.
func showFile(n string) bool {
switch filepath.Ext(n) {
case ".pdf":
case ".html":
case ".go":
default:
return isDoc(n)
}
return true
}
// showDir reports whether the given directory should be displayed in the list.
func showDir(n string) bool {
if len(n) > 0 && (n[0] == '.' || n[0] == '_') || n == "present" {
return false
}
return true
}
type dirListData struct {
Path string
Dirs, Slides, Articles, Other dirEntrySlice
}
type dirEntry struct {
Name, Path, Title string
}
type dirEntrySlice []dirEntry
func (s dirEntrySlice) Len() int { return len(s) }
func (s dirEntrySlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s dirEntrySlice) Less(i, j int) bool { return s[i].Name < s[j].Name }
golang-golang-x-tools-0.1.9+ds/cmd/present/doc.go 0000664 0000000 0000000 00000003172 14177515506 0021577 0 ustar 00root root 0000000 0000000 // 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.
/*
Present displays slide presentations and articles. It runs a web server that
presents slide and article files from the current directory.
It may be run as a stand-alone command or an App Engine app.
The setup of the Go version of NaCl is documented at:
https://golang.org/wiki/NativeClient
To use with App Engine, copy the files in the tools/cmd/present directory to the
root of your application and create an app.yaml file similar to this:
runtime: go111
handlers:
- url: /favicon.ico
static_files: static/favicon.ico
upload: static/favicon.ico
- url: /static
static_dir: static
- url: /.*
script: auto
# nobuild_files is a regexp that identifies which files to not build. It
# is useful for embedding static assets like code snippets and preventing
# them from producing build errors for your project.
nobuild_files: [path regexp for talk materials]
When running on App Engine, content will be served from the ./content/
subdirectory.
Present then can be tested in a local App Engine environment with
GAE_ENV=standard go run .
And deployed using
gcloud app deploy
Input files are named foo.extension, where "extension" defines the format of
the generated output. The supported formats are:
.slide // HTML5 slide presentation
.article // article format, such as a blog post
The present file format is documented by the present package:
https://pkg.go.dev/golang.org/x/tools/present
*/
package main
golang-golang-x-tools-0.1.9+ds/cmd/present/main.go 0000664 0000000 0000000 00000010501 14177515506 0021750 0 ustar 00root root 0000000 0000000 // 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.
package main
import (
"flag"
"fmt"
"go/build"
"log"
"net"
"net/http"
"net/url"
"os"
"strings"
"golang.org/x/tools/present"
)
const basePkg = "golang.org/x/tools/cmd/present"
var (
httpAddr = flag.String("http", "127.0.0.1:3999", "HTTP service address (e.g., '127.0.0.1:3999')")
originHost = flag.String("orighost", "", "host component of web origin URL (e.g., 'localhost')")
basePath = flag.String("base", "", "base path for slide template and static resources")
contentPath = flag.String("content", ".", "base path for presentation content")
usePlayground = flag.Bool("use_playground", false, "run code snippets using play.golang.org; if false, run them locally and deliver results by WebSocket transport")
nativeClient = flag.Bool("nacl", false, "use Native Client environment playground (prevents non-Go code execution) when using local WebSocket transport")
)
func main() {
flag.BoolVar(&present.PlayEnabled, "play", true, "enable playground (permit execution of arbitrary user code)")
flag.BoolVar(&present.NotesEnabled, "notes", false, "enable presenter notes (press 'N' from the browser to display them)")
flag.Parse()
if os.Getenv("GAE_ENV") == "standard" {
log.Print("Configuring for App Engine Standard")
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
*httpAddr = fmt.Sprintf("0.0.0.0:%s", port)
pwd, err := os.Getwd()
if err != nil {
log.Fatalf("Couldn't get pwd: %v\n", err)
}
*basePath = pwd
*usePlayground = true
*contentPath = "./content/"
}
if *basePath == "" {
p, err := build.Default.Import(basePkg, "", build.FindOnly)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't find gopresent files: %v\n", err)
fmt.Fprintf(os.Stderr, basePathMessage, basePkg)
os.Exit(1)
}
*basePath = p.Dir
}
err := initTemplates(*basePath)
if err != nil {
log.Fatalf("Failed to parse templates: %v", err)
}
ln, err := net.Listen("tcp", *httpAddr)
if err != nil {
log.Fatal(err)
}
defer ln.Close()
_, port, err := net.SplitHostPort(ln.Addr().String())
if err != nil {
log.Fatal(err)
}
origin := &url.URL{Scheme: "http"}
if *originHost != "" {
if strings.HasPrefix(*originHost, "https://") {
*originHost = strings.TrimPrefix(*originHost, "https://")
origin.Scheme = "https"
}
*originHost = strings.TrimPrefix(*originHost, "http://")
origin.Host = net.JoinHostPort(*originHost, port)
} else if ln.Addr().(*net.TCPAddr).IP.IsUnspecified() {
name, _ := os.Hostname()
origin.Host = net.JoinHostPort(name, port)
} else {
reqHost, reqPort, err := net.SplitHostPort(*httpAddr)
if err != nil {
log.Fatal(err)
}
if reqPort == "0" {
origin.Host = net.JoinHostPort(reqHost, port)
} else {
origin.Host = *httpAddr
}
}
initPlayground(*basePath, origin)
http.Handle("/static/", http.FileServer(http.Dir(*basePath)))
if !ln.Addr().(*net.TCPAddr).IP.IsLoopback() &&
present.PlayEnabled && !*nativeClient && !*usePlayground {
log.Print(localhostWarning)
}
log.Printf("Open your web browser and visit %s", origin.String())
if present.NotesEnabled {
log.Println("Notes are enabled, press 'N' from the browser to display them.")
}
log.Fatal(http.Serve(ln, nil))
}
func environ(vars ...string) []string {
env := os.Environ()
for _, r := range vars {
k := strings.SplitAfter(r, "=")[0]
var found bool
for i, v := range env {
if strings.HasPrefix(v, k) {
env[i] = r
found = true
}
}
if !found {
env = append(env, r)
}
}
return env
}
const basePathMessage = `
By default, gopresent locates the slide template files and associated
static content by looking for a %q package
in your Go workspaces (GOPATH).
You may use the -base flag to specify an alternate location.
`
const localhostWarning = `
WARNING! WARNING! WARNING!
The present server appears to be listening on an address that is not localhost
and is configured to run code snippets locally. Anyone with access to this address
and port will have access to this machine as the user running present.
To avoid this message, listen on localhost, run with -play=false, or run with
-play_socket=false.
If you don't understand this message, hit Control-C to terminate this process.
WARNING! WARNING! WARNING!
`
golang-golang-x-tools-0.1.9+ds/cmd/present/play.go 0000664 0000000 0000000 00000004756 14177515506 0022010 0 ustar 00root root 0000000 0000000 // Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path/filepath"
"runtime"
"time"
"golang.org/x/tools/godoc/static"
"golang.org/x/tools/playground/socket"
"golang.org/x/tools/present"
// This will register a handler at /compile that will proxy to the
// respective endpoints at play.golang.org. This allows the frontend to call
// these endpoints without needing cross-origin request sharing (CORS).
// Note that this is imported regardless of whether the endpoints are used or
// not (in the case of a local socket connection, they are not called).
_ "golang.org/x/tools/playground"
)
var scripts = []string{"jquery.js", "jquery-ui.js", "playground.js", "play.js"}
// playScript registers an HTTP handler at /play.js that serves all the
// scripts specified by the variable above, and appends a line that
// initializes the playground with the specified transport.
func playScript(root, transport string) {
modTime := time.Now()
var buf bytes.Buffer
for _, p := range scripts {
if s, ok := static.Files[p]; ok {
buf.WriteString(s)
continue
}
b, err := ioutil.ReadFile(filepath.Join(root, "static", p))
if err != nil {
panic(err)
}
buf.Write(b)
}
fmt.Fprintf(&buf, "\ninitPlayground(new %v());\n", transport)
b := buf.Bytes()
http.HandleFunc("/play.js", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "application/javascript")
http.ServeContent(w, r, "", modTime, bytes.NewReader(b))
})
}
func initPlayground(basepath string, origin *url.URL) {
if !present.PlayEnabled {
return
}
if *usePlayground {
playScript(basepath, "HTTPTransport")
return
}
if *nativeClient {
// When specifying nativeClient, non-Go code cannot be executed
// because the NaCl setup doesn't support doing so.
socket.RunScripts = false
socket.Environ = func() []string {
if runtime.GOARCH == "amd64" {
return environ("GOOS=nacl", "GOARCH=amd64p32")
}
return environ("GOOS=nacl")
}
}
playScript(basepath, "SocketTransport")
http.Handle("/socket", socket.NewHandler(origin))
}
func playable(c present.Code) bool {
play := present.PlayEnabled && c.Play
// Restrict playable files to only Go source files when using play.golang.org,
// since there is no method to execute shell scripts there.
if *usePlayground {
return play && c.Ext == ".go"
}
return play
}
golang-golang-x-tools-0.1.9+ds/cmd/present/static/ 0000775 0000000 0000000 00000000000 14177515506 0021767 5 ustar 00root root 0000000 0000000 golang-golang-x-tools-0.1.9+ds/cmd/present/static/article.css 0000664 0000000 0000000 00000004364 14177515506 0024133 0 ustar 00root root 0000000 0000000 body {
margin: 0;
font-family: Helvetica, Arial, sans-serif;
font-size: 16px;
}
pre,
code {
font-family: Menlo, monospace;
font-size: 14px;
}
pre {
line-height: 18px;
margin: 0;
padding: 0;
}
a {
color: #375eab;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
p,
ul,
ol {
margin: 20px;
}
h1,
h2,
h3,
h4 {
margin: 20px 0;
padding: 0;
color: #375eab;
font-weight: bold;
}
h1 {
font-size: 18px;
padding: 2px 5px;
}
h2 {
font-size: 16px;
}
h3 {
font-size: 16px;
}
h3,
h4 {
margin: 20px 5px;
}
h4 {
font-size: 16px;
}
div#heading {
margin: 0 0 10px 0;
padding: 21px 0;
font-size: 20px;
font-weight: bold;
}
div#heading .author {
padding-top: 10px;
font-size: 14px;
font-weight: normal;
}
div#topbar {
}
body {
text-align: center;
}
div#page {
width: 100%;
}
div#page > .container,
div#topbar > .container {
text-align: left;
margin-left: auto;
margin-right: auto;
padding: 0 20px;
width: 900px;
}
div#page.wide > .container,
div#topbar.wide > .container {
width: auto;
}
div#footer {
text-align: center;
color: #666;
font-size: 14px;
margin: 40px 0;
}
.author p {
margin: 0;
padding: 0 20px;
}
div.code,
div.output {
margin: 20px 20px 20px 40px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
div.output {
padding: 10px;
}
div.code {
background: white;
}
div.output {
background: black;
}
div.output .stdout {
color: #e6e6e6;
}
div.output .stderr {
color: rgb(244, 74, 63);
}
div.output .system {
color: rgb(255, 209, 77);
}
.buttons {
margin-left: 20px;
}
div.output .buttons {
margin-left: 0;
margin-bottom: 10px;
}
#toc {
float: right;
margin: 0px 10px;
padding: 10px;
border: 1px solid #e5ecf9;
background-color: #eee;
box-shadow: 3px 3px 2px #888888;
max-width: 33%;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
#tochead {
font-weight: bold;
font-variant: small-caps;
font-size: 100%;
text-align: center;
padding-bottom: 5px;
}
#toc ul,
#toc a {
list-style-type: none;
padding-left: 0px;
color: black;
margin: 0px;
}
ul.toc-inner a {
padding-left: 10px !important;
}
@media print {
.no-print,
.no-print * {
display: none !important;
}
}
golang-golang-x-tools-0.1.9+ds/cmd/present/static/dir.css 0000664 0000000 0000000 00000004534 14177515506 0023265 0 ustar 00root root 0000000 0000000 /* copied from $GOROOT/doc/style.css */
body {
margin: 0;
font-family: Helvetica, Arial, sans-serif;
font-size: 16px;
}
pre,
code {
font-family: Menlo, monospace;
font-size: 14px;
}
pre {
line-height: 18px;
}
pre .comment {
color: #375eab;
}
pre .highlight,
pre .highlight-comment,
pre .selection-highlight,
pre .selection-highlight-comment {
background: #ffff00;
}
pre .selection,
pre .selection-comment {
background: #ff9632;
}
pre .ln {
color: #999;
}
body {
color: #222;
}
a,
.exampleHeading .text {
color: #375eab;
text-decoration: none;
}
a:hover,
.exampleHeading .text:hover {
text-decoration: underline;
}
p,
pre,
ul,
ol {
margin: 20px;
}
pre {
background: #e9e9e9;
padding: 10px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
h1,
h2,
h3,
h4,
.rootHeading {
margin: 20px 0;
padding: 0;
color: #375eab;
font-weight: bold;
}
h1 {
font-size: 24px;
}
h2 {
font-size: 20px;
background: #e0ebf5;
padding: 2px 5px;
}
h3 {
font-size: 20px;
}
h3,
h4 {
margin: 20px 5px;
}
h4 {
font-size: 16px;
}
dl {
margin: 20px;
}
dd {
margin: 2px 20px;
}
dl,
dd {
font-size: 14px;
}
div#nav table td {
vertical-align: top;
}
div#heading {
float: left;
margin: 0 0 10px 0;
padding: 21px 0;
font-size: 20px;
font-weight: normal;
}
div#heading a {
color: #222;
text-decoration: none;
}
div#topbar {
background: #e0ebf5;
height: 64px;
}
body {
text-align: center;
}
div#page,
div#topbar > .container {
clear: both;
text-align: left;
margin-left: auto;
margin-right: auto;
padding: 0 20px;
width: 900px;
}
div#page.wide,
div#topbar > .wide {
width: auto;
}
div#plusone {
float: right;
}
div#footer {
color: #666;
font-size: 14px;
margin: 40px 0;
}
div#menu > a,
div#menu > input {
padding: 10px;
text-decoration: none;
font-size: 16px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
div#menu > a,
div#menu > input {
border: 1px solid #375eab;
}
div#menu > a {
color: white;
background: #375eab;
}
div#menu {
float: right;
min-width: 590px;
padding: 10px 0;
text-align: right;
}
div#menu > a {
margin-right: 5px;
margin-bottom: 10px;
padding: 10px;
}
div#menu > input {
position: relative;
top: 1px;
width: 60px;
background: white;
color: #222;
}
div#menu > input.inactive {
color: #999;
}
golang-golang-x-tools-0.1.9+ds/cmd/present/static/dir.js 0000664 0000000 0000000 00000002031 14177515506 0023077 0 ustar 00root root 0000000 0000000 // Copyright 2012 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.
// copied from $GOROOT/doc/godocs.js
function bindEvent(el, e, fn) {
if (el.addEventListener) {
el.addEventListener(e, fn, false);
} else if (el.attachEvent) {
el.attachEvent('on' + e, fn);
}
}
function godocs_bindSearchEvents() {
var search = document.getElementById('search');
if (!search) {
// no search box (index disabled)
return;
}
function clearInactive() {
if (search.className == 'inactive') {
search.value = '';
search.className = '';
}
}
function restoreInactive() {
if (search.value !== '') {
return;
}
if (search.type != 'search') {
search.value = search.getAttribute('placeholder');
}
search.className = 'inactive';
}
restoreInactive();
bindEvent(search, 'focus', clearInactive);
bindEvent(search, 'blur', restoreInactive);
}
bindEvent(window, 'load', godocs_bindSearchEvents);
golang-golang-x-tools-0.1.9+ds/cmd/present/static/favicon.ico 0000664 0000000 0000000 00000001421 14177515506 0024106 0 ustar 00root root 0000000 0000000 PNG
IHDR sO/ gAMA OX2 tEXtSoftware Adobe ImageReadyqe<