Try and use packaged go.tools

This commit is contained in:
Sébastien Delafond
2014-07-10 00:29:40 +02:00
parent 991aa67efb
commit 7f8edee078
524 changed files with 9 additions and 108303 deletions
+7
View File
@@ -1,3 +1,10 @@
aptly (0.5-4) unstable; urgency=medium
* Collect licenses by going over files one by one
* Use packaged golang-go.tools
-- Sebastien Delafond <seb@debian.org> Thu, 10 Jul 2014 00:49:09 +0200
aptly (0.5-3) unstable; urgency=low
* Licensing:
+1 -1
View File
@@ -2,7 +2,7 @@ Source: aptly
Section: utils
Priority: extra
Maintainer: Sebastien Delafond <seb@debian.org>
Build-Depends: debhelper (>= 9.0.0), golang (>= 1.1)
Build-Depends: debhelper (>= 9.0.0), golang (>= 1.1), golang-go.tools
Standards-Version: 3.9.5
Homepage: http://www.aptly.info
Vcs-Git: https://github.com/smira/aptly.git
+1 -17
View File
@@ -42,26 +42,10 @@ Files: _vendor/src/github.com/syndtr/goleveldb/leveldb/util/buffer*.go
Copyright: 2009 The Go Authors
License: BSD-3-clause
Files: _vendor/src/code.google.com/p/go.crypto/* _vendor/src/code.google.com/p/go.tools/* _vendor/src/github.com/smira/flag/* _vendor/src/github.com/golang/lint/* _vendor/src/code.google.com/p/gographviz/scanner/scanner.go
Files: _vendor/src/code.google.com/p/go.crypto/* _vendor/src/github.com/smira/flag/* _vendor/src/github.com/golang/lint/* _vendor/src/code.google.com/p/gographviz/scanner/scanner.go
Copyright: 2009 The Go Authors
License: BSD-3-clause
Files: _vendor/src/code.google.com/p/go.tools/godoc/static/godoc.html
License: CC-BY-3.0
Files: _vendor/src/code.google.com/p/go.tools/godoc/static/jquery.js
Copyright: 2005, 2014 jQuery Foundation and other contributors
License: MIT
Files: _vendor/src/code.google.com/p/go.tools/godoc/static/jquery.treeview.js
Copyright: 2007 Jörn Zaefferer
License: Dual MIT and GPL
See: /usr/share/common-licenses/GPL-1
Files: _vendor/src/code.google.com/p/go.tools/godoc/static/static.go
Copyright: 2009 The Go Authors
License: CC-BY-3.0
Files: _vendor/src/code.google.com/p/go-uuid/uuid/*
Copyright: 2009 Google Inc.
License: BSD-3-clause
@@ -1,3 +0,0 @@
# Add no patterns to .hgignore except for files generated by the build.
syntax:glob
last-change
@@ -1,3 +0,0 @@
# 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.
@@ -1,3 +0,0 @@
# 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.
@@ -1,27 +0,0 @@
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.
@@ -1,22 +0,0 @@
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.
@@ -1,10 +0,0 @@
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 oracle and the test coverage tool, can be fetched with "go get".
Packages include a type-checker for Go and an implementation of the
Static Single Assignment form (SSA) representation for Go programs.
To submit changes to this repository, see http://golang.org/doc/contribute.html.
@@ -1,625 +0,0 @@
// 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 astutil
// This file defines utilities for working with source positions.
import (
"fmt"
"go/ast"
"go/token"
"sort"
)
// PathEnclosingInterval returns the node that encloses the source
// interval [start, end), and all its ancestors up to the AST root.
//
// The definition of "enclosing" used by this function considers
// additional whitespace abutting a node to be enclosed by it.
// In this example:
//
// z := x + y // add them
// <-A->
// <----B----->
//
// the ast.BinaryExpr(+) node is considered to enclose interval B
// even though its [Pos()..End()) is actually only interval A.
// This behaviour makes user interfaces more tolerant of imperfect
// input.
//
// This function treats tokens as nodes, though they are not included
// in the result. e.g. PathEnclosingInterval("+") returns the
// enclosing ast.BinaryExpr("x + y").
//
// If start==end, the 1-char interval following start is used instead.
//
// The 'exact' result is true if the interval contains only path[0]
// and perhaps some adjacent whitespace. It is false if the interval
// overlaps multiple children of path[0], or if it contains only
// interior whitespace of path[0].
// In this example:
//
// z := x + y // add them
// <--C--> <---E-->
// ^
// D
//
// intervals C, D and E are inexact. C is contained by the
// z-assignment statement, because it spans three of its children (:=,
// x, +). So too is the 1-char interval D, because it contains only
// interior whitespace of the assignment. E is considered interior
// whitespace of the BlockStmt containing the assignment.
//
// Precondition: [start, end) both lie within the same file as root.
// TODO(adonovan): return (nil, false) in this case and remove precond.
// Requires FileSet; see loader.tokenFileContainsPos.
//
// Postcondition: path is never nil; it always contains at least 'root'.
//
func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) {
// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
var visit func(node ast.Node) bool
visit = func(node ast.Node) bool {
path = append(path, node)
nodePos := node.Pos()
nodeEnd := node.End()
// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
// Intersect [start, end) with interval of node.
if start < nodePos {
start = nodePos
}
if end > nodeEnd {
end = nodeEnd
}
// Find sole child that contains [start, end).
children := childrenOf(node)
l := len(children)
for i, child := range children {
// [childPos, childEnd) is unaugmented interval of child.
childPos := child.Pos()
childEnd := child.End()
// [augPos, augEnd) is whitespace-augmented interval of child.
augPos := childPos
augEnd := childEnd
if i > 0 {
augPos = children[i-1].End() // start of preceding whitespace
}
if i < l-1 {
nextChildPos := children[i+1].Pos()
// Does [start, end) lie between child and next child?
if start >= augEnd && end <= nextChildPos {
return false // inexact match
}
augEnd = nextChildPos // end of following whitespace
}
// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
// i, augPos, augEnd, start, end) // debugging
// Does augmented child strictly contain [start, end)?
if augPos <= start && end <= augEnd {
_, isToken := child.(tokenNode)
return isToken || visit(child)
}
// Does [start, end) overlap multiple children?
// i.e. left-augmented child contains start
// but LR-augmented child does not contain end.
if start < childEnd && end > augEnd {
break
}
}
// No single child contained [start, end),
// so node is the result. Is it exact?
// (It's tempting to put this condition before the
// child loop, but it gives the wrong result in the
// case where a node (e.g. ExprStmt) and its sole
// child have equal intervals.)
if start == nodePos && end == nodeEnd {
return true // exact match
}
return false // inexact: overlaps multiple children
}
if start > end {
start, end = end, start
}
if start < root.End() && end > root.Pos() {
if start == end {
end = start + 1 // empty interval => interval of size 1
}
exact = visit(root)
// Reverse the path:
for i, l := 0, len(path); i < l/2; i++ {
path[i], path[l-1-i] = path[l-1-i], path[i]
}
} else {
// Selection lies within whitespace preceding the
// first (or following the last) declaration in the file.
// The result nonetheless always includes the ast.File.
path = append(path, root)
}
return
}
// tokenNode is a dummy implementation of ast.Node for a single token.
// They are used transiently by PathEnclosingInterval but never escape
// this package.
//
type tokenNode struct {
pos token.Pos
end token.Pos
}
func (n tokenNode) Pos() token.Pos {
return n.pos
}
func (n tokenNode) End() token.Pos {
return n.end
}
func tok(pos token.Pos, len int) ast.Node {
return tokenNode{pos, pos + token.Pos(len)}
}
// childrenOf returns the direct non-nil children of ast.Node n.
// It may include fake ast.Node implementations for bare tokens.
// it is not safe to call (e.g.) ast.Walk on such nodes.
//
func childrenOf(n ast.Node) []ast.Node {
var children []ast.Node
// First add nodes for all true subtrees.
ast.Inspect(n, func(node ast.Node) bool {
if node == n { // push n
return true // recur
}
if node != nil { // push child
children = append(children, node)
}
return false // no recursion
})
// Then add fake Nodes for bare tokens.
switch n := n.(type) {
case *ast.ArrayType:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Elt.End(), len("]")))
case *ast.AssignStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.BasicLit:
children = append(children,
tok(n.ValuePos, len(n.Value)))
case *ast.BinaryExpr:
children = append(children, tok(n.OpPos, len(n.Op.String())))
case *ast.BlockStmt:
children = append(children,
tok(n.Lbrace, len("{")),
tok(n.Rbrace, len("}")))
case *ast.BranchStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.CallExpr:
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
if n.Ellipsis != 0 {
children = append(children, tok(n.Ellipsis, len("...")))
}
case *ast.CaseClause:
if n.List == nil {
children = append(children,
tok(n.Case, len("default")))
} else {
children = append(children,
tok(n.Case, len("case")))
}
children = append(children, tok(n.Colon, len(":")))
case *ast.ChanType:
switch n.Dir {
case ast.RECV:
children = append(children, tok(n.Begin, len("<-chan")))
case ast.SEND:
children = append(children, tok(n.Begin, len("chan<-")))
case ast.RECV | ast.SEND:
children = append(children, tok(n.Begin, len("chan")))
}
case *ast.CommClause:
if n.Comm == nil {
children = append(children,
tok(n.Case, len("default")))
} else {
children = append(children,
tok(n.Case, len("case")))
}
children = append(children, tok(n.Colon, len(":")))
case *ast.Comment:
// nop
case *ast.CommentGroup:
// nop
case *ast.CompositeLit:
children = append(children,
tok(n.Lbrace, len("{")),
tok(n.Rbrace, len("{")))
case *ast.DeclStmt:
// nop
case *ast.DeferStmt:
children = append(children,
tok(n.Defer, len("defer")))
case *ast.Ellipsis:
children = append(children,
tok(n.Ellipsis, len("...")))
case *ast.EmptyStmt:
// nop
case *ast.ExprStmt:
// nop
case *ast.Field:
// TODO(adonovan): Field.{Doc,Comment,Tag}?
case *ast.FieldList:
children = append(children,
tok(n.Opening, len("(")),
tok(n.Closing, len(")")))
case *ast.File:
// TODO test: Doc
children = append(children,
tok(n.Package, len("package")))
case *ast.ForStmt:
children = append(children,
tok(n.For, len("for")))
case *ast.FuncDecl:
// TODO(adonovan): FuncDecl.Comment?
// Uniquely, FuncDecl breaks the invariant that
// preorder traversal yields tokens in lexical order:
// in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
//
// As a workaround, we inline the case for FuncType
// here and order things correctly.
//
children = nil // discard ast.Walk(FuncDecl) info subtrees
children = append(children, tok(n.Type.Func, len("func")))
if n.Recv != nil {
children = append(children, n.Recv)
}
children = append(children, n.Name)
if n.Type.Params != nil {
children = append(children, n.Type.Params)
}
if n.Type.Results != nil {
children = append(children, n.Type.Results)
}
if n.Body != nil {
children = append(children, n.Body)
}
case *ast.FuncLit:
// nop
case *ast.FuncType:
if n.Func != 0 {
children = append(children,
tok(n.Func, len("func")))
}
case *ast.GenDecl:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
if n.Lparen != 0 {
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
}
case *ast.GoStmt:
children = append(children,
tok(n.Go, len("go")))
case *ast.Ident:
children = append(children,
tok(n.NamePos, len(n.Name)))
case *ast.IfStmt:
children = append(children,
tok(n.If, len("if")))
case *ast.ImportSpec:
// TODO(adonovan): ImportSpec.{Doc,EndPos}?
case *ast.IncDecStmt:
children = append(children,
tok(n.TokPos, len(n.Tok.String())))
case *ast.IndexExpr:
children = append(children,
tok(n.Lbrack, len("{")),
tok(n.Rbrack, len("}")))
case *ast.InterfaceType:
children = append(children,
tok(n.Interface, len("interface")))
case *ast.KeyValueExpr:
children = append(children,
tok(n.Colon, len(":")))
case *ast.LabeledStmt:
children = append(children,
tok(n.Colon, len(":")))
case *ast.MapType:
children = append(children,
tok(n.Map, len("map")))
case *ast.ParenExpr:
children = append(children,
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
case *ast.RangeStmt:
children = append(children,
tok(n.For, len("for")),
tok(n.TokPos, len(n.Tok.String())))
case *ast.ReturnStmt:
children = append(children,
tok(n.Return, len("return")))
case *ast.SelectStmt:
children = append(children,
tok(n.Select, len("select")))
case *ast.SelectorExpr:
// nop
case *ast.SendStmt:
children = append(children,
tok(n.Arrow, len("<-")))
case *ast.SliceExpr:
children = append(children,
tok(n.Lbrack, len("[")),
tok(n.Rbrack, len("]")))
case *ast.StarExpr:
children = append(children, tok(n.Star, len("*")))
case *ast.StructType:
children = append(children, tok(n.Struct, len("struct")))
case *ast.SwitchStmt:
children = append(children, tok(n.Switch, len("switch")))
case *ast.TypeAssertExpr:
children = append(children,
tok(n.Lparen-1, len(".")),
tok(n.Lparen, len("(")),
tok(n.Rparen, len(")")))
case *ast.TypeSpec:
// TODO(adonovan): TypeSpec.{Doc,Comment}?
case *ast.TypeSwitchStmt:
children = append(children, tok(n.Switch, len("switch")))
case *ast.UnaryExpr:
children = append(children, tok(n.OpPos, len(n.Op.String())))
case *ast.ValueSpec:
// TODO(adonovan): ValueSpec.{Doc,Comment}?
default:
// Includes *ast.BadDecl, *ast.BadExpr, *ast.BadStmt.
panic(fmt.Sprintf("unexpected node type %T", n))
}
// TODO(adonovan): opt: merge the logic of ast.Inspect() into
// the switch above so we can make interleaved callbacks for
// both Nodes and Tokens in the right order and avoid the need
// to sort.
sort.Sort(byPos(children))
return children
}
type byPos []ast.Node
func (sl byPos) Len() int {
return len(sl)
}
func (sl byPos) Less(i, j int) bool {
return sl[i].Pos() < sl[j].Pos()
}
func (sl byPos) Swap(i, j int) {
sl[i], sl[j] = sl[j], sl[i]
}
// NodeDescription returns a description of the concrete type of n suitable
// for a user interface.
//
// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
// StarExpr) we could be much more specific given the path to the AST
// root. Perhaps we should do that.
//
func NodeDescription(n ast.Node) string {
switch n := n.(type) {
case *ast.ArrayType:
return "array type"
case *ast.AssignStmt:
return "assignment"
case *ast.BadDecl:
return "bad declaration"
case *ast.BadExpr:
return "bad expression"
case *ast.BadStmt:
return "bad statement"
case *ast.BasicLit:
return "basic literal"
case *ast.BinaryExpr:
return fmt.Sprintf("binary %s operation", n.Op)
case *ast.BlockStmt:
return "block"
case *ast.BranchStmt:
switch n.Tok {
case token.BREAK:
return "break statement"
case token.CONTINUE:
return "continue statement"
case token.GOTO:
return "goto statement"
case token.FALLTHROUGH:
return "fall-through statement"
}
case *ast.CallExpr:
return "function call (or conversion)"
case *ast.CaseClause:
return "case clause"
case *ast.ChanType:
return "channel type"
case *ast.CommClause:
return "communication clause"
case *ast.Comment:
return "comment"
case *ast.CommentGroup:
return "comment group"
case *ast.CompositeLit:
return "composite literal"
case *ast.DeclStmt:
return NodeDescription(n.Decl) + " statement"
case *ast.DeferStmt:
return "defer statement"
case *ast.Ellipsis:
return "ellipsis"
case *ast.EmptyStmt:
return "empty statement"
case *ast.ExprStmt:
return "expression statement"
case *ast.Field:
// Can be any of these:
// struct {x, y int} -- struct field(s)
// struct {T} -- anon struct field
// interface {I} -- interface embedding
// interface {f()} -- interface method
// func (A) func(B) C -- receiver, param(s), result(s)
return "field/method/parameter"
case *ast.FieldList:
return "field/method/parameter list"
case *ast.File:
return "source file"
case *ast.ForStmt:
return "for loop"
case *ast.FuncDecl:
return "function declaration"
case *ast.FuncLit:
return "function literal"
case *ast.FuncType:
return "function type"
case *ast.GenDecl:
switch n.Tok {
case token.IMPORT:
return "import declaration"
case token.CONST:
return "constant declaration"
case token.TYPE:
return "type declaration"
case token.VAR:
return "variable declaration"
}
case *ast.GoStmt:
return "go statement"
case *ast.Ident:
return "identifier"
case *ast.IfStmt:
return "if statement"
case *ast.ImportSpec:
return "import specification"
case *ast.IncDecStmt:
if n.Tok == token.INC {
return "increment statement"
}
return "decrement statement"
case *ast.IndexExpr:
return "index expression"
case *ast.InterfaceType:
return "interface type"
case *ast.KeyValueExpr:
return "key/value association"
case *ast.LabeledStmt:
return "statement label"
case *ast.MapType:
return "map type"
case *ast.Package:
return "package"
case *ast.ParenExpr:
return "parenthesized " + NodeDescription(n.X)
case *ast.RangeStmt:
return "range loop"
case *ast.ReturnStmt:
return "return statement"
case *ast.SelectStmt:
return "select statement"
case *ast.SelectorExpr:
return "selector"
case *ast.SendStmt:
return "channel send"
case *ast.SliceExpr:
return "slice expression"
case *ast.StarExpr:
return "*-operation" // load/store expr or pointer type
case *ast.StructType:
return "struct type"
case *ast.SwitchStmt:
return "switch statement"
case *ast.TypeAssertExpr:
return "type assertion"
case *ast.TypeSpec:
return "type specification"
case *ast.TypeSwitchStmt:
return "type switch"
case *ast.UnaryExpr:
return fmt.Sprintf("unary %s operation", n.Op)
case *ast.ValueSpec:
return "value specification"
}
panic(fmt.Sprintf("unexpected node type: %T", n))
}
@@ -1,195 +0,0 @@
// 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 astutil_test
// This file defines tests of PathEnclosingInterval.
// TODO(adonovan): exhaustive tests that run over the whole input
// tree, not just handcrafted examples.
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"strings"
"testing"
"code.google.com/p/go.tools/astutil"
)
// 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()
}
// findInterval parses input and returns the [start, end) positions of
// the first occurrence of substr in input. f==nil indicates failure;
// an error has already been reported in that case.
//
func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
f, err := parser.ParseFile(fset, "<input>", input, 0)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
i := strings.Index(input, substr)
if i < 0 {
t.Errorf("%q is not a substring of input", substr)
f = nil
return
}
filePos := fset.File(f.Package)
return f, filePos.Pos(i), filePos.Pos(i + len(substr))
}
// Common input for following tests.
const input = `
// Hello.
package main
import "fmt"
func f() {}
func main() {
z := (x + y) // add them
f() // NB: ExprStmt and its CallExpr have same Pos/End
}
`
func TestPathEnclosingInterval_Exact(t *testing.T) {
// For the exact tests, we check that a substring is mapped to
// the canonical string for the node it denotes.
tests := []struct {
substr string // first occurrence of this string indicates interval
node string // complete text of expected containing node
}{
{"package",
input[11 : len(input)-1]},
{"\npack",
input[11 : len(input)-1]},
{"main",
"main"},
{"import",
"import \"fmt\""},
{"\"fmt\"",
"\"fmt\""},
{"\nfunc f() {}\n",
"func f() {}"},
{"x ",
"x"},
{" y",
"y"},
{"z",
"z"},
{" + ",
"x + y"},
{" :=",
"z := (x + y)"},
{"x + y",
"x + y"},
{"(x + y)",
"(x + y)"},
{" (x + y) ",
"(x + y)"},
{" (x + y) // add",
"(x + y)"},
{"func",
"func f() {}"},
{"func f() {}",
"func f() {}"},
{"\nfun",
"func f() {}"},
{" f",
"f"},
}
for _, test := range tests {
f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
if f == nil {
continue
}
path, exact := astutil.PathEnclosingInterval(f, start, end)
if !exact {
t.Errorf("PathEnclosingInterval(%q) not exact", test.substr)
continue
}
if len(path) == 0 {
if test.node != "" {
t.Errorf("PathEnclosingInterval(%q).path: got [], want %q",
test.substr, test.node)
}
continue
}
if got := input[path[0].Pos():path[0].End()]; got != test.node {
t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)",
test.substr, got, test.node, pathToString(path))
continue
}
}
}
func TestPathEnclosingInterval_Paths(t *testing.T) {
// For these tests, we check only the path of the enclosing
// node, but not its complete text because it's often quite
// large when !exact.
tests := []struct {
substr string // first occurrence of this string indicates interval
path string // the pathToString(),exact of the expected path
}{
{"// add",
"[BlockStmt FuncDecl File],false"},
{"(x + y",
"[ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
{"x +",
"[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"},
{"z := (x",
"[AssignStmt BlockStmt FuncDecl File],false"},
{"func f",
"[FuncDecl File],false"},
{"func f()",
"[FuncDecl File],false"},
{" f()",
"[FuncDecl File],false"},
{"() {}",
"[FuncDecl File],false"},
{"// Hello",
"[File],false"},
{" f",
"[Ident FuncDecl File],true"},
{"func ",
"[FuncDecl File],true"},
{"mai",
"[Ident File],true"},
{"f() // NB",
"[CallExpr ExprStmt BlockStmt FuncDecl File],true"},
}
for _, test := range tests {
f, start, end := findInterval(t, new(token.FileSet), input, test.substr)
if f == nil {
continue
}
path, exact := astutil.PathEnclosingInterval(f, start, end)
if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path {
t.Errorf("PathEnclosingInterval(%q): got %q, want %q",
test.substr, got, test.path)
continue
}
}
}
@@ -1,419 +0,0 @@
// 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 astutil contains common utilities for working with the Go AST.
package astutil
import (
"bufio"
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"path"
"strconv"
"strings"
)
// AddImport adds the import path to the file f, if absent.
func AddImport(fset *token.FileSet, f *ast.File, ipath string) (added bool) {
return AddNamedImport(fset, f, "", ipath)
}
// AddNamedImport adds the import path to the file f, if absent.
// If name is not empty, it is used to rename the import.
//
// For example, calling
// AddNamedImport(f, "pathpkg", "path")
// adds
// import pathpkg "path"
func AddNamedImport(fset *token.FileSet, f *ast.File, name, ipath string) (added bool) {
if imports(f, ipath) {
return false
}
newImport := &ast.ImportSpec{
Path: &ast.BasicLit{
Kind: token.STRING,
Value: strconv.Quote(ipath),
},
}
if name != "" {
newImport.Name = &ast.Ident{Name: name}
}
// Find an import decl to add to.
var (
bestMatch = -1
lastImport = -1
impDecl *ast.GenDecl
impIndex = -1
hasImports = false
)
for i, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl)
if ok && gen.Tok == token.IMPORT {
hasImports = true
lastImport = i
// Do not add to import "C", to avoid disrupting the
// association with its doc comment, breaking cgo.
if declImports(gen, "C") {
continue
}
// Compute longest shared prefix with imports in this block.
for j, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
n := matchLen(importPath(impspec), ipath)
if n > bestMatch {
bestMatch = n
impDecl = gen
impIndex = j
}
}
}
}
// If no import decl found, add one after the last import.
if impDecl == nil {
// TODO(bradfitz): remove this hack. See comment below on
// addImportViaSourceModification.
if !hasImports {
f2, err := addImportViaSourceModification(fset, f, name, ipath)
if err == nil {
*f = *f2
return true
}
log.Printf("addImportViaSourceModification error: %v", err)
}
// TODO(bradfitz): fix above and resume using this old code:
impDecl = &ast.GenDecl{
Tok: token.IMPORT,
}
f.Decls = append(f.Decls, nil)
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
f.Decls[lastImport+1] = impDecl
}
// Ensure the import decl has parentheses, if needed.
if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() {
impDecl.Lparen = impDecl.Pos()
}
insertAt := impIndex + 1
if insertAt == 0 {
insertAt = len(impDecl.Specs)
}
impDecl.Specs = append(impDecl.Specs, nil)
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
impDecl.Specs[insertAt] = newImport
if insertAt > 0 {
// Assign same position as the previous import,
// so that the sorter sees it as being in the same block.
prev := impDecl.Specs[insertAt-1]
newImport.Path.ValuePos = prev.Pos()
newImport.EndPos = prev.Pos()
}
if len(impDecl.Specs) > 1 && impDecl.Lparen == 0 {
// set Lparen to something not zero, so the printer prints
// the full block rather just the first ImportSpec.
impDecl.Lparen = 1
}
f.Imports = append(f.Imports, newImport)
return true
}
// DeleteImport deletes the import path from the file f, if present.
func DeleteImport(fset *token.FileSet, f *ast.File, path string) (deleted bool) {
oldImport := importSpec(f, path)
// Find the import node that imports path, if any.
for i, decl := range f.Decls {
gen, ok := decl.(*ast.GenDecl)
if !ok || gen.Tok != token.IMPORT {
continue
}
for j, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
if oldImport != impspec {
continue
}
// We found an import spec that imports path.
// Delete it.
deleted = true
copy(gen.Specs[j:], gen.Specs[j+1:])
gen.Specs = gen.Specs[:len(gen.Specs)-1]
// If this was the last import spec in this decl,
// delete the decl, too.
if len(gen.Specs) == 0 {
copy(f.Decls[i:], f.Decls[i+1:])
f.Decls = f.Decls[:len(f.Decls)-1]
} else if len(gen.Specs) == 1 {
gen.Lparen = token.NoPos // drop parens
}
if j > 0 {
// We deleted an entry but now there will be
// a blank line-sized hole where the import was.
// Close the hole by making the previous
// import appear to "end" where this one did.
gen.Specs[j-1].(*ast.ImportSpec).EndPos = impspec.End()
}
break
}
}
// Delete it from f.Imports.
for i, imp := range f.Imports {
if imp == oldImport {
copy(f.Imports[i:], f.Imports[i+1:])
f.Imports = f.Imports[:len(f.Imports)-1]
break
}
}
return
}
// RewriteImport rewrites any import of path oldPath to path newPath.
func RewriteImport(fset *token.FileSet, f *ast.File, oldPath, newPath string) (rewrote bool) {
for _, imp := range f.Imports {
if importPath(imp) == oldPath {
rewrote = true
// record old End, because the default is to compute
// it using the length of imp.Path.Value.
imp.EndPos = imp.End()
imp.Path.Value = strconv.Quote(newPath)
}
}
return
}
// UsesImport reports whether a given import is used.
func UsesImport(f *ast.File, path string) (used bool) {
spec := importSpec(f, path)
if spec == nil {
return
}
name := spec.Name.String()
switch name {
case "<nil>":
// If the package name is not explicitly specified,
// make an educated guess. This is not guaranteed to be correct.
lastSlash := strings.LastIndex(path, "/")
if lastSlash == -1 {
name = path
} else {
name = path[lastSlash+1:]
}
case "_", ".":
// Not sure if this import is used - err on the side of caution.
return true
}
ast.Walk(visitFn(func(n ast.Node) {
sel, ok := n.(*ast.SelectorExpr)
if ok && isTopName(sel.X, name) {
used = true
}
}), f)
return
}
type visitFn func(node ast.Node)
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
fn(node)
return fn
}
// imports returns true if f imports path.
func imports(f *ast.File, path string) bool {
return importSpec(f, path) != nil
}
// importSpec returns the import spec if f imports path,
// or nil otherwise.
func importSpec(f *ast.File, path string) *ast.ImportSpec {
for _, s := range f.Imports {
if importPath(s) == path {
return s
}
}
return nil
}
// importPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func importPath(s *ast.ImportSpec) string {
t, err := strconv.Unquote(s.Path.Value)
if err == nil {
return t
}
return ""
}
// declImports reports whether gen contains an import of path.
func declImports(gen *ast.GenDecl, path string) bool {
if gen.Tok != token.IMPORT {
return false
}
for _, spec := range gen.Specs {
impspec := spec.(*ast.ImportSpec)
if importPath(impspec) == path {
return true
}
}
return false
}
// RenameTop renames all references to the top-level name old.
// It returns true if it makes any changes.
func RenameTop(f *ast.File, old, new string) bool {
var fixed bool
// Rename any conflicting imports
// (assuming package name is last element of path).
for _, s := range f.Imports {
if s.Name != nil {
if s.Name.Name == old {
s.Name.Name = new
fixed = true
}
} else {
_, thisName := path.Split(importPath(s))
if thisName == old {
s.Name = ast.NewIdent(new)
fixed = true
}
}
}
// Rename any top-level declarations.
for _, d := range f.Decls {
switch d := d.(type) {
case *ast.FuncDecl:
if d.Recv == nil && d.Name.Name == old {
d.Name.Name = new
d.Name.Obj.Name = new
fixed = true
}
case *ast.GenDecl:
for _, s := range d.Specs {
switch s := s.(type) {
case *ast.TypeSpec:
if s.Name.Name == old {
s.Name.Name = new
s.Name.Obj.Name = new
fixed = true
}
case *ast.ValueSpec:
for _, n := range s.Names {
if n.Name == old {
n.Name = new
n.Obj.Name = new
fixed = true
}
}
}
}
}
}
// Rename top-level old to new, both unresolved names
// (probably defined in another file) and names that resolve
// to a declaration we renamed.
ast.Walk(visitFn(func(n ast.Node) {
id, ok := n.(*ast.Ident)
if ok && isTopName(id, old) {
id.Name = new
fixed = true
}
if ok && id.Obj != nil && id.Name == old && id.Obj.Name == new {
id.Name = id.Obj.Name
fixed = true
}
}), f)
return fixed
}
// matchLen returns the length of the longest prefix shared by x and y.
func matchLen(x, y string) int {
i := 0
for i < len(x) && i < len(y) && x[i] == y[i] {
i++
}
return i
}
// isTopName returns true if n is a top-level unresolved identifier with the given name.
func isTopName(n ast.Expr, name string) bool {
id, ok := n.(*ast.Ident)
return ok && id.Name == name && id.Obj == nil
}
// Imports returns the file imports grouped by paragraph.
func Imports(fset *token.FileSet, f *ast.File) [][]*ast.ImportSpec {
var groups [][]*ast.ImportSpec
for _, decl := range f.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok || genDecl.Tok != token.IMPORT {
break
}
group := []*ast.ImportSpec{}
var lastLine int
for _, spec := range genDecl.Specs {
importSpec := spec.(*ast.ImportSpec)
pos := importSpec.Path.ValuePos
line := fset.Position(pos).Line
if lastLine > 0 && pos > 0 && line-lastLine > 1 {
groups = append(groups, group)
group = []*ast.ImportSpec{}
}
group = append(group, importSpec)
lastLine = line
}
groups = append(groups, group)
}
return groups
}
// NOTE(bradfitz): this is a bit of a hack for golang.org/issue/6884
// because we can't get the comment positions correct. Instead of modifying
// the AST, we print it, modify the text, and re-parse it. Gross.
func addImportViaSourceModification(fset *token.FileSet, f *ast.File, name, ipath string) (*ast.File, error) {
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {
return nil, fmt.Errorf("Error formatting ast.File node: %v", err)
}
var out bytes.Buffer
sc := bufio.NewScanner(bytes.NewReader(buf.Bytes()))
didAdd := false
for sc.Scan() {
ln := sc.Text()
out.WriteString(ln)
out.WriteByte('\n')
if !didAdd && strings.HasPrefix(ln, "package ") {
fmt.Fprintf(&out, "\nimport %s %q\n\n", name, ipath)
didAdd = true
}
}
if err := sc.Err(); err != nil {
return nil, err
}
return parser.ParseFile(fset, "", out.Bytes(), parser.ParseComments)
}
@@ -1,683 +0,0 @@
// 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 astutil
import (
"bytes"
"go/ast"
"go/format"
"go/parser"
"go/token"
"reflect"
"strconv"
"testing"
)
var fset = token.NewFileSet()
func parse(t *testing.T, name, in string) *ast.File {
file, err := parser.ParseFile(fset, name, in, parser.ParseComments)
if err != nil {
t.Fatalf("%s parse: %v", name, err)
}
return file
}
func print(t *testing.T, name string, f *ast.File) string {
var buf bytes.Buffer
if err := format.Node(&buf, fset, f); err != nil {
t.Fatalf("%s gofmt: %v", name, err)
}
return string(buf.Bytes())
}
type test struct {
name string
renamedPkg string
pkg string
in string
out string
broken bool // known broken
}
var addTests = []test{
{
name: "leave os alone",
pkg: "os",
in: `package main
import (
"os"
)
`,
out: `package main
import (
"os"
)
`,
},
{
name: "import.1",
pkg: "os",
in: `package main
`,
out: `package main
import "os"
`,
},
{
name: "import.2",
pkg: "os",
in: `package main
// Comment
import "C"
`,
out: `package main
// Comment
import "C"
import "os"
`,
},
{
name: "import.3",
pkg: "os",
in: `package main
// Comment
import "C"
import (
"io"
"utf8"
)
`,
out: `package main
// Comment
import "C"
import (
"io"
"os"
"utf8"
)
`,
},
{
name: "import.17",
pkg: "x/y/z",
in: `package main
// Comment
import "C"
import (
"a"
"b"
"x/w"
"d/f"
)
`,
out: `package main
// Comment
import "C"
import (
"a"
"b"
"x/w"
"x/y/z"
"d/f"
)
`,
},
{
name: "import into singular block",
pkg: "bytes",
in: `package main
import "os"
`,
out: `package main
import (
"bytes"
"os"
)
`,
},
{
name: "",
renamedPkg: "fmtpkg",
pkg: "fmt",
in: `package main
import "os"
`,
out: `package main
import (
fmtpkg "fmt"
"os"
)
`,
},
{
name: "struct comment",
pkg: "time",
in: `package main
// This is a comment before a struct.
type T struct {
t time.Time
}
`,
out: `package main
import "time"
// This is a comment before a struct.
type T struct {
t time.Time
}
`,
},
}
func TestAddImport(t *testing.T) {
for _, test := range addTests {
file := parse(t, test.name, test.in)
var before bytes.Buffer
ast.Fprint(&before, fset, file, nil)
AddNamedImport(fset, file, test.renamedPkg, test.pkg)
if got := print(t, test.name, file); got != test.out {
if test.broken {
t.Logf("%s is known broken:\ngot: %s\nwant: %s", test.name, got, test.out)
} else {
t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out)
}
var after bytes.Buffer
ast.Fprint(&after, fset, file, nil)
t.Logf("AST before:\n%s\nAST after:\n%s\n", before.String(), after.String())
}
}
}
func TestDoubleAddImport(t *testing.T) {
file := parse(t, "doubleimport", "package main\n")
AddImport(fset, file, "os")
AddImport(fset, file, "bytes")
want := `package main
import (
"bytes"
"os"
)
`
if got := print(t, "doubleimport", file); got != want {
t.Errorf("got: %s\nwant: %s", got, want)
}
}
var deleteTests = []test{
{
name: "import.4",
pkg: "os",
in: `package main
import (
"os"
)
`,
out: `package main
`,
},
{
name: "import.5",
pkg: "os",
in: `package main
// Comment
import "C"
import "os"
`,
out: `package main
// Comment
import "C"
`,
},
{
name: "import.6",
pkg: "os",
in: `package main
// Comment
import "C"
import (
"io"
"os"
"utf8"
)
`,
out: `package main
// Comment
import "C"
import (
"io"
"utf8"
)
`,
},
{
name: "import.7",
pkg: "io",
in: `package main
import (
"io" // a
"os" // b
"utf8" // c
)
`,
out: `package main
import (
// a
"os" // b
"utf8" // c
)
`,
},
{
name: "import.8",
pkg: "os",
in: `package main
import (
"io" // a
"os" // b
"utf8" // c
)
`,
out: `package main
import (
"io" // a
// b
"utf8" // c
)
`,
},
{
name: "import.9",
pkg: "utf8",
in: `package main
import (
"io" // a
"os" // b
"utf8" // c
)
`,
out: `package main
import (
"io" // a
"os" // b
// c
)
`,
},
{
name: "import.10",
pkg: "io",
in: `package main
import (
"io"
"os"
"utf8"
)
`,
out: `package main
import (
"os"
"utf8"
)
`,
},
{
name: "import.11",
pkg: "os",
in: `package main
import (
"io"
"os"
"utf8"
)
`,
out: `package main
import (
"io"
"utf8"
)
`,
},
{
name: "import.12",
pkg: "utf8",
in: `package main
import (
"io"
"os"
"utf8"
)
`,
out: `package main
import (
"io"
"os"
)
`,
},
{
name: "handle.raw.quote.imports",
pkg: "os",
in: "package main\n\nimport `os`",
out: `package main
`,
},
}
func TestDeleteImport(t *testing.T) {
for _, test := range deleteTests {
file := parse(t, test.name, test.in)
DeleteImport(fset, file, test.pkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out)
}
}
}
type rewriteTest struct {
name string
srcPkg string
dstPkg string
in string
out string
}
var rewriteTests = []rewriteTest{
{
name: "import.13",
srcPkg: "utf8",
dstPkg: "encoding/utf8",
in: `package main
import (
"io"
"os"
"utf8" // thanks ken
)
`,
out: `package main
import (
"encoding/utf8" // thanks ken
"io"
"os"
)
`,
},
{
name: "import.14",
srcPkg: "asn1",
dstPkg: "encoding/asn1",
in: `package main
import (
"asn1"
"crypto"
"crypto/rsa"
_ "crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"time"
)
var x = 1
`,
out: `package main
import (
"crypto"
"crypto/rsa"
_ "crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"time"
)
var x = 1
`,
},
{
name: "import.15",
srcPkg: "url",
dstPkg: "net/url",
in: `package main
import (
"bufio"
"net"
"path"
"url"
)
var x = 1 // comment on x, not on url
`,
out: `package main
import (
"bufio"
"net"
"net/url"
"path"
)
var x = 1 // comment on x, not on url
`,
},
{
name: "import.16",
srcPkg: "http",
dstPkg: "net/http",
in: `package main
import (
"flag"
"http"
"log"
"text/template"
)
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
`,
out: `package main
import (
"flag"
"log"
"net/http"
"text/template"
)
var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
`,
},
}
func TestRewriteImport(t *testing.T) {
for _, test := range rewriteTests {
file := parse(t, test.name, test.in)
RewriteImport(fset, file, test.srcPkg, test.dstPkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out)
}
}
}
var renameTests = []rewriteTest{
{
name: "rename pkg use",
srcPkg: "bytes",
dstPkg: "bytes_",
in: `package main
func f() []byte {
buf := new(bytes.Buffer)
return buf.Bytes()
}
`,
out: `package main
func f() []byte {
buf := new(bytes_.Buffer)
return buf.Bytes()
}
`,
},
}
func TestRenameTop(t *testing.T) {
for _, test := range renameTests {
file := parse(t, test.name, test.in)
RenameTop(file, test.srcPkg, test.dstPkg)
if got := print(t, test.name, file); got != test.out {
t.Errorf("%s:\ngot: %s\nwant: %s", test.name, got, test.out)
}
}
}
var importsTests = []struct {
name string
in string
want [][]string
}{
{
name: "no packages",
in: `package foo
`,
want: nil,
},
{
name: "one group",
in: `package foo
import (
"fmt"
"testing"
)
`,
want: [][]string{{"fmt", "testing"}},
},
{
name: "four groups",
in: `package foo
import "C"
import (
"fmt"
"testing"
"appengine"
"myproject/mylib1"
"myproject/mylib2"
)
`,
want: [][]string{
{"C"},
{"fmt", "testing"},
{"appengine"},
{"myproject/mylib1", "myproject/mylib2"},
},
},
{
name: "multiple factored groups",
in: `package foo
import (
"fmt"
"testing"
"appengine"
)
import (
"reflect"
"bytes"
)
`,
want: [][]string{
{"fmt", "testing"},
{"appengine"},
{"reflect"},
{"bytes"},
},
},
}
func unquote(s string) string {
res, err := strconv.Unquote(s)
if err != nil {
return "could_not_unquote"
}
return res
}
func TestImports(t *testing.T) {
fset := token.NewFileSet()
for _, test := range importsTests {
f, err := parser.ParseFile(fset, "test.go", test.in, 0)
if err != nil {
t.Errorf("%s: %v", test.name, err)
continue
}
var got [][]string
for _, block := range Imports(fset, f) {
var b []string
for _, spec := range block {
b = append(b, unquote(spec.Path.Value))
}
got = append(got, b)
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("Imports(%s)=%v, want %v", test.name, got, test.want)
}
}
}
@@ -1,57 +0,0 @@
// 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 (
"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"`
Href string `xml:"href,attr"`
}
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"))
}
@@ -1,424 +0,0 @@
// 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 (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"code.google.com/p/go.tools/blog/atom"
"code.google.com/p/go.tools/present"
)
var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
// 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.
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
}
// 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
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
root := filepath.Join(cfg.TemplatePath, "root.tmpl")
parse := func(name string) (*template.Template, error) {
t := template.New("").Funcs(funcMap)
return t.ParseFiles(root, filepath.Join(cfg.TemplatePath, name))
}
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.
err = s.loadDocs(filepath.Clean(cfg.ContentPath))
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 {
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 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
}
html := new(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)
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)
}
}
// 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
}
e := &atom.Entry{
Title: doc.Title,
ID: feed.ID + doc.Path,
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
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}
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:
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
}
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) }
@@ -1,160 +0,0 @@
// 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"
)
var (
changedOnly = flag.Bool("changed", false, "show only benchmarks that have changed")
magSort = flag.Bool("mag", false, "sort benchmarks by magnitude of change")
)
const usageFooter = `
Each input file should be from:
go test -test.run=NONE -test.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() {
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(ByDeltaNsOp(cmps))
} else {
sort.Sort(ByParseOrder(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(NsOp) {
continue
}
if delta := cmp.DeltaNsOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprintf(w, "benchmark\told ns/op\tnew ns/op\tdelta\t\n")
header = true
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", cmp.Name(), formatNs(cmp.Before.NsOp), formatNs(cmp.After.NsOp), delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaMbS(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(MbS) {
continue
}
if delta := cmp.DeltaMbS(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprintf(w, "\nbenchmark\told MB/s\tnew MB/s\tspeedup\t\n")
header = true
}
fmt.Fprintf(w, "%s\t%.2f\t%.2f\t%s\t\n", cmp.Name(), cmp.Before.MbS, cmp.After.MbS, delta.Multiple())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaAllocsOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(AllocsOp) {
continue
}
if delta := cmp.DeltaAllocsOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprintf(w, "\nbenchmark\told allocs\tnew allocs\tdelta\t\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\t\n", cmp.Name(), cmp.Before.AllocsOp, cmp.After.AllocsOp, delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaBOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(BOp) {
continue
}
if delta := cmp.DeltaBOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprintf(w, "\nbenchmark\told bytes\tnew bytes\tdelta\t\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\t\n", cmp.Name(), cmp.Before.BOp, cmp.After.BOp, cmp.DeltaBOp().Percent())
}
}
}
func fatal(msg interface{}) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}
func parseFile(path string) BenchSet {
f, err := os.Open(path)
if err != nil {
fatal(err)
}
bb, err := ParseBenchSet(f)
if err != nil {
fatal(err)
}
return bb
}
// 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)
}
@@ -1,148 +0,0 @@
// 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"
)
// BenchCmp is a pair of benchmarks.
type BenchCmp struct {
Before *Bench
After *Bench
}
// Correlate correlates benchmarks from two BenchSets.
func Correlate(before, after BenchSet) (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) DeltaNsOp() Delta { return Delta{c.Before.NsOp, c.After.NsOp} }
func (c BenchCmp) DeltaMbS() Delta { return Delta{c.Before.MbS, c.After.MbS} }
func (c BenchCmp) DeltaBOp() Delta { return Delta{float64(c.Before.BOp), float64(c.After.BOp)} }
func (c BenchCmp) DeltaAllocsOp() Delta {
return Delta{float64(c.Before.AllocsOp), float64(c.After.AllocsOp)}
}
// 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()
}
// ByDeltaNsOp sorts BenchCmps lexicographically by change
// in ns/op, descending, then by benchmark name.
type ByDeltaNsOp []BenchCmp
func (x ByDeltaNsOp) Len() int { return len(x) }
func (x ByDeltaNsOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaNsOp) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaNsOp) }
// ByDeltaMbS sorts BenchCmps lexicographically by change
// in MB/s, descending, then by benchmark name.
type ByDeltaMbS []BenchCmp
func (x ByDeltaMbS) Len() int { return len(x) }
func (x ByDeltaMbS) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaMbS) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaMbS) }
// ByDeltaBOp sorts BenchCmps lexicographically by change
// in B/op, descending, then by benchmark name.
type ByDeltaBOp []BenchCmp
func (x ByDeltaBOp) Len() int { return len(x) }
func (x ByDeltaBOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaBOp) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaBOp) }
// ByDeltaAllocsOp sorts BenchCmps lexicographically by change
// in allocs/op, descending, then by benchmark name.
type ByDeltaAllocsOp []BenchCmp
func (x ByDeltaAllocsOp) Len() int { return len(x) }
func (x ByDeltaAllocsOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaAllocsOp) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaAllocsOp) }
@@ -1,131 +0,0 @@
// 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"
)
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<counter><num benches><b = before | a = after>
// Read this: "<counter> of <num benches>, from <before|after>".
before := BenchSet{
"BenchmarkOneEach": []*Bench{{Name: "BenchmarkOneEach", N: 0x11b}},
"BenchmarkOneToNone": []*Bench{{Name: "BenchmarkOneToNone"}},
"BenchmarkOneToTwo": []*Bench{{Name: "BenchmarkOneToTwo"}},
"BenchmarkTwoToOne": []*Bench{
{Name: "BenchmarkTwoToOne"},
{Name: "BenchmarkTwoToOne"},
},
"BenchmarkTwoEach": []*Bench{
{Name: "BenchmarkTwoEach", N: 0x12b},
{Name: "BenchmarkTwoEach", N: 0x22b},
},
}
after := BenchSet{
"BenchmarkOneEach": []*Bench{{Name: "BenchmarkOneEach", N: 0x11a}},
"BenchmarkNoneToOne": []*Bench{{Name: "BenchmarkNoneToOne"}},
"BenchmarkTwoToOne": []*Bench{{Name: "BenchmarkTwoToOne"}},
"BenchmarkOneToTwo": []*Bench{
{Name: "BenchmarkOneToTwo"},
{Name: "BenchmarkOneToTwo"},
},
"BenchmarkTwoEach": []*Bench{
{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{
{&Bench{Name: "BenchmarkMuchFaster", NsOp: 10, ord: 3}, &Bench{Name: "BenchmarkMuchFaster", NsOp: 1}},
{&Bench{Name: "BenchmarkSameB", NsOp: 5, ord: 1}, &Bench{Name: "BenchmarkSameB", NsOp: 5}},
{&Bench{Name: "BenchmarkSameA", NsOp: 5, ord: 2}, &Bench{Name: "BenchmarkSameA", NsOp: 5}},
{&Bench{Name: "BenchmarkSlower", NsOp: 10, ord: 0}, &Bench{Name: "BenchmarkSlower", NsOp: 11}},
}
// Test just one magnitude-based sort order; they are symmetric.
sort.Sort(ByDeltaNsOp(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)
}
}
@@ -1,37 +0,0 @@
// 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 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
@@ -1,127 +0,0 @@
// 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 (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
// Flags used by Bench.Measured to indicate
// which measurements a Bench contains.
const (
NsOp = 1 << iota
MbS
BOp
AllocsOp
)
// Bench is one run of a single benchmark.
type Bench struct {
Name string // benchmark name
N int // number of iterations
NsOp float64 // nanoseconds per iteration
MbS float64 // MB processed per second
BOp uint64 // bytes allocated per iteration
AllocsOp uint64 // allocs per iteration
Measured int // which measurements were recorded
ord int // ordinal position within a benchmark run, used for sorting
}
// ParseLine extracts a Bench from a single line of testing.B output.
func ParseLine(line string) (*Bench, 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 := &Bench{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 *Bench) parseMeasurement(quant string, unit string) {
switch unit {
case "ns/op":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.NsOp = f
b.Measured |= NsOp
}
case "MB/s":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.MbS = f
b.Measured |= MbS
}
case "B/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.BOp = i
b.Measured |= BOp
}
case "allocs/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocsOp = i
b.Measured |= AllocsOp
}
}
}
func (b *Bench) String() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "%s %d", b.Name, b.N)
if b.Measured&NsOp != 0 {
fmt.Fprintf(buf, " %.2f ns/op", b.NsOp)
}
if b.Measured&MbS != 0 {
fmt.Fprintf(buf, " %.2f MB/s", b.MbS)
}
if b.Measured&BOp != 0 {
fmt.Fprintf(buf, " %d B/op", b.BOp)
}
if b.Measured&AllocsOp != 0 {
fmt.Fprintf(buf, " %d allocs/op", b.AllocsOp)
}
return buf.String()
}
// BenchSet is a collection of benchmarks from one
// testing.B run, keyed by name to faciliate comparison.
type BenchSet map[string][]*Bench
// Parse extracts a BenchSet from testing.B output. Parse
// preserves the order of benchmarks that have identical names.
func ParseBenchSet(r io.Reader) (BenchSet, error) {
bb := make(BenchSet)
scan := bufio.NewScanner(r)
ord := 0
for scan.Scan() {
if b, err := ParseLine(scan.Text()); err == nil {
b.ord = ord
bb[b.Name] = append(bb[b.Name], b)
ord++
}
}
if err := scan.Err(); err != nil {
return nil, err
}
return bb, nil
}
@@ -1,154 +0,0 @@
// 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"
"strings"
"testing"
)
func TestParseLine(t *testing.T) {
cases := []struct {
line string
want *Bench
err bool // expect an error
}{
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op",
want: &Bench{
Name: "BenchmarkEncrypt",
N: 100000000, NsOp: 19.6,
Measured: NsOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s",
want: &Bench{
Name: "BenchmarkEncrypt",
N: 100000000, NsOp: 19.6, MbS: 817.77,
Measured: NsOp | MbS,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77",
want: &Bench{
Name: "BenchmarkEncrypt",
N: 100000000, NsOp: 19.6,
Measured: NsOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 5 allocs/op",
want: &Bench{
Name: "BenchmarkEncrypt",
N: 100000000, NsOp: 19.6, MbS: 817.77, AllocsOp: 5,
Measured: NsOp | MbS | AllocsOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 3 B/op 5 allocs/op",
want: &Bench{
Name: "BenchmarkEncrypt",
N: 100000000, NsOp: 19.6, MbS: 817.77, BOp: 3, AllocsOp: 5,
Measured: NsOp | MbS | BOp | AllocsOp,
},
},
// 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: &Bench{
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 TestParseBenchSet(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 := BenchSet{
"BenchmarkReadRequestApachebench": []*Bench{
{
Name: "BenchmarkReadRequestApachebench",
N: 1000000, NsOp: 2960, MbS: 27.70, BOp: 839, AllocsOp: 9,
Measured: NsOp | MbS | BOp | AllocsOp,
ord: 2,
},
},
"BenchmarkClientServerParallel64": []*Bench{
{
Name: "BenchmarkClientServerParallel64",
N: 50000, NsOp: 59192, BOp: 7028, AllocsOp: 60,
Measured: NsOp | BOp | AllocsOp,
ord: 3,
},
},
"BenchmarkEncrypt": []*Bench{
{
Name: "BenchmarkEncrypt",
N: 100000000, NsOp: 19.6,
Measured: NsOp,
ord: 0,
},
{
Name: "BenchmarkEncrypt",
N: 5000000, NsOp: 517,
Measured: NsOp,
ord: 1,
},
},
}
have, err := ParseBenchSet(strings.NewReader(in))
if err != nil {
t.Fatalf("unexpected err during ParseBenchSet: %v", err)
}
if !reflect.DeepEqual(want, have) {
t.Errorf("parsed bench set incorrectly, want %v have %v", want, have)
}
}
@@ -1,645 +0,0 @@
// 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"
)
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.Fprintln(os.Stderr, usageMessage)
fmt.Fprintln(os.Stderr, "Flags:")
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 {
// }
// }
const backupToElse = token.Pos(len("else ")) // The AST doesn't remember the else location. We can make an accurate guess.
switch stmt := n.Else.(type) {
case *ast.IfStmt:
block := &ast.BlockStmt{
Lbrace: stmt.If - backupToElse, // 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 -= backupToElse // So the block 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
}
}
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
// whitepace 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, 0)
if err != nil {
log.Fatalf("cover: %s: %s", name, err)
}
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)
}
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 unecessary (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) {
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:
return s.Body.Lbrace
case *ast.ForStmt:
return s.Body.Lbrace
case *ast.LabeledStmt:
return f.statementBoundary(s.Stmt)
case *ast.RangeStmt:
// Ranges might loop over things with function literals.: for _ = range []func(){ ... } {.
// TODO: There are a few other such possibilities, but they're extremely unlikely.
found, pos := hasFuncLiteral(s.X)
if found {
return pos
}
return s.Body.Lbrace
case *ast.SwitchStmt:
return s.Body.Lbrace
case *ast.SelectStmt:
return s.Body.Lbrace
case *ast.TypeSwitchStmt:
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
}
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] }
// 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)
fmt.Fprintf(os.Stderr, "\t%s:#%d,#%d %s:#%d,#%d\n", f.name, t[i-1].startByte, t[i-1].endByte, f.name, t[i].startByte, 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")
}
@@ -1,87 +0,0 @@
// 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"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
)
const (
// Data directory, also the package directory for the test.
testdata = "testdata"
// Binaries we compile.
testcover = "./testcover.exe"
)
var (
// Files we use.
testMain = filepath.Join(testdata, "main.go")
testTest = filepath.Join(testdata, "test.go")
coverInput = filepath.Join(testdata, "test_line.go")
coverOutput = filepath.Join(testdata, "test_cover.go")
)
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) {
// 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)
// defer removal of test_line.go
if !debug {
defer os.Remove(coverInput)
}
// go build -o testcover
cmd := exec.Command("go", "build", "-o", testcover)
run(cmd, t)
// defer removal of testcover
defer os.Remove(testcover)
// ./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)
}
}
@@ -1,21 +0,0 @@
// 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'.
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
@@ -1,166 +0,0 @@
// 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"
"code.google.com/p/go.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: init 100.0%
// fmt/format.go: computePadding 84.6%
// ...
// fmt/scan.go: doScan 100.0%
// fmt/scan.go: advance 96.2%
// fmt/scan.go: doScanf 96.8%
// total: (statements) 91.4%
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:\t%s\t%.1f%%\n", fn, 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
}
@@ -1,262 +0,0 @@
// 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"
"html/template"
"io"
"io/ioutil"
"math"
"os"
"os/exec"
"path/filepath"
"runtime"
"code.google.com/p/go.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()),
})
}
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)
}
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
}
// 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, `<span class="cov%v" title="%v">`, n, b.Count)
} else {
dst.WriteString("</span>")
}
boundaries = boundaries[1:]
}
switch b := src[i]; b {
case '>':
dst.WriteString("&gt;")
case '<':
dst.WriteString("&lt;")
case '&':
dst.WriteString("&amp;")
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
}
const tmplHTML = `
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
{{colors}}
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
{{range $i, $f := .Files}}
<option value="file{{$i}}">{{$f.Name}}</option>
{{end}}
</select>
</div>
<div id="legend">
<span>not tracked</span>
{{if .Set}}
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
{{else}}
<span class="cov0">no coverage</span>
<span class="cov1">low coverage</span>
<span class="cov2">*</span>
<span class="cov3">*</span>
<span class="cov4">*</span>
<span class="cov5">*</span>
<span class="cov6">*</span>
<span class="cov7">*</span>
<span class="cov8">*</span>
<span class="cov9">*</span>
<span class="cov10">high coverage</span>
{{end}}
</div>
</div>
<div id="content">
{{range $i, $f := .Files}}
<pre class="file" id="file{{$i}}" {{if $i}}style="display: none"{{end}}>{{$f.Body}}</pre>
{{end}}
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible = document.getElementById('file0');
files.addEventListener('change', onChange, false);
function onChange() {
visible.style.display = 'none';
visible = document.getElementById(files.value);
visible.style.display = 'block';
window.scrollTo(0, 0);
}
})();
</script>
</html>
`
@@ -1,93 +0,0 @@
// 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
}
}
if !PASS {
fmt.Fprintf(os.Stderr, "FAIL\n")
os.Exit(2)
}
}
// 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
}
@@ -1,177 +0,0 @@
// 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()
}
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)
}
}
}
func testFor() {
for i := 0; i < 10; i++ {
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; i++ {
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 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
}
}
}
@@ -1,121 +0,0 @@
// The eg command performs example-based refactoring.
package main
import (
"flag"
"fmt"
"go/parser"
"go/printer"
"go/token"
"os"
"path/filepath"
"code.google.com/p/go.tools/go/loader"
"code.google.com/p/go.tools/refactor/eg"
)
var (
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] <args>...
-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.
` + loader.FromArgsUsage
func main() {
if err := doMain(); err != nil {
fmt.Fprintf(os.Stderr, "%s: %s.\n", filepath.Base(os.Args[0]), err)
os.Exit(1)
}
}
func doMain() error {
flag.Parse()
args := flag.Args()
if *helpFlag {
fmt.Fprint(os.Stderr, eg.Help)
os.Exit(2)
}
if *templateFlag == "" {
return fmt.Errorf("no -t template.go file specified")
}
conf := loader.Config{
Fset: token.NewFileSet(),
ParserMode: parser.ParseComments,
SourceImports: true,
}
// The first Created package is the template.
if err := conf.CreateFromFilenames("template", *templateFlag); err != nil {
return err // e.g. "foo.go:1: syntax error"
}
if len(args) == 0 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}
if _, err := conf.FromArgs(args, true); err != nil {
return err
}
// Load, parse and type-check the whole program.
iprog, err := conf.Load()
if err != nil {
return err
}
// Analyze the template.
template := iprog.Created[0]
xform, err := eg.NewTransformer(iprog.Fset, template, *verboseFlag)
if err != nil {
return err
}
// Apply it to the input packages.
var pkgs []*loader.PackageInfo
if *transitiveFlag {
for _, info := range iprog.AllPackages {
pkgs = append(pkgs, info)
}
} else {
pkgs = iprog.InitialPackages()
}
var hadErrors bool
for _, pkg := range pkgs {
if pkg == template {
continue
}
for _, file := range pkg.Files {
n := xform.Transform(&pkg.Info, pkg.Pkg, file)
if n == 0 {
continue
}
filename := iprog.Fset.File(file.Pos()).Name()
fmt.Fprintf(os.Stderr, "=== %s (%d matches):\n", filename, n)
if *writeFlag {
if err := eg.WriteAST(iprog.Fset, filename, file); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
hadErrors = true
}
} else {
printer.Fprint(os.Stdout, iprog.Fset, file)
}
}
}
if hadErrors {
os.Exit(1)
}
return nil
}
@@ -1,69 +0,0 @@
// 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 "code.google.com/p/go.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
// 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
@@ -1,15 +0,0 @@
// 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 (
"code.google.com/p/go.tools/go/gcimporter"
)
func init() {
register("gc", gcimporter.Import)
}
@@ -1,125 +0,0 @@
// 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 (
"debug/elf"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"code.google.com/p/go.tools/go/gccgoimporter"
"code.google.com/p/go.tools/go/importer"
"code.google.com/p/go.tools/go/types"
)
func init() {
incpaths := []string{"/"}
// importer for default gccgo
var inst gccgoimporter.GccgoInstallation
inst.InitFromDriver("gccgo")
register("gccgo", inst.GetImporter(incpaths))
// importer for gccgo using condensed export format (experimental)
register("gccgo-new", getNewImporter(append(append(incpaths, inst.SearchPaths()...), ".")))
}
// This function is an adjusted variant of gccgoimporter.GccgoInstallation.GetImporter.
func getNewImporter(searchpaths []string) types.Importer {
return func(imports map[string]*types.Package, pkgpath string) (pkg *types.Package, err error) {
if pkgpath == "unsafe" {
return types.Unsafe, nil
}
fpath, err := findExportFile(searchpaths, pkgpath)
if err != nil {
return
}
reader, closer, err := openExportFile(fpath)
if err != nil {
return nil, err
}
defer closer.Close()
// TODO(gri) At the moment we just read the entire file.
// We should change importer.ImportData to take an io.Reader instead.
data, err := ioutil.ReadAll(reader)
if err != nil && err != io.EOF {
return nil, err
}
return importer.ImportData(packages, data)
}
}
// This function is an exact copy of gccgoimporter.findExportFile.
func findExportFile(searchpaths []string, pkgpath string) (string, error) {
for _, spath := range searchpaths {
pkgfullpath := filepath.Join(spath, pkgpath)
pkgdir, name := filepath.Split(pkgfullpath)
for _, filepath := range [...]string{
pkgfullpath,
pkgfullpath + ".gox",
pkgdir + "lib" + name + ".so",
pkgdir + "lib" + name + ".a",
pkgfullpath + ".o",
} {
fi, err := os.Stat(filepath)
if err == nil && !fi.IsDir() {
return filepath, nil
}
}
}
return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":"))
}
// This function is an exact copy of gccgoimporter.openExportFile.
func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) {
f, err := os.Open(fpath)
if err != nil {
return
}
defer func() {
if err != nil {
f.Close()
}
}()
closer = f
var magic [4]byte
_, err = f.ReadAt(magic[:], 0)
if err != nil {
return
}
if string(magic[:]) == "v1;\n" {
// Raw export data.
reader = f
return
}
ef, err := elf.NewFile(f)
if err != nil {
return
}
sec := ef.Section(".go_export")
if sec == nil {
err = fmt.Errorf("%s: .go_export section not found", fpath)
return
}
reader = sec.Open()
return
}
@@ -1,207 +0,0 @@
// 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"
"io/ioutil"
"os"
"path/filepath"
"strings"
"code.google.com/p/go.tools/go/types"
)
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
importFailed = errors.New("import failed")
)
// map of imported packages
var packages = make(map[string]*types.Package)
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")
}
imp := tryImports
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(packages, 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(packages map[string]*types.Package, 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(packages, actual)
if err == nil {
break
}
logf("\t=> importing %q failed: %s\n", actual, err)
}
return
}
// tryImports is an importer that tries all registered importers
// successively until one of them succeeds or all of them failed.
func tryImports(packages map[string]*types.Package, path string) (pkg *types.Package, err error) {
for i, imp := range importers {
logf("\t\ttrying %s import\n", sources[i])
pkg, err = imp(packages, path)
if err == nil {
break
}
logf("\t\t=> %s import failed: %s\n", sources[i], err)
}
return
}
// protect protects an importer imp from panics and returns the protected importer.
func protect(imp types.Importer) types.Importer {
return func(packages map[string]*types.Package, path string) (pkg *types.Package, err error) {
defer func() {
if recover() != nil {
pkg = nil
err = importFailed
}
}()
return imp(packages, path)
}
}
// 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)
}
}
}
@@ -1,365 +0,0 @@
// 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/token"
"io"
"math/big"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
)
// 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)
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())
p.writeType(p.pkg, obj.Type().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 exact.Value) *big.Int {
// compute big-endian representation of v
b := exact.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 exact.Value) string {
if exact.Sign(v) == 0 {
return "0.0"
}
// x != 0
// convert |v| into a big.Rat x
x := new(big.Rat).SetFrac(absInt(exact.Num(v)), absInt(exact.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 exact.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 exact.
func valString(v exact.Value, floatFmt bool) string {
switch v.Kind() {
case exact.Int:
if floatFmt {
return floatString(v)
}
case exact.Float:
return floatString(v)
case exact.Complex:
re := exact.Real(v)
im := exact.Imag(v)
var s string
if exact.Sign(re) != 0 {
s = floatString(re)
if exact.Sign(im) >= 0 {
s += " + "
} else {
s += " - "
im = exact.UnaryOp(token.SUB, im, 0) // negate im
}
}
// im != 0, otherwise v would be exact.Int or exact.Float
return s + floatString(im) + "i"
}
return v.String()
}
func (p *printer) printObj(obj types.Object) {
p.print(obj.Name())
// don't write untyped types (for constants)
typ, basic := obj.Type().Underlying().(*types.Basic)
if basic && typ.Info()&types.IsUntyped == 0 {
p.print(" ")
p.writeType(p.pkg, typ)
}
// write constant value
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
}
@@ -1,19 +0,0 @@
// 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 (
"code.google.com/p/go.tools/go/types"
)
func init() {
register("source", sourceImporter)
}
func sourceImporter(packages map[string]*types.Package, path string) (*types.Package, error) {
panic("unimplemented")
}
@@ -1,242 +0,0 @@
// 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 "code.google.com/p/go.tools/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("<nil>")
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.Embedded(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 := "<Named w/o object>"
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)
}
@@ -1,56 +0,0 @@
godoc on appengine
------------------
Prerequisites
-------------
* Go appengine SDK
https://developers.google.com/appengine/downloads#Google_App_Engine_SDK_for_Go
* Go sources at tip under $GOROOT
* Godoc sources at tip inside $GOPATH
(go get -d code.google.com/p/go.tools/cmd/godoc)
Directory structure
-------------------
* Let $APPDIR be the directory containing the app engine files.
(e.g., $APPDIR=$HOME/godoc-app)
* $APPDIR contains the following entries (this may change depending on
app-engine release and version of godoc):
app.yaml
code.google.com/p/go.tools/cmd/godoc
godoc.zip
index.split.*
* The app.yaml file is set up per app engine documentation.
For instance:
application: godoc-app
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
Configuring and running godoc
-----------------------------
To configure godoc, run
bash setup-godoc-app.bash
to prepare an $APPDIR as described above. See the script for details on usage.
To run godoc locally, using the App Engine development server, run
<path to go_appengine>/dev_appserver.py $APPDIR
godoc should come up at http://localhost:8080 .
@@ -1,63 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine
package main
// This file replaces main.go when running godoc under app-engine.
// See README.godoc-app for details.
import (
"archive/zip"
"log"
"path"
"code.google.com/p/go.tools/godoc"
"code.google.com/p/go.tools/godoc/static"
"code.google.com/p/go.tools/godoc/vfs"
"code.google.com/p/go.tools/godoc/vfs/mapfs"
"code.google.com/p/go.tools/godoc/vfs/zipfs"
)
func init() {
playEnabled = true
log.Println("initializing godoc ...")
log.Printf(".zip file = %s", zipFilename)
log.Printf(".zip GOROOT = %s", zipGoroot)
log.Printf("index files = %s", indexFilenames)
goroot := path.Join("/", zipGoroot) // fsHttp paths are relative to '/'
// read .zip file and set up file systems
const zipfile = zipFilename
rc, err := zip.OpenReader(zipfile)
if err != nil {
log.Fatalf("%s: %s\n", zipfile, err)
}
// rc is never closed (app running forever)
fs.Bind("/", zipfs.New(rc, zipFilename), goroot, vfs.BindReplace)
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
corpus := godoc.NewCorpus(fs)
corpus.Verbose = false
corpus.IndexEnabled = true
corpus.IndexFiles = indexFilenames
if err := corpus.Init(); err != nil {
log.Fatal(err)
}
go corpus.RunIndexer()
pres = godoc.NewPresentation(corpus)
pres.TabWidth = 8
pres.ShowPlayground = true
pres.ShowExamples = true
pres.DeclLinks = true
readTemplates(pres, true)
registerHandlers(pres)
log.Println("godoc initialization complete")
}
@@ -1,81 +0,0 @@
// 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"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"code.google.com/p/go.tools/blog"
"code.google.com/p/go.tools/godoc/redirect"
)
const (
blogRepo = "code.google.com/p/go.blog"
blogURL = "http://blog.golang.org/"
blogPath = "/blog/"
)
var (
blogServer http.Handler // set by blogInit
blogInitOnce sync.Once
playEnabled bool
)
func init() {
// Initialize blog only when first accessed.
http.HandleFunc(blogPath, func(w http.ResponseWriter, r *http.Request) {
blogInitOnce.Do(blogInit)
blogServer.ServeHTTP(w, r)
})
}
func blogInit() {
// Binary distributions will include the blog content in "/blog".
root := filepath.Join(runtime.GOROOT(), "blog")
// Prefer content from go.blog repository if present.
if pkg, err := build.Import(blogRepo, "", build.FindOnly); err == nil {
root = pkg.Dir
}
// If content is not available fall back to redirect.
if fi, err := os.Stat(root); err != nil || !fi.IsDir() {
fmt.Fprintf(os.Stderr, "Blog content not available locally. "+
"To install, run \n\tgo get %v\n", blogRepo)
blogServer = http.HandlerFunc(blogRedirectHandler)
return
}
s, err := blog.NewServer(blog.Config{
BaseURL: blogPath,
BasePath: strings.TrimSuffix(blogPath, "/"),
ContentPath: filepath.Join(root, "content"),
TemplatePath: filepath.Join(root, "template"),
HomeArticles: 5,
PlayEnabled: playEnabled,
})
if err != nil {
log.Fatal(err)
}
blogServer = s
}
func blogRedirectHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == blogPath {
http.Redirect(w, r, blogURL, http.StatusFound)
return
}
blogPrefixHandler.ServeHTTP(w, r)
}
var blogPrefixHandler = redirect.PrefixHandler(blogPath, blogURL)
@@ -1,523 +0,0 @@
// 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.
// The /doc/codewalk/ tree is synthesized from codewalk descriptions,
// files named $GOROOT/doc/codewalk/*.xml.
// For an example and a description of the format, see
// http://golang.org/doc/codewalk/codewalk or run godoc -http=:6060
// and see http://localhost:6060/doc/codewalk/codewalk .
// That page is itself a codewalk; the source code for it is
// $GOROOT/doc/codewalk/codewalk.xml.
package main
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
pathpkg "path"
"regexp"
"sort"
"strconv"
"strings"
"text/template"
"unicode/utf8"
"code.google.com/p/go.tools/godoc"
"code.google.com/p/go.tools/godoc/vfs"
)
var codewalkHTML, codewalkdirHTML *template.Template
// Handler for /doc/codewalk/ and below.
func codewalk(w http.ResponseWriter, r *http.Request) {
relpath := r.URL.Path[len("/doc/codewalk/"):]
abspath := r.URL.Path
r.ParseForm()
if f := r.FormValue("fileprint"); f != "" {
codewalkFileprint(w, r, f)
return
}
// If directory exists, serve list of code walks.
dir, err := fs.Lstat(abspath)
if err == nil && dir.IsDir() {
codewalkDir(w, r, relpath, abspath)
return
}
// If file exists, serve using standard file server.
if err == nil {
pres.ServeFile(w, r)
return
}
// Otherwise append .xml and hope to find
// a codewalk description, but before trim
// the trailing /.
abspath = strings.TrimRight(abspath, "/")
cw, err := loadCodewalk(abspath + ".xml")
if err != nil {
log.Print(err)
pres.ServeError(w, r, relpath, err)
return
}
// Canonicalize the path and redirect if changed
if redir(w, r) {
return
}
pres.ServePage(w, godoc.Page{
Title: "Codewalk: " + cw.Title,
Tabtitle: cw.Title,
Body: applyTemplate(codewalkHTML, "codewalk", cw),
})
}
func redir(w http.ResponseWriter, r *http.Request) (redirected bool) {
canonical := pathpkg.Clean(r.URL.Path)
if !strings.HasSuffix(canonical, "/") {
canonical += "/"
}
if r.URL.Path != canonical {
url := *r.URL
url.Path = canonical
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
redirected = true
}
return
}
func applyTemplate(t *template.Template, name string, data interface{}) []byte {
var buf bytes.Buffer
if err := t.Execute(&buf, data); err != nil {
log.Printf("%s.Execute: %s", name, err)
}
return buf.Bytes()
}
// A Codewalk represents a single codewalk read from an XML file.
type Codewalk struct {
Title string `xml:"title,attr"`
File []string `xml:"file"`
Step []*Codestep `xml:"step"`
}
// A Codestep is a single step in a codewalk.
type Codestep struct {
// Filled in from XML
Src string `xml:"src,attr"`
Title string `xml:"title,attr"`
XML string `xml:",innerxml"`
// Derived from Src; not in XML.
Err error
File string
Lo int
LoByte int
Hi int
HiByte int
Data []byte
}
// String method for printing in template.
// Formats file address nicely.
func (st *Codestep) String() string {
s := st.File
if st.Lo != 0 || st.Hi != 0 {
s += fmt.Sprintf(":%d", st.Lo)
if st.Lo != st.Hi {
s += fmt.Sprintf(",%d", st.Hi)
}
}
return s
}
// loadCodewalk reads a codewalk from the named XML file.
func loadCodewalk(filename string) (*Codewalk, error) {
f, err := fs.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
cw := new(Codewalk)
d := xml.NewDecoder(f)
d.Entity = xml.HTMLEntity
err = d.Decode(cw)
if err != nil {
return nil, &os.PathError{Op: "parsing", Path: filename, Err: err}
}
// Compute file list, evaluate line numbers for addresses.
m := make(map[string]bool)
for _, st := range cw.Step {
i := strings.Index(st.Src, ":")
if i < 0 {
i = len(st.Src)
}
filename := st.Src[0:i]
data, err := vfs.ReadFile(fs, filename)
if err != nil {
st.Err = err
continue
}
if i < len(st.Src) {
lo, hi, err := addrToByteRange(st.Src[i+1:], 0, data)
if err != nil {
st.Err = err
continue
}
// Expand match to line boundaries.
for lo > 0 && data[lo-1] != '\n' {
lo--
}
for hi < len(data) && (hi == 0 || data[hi-1] != '\n') {
hi++
}
st.Lo = byteToLine(data, lo)
st.Hi = byteToLine(data, hi-1)
}
st.Data = data
st.File = filename
m[filename] = true
}
// Make list of files
cw.File = make([]string, len(m))
i := 0
for f := range m {
cw.File[i] = f
i++
}
sort.Strings(cw.File)
return cw, nil
}
// codewalkDir serves the codewalk directory listing.
// It scans the directory for subdirectories or files named *.xml
// and prepares a table.
func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string) {
type elem struct {
Name string
Title string
}
dir, err := fs.ReadDir(abspath)
if err != nil {
log.Print(err)
pres.ServeError(w, r, relpath, err)
return
}
var v []interface{}
for _, fi := range dir {
name := fi.Name()
if fi.IsDir() {
v = append(v, &elem{name + "/", ""})
} else if strings.HasSuffix(name, ".xml") {
cw, err := loadCodewalk(abspath + "/" + name)
if err != nil {
continue
}
v = append(v, &elem{name[0 : len(name)-len(".xml")], cw.Title})
}
}
pres.ServePage(w, godoc.Page{
Title: "Codewalks",
Body: applyTemplate(codewalkdirHTML, "codewalkdir", v),
})
}
// codewalkFileprint serves requests with ?fileprint=f&lo=lo&hi=hi.
// The filename f has already been retrieved and is passed as an argument.
// Lo and hi are the numbers of the first and last line to highlight
// in the response. This format is used for the middle window pane
// of the codewalk pages. It is a separate iframe and does not get
// the usual godoc HTML wrapper.
func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
abspath := f
data, err := vfs.ReadFile(fs, abspath)
if err != nil {
log.Print(err)
pres.ServeError(w, r, f, err)
return
}
lo, _ := strconv.Atoi(r.FormValue("lo"))
hi, _ := strconv.Atoi(r.FormValue("hi"))
if hi < lo {
hi = lo
}
lo = lineToByte(data, lo)
hi = lineToByte(data, hi+1)
// Put the mark 4 lines before lo, so that the iframe
// shows a few lines of context before the highlighted
// section.
n := 4
mark := lo
for ; mark > 0 && n > 0; mark-- {
if data[mark-1] == '\n' {
if n--; n == 0 {
break
}
}
}
io.WriteString(w, `<style type="text/css">@import "/doc/codewalk/codewalk.css";</style><pre>`)
template.HTMLEscape(w, data[0:mark])
io.WriteString(w, "<a name='mark'></a>")
template.HTMLEscape(w, data[mark:lo])
if lo < hi {
io.WriteString(w, "<div class='codewalkhighlight'>")
template.HTMLEscape(w, data[lo:hi])
io.WriteString(w, "</div>")
}
template.HTMLEscape(w, data[hi:])
io.WriteString(w, "</pre>")
}
// addrToByte evaluates the given address starting at offset start in data.
// It returns the lo and hi byte offset of the matched region within data.
// See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II
// for details on the syntax.
func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err error) {
var (
dir byte
prevc byte
charOffset bool
)
lo = start
hi = start
for addr != "" && err == nil {
c := addr[0]
switch c {
default:
err = errors.New("invalid address syntax near " + string(c))
case ',':
if len(addr) == 1 {
hi = len(data)
} else {
_, hi, err = addrToByteRange(addr[1:], hi, data)
}
return
case '+', '-':
if prevc == '+' || prevc == '-' {
lo, hi, err = addrNumber(data, lo, hi, prevc, 1, charOffset)
}
dir = c
case '$':
lo = len(data)
hi = len(data)
if len(addr) > 1 {
dir = '+'
}
case '#':
charOffset = true
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
var i int
for i = 1; i < len(addr); i++ {
if addr[i] < '0' || addr[i] > '9' {
break
}
}
var n int
n, err = strconv.Atoi(addr[0:i])
if err != nil {
break
}
lo, hi, err = addrNumber(data, lo, hi, dir, n, charOffset)
dir = 0
charOffset = false
prevc = c
addr = addr[i:]
continue
case '/':
var i, j int
Regexp:
for i = 1; i < len(addr); i++ {
switch addr[i] {
case '\\':
i++
case '/':
j = i + 1
break Regexp
}
}
if j == 0 {
j = i
}
pattern := addr[1:i]
lo, hi, err = addrRegexp(data, lo, hi, dir, pattern)
prevc = c
addr = addr[j:]
continue
}
prevc = c
addr = addr[1:]
}
if err == nil && dir != 0 {
lo, hi, err = addrNumber(data, lo, hi, dir, 1, charOffset)
}
if err != nil {
return 0, 0, err
}
return lo, hi, nil
}
// addrNumber applies the given dir, n, and charOffset to the address lo, hi.
// dir is '+' or '-', n is the count, and charOffset is true if the syntax
// used was #n. Applying +n (or +#n) means to advance n lines
// (or characters) after hi. Applying -n (or -#n) means to back up n lines
// (or characters) before lo.
// The return value is the new lo, hi.
func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, error) {
switch dir {
case 0:
lo = 0
hi = 0
fallthrough
case '+':
if charOffset {
pos := hi
for ; n > 0 && pos < len(data); n-- {
_, size := utf8.DecodeRune(data[pos:])
pos += size
}
if n == 0 {
return pos, pos, nil
}
break
}
// find next beginning of line
if hi > 0 {
for hi < len(data) && data[hi-1] != '\n' {
hi++
}
}
lo = hi
if n == 0 {
return lo, hi, nil
}
for ; hi < len(data); hi++ {
if data[hi] != '\n' {
continue
}
switch n--; n {
case 1:
lo = hi + 1
case 0:
return lo, hi + 1, nil
}
}
case '-':
if charOffset {
// Scan backward for bytes that are not UTF-8 continuation bytes.
pos := lo
for ; pos > 0 && n > 0; pos-- {
if data[pos]&0xc0 != 0x80 {
n--
}
}
if n == 0 {
return pos, pos, nil
}
break
}
// find earlier beginning of line
for lo > 0 && data[lo-1] != '\n' {
lo--
}
hi = lo
if n == 0 {
return lo, hi, nil
}
for ; lo >= 0; lo-- {
if lo > 0 && data[lo-1] != '\n' {
continue
}
switch n--; n {
case 1:
hi = lo
case 0:
return lo, hi, nil
}
}
}
return 0, 0, errors.New("address out of range")
}
// addrRegexp searches for pattern in the given direction starting at lo, hi.
// The direction dir is '+' (search forward from hi) or '-' (search backward from lo).
// Backward searches are unimplemented.
func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, error) {
re, err := regexp.Compile(pattern)
if err != nil {
return 0, 0, err
}
if dir == '-' {
// Could implement reverse search using binary search
// through file, but that seems like overkill.
return 0, 0, errors.New("reverse search not implemented")
}
m := re.FindIndex(data[hi:])
if len(m) > 0 {
m[0] += hi
m[1] += hi
} else if hi > 0 {
// No match. Wrap to beginning of data.
m = re.FindIndex(data)
}
if len(m) == 0 {
return 0, 0, errors.New("no match for " + pattern)
}
return m[0], m[1], nil
}
// lineToByte returns the byte index of the first byte of line n.
// Line numbers begin at 1.
func lineToByte(data []byte, n int) int {
if n <= 1 {
return 0
}
n--
for i, c := range data {
if c == '\n' {
if n--; n == 0 {
return i + 1
}
}
}
return len(data)
}
// byteToLine returns the number of the line containing the byte at index i.
func byteToLine(data []byte, i int) int {
l := 1
for j, c := range data {
if j == i {
return l
}
if c == '\n' {
l++
}
}
return l
}
@@ -1,144 +0,0 @@
// 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 has two modes.
Without the -http flag, it runs in command-line mode and prints plain text
documentation to standard output and exits. If both a library package and
a command with the same name exists, using the prefix cmd/ will force
documentation on the command rather than the library package. If the -src
flag is specified, godoc prints the exported interface of a package in Go
source form, or the implementation of a specific exported language entity:
godoc fmt # documentation for package fmt
godoc fmt Printf # documentation for fmt.Printf
godoc cmd/go # force documentation for the go command
godoc -src fmt # fmt package interface in Go source form
godoc -src fmt Printf # implementation of fmt.Printf
In command-line mode, the -q flag enables search queries against a godoc running
as a webserver. If no explicit server address is specified with the -server flag,
godoc first tries localhost:6060 and then http://golang.org.
godoc -q Reader
godoc -q math.Sin
godoc -server=:6060 -q sin
With the -http flag, it runs as a web server and presents the documentation as a
web page.
godoc -http=:6060
Usage:
godoc [flag] package [name ...]
The flags are:
-v
verbose mode
-q
arguments are considered search queries: a legal query is a
single identifier (such as ToLower) or a qualified identifier
(such as math.Sin)
-src
print (exported) source in command-line mode
-tabwidth=4
width of tabs in units of spaces
-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)
-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", ".*")
-html
print HTML in command-line mode
-goroot=$GOROOT
Go root directory
-http=addr
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
-server=addr
webserver address for command line searches
-analysis=type,pointer
comma-separated list of analyses to perform
"type": display identifier resolution, type info, method sets,
'implements', and static callees
"pointer" display channel peers, callers and dynamic callees
(significantly slower)
See http://golang.org/lib/godoc/analysis/help.html for details.
-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 godoc runs as a web server and -index 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.
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 then the extracted documentation
text present the page in textual (command-line) form rather than HTML
flat present flat (not indented) directory listings using full paths
For instance, http://golang.org/pkg/math/big/?m=all,text shows the documentation
for all (not just the exported) declarations of package big, in textual form (as
it would appear when using godoc from the command line: "godoc -src math/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 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 http://golang.org/pkg/go/doc/#ToHTML for the exact rules.
See "Godoc: documenting Go code" for how to write good comments for godoc:
http://golang.org/doc/articles/godoc_documenting_go_code.html
*/
package main
@@ -1,278 +0,0 @@
// 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"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
"time"
)
var godocTests = []struct {
args []string
matches []string // regular expressions
dontmatch []string // regular expressions
}{
{
args: []string{"fmt"},
matches: []string{
`import "fmt"`,
`Package fmt implements formatted I/O`,
},
},
{
args: []string{"io", "WriteString"},
matches: []string{
`func WriteString\(`,
`WriteString writes the contents of the string s to w`,
},
},
{
args: []string{"nonexistingpkg"},
matches: []string{
`no such file or directory|does not exist|cannot find the file`,
},
},
{
args: []string{"fmt", "NonexistentSymbol"},
matches: []string{
`No match found\.`,
},
},
{
args: []string{"-src", "syscall", "Open"},
matches: []string{
`func Open\(`,
},
dontmatch: []string{
`No match found\.`,
},
},
}
// 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()) {
tmp, err := ioutil.TempDir("", "godoc-regtest-")
if err != nil {
t.Fatal(err)
}
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) }
}
// Basic regression test for godoc command-line tool.
func TestCLI(t *testing.T) {
bin, cleanup := buildGodoc(t)
defer cleanup()
for _, test := range godocTests {
cmd := exec.Command(bin, test.args...)
cmd.Args[0] = "godoc"
out, err := cmd.CombinedOutput()
if err != nil {
t.Errorf("Running with args %#v: %v", test.args, err)
continue
}
for _, pat := range test.matches {
re := regexp.MustCompile(pat)
if !re.Match(out) {
t.Errorf("godoc %v =\n%s\nwanted /%v/", strings.Join(test.args, " "), out, pat)
}
}
for _, pat := range test.dontmatch {
re := regexp.MustCompile(pat)
if re.Match(out) {
t.Errorf("godoc %v =\n%s\ndid not want /%v/", strings.Join(test.args, " "), out, pat)
}
}
}
}
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 waitForServer(t *testing.T, address string) {
// Poll every 50ms for a total of 5s.
for i := 0; i < 100; i++ {
time.Sleep(50 * time.Millisecond)
conn, err := net.Dial("tcp", address)
if err != nil {
continue
}
conn.Close()
return
}
t.Fatalf("Server %q failed to respond in 5 seconds", address)
}
// Basic integration test for godoc HTTP interface.
func TestWeb(t *testing.T) {
bin, cleanup := buildGodoc(t)
defer cleanup()
addr := serverAddress(t)
cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr))
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 cmd.Process.Kill()
waitForServer(t, addr)
tests := []struct{ path, substr string }{
{"/", "Go is an open source programming language"},
{"/pkg/fmt/", "Package fmt implements formatted I/O"},
{"/src/pkg/fmt/", "scan_test.go"},
{"/src/pkg/fmt/print.go", "// Println formats using"},
}
for _, test := range tests {
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)
resp.Body.Close()
if err != nil {
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
}
if bytes.Index(body, []byte(test.substr)) < 0 {
t.Errorf("GET %s: want substring %q in body, got:\n%s",
url, test.substr, string(body))
}
}
}
// Basic integration test for godoc -analysis=type (via HTTP interface).
func TestTypeAnalysis(t *testing.T) {
// Write a fake GOROOT/GOPATH.
tmpdir, err := ioutil.TempDir("", "godoc-analysis")
if err != nil {
t.Fatal("ioutil.TempDir failed: %s", err)
}
defer os.RemoveAll(tmpdir)
for _, f := range []struct{ file, content string }{
{"goroot/src/pkg/lib/lib.go", `
package lib
type T struct{}
const C = 3
var V T
func (T) F() int { return C }
`},
{"gopath/src/app/main.go", `
package main
import "lib"
func main() { print(lib.V) }
`},
} {
file := filepath.Join(tmpdir, f.file)
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
t.Fatalf("MkdirAll(%s) failed: %s", filepath.Dir(file), err)
}
if err := ioutil.WriteFile(file, []byte(f.content), 0644); err != nil {
t.Fatal(err)
}
}
// Start the server.
bin, cleanup := buildGodoc(t)
defer cleanup()
addr := serverAddress(t)
cmd := exec.Command(bin, fmt.Sprintf("-http=%s", addr), "-analysis=type")
cmd.Env = append(cmd.Env, fmt.Sprintf("GOROOT=%s/goroot", tmpdir))
cmd.Env = append(cmd.Env, fmt.Sprintf("GOPATH=%s/gopath", tmpdir))
cmd.Env = append(cmd.Env, os.Environ()...)
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 cmd.Process.Kill()
waitForServer(t, addr)
t0 := time.Now()
// Make an HTTP request and check for a regular expression match.
// The patterns are very crude checks that basic type information
// has been annotated onto the source view.
tryagain:
for _, test := range []struct{ url, pattern string }{
{"/src/pkg/lib/lib.go", "L2.*package .*Package docs for lib.*/pkg/lib"},
{"/src/pkg/lib/lib.go", "L3.*type .*type info for T.*struct"},
{"/src/pkg/lib/lib.go", "L5.*var V .*type T struct"},
{"/src/pkg/lib/lib.go", "L6.*func .*type T struct.*T.*return .*const C untyped int.*C"},
{"/src/pkg/app/main.go", "L2.*package .*Package docs for app"},
{"/src/pkg/app/main.go", "L3.*import .*Package docs for lib.*lib"},
{"/src/pkg/app/main.go", "L4.*func main.*package lib.*lib.*var lib.V lib.T.*V"},
} {
url := fmt.Sprintf("http://%s%s", addr, test.url)
resp, err := http.Get(url)
if err != nil {
t.Errorf("GET %s failed: %s", url, err)
continue
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
continue
}
if !bytes.Contains(body, []byte("Static analysis features")) {
// Type analysis results usually become available within
// ~4ms after godoc startup (for this input on my machine).
if elapsed := time.Since(t0); elapsed > 500*time.Millisecond {
t.Fatalf("type analysis results still unavailable after %s", elapsed)
}
time.Sleep(10 * time.Millisecond)
goto tryagain
}
match, err := regexp.Match(test.pattern, body)
if err != nil {
t.Errorf("regexp.Match(%q) failed: %s", test.pattern, err)
continue
}
if !match {
// This is a really ugly failure message.
t.Errorf("GET %s: body doesn't match %q, got:\n%s",
url, test.pattern, string(body))
}
}
}
@@ -1,83 +0,0 @@
// 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.
// The /doc/codewalk/ tree is synthesized from codewalk descriptions,
// files named $GOROOT/doc/codewalk/*.xml.
// For an example and a description of the format, see
// http://golang.org/doc/codewalk/codewalk or run godoc -http=:6060
// and see http://localhost:6060/doc/codewalk/codewalk .
// That page is itself a codewalk; the source code for it is
// $GOROOT/doc/codewalk/codewalk.xml.
package main
import (
"log"
"net/http"
"text/template"
"code.google.com/p/go.tools/godoc"
"code.google.com/p/go.tools/godoc/redirect"
"code.google.com/p/go.tools/godoc/vfs"
)
var (
pres *godoc.Presentation
fs = vfs.NameSpace{}
)
func registerHandlers(pres *godoc.Presentation) {
if pres == nil {
panic("nil Presentation")
}
http.HandleFunc("/doc/codewalk/", codewalk)
http.Handle("/doc/play/", pres.FileServer())
http.Handle("/robots.txt", pres.FileServer())
http.Handle("/", pres)
http.Handle("/pkg/C/", redirect.Handler("/cmd/cgo/"))
redirect.Register(nil)
}
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, html bool) {
p.PackageText = readTemplate("package.txt")
p.SearchText = readTemplate("search.txt")
if html || p.HTMLMode {
codewalkHTML = readTemplate("codewalk.html")
codewalkdirHTML = readTemplate("codewalkdir.html")
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.SearchHTML = readTemplate("search.html")
p.SearchDocHTML = readTemplate("searchdoc.html")
p.SearchCodeHTML = readTemplate("searchcode.html")
p.SearchTxtHTML = readTemplate("searchtxt.html")
p.SearchDescXML = readTemplate("opensearch.xml")
}
}
@@ -1,325 +0,0 @@
// 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/ main landing page
// http://godoc/doc/ serve from $GOROOT/doc - spec, mem, etc.
// 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)
//
// Command-line interface:
//
// godoc packagepath [name ...]
//
// godoc compress/zlib
// - prints doc for package compress/zlib
// godoc crypto/block Cipher NewCMAC
// - prints doc for Cipher and NewCMAC in package crypto/block
// +build !appengine
package main
import (
"archive/zip"
_ "expvar" // to serve /debug/vars
"flag"
"fmt"
"go/build"
"log"
"net/http"
"net/http/httptest"
_ "net/http/pprof" // to serve /debug/pprof/*
"net/url"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"code.google.com/p/go.tools/godoc"
"code.google.com/p/go.tools/godoc/analysis"
"code.google.com/p/go.tools/godoc/static"
"code.google.com/p/go.tools/godoc/vfs"
"code.google.com/p/go.tools/godoc/vfs/gatefs"
"code.google.com/p/go.tools/godoc/vfs/mapfs"
"code.google.com/p/go.tools/godoc/vfs/zipfs"
)
const (
defaultAddr = ":6060" // default webserver address
toolsPath = "code.google.com/p/go.tools/cmd/"
)
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")
analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html`)
// network
httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')")
serverAddr = flag.String("server", "", "webserver address for command line searches")
// layout control
html = flag.Bool("html", false, "print HTML in command-line mode")
srcMode = flag.Bool("src", false, "print (exported) source in command-line mode")
urlFlag = flag.String("url", "", "print HTML for named URL")
// command-line searches
query = flag.Bool("q", false, "arguments are considered search queries")
verbose = flag.Bool("v", false, "verbose mode")
// file system roots
// TODO(gri) consider the invariant that goroot always end in '/'
goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
// layout control
tabWidth = flag.Int("tabwidth", 4, "tab width")
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
templateDir = flag.String("templates", "", "directory containing alternate template files")
showPlayground = flag.Bool("play", false, "enable playground in web interface")
showExamples = flag.Bool("ex", false, "show examples in command line mode")
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")
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")
)
func usage() {
fmt.Fprintf(os.Stderr,
"usage: godoc package [name ...]\n"+
" 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 := httptest.NewRecorder()
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.HeaderMap.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 main() {
flag.Usage = usage
flag.Parse()
playEnabled = *showPlayground
// Check usage: either server and no args, command line and args, or index creation mode
if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex {
usage()
}
var fsGate chan bool
fsGate = make(chan bool, 20)
// Determine file system to use.
if *zipfile == "" {
// use file system of underlying OS
fs.Bind("/", gatefs.New(vfs.OS(*goroot), fsGate), "/", 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)
} else {
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
}
// Bind $GOPATH trees into Go root.
for _, p := range filepath.SplitList(build.Default.GOPATH) {
fs.Bind("/src/pkg", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
}
httpMode := *httpAddr != ""
var typeAnalysis, pointerAnalysis bool
if *analysisFlag != "" {
for _, a := range strings.Split(*analysisFlag, ",") {
switch a {
case "type":
typeAnalysis = true
case "pointer":
pointerAnalysis = true
default:
log.Fatalf("unknown analysis: %s", a)
}
}
}
corpus := godoc.NewCorpus(fs)
corpus.Verbose = *verbose
corpus.MaxResults = *maxResults
corpus.IndexEnabled = *indexEnabled && httpMode
if *maxResults == 0 {
corpus.IndexFullText = false
}
corpus.IndexFiles = *indexFiles
corpus.IndexThrottle = *indexThrottle
if *writeIndex {
corpus.IndexThrottle = 1.0
}
if *writeIndex || httpMode || *urlFlag != "" {
if err := corpus.Init(); err != nil {
log.Fatal(err)
}
}
pres = godoc.NewPresentation(corpus)
pres.TabWidth = *tabWidth
pres.ShowTimestamps = *showTimestamps
pres.ShowPlayground = *showPlayground
pres.ShowExamples = *showExamples
pres.DeclLinks = *declLinks
pres.SrcMode = *srcMode
pres.HTMLMode = *html
if *notesRx != "" {
pres.NotesRx = regexp.MustCompile(*notesRx)
}
readTemplates(pres, httpMode || *urlFlag != "")
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
}
if httpMode {
// HTTP server mode.
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)
log.Printf("tabwidth = %d", *tabWidth)
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 type/pointer analysis.
if typeAnalysis || pointerAnalysis {
go analysis.Run(pointerAnalysis, &corpus.Analysis)
}
// Start http server.
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
}
return
}
if *query {
handleRemoteSearch()
return
}
if err := godoc.CommandLine(os.Stdout, fs, pres, flag.Args()); err != nil {
log.Print(err)
}
}
@@ -1,44 +0,0 @@
// 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 (
"encoding/json"
"fmt"
"go/format"
"net/http"
// This package registers "/compile" and "/share" handlers
// that redirect to the golang.org playground.
_ "code.google.com/p/go.tools/playground"
)
func init() {
http.HandleFunc("/fmt", fmtHandler)
}
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)
}
json.NewEncoder(w).Encode(resp)
}
// disabledHandler serves a 501 "Not Implemented" response.
func disabledHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
fmt.Fprint(w, "This functionality is not available via local godoc.")
}
@@ -1,72 +0,0 @@
// 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.
// +build !appengine
package main
import (
"errors"
"flag"
"io"
"log"
"net/http"
"net/url"
"os"
)
func handleRemoteSearch() {
// Command-line queries.
for i := 0; i < flag.NArg(); i++ {
res, err := remoteSearch(flag.Arg(i))
if err != nil {
log.Fatalf("remoteSearch: %s", err)
}
io.Copy(os.Stdout, res.Body)
}
return
}
// remoteSearchURL returns the search URL for a given query as needed by
// remoteSearch. If html is set, an html result is requested; otherwise
// the result is in textual form.
// Adjust this function as necessary if modeNames or FormValue parameters
// change.
func remoteSearchURL(query string, html bool) string {
s := "/search?m=text&q="
if html {
s = "/search?q="
}
return s + url.QueryEscape(query)
}
func remoteSearch(query string) (res *http.Response, err error) {
// list of addresses to try
var addrs []string
if *serverAddr != "" {
// explicit server address - only try this one
addrs = []string{*serverAddr}
} else {
addrs = []string{
defaultAddr,
"golang.org",
}
}
// remote search
search := remoteSearchURL(query, *html)
for _, addr := range addrs {
url := "http://" + addr + search
res, err = http.Get(url)
if err == nil && res.StatusCode == http.StatusOK {
break
}
}
if err == nil && res.StatusCode != http.StatusOK {
err = errors.New(res.Status)
}
return
}
@@ -1,134 +0,0 @@
#!/usr/bin/env bash
# Copyright 2011 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# This script creates a complete godoc app in $APPDIR.
# It copies the cmd/godoc and src/pkg/go/... sources from GOROOT,
# synthesizes an app.yaml file, and creates the .zip, index, and
# configuration files.
#
# If an argument is provided it is assumed to be the app-engine godoc directory.
# Without an argument, $APPDIR is used instead. If GOROOT is not set, "go env"
# is consulted to find the $GOROOT.
#
# The script creates a .zip file representing the $GOROOT file system
# and computes the correspondig search index files. These files are then
# copied to $APPDIR. A corresponding godoc configuration file is created
# in $APPDIR/appconfig.go.
ZIPFILE=godoc.zip
INDEXFILE=godoc.index
SPLITFILES=index.split.
GODOC=code.google.com/p/go.tools/cmd/godoc
CONFIGFILE=$GODOC/appconfig.go
error() {
echo "error: $1"
exit 2
}
getArgs() {
if [ -z $APPENGINE_SDK ]; then
error "APPENGINE_SDK environment variable not set"
fi
if [ ! -x $APPENGINE_SDK/go ]; then
error "couldn't find go comment in $APPENGINE_SDK"
fi
if [ -z $GOROOT ]; then
GOROOT=$(go env GOROOT)
echo "GOROOT not set explicitly, using go env value instead"
fi
if [ -z $APPDIR ]; then
if [ $# == 0 ]; then
error "APPDIR not set, and no argument provided"
fi
APPDIR=$1
echo "APPDIR not set, using argument instead"
fi
# safety checks
if [ ! -d $GOROOT ]; then
error "$GOROOT is not a directory"
fi
if [ -e $APPDIR ]; then
error "$APPDIR exists; check and remove it before trying again"
fi
# reporting
echo "GOROOT = $GOROOT"
echo "APPDIR = $APPDIR"
}
fetchGodoc() {
echo "*** Fetching godoc (if not already in GOPATH)"
unset GOBIN
go=$APPENGINE_SDK/go
$go get -d -tags appengine $GODOC
mkdir -p $APPDIR/$GODOC
cp $(find $($go list -f '{{.Dir}}' $GODOC) -type f -depth 1) $APPDIR/$GODOC/
}
makeAppYaml() {
echo "*** make $APPDIR/app.yaml"
cat > $APPDIR/app.yaml <<EOF
application: godoc
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
EOF
}
makeZipfile() {
echo "*** make $APPDIR/$ZIPFILE"
zip -q -r $APPDIR/$ZIPFILE $GOROOT/*
}
makeIndexfile() {
echo "*** make $APPDIR/$INDEXFILE"
GOPATH= godoc -write_index -index_files=$APPDIR/$INDEXFILE -zip=$APPDIR/$ZIPFILE
}
splitIndexfile() {
echo "*** split $APPDIR/$INDEXFILE"
split -b8m $APPDIR/$INDEXFILE $APPDIR/$SPLITFILES
}
makeConfigfile() {
echo "*** make $APPDIR/$CONFIGFILE"
cat > $APPDIR/$CONFIGFILE <<EOF
package main
// GENERATED FILE - DO NOT MODIFY BY HAND.
// (generated by $GOROOT/src/cmd/godoc/setup-godoc-app.bash)
const (
// .zip filename
zipFilename = "$ZIPFILE"
// goroot directory in .zip file
zipGoroot = "$GOROOT"
// glob pattern describing search index files
// (if empty, the index is built at run-time)
indexFilenames = "$SPLITFILES*"
)
EOF
}
getArgs "$@"
set -e
mkdir $APPDIR
fetchGodoc
makeAppYaml
makeZipfile
makeIndexfile
splitIndexfile
makeConfigfile
echo "*** setup complete"
@@ -1,33 +0,0 @@
/*
Command goimports updates your Go import lines,
adding missing ones and removing unreferenced ones.
$ go get code.google.com/p/go.tools/cmd/goimports
It's a drop-in replacement for your editor's gofmt-on-save hook.
It has the the same command-line interface as gofmt and formats
your code in the same way.
For emacs, make sure you have the latest (Go 1.2+) go-mode.el:
https://go.googlecode.com/hg/misc/emacs/go-mode.el
Then in your .emacs file:
(setq gofmt-command "goimports")
(add-to-list 'load-path "/home/you/goroot/misc/emacs/")
(require 'go-mode-load)
(add-hook 'before-save-hook 'gofmt-before-save)
For vim, set "gofmt_command" to "goimports":
https://code.google.com/p/go/source/detail?r=39c724dd7f252
https://code.google.com/p/go/source/browse#hg%2Fmisc%2Fvim
etc
For GoSublime, follow the steps described here:
http://michaelwhatcott.com/gosublime-goimports/
For other editors, you probably know what to do.
Happy hacking!
*/
package main
@@ -1,195 +0,0 @@
// 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/scanner"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"code.google.com/p/go.tools/imports"
)
var (
// main operation modes
list = flag.Bool("l", false, "list files whose formatting differs from goimport's")
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
options = &imports.Options{
TabWidth: 8,
TabIndent: true,
Comments: true,
Fragment: true,
}
exitCode = 0
)
func init() {
flag.BoolVar(&options.AllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
}
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")
}
func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
opt := options
if stdin {
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
}
res, err := imports.Process(filename, src, opt)
if err != nil {
return err
}
if !bytes.Equal(src, res) {
// formatting has changed
if *list {
fmt.Fprintln(out, filename)
}
if *write {
err = ioutil.WriteFile(filename, res, 0)
if err != nil {
return err
}
}
if *doDiff {
data, err := diff(src, res)
if err != nil {
return fmt.Errorf("computing diff: %s", err)
}
fmt.Printf("diff %s gofmt/%s\n", filename, 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, false)
}
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)
}
func gofmtMain() {
flag.Usage = usage
flag.Parse()
if options.TabWidth < 0 {
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth)
exitCode = 2
return
}
if flag.NArg() == 0 {
if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
report(err)
}
return
}
for i := 0; i < flag.NArg(); i++ {
path := flag.Arg(i)
switch dir, err := os.Stat(path); {
case err != nil:
report(err)
case dir.IsDir():
walkDir(path)
default:
if err := processFile(path, nil, os.Stdout, false); err != nil {
report(err)
}
}
}
}
func diff(b1, b2 []byte) (data []byte, err error) {
f1, err := ioutil.TempFile("", "gofmt")
if err != nil {
return
}
defer os.Remove(f1.Name())
defer f1.Close()
f2, err := ioutil.TempFile("", "gofmt")
if err != nil {
return
}
defer os.Remove(f2.Name())
defer f2.Close()
f1.Write(b1)
f2.Write(b2)
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).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.
err = nil
}
return
}
@@ -1,62 +0,0 @@
// 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.
/*
The gotype command does syntactic and semantic analysis of Go files
and packages like the front-end of a Go compiler. 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.
If a single path is specified that is a directory, gotype checks
the Go files in that directory; they must all belong to the same
package.
Otherwise, each path must be the filename of Go file belonging to
the same package.
Usage:
gotype [flags] [path...]
The flags are:
-a
use all (incl. _test.go) files when processing a directory
-e
report all errors (not just the first 10)
-v
verbose mode
-gccgo
use gccimporter instead of gcimporter
Debugging flags:
-seq
parse sequentially, rather than in parallel
-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 in the directory dir and print the processed files:
gotype -v dir
To check an entire package including tests in the local directory:
gotype -a .
To verify the output of a pipe:
echo "package foo" | gotype
*/
package main
@@ -1,262 +0,0 @@
// 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.
package main
import (
"flag"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/scanner"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"time"
"code.google.com/p/go.tools/go/gccgoimporter"
_ "code.google.com/p/go.tools/go/gcimporter"
"code.google.com/p/go.tools/go/types"
)
var (
// main operation modes
allFiles = flag.Bool("a", false, "use all (incl. _test.go) files when processing a directory")
allErrors = flag.Bool("e", false, "report all errors (not just the first 10)")
verbose = flag.Bool("v", false, "verbose mode")
gccgo = flag.Bool("gccgo", false, "use gccgoimporter instead of gcimporter")
// debugging support
sequential = flag.Bool("seq", false, "parse sequentially, rather than in parallel")
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
parserMode parser.Mode
sizes types.Sizes
)
func initParserMode() {
if *allErrors {
parserMode |= parser.AllErrors
}
if *printTrace {
parserMode |= parser.Trace
}
if *parseComments && (*printAST || *printTrace) {
parserMode |= parser.ParseComments
}
}
func initSizes() {
wordSize := 8
maxAlign := 8
switch build.Default.GOARCH {
case "386", "arm":
wordSize = 4
maxAlign = 4
// add more cases as needed
}
sizes = &types.StdSizes{WordSize: int64(wordSize), MaxAlign: int64(maxAlign)}
}
func usage() {
fmt.Fprintln(os.Stderr, "usage: gotype [flags] [path ...]")
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("<standard input>", src)
}
func parseFiles(filenames []string) ([]*ast.File, error) {
files := make([]*ast.File, len(filenames))
if *sequential {
for i, filename := range filenames {
var err error
files[i], err = parse(filename, nil)
if err != nil {
return nil, err // leave unfinished goroutines hanging
}
}
} else {
type parseResult struct {
file *ast.File
err error
}
out := make(chan parseResult)
for _, filename := range filenames {
go func(filename string) {
file, err := parse(filename, nil)
out <- parseResult{file, err}
}(filename)
}
for i := range filenames {
res := <-out
if res.err != nil {
return nil, res.err // leave unfinished goroutines hanging
}
files[i] = res.file
}
}
return files, nil
}
func parseDir(dirname string) ([]*ast.File, error) {
ctxt := build.Default
pkginfo, err := ctxt.ImportDir(dirname, 0)
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
return nil, err
}
filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
if *allFiles {
filenames = append(filenames, pkginfo.TestGoFiles...)
}
// complete file names
for i, filename := range filenames {
filenames[i] = filepath.Join(dirname, filename)
}
return parseFiles(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{}
conf := types.Config{
FakeImportC: true,
Error: func(err error) {
if !*allErrors && errorCount >= 10 {
panic(bailout{})
}
report(err)
},
Sizes: sizes,
}
if *gccgo {
var inst gccgoimporter.GccgoInstallation
inst.InitFromDriver("gccgo")
conf.Import = inst.GetImporter(nil)
}
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() {
runtime.GOMAXPROCS(runtime.NumCPU()) // remove this once runtime is smarter
flag.Usage = usage
flag.Parse()
if *printAST || *printTrace {
*sequential = true
}
initParserMode()
initSizes()
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))
}
}
@@ -1,348 +0,0 @@
// 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: code.google.com/p/go.tools/present
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"regexp"
"strings"
"code.google.com/p/go.net/html"
"code.google.com/p/go.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))
parseStyles(style)
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) {
if style == nil || style.FirstChild == nil {
log.Println("couldn't find styles")
return
}
s := bufio.NewScanner(strings.NewReader(style.FirstChild.Data))
findRule := func(b []byte, atEOF bool) (advance int, token []byte, err error) {
if i := bytes.Index(b, []byte("{")); i >= 0 {
token = bytes.TrimSpace(b[:i])
advance = i
}
return
}
findBody := func(b []byte, atEOF bool) (advance int, token []byte, err error) {
if len(b) == 0 {
return
}
if b[0] != '{' {
err = fmt.Errorf("expected {, got %c", b[0])
return
}
if i := bytes.Index(b, []byte("}")); i < 0 {
err = fmt.Errorf("can't find closing }")
return
} else {
token = b[1:i]
advance = i + 1
}
return
}
s.Split(findRule)
for s.Scan() {
rule := s.Text()
s.Split(findBody)
if !s.Scan() {
break
}
b := strings.ToLower(s.Text())
switch {
case strings.Contains(b, "italic"):
cssRules[rule] = Italic
case strings.Contains(b, "bold"):
cssRules[rule] = Bold
case strings.Contains(b, "Consolas") || strings.Contains(b, "Courier New"):
cssRules[rule] = Code
}
s.Split(findRule)
}
if err := s.Err(); err != nil {
log.Println(err)
}
}
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:
fmt.Fprintf(&buf, "[[%s][%s]]", attr(n, "href"), childText(n))
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)
}
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 hasAttr(key, val string) selector {
return func(n *html.Node) bool {
for _, a := range n.Attr {
if a.Key == key && a.Val == val {
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 findAll(node *html.Node, fn selector) (nodes []*html.Node) {
walk(node, func(n *html.Node) bool {
if fn(n) {
nodes = append(nodes, n)
}
return true
})
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)
}
}
}
@@ -1,50 +0,0 @@
#!/bin/bash
#
# Simple test of Go oracle/Emacs integration.
# Requires that GOROOT and GOPATH are set.
# Side effect: builds and installs oracle in $GOROOT.
set -eu
[ -z "$GOROOT" ] && { echo "Error: GOROOT is unset." >&2; exit 1; }
[ -z "$GOPATH" ] && { echo "Error: GOPATH is unset." >&2; exit 1; }
log=/tmp/$(basename $0)-$$.log
thisdir=$(dirname $0)
function die() {
echo "Error: $@."
cat $log
exit 1
} >&2
trap "rm -f $log" EXIT
# Build and install oracle.
go get code.google.com/p/go.tools/cmd/oracle || die "'go get' failed"
mv -f $GOPATH/bin/oracle $GOROOT/bin/
$GOROOT/bin/oracle >$log 2>&1 || true # (prints usage and exits 1)
grep -q "Run.*help" $log || die "$GOROOT/bin/oracle not installed"
# Run Emacs, set the scope to the oracle tool itself,
# load ./main.go, and describe the "fmt" import.
emacs --batch --no-splash --no-window-system --no-init \
--load $GOROOT/misc/emacs/go-mode.el \
--load $thisdir/oracle.el \
--eval '
(progn
(setq go-oracle-scope "code.google.com/p/go.tools/cmd/oracle")
(find-file "'$thisdir'/main.go")
(search-forward "\"fmt\"")
(backward-char)
(go-oracle-describe)
(princ (with-current-buffer "*go-oracle*"
(buffer-substring-no-properties (point-min) (point-max))))
(kill-emacs 0))
' main.go >$log 2>&1 || die "emacs command failed"
# Check that Println is mentioned.
grep -q "fmt/print.go.*func Println" $log || die "didn't find expected lines in log; got:"
echo "PASS"
@@ -1,192 +0,0 @@
// 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.
// oracle: a tool for answering questions about Go source code.
// http://golang.org/s/oracle-design
// http://golang.org/s/oracle-user-manual
//
// Run with -help flag or help subcommand for usage information.
//
package main
import (
"bufio"
"encoding/json"
"encoding/xml"
"flag"
"fmt"
"go/build"
"io"
"log"
"os"
"runtime"
"runtime/pprof"
"code.google.com/p/go.tools/go/loader"
"code.google.com/p/go.tools/oracle"
)
var posFlag = flag.String("pos", "",
"Filename and byte offset or extent of a syntax element about which to query, "+
"e.g. foo.go:#123,#456, bar.go:#123.")
var ptalogFlag = flag.String("ptalog", "",
"Location of the points-to analysis log file, or empty to disable logging.")
var formatFlag = flag.String("format", "plain", "Output format. One of {plain,json,xml}.")
// TODO(adonovan): flip this flag after PTA presolver is implemented.
var reflectFlag = flag.Bool("reflect", false, "Analyze reflection soundly (slow).")
const useHelp = "Run 'oracle -help' for more information.\n"
const helpMessage = `Go source code oracle.
Usage: oracle [<flag> ...] <mode> <args> ...
The -format flag controls the output format:
plain an editor-friendly format in which every line of output
is of the form "pos: text", where pos is "-" if unknown.
json structured data in JSON syntax.
xml structured data in XML syntax.
The -pos flag is required in all modes except 'callgraph'.
The mode argument determines the query to perform:
callees show possible targets of selected function call
callers show possible callers of selected function
callgraph show complete callgraph of program
callstack show path from callgraph root to selected function
describe describe selected syntax: definition, methods, etc
freevars show free variables of selection
implements show 'implements' relation for selected package
peers show send/receive corresponding to selected channel op
referrers show all refs to entity denoted by selected identifier
The user manual is available here: http://golang.org/s/oracle-user-manual
Examples:
Describe the syntax at offset 530 in this file (an import spec):
% oracle -pos=src/code.google.com/p/go.tools/cmd/oracle/main.go:#530 describe \
code.google.com/p/go.tools/cmd/oracle
Print the callgraph of the trivial web-server in JSON format:
% oracle -format=json src/pkg/net/http/triv.go callgraph
` + loader.FromArgsUsage
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
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 printHelp() {
fmt.Fprintln(os.Stderr, helpMessage)
fmt.Fprintln(os.Stderr, "Flags:")
flag.PrintDefaults()
}
func main() {
// 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) == 0 || args[0] == "" {
fmt.Fprint(os.Stderr, "Error: a mode argument is required.\n"+useHelp)
os.Exit(2)
}
mode := args[0]
args = args[1:]
if mode == "help" {
printHelp()
os.Exit(2)
}
if len(args) == 0 && mode != "what" {
fmt.Fprint(os.Stderr, "Error: no package arguments.\n"+useHelp)
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() {
buf.Flush()
f.Close()
}()
}
}
// Profiling support.
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
// -format flag
switch *formatFlag {
case "json", "plain", "xml":
// ok
default:
fmt.Fprintf(os.Stderr, "Error: illegal -format value: %q.\n"+useHelp, *formatFlag)
os.Exit(2)
}
// Ask the oracle.
res, err := oracle.Query(args, mode, *posFlag, ptalog, &build.Default, *reflectFlag)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s.\n", err)
os.Exit(1)
}
// Print the result.
switch *formatFlag {
case "json":
b, err := json.MarshalIndent(res.Serial(), "", "\t")
if err != nil {
fmt.Fprintf(os.Stderr, "JSON error: %s.\n", err)
os.Exit(1)
}
os.Stdout.Write(b)
case "xml":
b, err := xml.MarshalIndent(res.Serial(), "", "\t")
if err != nil {
fmt.Fprintf(os.Stderr, "XML error: %s.\n", err)
os.Exit(1)
}
os.Stdout.Write(b)
case "plain":
res.WriteTo(os.Stdout)
}
}
@@ -1,223 +0,0 @@
;;;
;;; Integration of the Go 'oracle' analysis tool into Emacs.
;;;
;;; To install the Go oracle, run:
;;; % export GOROOT=... GOPATH=...
;;; % go get code.google.com/p/go.tools/cmd/oracle
;;; % mv $GOPATH/bin/oracle $GOROOT/bin/
;;;
;;; Load this file into Emacs and set go-oracle-scope to your
;;; configuration. Then, find a file of Go source code, enable
;;; go-oracle-mode, select an expression of interest, and press `C-c C-o d'
;;; (for "describe") or run one of the other go-oracle-xxx commands.
;;;
;;; TODO(adonovan): simplify installation and configuration by making
;;; oracle a subcommand of 'go tool'.
(require 'compile)
(require 'go-mode)
(require 'cl)
(defgroup go-oracle nil
"Options specific to the Go oracle."
:group 'go)
(defcustom go-oracle-command (concat (car (go-root-and-paths)) "/bin/oracle")
"The Go oracle command; the default is $GOROOT/bin/oracle."
:type 'string
:group 'go-oracle)
(defcustom go-oracle-scope ""
"The scope of the analysis. See `go-oracle-set-scope'."
:type 'string
:group 'go-oracle)
(defvar go-oracle--scope-history
nil
"History of values supplied to `go-oracle-set-scope'.")
;; TODO(adonovan): I'd like to get rid of this separate mode since it
;; makes it harder to use the oracle.
(defvar go-oracle-mode-map
(let ((m (make-sparse-keymap)))
(define-key m (kbd "C-c C-o t") #'go-oracle-describe) ; t for type
(define-key m (kbd "C-c C-o f") #'go-oracle-freevars)
(define-key m (kbd "C-c C-o g") #'go-oracle-callgraph)
(define-key m (kbd "C-c C-o i") #'go-oracle-implements)
(define-key m (kbd "C-c C-o c") #'go-oracle-peers) ; c for channel
(define-key m (kbd "C-c C-o r") #'go-oracle-referrers)
(define-key m (kbd "C-c C-o d") #'go-oracle-definition)
(define-key m (kbd "C-c C-o p") #'go-oracle-pointsto)
(define-key m (kbd "C-c C-o s") #'go-oracle-callstack)
(define-key m (kbd "C-c C-o <") #'go-oracle-callers)
(define-key m (kbd "C-c C-o >") #'go-oracle-callees)
m))
;; TODO(dominikh): Rethink set-scope some. Setting it to a file is
;; painful because it doesn't use find-file, and variables/~ aren't
;; expanded. Setting it to an import path is somewhat painful because
;; it doesn't make use of go-mode's import path completion. One option
;; would be having two different functions, but then we can't
;; automatically call it when no scope has been set. Also it wouldn't
;; easily allow specifying more than one file/package.
(defun go-oracle-set-scope ()
"Set the scope for the Go oracle, prompting the user to edit the
previous scope.
The scope specifies a set of arguments, separated by spaces.
It may be:
1) a set of packages whose main() functions will be analyzed.
2) a list of *.go filenames; they will treated like as a single
package (see #3).
3) a single package whose main() function and/or Test* functions
will be analyzed.
In the common case, this is similar to the argument(s) you would
specify to 'go build'."
(interactive)
(let ((scope (read-from-minibuffer "Go oracle scope: "
go-oracle-scope
nil
nil
'go-oracle--scope-history)))
(if (string-equal "" scope)
(error "You must specify a non-empty scope for the Go oracle"))
(setq go-oracle-scope scope)))
(defun go-oracle--run (mode)
"Run the Go oracle in the specified MODE, passing it the
selected region of the current buffer. Process the output to
replace each file name with a small hyperlink. Display the
result."
(if (not buffer-file-name)
(error "Cannot use oracle on a buffer without a file name"))
;; It's not sufficient to save a modified buffer since if
;; gofmt-before-save is on the before-save-hook, saving will
;; disturb the selected region.
(if (buffer-modified-p)
(error "Please save the buffer before invoking go-oracle"))
(if (string-equal "" go-oracle-scope)
(go-oracle-set-scope))
(let* ((filename (file-truename buffer-file-name))
(posflag (if (use-region-p)
(format "-pos=%s:#%d,#%d"
filename
(1- (go--position-bytes (region-beginning)))
(1- (go--position-bytes (region-end))))
(format "-pos=%s:#%d"
filename
(1- (position-bytes (point))))))
;; This would be simpler if we could just run 'go tool oracle'.
(env-vars (go-root-and-paths))
(goroot-env (concat "GOROOT=" (car env-vars)))
(gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":"))))
(with-current-buffer (get-buffer-create "*go-oracle*")
(setq buffer-read-only nil)
(erase-buffer)
(insert "Go Oracle\n")
(let ((args (append (list go-oracle-command nil t nil posflag mode)
(split-string go-oracle-scope " " t))))
;; Log the command to *Messages*, for debugging.
(message "Command: %s:" args)
(message nil) ; clears/shrinks minibuffer
(message "Running oracle...")
;; Use dynamic binding to modify/restore the environment
(let ((process-environment (list* goroot-env gopath-env process-environment)))
(apply #'call-process args)))
(insert "\n")
(compilation-mode)
(setq compilation-error-screen-columns nil)
;; Hide the file/line info to save space.
;; Replace each with a little widget.
;; compilation-mode + this loop = slooow.
;; TODO(adonovan): have oracle give us JSON
;; and we'll do the markup directly.
(let ((buffer-read-only nil)
(p 1))
(while (not (null p))
(let ((np (compilation-next-single-property-change p 'compilation-message)))
;; TODO(adonovan): this can be verbose in the *Messages* buffer.
;; (message "Post-processing link (%d%%)" (/ (* p 100) (point-max)))
(if np
(when (equal (line-number-at-pos p) (line-number-at-pos np))
;; np is (typically) the space following ":"; consume it too.
(put-text-property p np 'display "")
(goto-char np)
(insert " ")))
(setq p np)))
(message nil))
(let ((w (display-buffer (current-buffer))))
(balance-windows)
(shrink-window-if-larger-than-buffer w)
(set-window-point w (point-min))))))
(defun go-oracle-callees ()
"Show possible callees of the function call at the current point."
(interactive)
(go-oracle--run "callees"))
(defun go-oracle-callers ()
"Show the set of callers of the function containing the current point."
(interactive)
(go-oracle--run "callers"))
(defun go-oracle-callgraph ()
"Show the callgraph of the current program."
(interactive)
(go-oracle--run "callgraph"))
(defun go-oracle-callstack ()
"Show an arbitrary path from a root of the call graph to the
function containing the current point."
(interactive)
(go-oracle--run "callstack"))
(defun go-oracle-definition ()
"Show the definition of the selected identifier."
(interactive)
(go-oracle--run "definition"))
(defun go-oracle-describe ()
"Describe the selected syntax, its kind, type and methods."
(interactive)
(go-oracle--run "describe"))
(defun go-oracle-pointsto ()
"Show what the selected expression points to."
(interactive)
(go-oracle--run "pointsto"))
(defun go-oracle-implements ()
"Describe the 'implements' relation for types in the package
containing the current point."
(interactive)
(go-oracle--run "implements"))
(defun go-oracle-freevars ()
"Enumerate the free variables of the current selection."
(interactive)
(go-oracle--run "freevars"))
(defun go-oracle-peers ()
"Enumerate the set of possible corresponding sends/receives for
this channel receive/send operation."
(interactive)
(go-oracle--run "peers"))
(defun go-oracle-referrers ()
"Enumerate all references to the object denoted by the selected
identifier."
(interactive)
(go-oracle--run "referrers"))
;; TODO(dominikh): better docstring
(define-minor-mode go-oracle-mode "Oracle minor mode for go-mode
Keys specific to go-oracle-mode:
\\{go-oracle-mode-map}"
nil " oracle" go-oracle-mode-map)
(provide 'go-oracle)
@@ -1,107 +0,0 @@
" -*- text -*-
" oracle.vim -- Vim integration for the Go oracle.
"
" Load with (e.g.) :source oracle.vim
" Call with (e.g.) :GoOracleDescribe
" while cursor or selection is over syntax of interest.
" Run :copen to show the quick-fix file.
"
" This is an absolutely rudimentary integration of the Go Oracle into
" Vim's quickfix mechanism and it needs a number of usability
" improvements before it can be practically useful to Vim users.
" Voluntary contributions welcomed!
"
" TODO(adonovan):
" - reject buffers with no filename.
" - hide all filenames in quickfix buffer.
" Get the path to the Go oracle executable.
func! s:go_oracle_bin()
let [ext, sep] = (has('win32') || has('win64') ? ['.exe', ';'] : ['', ':'])
let go_oracle = globpath(join(split($GOPATH, sep), ','), '/bin/oracle' . ext)
if go_oracle == ''
let go_oracle = globpath($GOROOT, '/bin/oracle' . ext)
endif
return go_oracle
endfunction
let s:go_oracle = s:go_oracle_bin()
func! s:qflist(output)
let qflist = []
" Parse GNU-style 'file:line.col-line.col: message' format.
let mx = '^\(\a:[\\/][^:]\+\|[^:]\+\):\(\d\+\):\(\d\+\):\(.*\)$'
for line in split(a:output, "\n")
let ml = matchlist(line, mx)
" Ignore non-match lines or warnings
if ml == [] || ml[4] =~ '^ warning:'
continue
endif
let item = {
\ 'filename': ml[1],
\ 'text': ml[4],
\ 'lnum': ml[2],
\ 'col': ml[3],
\}
let bnr = bufnr(fnameescape(ml[1]))
if bnr != -1
let item['bufnr'] = bnr
endif
call add(qflist, item)
endfor
call setqflist(qflist)
cwindow
endfun
func! s:getpos(l, c)
if &encoding != 'utf-8'
let buf = a:l == 1 ? '' : (join(getline(1, a:l-1), "\n") . "\n")
let buf .= a:c == 1 ? '' : getline('.')[:a:c-2]
return len(iconv(buf, &encoding, 'utf-8'))
endif
return line2byte(a:l) + (a:c-2)
endfun
func! s:RunOracle(mode, selected) range abort
let fname = expand('%:p')
let sname = get(g:, 'go_oracle_scope_file', fname)
if a:selected != -1
let pos1 = s:getpos(line("'<"), col("'<"))
let pos2 = s:getpos(line("'>"), col("'>"))
let cmd = printf('%s -pos=%s:#%d,#%d %s %s',
\ s:go_oracle,
\ shellescape(fname), pos1, pos2, a:mode, shellescape(sname))
else
let pos = s:getpos(line('.'), col('.'))
let cmd = printf('%s -pos=%s:#%d %s %s',
\ s:go_oracle,
\ shellescape(fname), pos, a:mode, shellescape(sname))
endif
call s:qflist(system(cmd))
endfun
" Describe the expression at the current point.
command! -range=% GoOracleDescribe
\ call s:RunOracle('describe', <count>)
" Show possible callees of the function call at the current point.
command! -range=% GoOracleCallees
\ call s:RunOracle('callees', <count>)
" Show the set of callers of the function containing the current point.
command! -range=% GoOracleCallers
\ call s:RunOracle('callers', <count>)
" Show the callgraph of the current program.
command! -range=% GoOracleCallgraph
\ call s:RunOracle('callgraph', <count>)
" Describe the 'implements' relation for types in the
" package containing the current point.
command! -range=% GoOracleImplements
\ call s:RunOracle('implements', <count>)
" Enumerate the set of possible corresponding sends/receives for
" this channel receive/send operation.
command! -range=% GoOracleChannelPeers
\ call s:RunOracle('peers', <count>)
@@ -1,210 +0,0 @@
// 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.
// ssadump: a tool for displaying and interpreting the SSA form of Go programs.
package main
import (
"flag"
"fmt"
"go/build"
"os"
"runtime"
"runtime/pprof"
"code.google.com/p/go.tools/go/loader"
"code.google.com/p/go.tools/go/ssa"
"code.google.com/p/go.tools/go/ssa/interp"
"code.google.com/p/go.tools/go/types"
)
var buildFlag = flag.String("build", "", `Options controlling the SSA builder.
The value is a sequence of zero or more of these letters:
C perform sanity [C]hecking of the SSA form.
D include [D]ebug info for every function.
P log [P]ackage inventory.
F log [F]unction SSA code.
S log [S]ource locations as SSA builder progresses.
G use binary object files from gc to provide imports (no code).
L build distinct packages seria[L]ly instead of in parallel.
N build [N]aive SSA form: don't replace local loads/stores with registers.
`)
var testFlag = flag.Bool("test", false, "Loads test code (*_test.go) for imported packages.")
var runFlag = flag.Bool("run", false, "Invokes the SSA interpreter on the program.")
var interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter.
The value is a sequence of zero or more more of these letters:
R disable [R]ecover() from panic; show interpreter crash instead.
T [T]race execution of the program. Best for single-threaded programs!
`)
const usage = `SSA builder and interpreter.
Usage: ssadump [<flag> ...] <args> ...
Use -help flag to display options.
Examples:
% ssadump -build=FPG hello.go # quickly dump SSA form of a single package
% ssadump -run -interp=T hello.go # interpret a program, with tracing
% ssadump -run -test unicode -- -test.v # interpret the unicode package's tests, verbosely
` + loader.FromArgsUsage +
`
When -run is specified, ssadump will run the program.
The entry point depends on the -test flag:
if clear, it runs the first package named main.
if set, it runs the tests of each package.
`
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
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() {
if err := doMain(); err != nil {
fmt.Fprintf(os.Stderr, "ssadump: %s.\n", err)
os.Exit(1)
}
}
func doMain() error {
flag.Parse()
args := flag.Args()
conf := loader.Config{
Build: &build.Default,
SourceImports: true,
}
// TODO(adonovan): make go/types choose its default Sizes from
// build.Default or a specified *build.Context.
var wordSize int64 = 8
switch conf.Build.GOARCH {
case "386", "arm":
wordSize = 4
}
conf.TypeChecker.Sizes = &types.StdSizes{
MaxAlign: 8,
WordSize: wordSize,
}
var mode ssa.BuilderMode
for _, c := range *buildFlag {
switch c {
case 'D':
mode |= ssa.GlobalDebug
case 'P':
mode |= ssa.LogPackages | ssa.BuildSerially
case 'F':
mode |= ssa.LogFunctions | ssa.BuildSerially
case 'S':
mode |= ssa.LogSource | ssa.BuildSerially
case 'C':
mode |= ssa.SanityCheckFunctions
case 'N':
mode |= ssa.NaiveForm
case 'G':
conf.SourceImports = false
case 'L':
mode |= ssa.BuildSerially
default:
return fmt.Errorf("unknown -build option: '%c'", c)
}
}
var interpMode interp.Mode
for _, c := range *interpFlag {
switch c {
case 'T':
interpMode |= interp.EnableTracing
case 'R':
interpMode |= interp.DisableRecover
default:
return fmt.Errorf("unknown -interp option: '%c'", c)
}
}
if len(args) == 0 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}
// Profiling support.
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
// Use the initial packages from the command line.
args, err := conf.FromArgs(args, *testFlag)
if err != nil {
return err
}
// The interpreter needs the runtime package.
if *runFlag {
conf.Import("runtime")
}
// Load, parse and type-check the whole program.
iprog, err := conf.Load()
if err != nil {
return err
}
// Create and build SSA-form program representation.
prog := ssa.Create(iprog, mode)
prog.BuildAll()
// Run the interpreter.
if *runFlag {
var main *ssa.Package
pkgs := prog.AllPackages()
if *testFlag {
// If -test, run all packages' tests.
if len(pkgs) > 0 {
main = prog.CreateTestMainPackage(pkgs...)
}
if main == nil {
return fmt.Errorf("no tests")
}
} else {
// Otherwise, run main.main.
for _, pkg := range pkgs {
if pkg.Object.Name() == "main" {
main = pkg
if main.Func("main") == nil {
return fmt.Errorf("no func main() in main package")
}
break
}
}
if main == nil {
return fmt.Errorf("no main package")
}
}
if runtime.GOARCH != build.Default.GOARCH {
return fmt.Errorf("cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s)",
build.Default.GOARCH, runtime.GOARCH)
}
interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args)
}
return nil
}
@@ -1,533 +0,0 @@
// 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.
// Identify mismatches between assembly files and Go func declarations.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/token"
"regexp"
"strconv"
"strings"
)
// 'kind' is a kind of assembly variable.
// The kinds 1, 2, 4, 8 stand for values of that size.
type asmKind int
// These special kinds are not valid sizes.
const (
asmString asmKind = 100 + iota
asmSlice
asmInterface
asmEmptyInterface
)
// An asmArch describes assembly parameters for an architecture
type asmArch struct {
name string
ptrSize int
intSize int
bigEndian bool
}
// An asmFunc describes the expected variables for a function on a given architecture.
type asmFunc struct {
arch *asmArch
size int // size of all arguments
vars map[string]*asmVar
varByOffset map[int]*asmVar
}
// An asmVar describes a single assembly variable.
type asmVar struct {
name string
kind asmKind
typ string
off int
size int
inner []*asmVar
}
var (
asmArch386 = asmArch{"386", 4, 4, false}
asmArchArm = asmArch{"arm", 4, 4, false}
asmArchAmd64 = asmArch{"amd64", 8, 8, false}
arches = []*asmArch{
&asmArch386,
&asmArchArm,
&asmArchAmd64,
}
)
var (
re = regexp.MustCompile
asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
asmTEXT = re(`\bTEXT\b.*·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+]+))?(?:\s*,\s*\$([0-9]+)(?:-([0-9]+))?)?`)
asmDATA = re(`\b(DATA|GLOBL)\b`)
asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
asmUnnamedFP = re(`[^+\-0-9]](([0-9]+)\(FP\))`)
asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
)
func asmCheck(pkg *Package) {
if !vet("asmdecl") {
return
}
// No work if no assembly files.
if !pkg.hasFileWithSuffix(".s") {
return
}
// Gather declarations. knownFunc[name][arch] is func description.
knownFunc := make(map[string]map[string]*asmFunc)
for _, f := range pkg.files {
if f.file != nil {
for _, decl := range f.file.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
knownFunc[decl.Name.Name] = f.asmParseDecl(decl)
}
}
}
}
var fn *asmFunc
for _, f := range pkg.files {
if !strings.HasSuffix(f.name, ".s") {
continue
}
Println("Checking file", f.name)
// Determine architecture from file name if possible.
var arch string
for _, a := range arches {
if strings.HasSuffix(f.name, "_"+a.name+".s") {
arch = a.name
break
}
}
lines := strings.SplitAfter(string(f.content), "\n")
for lineno, line := range lines {
lineno++
badf := func(format string, args ...interface{}) {
f.Badf(token.NoPos, "%s:%d: [%s] %s", f.name, lineno, arch, fmt.Sprintf(format, args...))
}
if arch == "" {
// Determine architecture from +build line if possible.
if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
Fields:
for _, fld := range strings.Fields(m[1]) {
for _, a := range arches {
if a.name == fld {
arch = a.name
break Fields
}
}
}
}
}
if m := asmTEXT.FindStringSubmatch(line); m != nil {
if arch == "" {
f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name)
return
}
fn = knownFunc[m[1]][arch]
if fn != nil {
size, _ := strconv.Atoi(m[4])
if size != fn.size && (m[2] != "7" && !strings.Contains(m[2], "NOSPLIT") || size != 0) {
badf("wrong argument size %d; expected $...-%d", size, fn.size)
}
}
continue
} else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
// function, but not visible from Go (didn't match asmTEXT), so stop checking
fn = nil
continue
}
if asmDATA.FindStringSubmatch(line) != nil {
fn = nil
}
if fn == nil {
continue
}
for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
badf("use of unnamed argument %s", m[1])
}
for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
name := m[1]
off := 0
if m[2] != "" {
off, _ = strconv.Atoi(m[2])
}
v := fn.vars[name]
if v == nil {
// Allow argframe+0(FP).
if name == "argframe" && off == 0 {
continue
}
v = fn.varByOffset[off]
if v != nil {
badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
} else {
badf("unknown variable %s", name)
}
continue
}
asmCheckVar(badf, fn, line, m[0], off, v)
}
}
}
}
// asmParseDecl parses a function decl for expected assembly variables.
func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc {
var (
arch *asmArch
fn *asmFunc
offset int
failed bool
)
addVar := func(outer string, v asmVar) {
if vo := fn.vars[outer]; vo != nil {
vo.inner = append(vo.inner, &v)
}
fn.vars[v.name] = &v
for i := 0; i < v.size; i++ {
fn.varByOffset[v.off+i] = &v
}
}
addParams := func(list []*ast.Field) {
for i, fld := range list {
// Determine alignment, size, and kind of type in declaration.
var align, size int
var kind asmKind
names := fld.Names
typ := f.gofmt(fld.Type)
switch t := fld.Type.(type) {
default:
switch typ {
default:
f.Warnf(fld.Type.Pos(), "unknown assembly argument type %s", typ)
failed = true
return
case "int8", "uint8", "byte", "bool":
size = 1
case "int16", "uint16":
size = 2
case "int32", "uint32", "float32":
size = 4
case "int64", "uint64", "float64":
align = arch.ptrSize
size = 8
case "int", "uint":
size = arch.intSize
case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer":
size = arch.ptrSize
case "string":
size = arch.ptrSize * 2
align = arch.ptrSize
kind = asmString
}
case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.StarExpr:
size = arch.ptrSize
case *ast.InterfaceType:
align = arch.ptrSize
size = 2 * arch.ptrSize
if len(t.Methods.List) > 0 {
kind = asmInterface
} else {
kind = asmEmptyInterface
}
case *ast.ArrayType:
if t.Len == nil {
size = arch.ptrSize + 2*arch.intSize
align = arch.ptrSize
kind = asmSlice
break
}
f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
failed = true
case *ast.StructType:
f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
failed = true
}
if align == 0 {
align = size
}
if kind == 0 {
kind = asmKind(size)
}
offset += -offset & (align - 1)
// Create variable for each name being declared with this type.
if len(names) == 0 {
name := "unnamed"
if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 {
// Assume assembly will refer to single unnamed result as r.
name = "ret"
}
names = []*ast.Ident{{Name: name}}
}
for _, id := range names {
name := id.Name
addVar("", asmVar{
name: name,
kind: kind,
typ: typ,
off: offset,
size: size,
})
switch kind {
case 8:
if arch.ptrSize == 4 {
w1, w2 := "lo", "hi"
if arch.bigEndian {
w1, w2 = w2, w1
}
addVar(name, asmVar{
name: name + "_" + w1,
kind: 4,
typ: "half " + typ,
off: offset,
size: 4,
})
addVar(name, asmVar{
name: name + "_" + w2,
kind: 4,
typ: "half " + typ,
off: offset + 4,
size: 4,
})
}
case asmEmptyInterface:
addVar(name, asmVar{
name: name + "_type",
kind: asmKind(arch.ptrSize),
typ: "interface type",
off: offset,
size: arch.ptrSize,
})
addVar(name, asmVar{
name: name + "_data",
kind: asmKind(arch.ptrSize),
typ: "interface data",
off: offset + arch.ptrSize,
size: arch.ptrSize,
})
case asmInterface:
addVar(name, asmVar{
name: name + "_itable",
kind: asmKind(arch.ptrSize),
typ: "interface itable",
off: offset,
size: arch.ptrSize,
})
addVar(name, asmVar{
name: name + "_data",
kind: asmKind(arch.ptrSize),
typ: "interface data",
off: offset + arch.ptrSize,
size: arch.ptrSize,
})
case asmSlice:
addVar(name, asmVar{
name: name + "_base",
kind: asmKind(arch.ptrSize),
typ: "slice base",
off: offset,
size: arch.ptrSize,
})
addVar(name, asmVar{
name: name + "_len",
kind: asmKind(arch.intSize),
typ: "slice len",
off: offset + arch.ptrSize,
size: arch.intSize,
})
addVar(name, asmVar{
name: name + "_cap",
kind: asmKind(arch.intSize),
typ: "slice cap",
off: offset + arch.ptrSize + arch.intSize,
size: arch.intSize,
})
case asmString:
addVar(name, asmVar{
name: name + "_base",
kind: asmKind(arch.ptrSize),
typ: "string base",
off: offset,
size: arch.ptrSize,
})
addVar(name, asmVar{
name: name + "_len",
kind: asmKind(arch.intSize),
typ: "string len",
off: offset + arch.ptrSize,
size: arch.intSize,
})
}
offset += size
}
}
}
m := make(map[string]*asmFunc)
for _, arch = range arches {
fn = &asmFunc{
arch: arch,
vars: make(map[string]*asmVar),
varByOffset: make(map[int]*asmVar),
}
offset = 0
addParams(decl.Type.Params.List)
if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
offset += -offset & (arch.ptrSize - 1)
addParams(decl.Type.Results.List)
}
fn.size = offset
m[arch.name] = fn
}
if failed {
return nil
}
return m
}
// asmCheckVar checks a single variable reference.
func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) {
m := asmOpcode.FindStringSubmatch(line)
if m == nil {
badf("cannot find assembly opcode")
}
// Determine operand sizes from instruction.
// Typically the suffix suffices, but there are exceptions.
var src, dst, kind asmKind
op := m[1]
switch fn.arch.name + "." + op {
case "386.FMOVLP":
src, dst = 8, 4
case "arm.MOVD":
src = 8
case "arm.MOVW":
src = 4
case "arm.MOVH", "arm.MOVHU":
src = 2
case "arm.MOVB", "arm.MOVBU":
src = 1
default:
if fn.arch.name == "386" || fn.arch.name == "amd64" {
if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
// FMOVDP, FXCHD, etc
src = 8
break
}
if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
// FMOVFP, FXCHF, etc
src = 4
break
}
if strings.HasSuffix(op, "SD") {
// MOVSD, SQRTSD, etc
src = 8
break
}
if strings.HasSuffix(op, "SS") {
// MOVSS, SQRTSS, etc
src = 4
break
}
if strings.HasPrefix(op, "SET") {
// SETEQ, etc
src = 1
break
}
switch op[len(op)-1] {
case 'B':
src = 1
case 'W':
src = 2
case 'L':
src = 4
case 'D', 'Q':
src = 8
}
}
}
if dst == 0 {
dst = src
}
// Determine whether the match we're holding
// is the first or second argument.
if strings.Index(line, expr) > strings.Index(line, ",") {
kind = dst
} else {
kind = src
}
vk := v.kind
vt := v.typ
switch vk {
case asmInterface, asmEmptyInterface, asmString, asmSlice:
// allow reference to first word (pointer)
vk = v.inner[0].kind
vt = v.inner[0].typ
}
if off != v.off {
var inner bytes.Buffer
for i, vi := range v.inner {
if len(v.inner) > 1 {
fmt.Fprintf(&inner, ",")
}
fmt.Fprintf(&inner, " ")
if i == len(v.inner)-1 {
fmt.Fprintf(&inner, "or ")
}
fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
}
badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
return
}
if kind != 0 && kind != vk {
var inner bytes.Buffer
if len(v.inner) > 0 {
fmt.Fprintf(&inner, " containing")
for i, vi := range v.inner {
if i > 0 && len(v.inner) > 2 {
fmt.Fprintf(&inner, ",")
}
fmt.Fprintf(&inner, " ")
if i > 0 && i == len(v.inner)-1 {
fmt.Fprintf(&inner, "and ")
}
fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
}
}
badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vk, inner.String())
}
}
@@ -1,44 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
This file contains the code to check for useless assignments.
*/
package main
import (
"go/ast"
"go/token"
"reflect"
)
// TODO: should also check for assignments to struct fields inside methods
// that are on T instead of *T.
// checkAssignStmt checks for assignments of the form "<expr> = <expr>".
// These are almost always useless, and even when they aren't they are usually a mistake.
func (f *File) checkAssignStmt(stmt *ast.AssignStmt) {
if !vet("assign") {
return
}
if stmt.Tok != token.ASSIGN {
return // ignore :=
}
if len(stmt.Lhs) != len(stmt.Rhs) {
// If LHS and RHS have different cardinality, they can't be the same.
return
}
for i, lhs := range stmt.Lhs {
rhs := stmt.Rhs[i]
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
continue // short-circuit the heavy-weight gofmt check
}
le := f.gofmt(lhs)
re := f.gofmt(rhs)
if le == re {
f.Badf(stmt.Pos(), "self-assignment of %s to %s", re, le)
}
}
}
@@ -1,59 +0,0 @@
// 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 (
"go/ast"
"go/token"
)
// checkAtomicAssignment walks the assignment statement checking for common
// mistaken usage of atomic package, such as: x = atomic.AddUint64(&x, 1)
func (f *File) checkAtomicAssignment(n *ast.AssignStmt) {
if !vet("atomic") {
return
}
if len(n.Lhs) != len(n.Rhs) {
return
}
for i, right := range n.Rhs {
call, ok := right.(*ast.CallExpr)
if !ok {
continue
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
continue
}
pkg, ok := sel.X.(*ast.Ident)
if !ok || pkg.Name != "atomic" {
continue
}
switch sel.Sel.Name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
f.checkAtomicAddAssignment(n.Lhs[i], call)
}
}
}
// checkAtomicAddAssignment walks the atomic.Add* method calls checking for assigning the return value
// to the same variable being used in the operation
func (f *File) checkAtomicAddAssignment(left ast.Expr, call *ast.CallExpr) {
arg := call.Args[0]
broken := false
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
broken = f.gofmt(left) == f.gofmt(uarg.X)
} else if star, ok := left.(*ast.StarExpr); ok {
broken = f.gofmt(star.X) == f.gofmt(arg)
}
if broken {
f.Bad(left.Pos(), "direct assignment to atomic value")
}
}
@@ -1,91 +0,0 @@
// 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"
"os"
"strings"
"unicode"
)
var (
nl = []byte("\n")
slashSlash = []byte("//")
plusBuild = []byte("+build")
)
// checkBuildTag checks that build tags are in the correct location and well-formed.
func checkBuildTag(name string, data []byte) {
if !vet("buildtags") {
return
}
lines := bytes.SplitAfter(data, nl)
// Determine cutpoint where +build comments are no longer valid.
// They are valid in leading // comments in the file followed by
// a blank line.
var cutoff int
for i, line := range lines {
line = bytes.TrimSpace(line)
if len(line) == 0 {
cutoff = i
continue
}
if bytes.HasPrefix(line, slashSlash) {
continue
}
break
}
for i, line := range lines {
line = bytes.TrimSpace(line)
if !bytes.HasPrefix(line, slashSlash) {
continue
}
text := bytes.TrimSpace(line[2:])
if bytes.HasPrefix(text, plusBuild) {
fields := bytes.Fields(text)
if !bytes.Equal(fields[0], plusBuild) {
// Comment is something like +buildasdf not +build.
fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1)
continue
}
if i >= cutoff {
fmt.Fprintf(os.Stderr, "%s:%d: +build comment must appear before package clause and be followed by a blank line\n", name, i+1)
setExit(1)
continue
}
// Check arguments.
Args:
for _, arg := range fields[1:] {
for _, elem := range strings.Split(string(arg), ",") {
if strings.HasPrefix(elem, "!!") {
fmt.Fprintf(os.Stderr, "%s:%d: invalid double negative in build constraint: %s\n", name, i+1, arg)
setExit(1)
break Args
}
if strings.HasPrefix(elem, "!") {
elem = elem[1:]
}
for _, c := range elem {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
fmt.Fprintf(os.Stderr, "%s:%d: invalid non-alphanumeric build constraint: %s\n", name, i+1, arg)
setExit(1)
break Args
}
}
}
}
continue
}
// Comment with +build but not at beginning.
if bytes.Contains(line, plusBuild) && i < cutoff {
fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1)
continue
}
}
}
@@ -1,121 +0,0 @@
// 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.
// This file contains the test for unkeyed struct literals.
package main
import (
"flag"
"go/ast"
"strings"
"code.google.com/p/go.tools/cmd/vet/whitelist"
)
var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only")
// checkUnkeyedLiteral checks if a composite literal is a struct literal with
// unkeyed fields.
func (f *File) checkUnkeyedLiteral(c *ast.CompositeLit) {
if !vet("composites") {
return
}
typ := c.Type
for {
if typ1, ok := c.Type.(*ast.ParenExpr); ok {
typ = typ1
continue
}
break
}
switch typ.(type) {
case *ast.ArrayType:
return
case *ast.MapType:
return
case *ast.StructType:
return // a literal struct type does not need to use keys
case *ast.Ident:
// A simple type name like t or T does not need keys either,
// since it is almost certainly declared in the current package.
// (The exception is names being used via import . "pkg", but
// those are already breaking the Go 1 compatibility promise,
// so not reporting potential additional breakage seems okay.)
return
}
// Otherwise the type is a selector like pkg.Name.
// We only care if pkg.Name is a struct, not if it's a map, array, or slice.
isStruct, typeString := f.pkg.isStruct(c)
if !isStruct {
return
}
if typeString == "" { // isStruct doesn't know
typeString = f.gofmt(typ)
}
// It's a struct, or we can't tell it's not a struct because we don't have types.
// Check if the CompositeLit contains an unkeyed field.
allKeyValue := true
for _, e := range c.Elts {
if _, ok := e.(*ast.KeyValueExpr); !ok {
allKeyValue = false
break
}
}
if allKeyValue {
return
}
// Check that the CompositeLit's type has the form pkg.Typ.
s, ok := c.Type.(*ast.SelectorExpr)
if !ok {
return
}
pkg, ok := s.X.(*ast.Ident)
if !ok {
return
}
// Convert the package name to an import path, and compare to a whitelist.
path := pkgPath(f, pkg.Name)
if path == "" {
f.Badf(c.Pos(), "unresolvable package for %s.%s literal", pkg.Name, s.Sel.Name)
return
}
typeName := path + "." + s.Sel.Name
if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] {
return
}
f.Bad(c.Pos(), typeString+" composite literal uses unkeyed fields")
}
// pkgPath returns the import path "image/png" for the package name "png".
//
// This is based purely on syntax and convention, and not on the imported
// package's contents. It will be incorrect if a package name differs from the
// leaf element of the import path, or if the package was a dot import.
func pkgPath(f *File, pkgName string) (path string) {
for _, x := range f.file.Imports {
s := strings.Trim(x.Path.Value, `"`)
if x.Name != nil {
// Catch `import pkgName "foo/bar"`.
if x.Name.Name == pkgName {
return s
}
} else {
// Catch `import "pkgName"` or `import "foo/bar/pkgName"`.
if s == pkgName || strings.HasSuffix(s, "/"+pkgName) {
return s
}
}
}
return ""
}
@@ -1,101 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains the code to check that locks are not passed by value.
package main
import (
"bytes"
"fmt"
"go/ast"
"code.google.com/p/go.tools/go/types"
)
// checkCopyLocks checks whether a function might
// inadvertently copy a lock, by checking whether
// its receiver, parameters, or return values
// are locks.
func (f *File) checkCopyLocks(d *ast.FuncDecl) {
if !vet("copylocks") {
return
}
if d.Recv != nil && len(d.Recv.List) > 0 {
expr := d.Recv.List[0].Type
if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
f.Badf(expr.Pos(), "%s passes Lock by value: %v", d.Name.Name, path)
}
}
if d.Type.Params != nil {
for _, field := range d.Type.Params.List {
expr := field.Type
if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
f.Badf(expr.Pos(), "%s passes Lock by value: %v", d.Name.Name, path)
}
}
}
if d.Type.Results != nil {
for _, field := range d.Type.Results.List {
expr := field.Type
if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil {
f.Badf(expr.Pos(), "%s returns Lock by value: %v", d.Name.Name, path)
}
}
}
}
type typePath []types.Type
// pathString pretty-prints a typePath.
func (path typePath) String() string {
n := len(path)
var buf bytes.Buffer
for i := range path {
if i > 0 {
fmt.Fprint(&buf, " contains ")
}
// The human-readable path is in reverse order, outermost to innermost.
fmt.Fprint(&buf, path[n-i-1].String())
}
return buf.String()
}
// lockPath returns a typePath describing the location of a lock value
// contained in typ. If there is no contained lock, it returns nil.
func lockPath(tpkg *types.Package, typ types.Type) typePath {
if typ == nil {
return nil
}
// We're only interested in the case in which the underlying
// type is a struct. (Interfaces and pointers are safe to copy.)
styp, ok := typ.Underlying().(*types.Struct)
if !ok {
return nil
}
// We're looking for cases in which a reference to this type
// can be locked, but a value cannot. This differentiates
// embedded interfaces from embedded values.
if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil {
if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil {
return []types.Type{typ}
}
}
nfields := styp.NumFields()
for i := 0; i < nfields; i++ {
ftyp := styp.Field(i).Type()
subpath := lockPath(tpkg, ftyp)
if subpath != nil {
return append(subpath, typ)
}
}
return nil
}
@@ -1,280 +0,0 @@
// 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.
// Check for syntactically unreachable code.
package main
import (
"go/ast"
"go/token"
)
type deadState struct {
f *File
hasBreak map[ast.Stmt]bool
hasGoto map[string]bool
labels map[string]ast.Stmt
breakTarget ast.Stmt
reachable bool
}
// checkUnreachable checks a function body for dead code.
func (f *File) checkUnreachable(body *ast.BlockStmt) {
if !vet("unreachable") || body == nil {
return
}
d := &deadState{
f: f,
hasBreak: make(map[ast.Stmt]bool),
hasGoto: make(map[string]bool),
labels: make(map[string]ast.Stmt),
}
d.findLabels(body)
d.reachable = true
d.findDead(body)
}
// findLabels gathers information about the labels defined and used by stmt
// and about which statements break, whether a label is involved or not.
func (d *deadState) findLabels(stmt ast.Stmt) {
switch x := stmt.(type) {
default:
d.f.Warnf(x.Pos(), "internal error in findLabels: unexpected statement %T", x)
case *ast.AssignStmt,
*ast.BadStmt,
*ast.DeclStmt,
*ast.DeferStmt,
*ast.EmptyStmt,
*ast.ExprStmt,
*ast.GoStmt,
*ast.IncDecStmt,
*ast.ReturnStmt,
*ast.SendStmt:
// no statements inside
case *ast.BlockStmt:
for _, stmt := range x.List {
d.findLabels(stmt)
}
case *ast.BranchStmt:
switch x.Tok {
case token.GOTO:
d.hasGoto[x.Label.Name] = true
case token.BREAK:
stmt := d.breakTarget
if x.Label != nil {
stmt = d.labels[x.Label.Name]
}
if stmt != nil {
d.hasBreak[stmt] = true
}
}
case *ast.IfStmt:
d.findLabels(x.Body)
if x.Else != nil {
d.findLabels(x.Else)
}
case *ast.LabeledStmt:
d.labels[x.Label.Name] = x.Stmt
d.findLabels(x.Stmt)
// These cases are all the same, but the x.Body only works
// when the specific type of x is known, so the cases cannot
// be merged.
case *ast.ForStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.RangeStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.SelectStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.SwitchStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.TypeSwitchStmt:
outer := d.breakTarget
d.breakTarget = x
d.findLabels(x.Body)
d.breakTarget = outer
case *ast.CommClause:
for _, stmt := range x.Body {
d.findLabels(stmt)
}
case *ast.CaseClause:
for _, stmt := range x.Body {
d.findLabels(stmt)
}
}
}
// findDead walks the statement looking for dead code.
// If d.reachable is false on entry, stmt itself is dead.
// When findDead returns, d.reachable tells whether the
// statement following stmt is reachable.
func (d *deadState) findDead(stmt ast.Stmt) {
// Is this a labeled goto target?
// If so, assume it is reachable due to the goto.
// This is slightly conservative, in that we don't
// check that the goto is reachable, so
// L: goto L
// will not provoke a warning.
// But it's good enough.
if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] {
d.reachable = true
}
if !d.reachable {
switch stmt.(type) {
case *ast.EmptyStmt:
// do not warn about unreachable empty statements
default:
d.f.Bad(stmt.Pos(), "unreachable code")
d.reachable = true // silence error about next statement
}
}
switch x := stmt.(type) {
default:
d.f.Warnf(x.Pos(), "internal error in findDead: unexpected statement %T", x)
case *ast.AssignStmt,
*ast.BadStmt,
*ast.DeclStmt,
*ast.DeferStmt,
*ast.EmptyStmt,
*ast.GoStmt,
*ast.IncDecStmt,
*ast.SendStmt:
// no control flow
case *ast.BlockStmt:
for _, stmt := range x.List {
d.findDead(stmt)
}
case *ast.BranchStmt:
switch x.Tok {
case token.BREAK, token.GOTO, token.FALLTHROUGH:
d.reachable = false
case token.CONTINUE:
// NOTE: We accept "continue" statements as terminating.
// They are not necessary in the spec definition of terminating,
// because a continue statement cannot be the final statement
// before a return. But for the more general problem of syntactically
// identifying dead code, continue redirects control flow just
// like the other terminating statements.
d.reachable = false
}
case *ast.ExprStmt:
// Call to panic?
call, ok := x.X.(*ast.CallExpr)
if ok {
name, ok := call.Fun.(*ast.Ident)
if ok && name.Name == "panic" && name.Obj == nil {
d.reachable = false
}
}
case *ast.ForStmt:
d.findDead(x.Body)
d.reachable = x.Cond != nil || d.hasBreak[x]
case *ast.IfStmt:
d.findDead(x.Body)
if x.Else != nil {
r := d.reachable
d.reachable = true
d.findDead(x.Else)
d.reachable = d.reachable || r
} else {
// might not have executed if statement
d.reachable = true
}
case *ast.LabeledStmt:
d.findDead(x.Stmt)
case *ast.RangeStmt:
d.findDead(x.Body)
d.reachable = true
case *ast.ReturnStmt:
d.reachable = false
case *ast.SelectStmt:
// NOTE: Unlike switch and type switch below, we don't care
// whether a select has a default, because a select without a
// default blocks until one of the cases can run. That's different
// from a switch without a default, which behaves like it has
// a default with an empty body.
anyReachable := false
for _, comm := range x.Body.List {
d.reachable = true
for _, stmt := range comm.(*ast.CommClause).Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x]
case *ast.SwitchStmt:
anyReachable := false
hasDefault := false
for _, cas := range x.Body.List {
cc := cas.(*ast.CaseClause)
if cc.List == nil {
hasDefault = true
}
d.reachable = true
for _, stmt := range cc.Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
case *ast.TypeSwitchStmt:
anyReachable := false
hasDefault := false
for _, cas := range x.Body.List {
cc := cas.(*ast.CaseClause)
if cc.List == nil {
hasDefault = true
}
d.reachable = true
for _, stmt := range cc.Body {
d.findDead(stmt)
}
anyReachable = anyReachable || d.reachable
}
d.reachable = anyReachable || d.hasBreak[x] || !hasDefault
}
}
@@ -1,159 +0,0 @@
// 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.
/*
Vet examines Go source code and reports suspicious constructs, such as Printf
calls whose arguments do not align with the format string. Vet uses heuristics
that do not guarantee all reports are genuine problems, but it can find errors
not caught by the compilers.
It can be invoked three ways:
By package, from the go tool:
go vet package/path/name
vets the package whose path is provided.
By files:
go tool vet source/directory/*.go
vets the files named, all of which must be in the same package.
By directory:
go tool vet source/directory
recursively descends the directory, vetting each file in isolation.
Package-level type-checking is disabled, so the vetting is weaker.
Vet's exit code is 2 for erroneous invocation of the tool, 1 if a
problem was reported, and 0 otherwise. Note that the tool does not
check every possible problem and depends on unreliable heuristics
so it should be used as guidance only, not as a firm indicator of
program correctness.
By default all checks are performed. If any flags are explicitly set
to true, only those tests are run. Conversely, if any flag is
explicitly set to false, only those tests are disabled.
Thus -printf=true runs the printf check, -printf=false runs all checks
except the printf check.
Available checks:
1. Printf family
Flag -printf
Suspicious calls to functions in the Printf family, including any functions
with these names:
Print Printf Println
Fprint Fprintf Fprintln
Sprint Sprintf Sprintln
Error Errorf
Fatal Fatalf
Panic Panicf Panicln
If the function name ends with an 'f', the function is assumed to take
a format descriptor string in the manner of fmt.Printf. If not, vet
complains about arguments that look like format descriptor strings.
It also checks for errors such as using a Writer as the first argument of
Printf.
2. Methods
Flag -methods
Non-standard signatures for methods with familiar names, including:
Format GobEncode GobDecode MarshalJSON MarshalXML
Peek ReadByte ReadFrom ReadRune Scan Seek
UnmarshalJSON UnreadByte UnreadRune WriteByte
WriteTo
3. Struct tags
Flag -structtags
Struct tags that do not follow the format understood by reflect.StructTag.Get.
4. Unkeyed composite literals
Flag -composites
Composite struct literals that do not use the field-keyed syntax.
5. Assembly declarations
Flag -asmdecl
Mismatches between assembly files and Go function declarations.
6. Useless assignments
Flag -assign
Check for useless assignments.
7. Atomic mistakes
Flag -atomic
Common mistaken usages of the sync/atomic package.
8. Build tags
Flag -buildtags
Badly formed or misplaced +build tags.
9. Copying locks
Flag -copylocks
Locks that are erroneously passed by value.
10. Nil function comparison
Flag -nilfunc
Comparisons between functions and nil.
11. Range loop variables
Flag -rangeloops
Incorrect uses of range loop variables in closures.
12. Unreachable code
Flag -unreachable
Unreachable code.
13. Shadowed variables
Flag -shadow=false (experimental; must be set explicitly)
Variables that may have been unintentionally shadowed.
Other flags
These flags configure the behavior of vet:
-all (default true)
Check everything; disabled if any explicit check is requested.
-v
Verbose mode
-printfuncs
A comma-separated list of print-like functions to supplement
the standard list. Each entry is in the form Name:N where N
is the zero-based argument position of the first argument
involved in the print: either the format or the first print
argument for non-formatted prints. For example,
if you have Warn and Warnf functions that take an
io.Writer as their first argument, like Fprintf,
-printfuncs=Warn:1,Warnf:1
-shadowstrict
Whether to be strict about shadowing; can be noisy.
-test
For testing only: sets -all and -shadow.
*/
package main
@@ -1,562 +0,0 @@
// 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.
// Vet is a simple checker for static errors in Go source code.
// See doc.go for more information.
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/printer"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
_ "code.google.com/p/go.tools/go/gcimporter"
"code.google.com/p/go.tools/go/types"
)
// TODO: Need a flag to set build tags when parsing the package.
var verbose = flag.Bool("v", false, "verbose")
var strictShadowing = flag.Bool("shadowstrict", false, "whether to be strict about shadowing; can be noisy")
var testFlag = flag.Bool("test", false, "for testing only: sets -all and -shadow")
var exitCode = 0
// "all" is here only for the appearance of backwards compatibility.
// It has no effect; the triState flags do the work.
var all = flag.Bool("all", true, "check everything; disabled if any explicit check is requested")
// Flags to control which individual checks to perform.
var report = map[string]*triState{
"asmdecl": triStateFlag("asmdecl", unset, "check assembly against Go declarations"),
"assign": triStateFlag("assign", unset, "check for useless assignments"),
"atomic": triStateFlag("atomic", unset, "check for common mistaken usages of the sync/atomic package"),
"buildtags": triStateFlag("buildtags", unset, "check that +build tags are valid"),
"composites": triStateFlag("composites", unset, "check that composite literals used field-keyed elements"),
"copylocks": triStateFlag("copylocks", unset, "check that locks are not passed by value"),
"methods": triStateFlag("methods", unset, "check that canonically named methods are canonically defined"),
"nilfunc": triStateFlag("nilfunc", unset, "check for comparisons between functions and nil"),
"printf": triStateFlag("printf", unset, "check printf-like invocations"),
"rangeloops": triStateFlag("rangeloops", unset, "check that range loop variables are used correctly"),
"shadow": triStateFlag("shadow", unset, "check for shadowed variables (experimental; must be set explicitly)"),
"structtags": triStateFlag("structtags", unset, "check that struct field tags have canonical format"),
"unreachable": triStateFlag("unreachable", unset, "check for unreachable code"),
}
// experimental records the flags enabling experimental features. These must be
// requested explicitly; they are not enabled by -all.
var experimental = map[string]bool{
"shadow": true,
}
// setTrueCount record how many flags are explicitly set to true.
var setTrueCount int
// A triState is a boolean that knows whether it has been set to either true or false.
// It is used to identify if a flag appears; the standard boolean flag cannot
// distinguish missing from unset. It also satisfies flag.Value.
type triState int
const (
unset triState = iota
setTrue
setFalse
)
func triStateFlag(name string, value triState, usage string) *triState {
flag.Var(&value, name, usage)
return &value
}
// triState implements flag.Value, flag.Getter, and flag.boolFlag.
// They work like boolean flags: we can say vet -printf as well as vet -printf=true
func (ts *triState) Get() interface{} {
return *ts == setTrue
}
func (ts triState) isTrue() bool {
return ts == setTrue
}
func (ts *triState) Set(value string) error {
b, err := strconv.ParseBool(value)
if err != nil {
return err
}
if b {
*ts = setTrue
setTrueCount++
} else {
*ts = setFalse
}
return nil
}
func (ts *triState) String() string {
switch *ts {
case unset:
return "unset"
case setTrue:
return "true"
case setFalse:
return "false"
}
panic("not reached")
}
func (ts triState) IsBoolFlag() bool {
return true
}
// vet tells whether to report errors for the named check, a flag name.
func vet(name string) bool {
if *testFlag {
return true
}
return report[name].isTrue()
}
// setExit sets the value for os.Exit when it is called, later. It
// remembers the highest value.
func setExit(err int) {
if err > exitCode {
exitCode = err
}
}
// Usage is a replacement usage function for the flags package.
func Usage() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n")
fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n")
fmt.Fprintf(os.Stderr, "For more information run\n")
fmt.Fprintf(os.Stderr, "\tgodoc code.google.com/p/go.tools/cmd/vet\n\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
os.Exit(2)
}
// File is a wrapper for the state of a file used in the parser.
// The parse tree walkers are all methods of this type.
type File struct {
pkg *Package
fset *token.FileSet
name string
content []byte
file *ast.File
b bytes.Buffer // for use by methods
// The last "String() string" method receiver we saw while walking.
// This is used by the recursiveStringer method in print.go.
lastStringerReceiver *ast.Object
}
func main() {
flag.Usage = Usage
flag.Parse()
// If any flag is set, we run only those checks requested.
// If no flags are set true, set all the non-experimental ones not explicitly set (in effect, set the "-all" flag).
if setTrueCount == 0 {
for name, setting := range report {
if *setting == unset && !experimental[name] {
*setting = setTrue
}
}
}
if *printfuncs != "" {
for _, name := range strings.Split(*printfuncs, ",") {
if len(name) == 0 {
flag.Usage()
}
skip := 0
if colon := strings.LastIndex(name, ":"); colon > 0 {
var err error
skip, err = strconv.Atoi(name[colon+1:])
if err != nil {
errorf(`illegal format for "Func:N" argument %q; %s`, name, err)
}
name = name[:colon]
}
name = strings.ToLower(name)
if name[len(name)-1] == 'f' {
printfList[name] = skip
} else {
printList[name] = skip
}
}
}
if flag.NArg() == 0 {
Usage()
}
dirs := false
files := false
for _, name := range flag.Args() {
// Is it a directory?
fi, err := os.Stat(name)
if err != nil {
warnf("error walking tree: %s", err)
continue
}
if fi.IsDir() {
dirs = true
} else {
files = true
}
}
if dirs && files {
Usage()
}
if dirs {
for _, name := range flag.Args() {
walkDir(name)
}
return
}
if !doPackage(".", flag.Args()) {
warnf("no files checked")
}
os.Exit(exitCode)
}
// prefixDirectory places the directory name on the beginning of each name in the list.
func prefixDirectory(directory string, names []string) {
if directory != "." {
for i, name := range names {
names[i] = filepath.Join(directory, name)
}
}
}
// doPackageDir analyzes the single package found in the directory, if there is one,
// plus a test package, if there is one.
func doPackageDir(directory string) {
pkg, err := build.Default.ImportDir(directory, 0)
if err != nil {
// If it's just that there are no go source files, that's fine.
if _, nogo := err.(*build.NoGoError); nogo {
return
}
// Non-fatal: we are doing a recursive walk and there may be other directories.
warnf("cannot process directory %s: %s", directory, err)
return
}
var names []string
names = append(names, pkg.GoFiles...)
names = append(names, pkg.CgoFiles...)
names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
names = append(names, pkg.SFiles...)
prefixDirectory(directory, names)
doPackage(directory, names)
// Is there also a "foo_test" package? If so, do that one as well.
if len(pkg.XTestGoFiles) > 0 {
names = pkg.XTestGoFiles
prefixDirectory(directory, names)
doPackage(directory, names)
}
}
type Package struct {
path string
defs map[*ast.Ident]types.Object
uses map[*ast.Ident]types.Object
types map[ast.Expr]types.TypeAndValue
spans map[types.Object]Span
files []*File
typesPkg *types.Package
}
// doPackage analyzes the single package constructed from the named files.
// It returns whether any files were checked.
func doPackage(directory string, names []string) bool {
var files []*File
var astFiles []*ast.File
fs := token.NewFileSet()
for _, name := range names {
f, err := os.Open(name)
if err != nil {
// Warn but continue to next package.
warnf("%s: %s", name, err)
return false
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
warnf("%s: %s", name, err)
return false
}
checkBuildTag(name, data)
var parsedFile *ast.File
if strings.HasSuffix(name, ".go") {
parsedFile, err = parser.ParseFile(fs, name, bytes.NewReader(data), 0)
if err != nil {
warnf("%s: %s", name, err)
return false
}
astFiles = append(astFiles, parsedFile)
}
files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile})
}
if len(astFiles) == 0 {
return false
}
pkg := new(Package)
pkg.path = astFiles[0].Name.Name
pkg.files = files
// Type check the package.
err := pkg.check(fs, astFiles)
if err != nil && *verbose {
warnf("%s", err)
}
for _, file := range files {
file.pkg = pkg
if file.file != nil {
file.walkFile(file.name, file.file)
}
}
asmCheck(pkg)
return true
}
func visit(path string, f os.FileInfo, err error) error {
if err != nil {
warnf("walk error: %s", err)
return err
}
// One package per directory. Ignore the files themselves.
if !f.IsDir() {
return nil
}
doPackageDir(path)
return nil
}
func (pkg *Package) hasFileWithSuffix(suffix string) bool {
for _, f := range pkg.files {
if strings.HasSuffix(f.name, suffix) {
return true
}
}
return false
}
// walkDir recursively walks the tree looking for Go packages.
func walkDir(root string) {
filepath.Walk(root, visit)
}
// errorf formats the error to standard error, adding program
// identification and a newline, and exits.
func errorf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
os.Exit(2)
}
// warnf formats the error to standard error, adding program
// identification and a newline, but does not exit.
func warnf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...)
setExit(1)
}
// Println is fmt.Println guarded by -v.
func Println(args ...interface{}) {
if !*verbose {
return
}
fmt.Println(args...)
}
// Printf is fmt.Printf guarded by -v.
func Printf(format string, args ...interface{}) {
if !*verbose {
return
}
fmt.Printf(format+"\n", args...)
}
// Bad reports an error and sets the exit code..
func (f *File) Bad(pos token.Pos, args ...interface{}) {
f.Warn(pos, args...)
setExit(1)
}
// Badf reports a formatted error and sets the exit code.
func (f *File) Badf(pos token.Pos, format string, args ...interface{}) {
f.Warnf(pos, format, args...)
setExit(1)
}
// loc returns a formatted representation of the position.
func (f *File) loc(pos token.Pos) string {
if pos == token.NoPos {
return ""
}
// Do not print columns. Because the pos often points to the start of an
// expression instead of the inner part with the actual error, the
// precision can mislead.
posn := f.fset.Position(pos)
return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
}
// Warn reports an error but does not set the exit code.
func (f *File) Warn(pos token.Pos, args ...interface{}) {
fmt.Fprint(os.Stderr, f.loc(pos)+": "+fmt.Sprintln(args...))
}
// Warnf reports a formatted error but does not set the exit code.
func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, f.loc(pos)+": "+format+"\n", args...)
}
// walkFile walks the file's tree.
func (f *File) walkFile(name string, file *ast.File) {
Println("Checking file", name)
ast.Walk(f, file)
}
// Visit implements the ast.Visitor interface.
func (f *File) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.AssignStmt:
f.walkAssignStmt(n)
case *ast.BinaryExpr:
f.walkBinaryExpr(n)
case *ast.CallExpr:
f.walkCallExpr(n)
case *ast.CompositeLit:
f.walkCompositeLit(n)
case *ast.Field:
f.walkFieldTag(n)
case *ast.FuncDecl:
f.walkFuncDecl(n)
case *ast.FuncLit:
f.walkFuncLit(n)
case *ast.GenDecl:
f.walkGenDecl(n)
case *ast.InterfaceType:
f.walkInterfaceType(n)
case *ast.RangeStmt:
f.walkRangeStmt(n)
}
return f
}
// walkAssignStmt walks an assignment statement
func (f *File) walkAssignStmt(stmt *ast.AssignStmt) {
f.checkAssignStmt(stmt)
f.checkAtomicAssignment(stmt)
f.checkShadowAssignment(stmt)
}
func (f *File) walkBinaryExpr(expr *ast.BinaryExpr) {
f.checkNilFuncComparison(expr)
}
// walkCall walks a call expression.
func (f *File) walkCall(call *ast.CallExpr, name string) {
f.checkFmtPrintfCall(call, name)
}
// walkCallExpr walks a call expression.
func (f *File) walkCallExpr(call *ast.CallExpr) {
switch x := call.Fun.(type) {
case *ast.Ident:
f.walkCall(call, x.Name)
case *ast.SelectorExpr:
f.walkCall(call, x.Sel.Name)
}
}
// walkCompositeLit walks a composite literal.
func (f *File) walkCompositeLit(c *ast.CompositeLit) {
f.checkUnkeyedLiteral(c)
}
// walkFieldTag walks a struct field tag.
func (f *File) walkFieldTag(field *ast.Field) {
if field.Tag == nil {
return
}
f.checkCanonicalFieldTag(field)
}
// walkMethod walks the method's signature.
func (f *File) walkMethod(id *ast.Ident, t *ast.FuncType) {
f.checkCanonicalMethod(id, t)
}
// walkFuncDecl walks a function declaration.
func (f *File) walkFuncDecl(d *ast.FuncDecl) {
f.checkUnreachable(d.Body)
if d.Recv != nil {
f.walkMethod(d.Name, d.Type)
}
f.prepStringerReceiver(d)
f.checkCopyLocks(d)
}
// prepStringerReceiver checks whether the given declaration is a fmt.Stringer
// implementation, and if so sets the File's lastStringerReceiver field to the
// declaration's receiver object.
func (f *File) prepStringerReceiver(d *ast.FuncDecl) {
if !f.isStringer(d) {
return
}
if l := d.Recv.List; len(l) == 1 {
if n := l[0].Names; len(n) == 1 {
f.lastStringerReceiver = n[0].Obj
}
}
}
// isStringer returns true if the provided declaration is a "String() string"
// method; an implementation of fmt.Stringer.
func (f *File) isStringer(d *ast.FuncDecl) bool {
return d.Recv != nil && d.Name.Name == "String" && d.Type.Results != nil &&
len(d.Type.Params.List) == 0 && len(d.Type.Results.List) == 1 &&
f.pkg.types[d.Type.Results.List[0].Type].Type == types.Typ[types.String]
}
// walkGenDecl walks a general declaration.
func (f *File) walkGenDecl(d *ast.GenDecl) {
f.checkShadowDecl(d)
}
// walkFuncLit walks a function literal.
func (f *File) walkFuncLit(x *ast.FuncLit) {
f.checkUnreachable(x.Body)
}
// walkInterfaceType walks the method signatures of an interface.
func (f *File) walkInterfaceType(t *ast.InterfaceType) {
for _, field := range t.Methods.List {
for _, id := range field.Names {
f.walkMethod(id, field.Type.(*ast.FuncType))
}
}
}
// walkRangeStmt walks a range statement.
func (f *File) walkRangeStmt(n *ast.RangeStmt) {
checkRangeLoop(f, n)
}
// gofmt returns a string representation of the expression.
func (f *File) gofmt(x ast.Expr) string {
f.b.Reset()
printer.Fprint(&f.b, f.fset, x)
return f.b.String()
}
@@ -1,163 +0,0 @@
// 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.
// This file contains the code to check canonical methods.
package main
import (
"fmt"
"go/ast"
"go/printer"
"strings"
)
type MethodSig struct {
args []string
results []string
}
// canonicalMethods lists the input and output types for Go methods
// that are checked using dynamic interface checks. Because the
// checks are dynamic, such methods would not cause a compile error
// if they have the wrong signature: instead the dynamic check would
// fail, sometimes mysteriously. If a method is found with a name listed
// here but not the input/output types listed here, vet complains.
//
// A few of the canonical methods have very common names.
// For example, a type might implement a Scan method that
// has nothing to do with fmt.Scanner, but we still want to check
// the methods that are intended to implement fmt.Scanner.
// To do that, the arguments that have a = prefix are treated as
// signals that the canonical meaning is intended: if a Scan
// method doesn't have a fmt.ScanState as its first argument,
// we let it go. But if it does have a fmt.ScanState, then the
// rest has to match.
var canonicalMethods = map[string]MethodSig{
// "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
"Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
"GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder
"GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder
"MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler
"MarshalXML": {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler
"Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader)
"ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader
"ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom
"ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader
"Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner
"Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker
"UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler
"UnmarshalXML": {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler
"UnreadByte": {[]string{}, []string{"error"}},
"UnreadRune": {[]string{}, []string{"error"}},
"WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer)
"WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
}
func (f *File) checkCanonicalMethod(id *ast.Ident, t *ast.FuncType) {
if !vet("methods") {
return
}
// Expected input/output.
expect, ok := canonicalMethods[id.Name]
if !ok {
return
}
// Actual input/output
args := typeFlatten(t.Params.List)
var results []ast.Expr
if t.Results != nil {
results = typeFlatten(t.Results.List)
}
// Do the =s (if any) all match?
if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") {
return
}
// Everything must match.
if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") {
expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
if len(expect.results) == 1 {
expectFmt += " " + argjoin(expect.results)
} else if len(expect.results) > 1 {
expectFmt += " (" + argjoin(expect.results) + ")"
}
f.b.Reset()
if err := printer.Fprint(&f.b, f.fset, t); err != nil {
fmt.Fprintf(&f.b, "<%s>", err)
}
actual := f.b.String()
actual = strings.TrimPrefix(actual, "func")
actual = id.Name + actual
f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt)
}
}
func argjoin(x []string) string {
y := make([]string, len(x))
for i, s := range x {
if s[0] == '=' {
s = s[1:]
}
y[i] = s
}
return strings.Join(y, ", ")
}
// Turn parameter list into slice of types
// (in the ast, types are Exprs).
// Have to handle f(int, bool) and f(x, y, z int)
// so not a simple 1-to-1 conversion.
func typeFlatten(l []*ast.Field) []ast.Expr {
var t []ast.Expr
for _, f := range l {
if len(f.Names) == 0 {
t = append(t, f.Type)
continue
}
for _ = range f.Names {
t = append(t, f.Type)
}
}
return t
}
// Does each type in expect with the given prefix match the corresponding type in actual?
func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool {
for i, x := range expect {
if !strings.HasPrefix(x, prefix) {
continue
}
if i >= len(actual) {
return false
}
if !f.matchParamType(x, actual[i]) {
return false
}
}
if prefix == "" && len(actual) > len(expect) {
return false
}
return true
}
// Does this one type match?
func (f *File) matchParamType(expect string, actual ast.Expr) bool {
if strings.HasPrefix(expect, "=") {
expect = expect[1:]
}
// Strip package name if we're in that package.
if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' {
expect = expect[n+1:]
}
// Overkill but easy.
f.b.Reset()
printer.Fprint(&f.b, f.fset, actual)
return f.b.String() == expect
}
@@ -1,63 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
This file contains the code to check for useless function comparisons.
A useless comparison is one like f == nil as opposed to f() == nil.
*/
package main
import (
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
)
func (f *File) checkNilFuncComparison(e *ast.BinaryExpr) {
if !vet("nilfunc") {
return
}
// Only want == or != comparisons.
if e.Op != token.EQL && e.Op != token.NEQ {
return
}
// Only want comparisons with a nil identifier on one side.
var e2 ast.Expr
switch {
case f.isNil(e.X):
e2 = e.Y
case f.isNil(e.Y):
e2 = e.X
default:
return
}
// Only want identifiers or selector expressions.
var obj types.Object
switch v := e2.(type) {
case *ast.Ident:
obj = f.pkg.uses[v]
case *ast.SelectorExpr:
obj = f.pkg.uses[v.Sel]
default:
return
}
// Only want functions.
if _, ok := obj.(*types.Func); !ok {
return
}
f.Badf(e.Pos(), "comparison of function %v %v nil is always %v", obj.Name(), e.Op, e.Op == token.NEQ)
}
// isNil reports whether the provided expression is the built-in nil
// identifier.
func (f *File) isNil(e ast.Expr) bool {
return f.pkg.types[e].Type == types.Typ[types.UntypedNil]
}
@@ -1,503 +0,0 @@
// 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.
// This file contains the printf-checker.
package main
import (
"bytes"
"flag"
"go/ast"
"go/token"
"strconv"
"strings"
"unicode/utf8"
"code.google.com/p/go.tools/go/exact"
)
var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check")
// printfList records the formatted-print functions. The value is the location
// of the format parameter. Names are lower-cased so the lookup is
// case insensitive.
var printfList = map[string]int{
"errorf": 0,
"fatalf": 0,
"fprintf": 1,
"panicf": 0,
"printf": 0,
"sprintf": 0,
}
// printList records the unformatted-print functions. The value is the location
// of the first parameter to be printed. Names are lower-cased so the lookup is
// case insensitive.
var printList = map[string]int{
"error": 0,
"fatal": 0,
"fprint": 1, "fprintln": 1,
"panic": 0, "panicln": 0,
"print": 0, "println": 0,
"sprint": 0, "sprintln": 0,
}
// checkCall triggers the print-specific checks if the call invokes a print function.
func (f *File) checkFmtPrintfCall(call *ast.CallExpr, Name string) {
if !vet("printf") {
return
}
name := strings.ToLower(Name)
if skip, ok := printfList[name]; ok {
f.checkPrintf(call, Name, skip)
return
}
if skip, ok := printList[name]; ok {
f.checkPrint(call, Name, skip)
return
}
}
// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
// It is constructed by parsePrintfVerb.
type formatState struct {
verb rune // the format verb: 'd' for "%d"
format string // the full format directive from % through verb, "%.3d".
name string // Printf, Sprintf etc.
flags []byte // the list of # + etc.
argNums []int // the successive argument numbers that are consumed, adjusted to refer to actual arg in call
indexed bool // whether an indexing expression appears: %[1]d.
firstArg int // Index of first argument after the format in the Printf call.
// Used only during parse.
file *File
call *ast.CallExpr
argNum int // Which argument we're expecting to format now.
indexPending bool // Whether we have an indexed argument that has not resolved.
nbytes int // number of bytes of the format string consumed.
}
// checkPrintf checks a call to a formatted print routine such as Printf.
// call.Args[formatIndex] is (well, should be) the format argument.
func (f *File) checkPrintf(call *ast.CallExpr, name string, formatIndex int) {
if formatIndex >= len(call.Args) {
f.Bad(call.Pos(), "too few arguments in call to", name)
return
}
lit := f.pkg.types[call.Args[formatIndex]].Value
if lit == nil {
if *verbose {
f.Warn(call.Pos(), "can't check non-constant format in call to", name)
}
return
}
if lit.Kind() != exact.String {
f.Badf(call.Pos(), "constant %v not a string in call to %s", lit, name)
return
}
format := exact.StringVal(lit)
firstArg := formatIndex + 1 // Arguments are immediately after format string.
if !strings.Contains(format, "%") {
if len(call.Args) > firstArg {
f.Badf(call.Pos(), "no formatting directive in %s call", name)
}
return
}
// Hard part: check formats against args.
argNum := firstArg
indexed := false
for i, w := 0, 0; i < len(format); i += w {
w = 1
if format[i] == '%' {
state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum)
if state == nil {
return
}
w = len(state.format)
if state.indexed {
indexed = true
}
if !f.okPrintfArg(call, state) { // One error per format is enough.
return
}
if len(state.argNums) > 0 {
// Continue with the next sequential argument.
argNum = state.argNums[len(state.argNums)-1] + 1
}
}
}
// Dotdotdot is hard.
if call.Ellipsis.IsValid() && argNum >= len(call.Args)-1 {
return
}
// If the arguments were direct indexed, we assume the programmer knows what's up.
// Otherwise, there should be no leftover arguments.
if !indexed && argNum != len(call.Args) {
expect := argNum - firstArg
numArgs := len(call.Args) - firstArg
f.Badf(call.Pos(), "wrong number of args for format in %s call: %d needed but %d args", name, expect, numArgs)
}
}
// parseFlags accepts any printf flags.
func (s *formatState) parseFlags() {
for s.nbytes < len(s.format) {
switch c := s.format[s.nbytes]; c {
case '#', '0', '+', '-', ' ':
s.flags = append(s.flags, c)
s.nbytes++
default:
return
}
}
}
// scanNum advances through a decimal number if present.
func (s *formatState) scanNum() {
for ; s.nbytes < len(s.format); s.nbytes++ {
c := s.format[s.nbytes]
if c < '0' || '9' < c {
return
}
}
}
// parseIndex scans an index expression. It returns false if there is a syntax error.
func (s *formatState) parseIndex() bool {
if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' {
return true
}
// Argument index present.
s.indexed = true
s.nbytes++ // skip '['
start := s.nbytes
s.scanNum()
if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index")
return false
}
arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
if err != nil {
s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index: %s", err)
return false
}
s.nbytes++ // skip ']'
arg := int(arg32)
arg += s.firstArg - 1 // We want to zero-index the actual arguments.
s.argNum = arg
s.indexPending = true
return true
}
// parseNum scans a width or precision (or *). It returns false if there's a bad index expression.
func (s *formatState) parseNum() bool {
if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' {
if s.indexPending { // Absorb it.
s.indexPending = false
}
s.nbytes++
s.argNums = append(s.argNums, s.argNum)
s.argNum++
} else {
s.scanNum()
}
return true
}
// parsePrecision scans for a precision. It returns false if there's a bad index expression.
func (s *formatState) parsePrecision() bool {
// If there's a period, there may be a precision.
if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' {
s.flags = append(s.flags, '.') // Treat precision as a flag.
s.nbytes++
if !s.parseIndex() {
return false
}
if !s.parseNum() {
return false
}
}
return true
}
// parsePrintfVerb looks the formatting directive that begins the format string
// and returns a formatState that encodes what the directive wants, without looking
// at the actual arguments present in the call. The result is nil if there is an error.
func (f *File) parsePrintfVerb(call *ast.CallExpr, name, format string, firstArg, argNum int) *formatState {
state := &formatState{
format: format,
name: name,
flags: make([]byte, 0, 5),
argNum: argNum,
argNums: make([]int, 0, 1),
nbytes: 1, // There's guaranteed to be a percent sign.
indexed: false,
firstArg: firstArg,
file: f,
call: call,
}
// There may be flags.
state.parseFlags()
indexPending := false
// There may be an index.
if !state.parseIndex() {
return nil
}
// There may be a width.
if !state.parseNum() {
return nil
}
// There may be a precision.
if !state.parsePrecision() {
return nil
}
// Now a verb, possibly prefixed by an index (which we may already have).
if !indexPending && !state.parseIndex() {
return nil
}
if state.nbytes == len(state.format) {
f.Badf(call.Pos(), "missing verb at end of format string in %s call", name)
return nil
}
verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
state.verb = verb
state.nbytes += w
if verb != '%' {
state.argNums = append(state.argNums, state.argNum)
}
state.format = state.format[:state.nbytes]
return state
}
// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask.
type printfArgType int
const (
argBool printfArgType = 1 << iota
argInt
argRune
argString
argFloat
argComplex
argPointer
anyType printfArgType = ^0
)
type printVerb struct {
verb rune // User may provide verb through Formatter; could be a rune.
flags string // known flags are all ASCII
typ printfArgType
}
// Common flag sets for printf verbs.
const (
noFlag = ""
numFlag = " -+.0"
sharpNumFlag = " -+.0#"
allFlags = " -+.0#"
)
// printVerbs identifies which flags are known to printf for each verb.
// TODO: A type that implements Formatter may do what it wants, and vet
// will complain incorrectly.
var printVerbs = []printVerb{
// '-' is a width modifier, always valid.
// '.' is a precision for float, max width for strings.
// '+' is required sign for numbers, Go format for %v.
// '#' is alternate format for several verbs.
// ' ' is spacer for numbers
{'%', noFlag, 0},
{'b', numFlag, argInt | argFloat | argComplex},
{'c', "-", argRune | argInt},
{'d', numFlag, argInt},
{'e', numFlag, argFloat | argComplex},
{'E', numFlag, argFloat | argComplex},
{'f', numFlag, argFloat | argComplex},
{'F', numFlag, argFloat | argComplex},
{'g', numFlag, argFloat | argComplex},
{'G', numFlag, argFloat | argComplex},
{'o', sharpNumFlag, argInt},
{'p', "-#", argPointer},
{'q', " -+.0#", argRune | argInt | argString},
{'s', " -+.0", argString},
{'t', "-", argBool},
{'T', "-", anyType},
{'U', "-#", argRune | argInt},
{'v', allFlags, anyType},
{'x', sharpNumFlag, argRune | argInt | argString},
{'X', sharpNumFlag, argRune | argInt | argString},
}
// okPrintfArg compares the formatState to the arguments actually present,
// reporting any discrepancies it can discern. If the final argument is ellipsissed,
// there's little it can do for that.
func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) {
var v printVerb
found := false
// Linear scan is fast enough for a small list.
for _, v = range printVerbs {
if v.verb == state.verb {
found = true
break
}
}
if !found {
f.Badf(call.Pos(), "unrecognized printf verb %q", state.verb)
return false
}
for _, flag := range state.flags {
if !strings.ContainsRune(v.flags, rune(flag)) {
f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", state.verb, flag)
return false
}
}
// Verb is good. If len(state.argNums)>trueArgs, we have something like %.*s and all
// but the final arg must be an integer.
trueArgs := 1
if state.verb == '%' {
trueArgs = 0
}
nargs := len(state.argNums)
for i := 0; i < nargs-trueArgs; i++ {
argNum := state.argNums[i]
if !f.argCanBeChecked(call, i, true, state) {
return
}
arg := call.Args[argNum]
if !f.matchArgType(argInt, nil, arg) {
f.Badf(call.Pos(), "arg %s for * in printf format not of type int", f.gofmt(arg))
return false
}
}
if state.verb == '%' {
return true
}
argNum := state.argNums[len(state.argNums)-1]
if !f.argCanBeChecked(call, len(state.argNums)-1, false, state) {
return false
}
arg := call.Args[argNum]
if !f.matchArgType(v.typ, nil, arg) {
typeString := ""
if typ := f.pkg.types[arg].Type; typ != nil {
typeString = typ.String()
}
f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", f.gofmt(arg), state.verb, typeString)
return false
}
if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) {
f.Badf(call.Pos(), "arg %s for printf causes recursive call to String method", f.gofmt(arg))
return false
}
return true
}
// recursiveStringer reports whether the provided argument is r or &r for the
// fmt.Stringer receiver identifier r.
func (f *File) recursiveStringer(e ast.Expr) bool {
if f.lastStringerReceiver == nil {
return false
}
var obj *ast.Object
switch e := e.(type) {
case *ast.Ident:
obj = e.Obj
case *ast.UnaryExpr:
if id, ok := e.X.(*ast.Ident); ok && e.Op == token.AND {
obj = id.Obj
}
}
// It's unlikely to be a recursive stringer if it has a Format method.
if typ := f.pkg.types[e].Type; typ != nil {
// Not a perfect match; see issue 6259.
if f.hasMethod(typ, "Format") {
return false
}
}
// We compare the underlying Object, which checks that the identifier
// is the one we declared as the receiver for the String method in
// which this printf appears.
return obj == f.lastStringerReceiver
}
// argCanBeChecked reports whether the specified argument is statically present;
// it may be beyond the list of arguments or in a terminal slice... argument, which
// means we can't see it.
func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, isStar bool, state *formatState) bool {
argNum := state.argNums[formatArg]
if argNum < 0 {
// Shouldn't happen, so catch it with prejudice.
panic("negative arg num")
}
if argNum < len(call.Args)-1 {
return true // Always OK.
}
if call.Ellipsis.IsValid() {
return false // We just can't tell; there could be many more arguments.
}
if argNum < len(call.Args) {
return true
}
// There are bad indexes in the format or there are fewer arguments than the format needs.
// This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
arg := argNum - state.firstArg + 1 // People think of arguments as 1-indexed.
f.Badf(call.Pos(), `missing argument for %s("%s"): format reads arg %d, have only %d args`, state.name, state.format, arg, len(call.Args)-state.firstArg)
return false
}
// checkPrint checks a call to an unformatted print routine such as Println.
// call.Args[firstArg] is the first argument to be printed.
func (f *File) checkPrint(call *ast.CallExpr, name string, firstArg int) {
isLn := strings.HasSuffix(name, "ln")
isF := strings.HasPrefix(name, "F")
args := call.Args
// check for Println(os.Stderr, ...)
if firstArg == 0 && !isF && len(args) > 0 {
if sel, ok := args[0].(*ast.SelectorExpr); ok {
if x, ok := sel.X.(*ast.Ident); ok {
if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
f.Badf(call.Pos(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name)
}
}
}
}
if len(args) <= firstArg {
// If we have a call to a method called Error that satisfies the Error interface,
// then it's ok. Otherwise it's something like (*T).Error from the testing package
// and we need to check it.
if name == "Error" && f.isErrorMethodCall(call) {
return
}
// If it's an Error call now, it's probably for printing errors.
if !isLn {
// Check the signature to be sure: there are niladic functions called "error".
if firstArg != 0 || f.numArgsInSignature(call) != firstArg {
f.Badf(call.Pos(), "no args in %s call", name)
}
}
return
}
arg := args[firstArg]
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
if strings.Contains(lit.Value, "%") {
f.Badf(call.Pos(), "possible formatting directive in %s call", name)
}
}
if isLn {
// The last item, if a string, should not have a newline.
arg = args[len(call.Args)-1]
if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
if strings.HasSuffix(lit.Value, `\n"`) {
f.Badf(call.Pos(), "%s call ends with newline", name)
}
}
}
for _, arg := range args {
if f.recursiveStringer(arg) {
f.Badf(call.Pos(), "arg %s for print causes recursive call to String method", f.gofmt(arg))
}
}
}
@@ -1,65 +0,0 @@
// 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.
/*
This file contains the code to check range loop variables bound inside function
literals that are deferred or launched in new goroutines. We only check
instances where the defer or go statement is the last statement in the loop
body, as otherwise we would need whole program analysis.
For example:
for i, v := range s {
go func() {
println(i, v) // not what you might expect
}()
}
See: http://golang.org/doc/go_faq.html#closures_and_goroutines
*/
package main
import "go/ast"
// checkRangeLoop walks the body of the provided range statement, checking if
// its index or value variables are used unsafely inside goroutines or deferred
// function literals.
func checkRangeLoop(f *File, n *ast.RangeStmt) {
if !vet("rangeloops") {
return
}
key, _ := n.Key.(*ast.Ident)
val, _ := n.Value.(*ast.Ident)
if key == nil && val == nil {
return
}
sl := n.Body.List
if len(sl) == 0 {
return
}
var last *ast.CallExpr
switch s := sl[len(sl)-1].(type) {
case *ast.GoStmt:
last = s.Call
case *ast.DeferStmt:
last = s.Call
default:
return
}
lit, ok := last.Fun.(*ast.FuncLit)
if !ok {
return
}
ast.Inspect(lit.Body, func(n ast.Node) bool {
id, ok := n.(*ast.Ident)
if !ok || id.Obj == nil {
return true
}
if key != nil && id.Obj == key.Obj || val != nil && id.Obj == val.Obj {
f.Bad(id.Pos(), "range variable", id.Name, "enclosed by function")
}
return true
})
}
@@ -1,231 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
This file contains the code to check for shadowed variables.
A shadowed variable is a variable declared in an inner scope
with the same name and type as a variable in an outer scope,
and where the outer variable is mentioned after the inner one
is declared.
(This definition can be refined; the module generates too many
false positives and is not yet enabled by default.)
For example:
func BadRead(f *os.File, buf []byte) error {
var err error
for {
n, err := f.Read(buf) // shadows the function variable 'err'
if err != nil {
break // causes return of wrong value
}
foo(buf)
}
return err
}
*/
package main
import (
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
)
// Span stores the minimum range of byte positions in the file in which a
// given variable (types.Object) is mentioned. It is lexically defined: it spans
// from the beginning of its first mention to the end of its last mention.
// A variable is considered shadowed (if *strictShadowing is off) only if the
// shadowing variable is declared within the span of the shadowed variable.
// In other words, if a variable is shadowed but not used after the shadowed
// variable is declared, it is inconsequential and not worth complaining about.
// This simple check dramatically reduces the nuisance rate for the shadowing
// check, at least until something cleverer comes along.
//
// One wrinkle: A "naked return" is a silent use of a variable that the Span
// will not capture, but the compilers catch naked returns of shadowed
// variables so we don't need to.
//
// Cases this gets wrong (TODO):
// - If a for loop's continuation statement mentions a variable redeclared in
// the block, we should complain about it but don't.
// - A variable declared inside a function literal can falsely be identified
// as shadowing a variable in the outer function.
//
type Span struct {
min token.Pos
max token.Pos
}
// contains reports whether the position is inside the span.
func (s Span) contains(pos token.Pos) bool {
return s.min <= pos && pos < s.max
}
// growSpan expands the span for the object to contain the instance represented
// by the identifier.
func (pkg *Package) growSpan(ident *ast.Ident, obj types.Object) {
if *strictShadowing {
return // No need
}
pos := ident.Pos()
end := ident.End()
span, ok := pkg.spans[obj]
if ok {
if span.min > pos {
span.min = pos
}
if span.max < end {
span.max = end
}
} else {
span = Span{pos, end}
}
pkg.spans[obj] = span
}
// checkShadowAssignment checks for shadowing in a short variable declaration.
func (f *File) checkShadowAssignment(a *ast.AssignStmt) {
if !vet("shadow") {
return
}
if a.Tok != token.DEFINE {
return
}
if f.idiomaticShortRedecl(a) {
return
}
for _, expr := range a.Lhs {
ident, ok := expr.(*ast.Ident)
if !ok {
f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier")
return
}
f.checkShadowing(ident)
}
}
// idiomaticShortRedecl reports whether this short declaration can be ignored for
// the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
func (f *File) idiomaticShortRedecl(a *ast.AssignStmt) bool {
// Don't complain about deliberate redeclarations of the form
// i := i
// Such constructs are idiomatic in range loops to create a new variable
// for each iteration. Another example is
// switch n := n.(type)
if len(a.Rhs) != len(a.Lhs) {
return false
}
// We know it's an assignment, so the LHS must be all identifiers. (We check anyway.)
for i, expr := range a.Lhs {
lhs, ok := expr.(*ast.Ident)
if !ok {
f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier")
return true // Don't do any more processing.
}
switch rhs := a.Rhs[i].(type) {
case *ast.Ident:
if lhs.Name != rhs.Name {
return false
}
case *ast.TypeAssertExpr:
if id, ok := rhs.X.(*ast.Ident); ok {
if lhs.Name != id.Name {
return false
}
}
}
}
return true
}
// idiomaticRedecl reports whether this declaration spec can be ignored for
// the purposes of shadowing, that is, that any redeclarations it contains are deliberate.
func (f *File) idiomaticRedecl(d *ast.ValueSpec) bool {
// Don't complain about deliberate redeclarations of the form
// var i, j = i, j
if len(d.Names) != len(d.Values) {
return false
}
for i, lhs := range d.Names {
if rhs, ok := d.Values[i].(*ast.Ident); ok {
if lhs.Name != rhs.Name {
return false
}
}
}
return true
}
// checkShadowDecl checks for shadowing in a general variable declaration.
func (f *File) checkShadowDecl(d *ast.GenDecl) {
if !vet("shadow") {
return
}
if d.Tok != token.VAR {
return
}
for _, spec := range d.Specs {
valueSpec, ok := spec.(*ast.ValueSpec)
if !ok {
f.Badf(spec.Pos(), "invalid AST: var GenDecl not ValueSpec")
return
}
// Don't complain about deliberate redeclarations of the form
// var i = i
if f.idiomaticRedecl(valueSpec) {
return
}
for _, ident := range valueSpec.Names {
f.checkShadowing(ident)
}
}
}
// checkShadowing checks whether the identifier shadows an identifier in an outer scope.
func (f *File) checkShadowing(ident *ast.Ident) {
if ident.Name == "_" {
// Can't shadow the blank identifier.
return
}
obj := f.pkg.defs[ident]
if obj == nil {
return
}
// obj.Parent.Parent is the surrounding scope. If we can find another declaration
// starting from there, we have a shadowed variable.
shadowed := obj.Parent().Parent().LookupParent(obj.Name())
if shadowed == nil {
return
}
// Don't complain if it's shadowing a universe-declared variable; that's fine.
if shadowed.Parent() == types.Universe {
return
}
if *strictShadowing {
// The shadowed variable must appear before this one to be an instance of shadowing.
if shadowed.Pos() > ident.Pos() {
return
}
} else {
// Don't complain if the span of validity of the shadowed variable doesn't include
// the shadowing variable.
span, ok := f.pkg.spans[shadowed]
if !ok {
f.Badf(ident.Pos(), "internal error: no range for %s", ident.Name)
return
}
if !span.contains(ident.Pos()) {
return
}
}
// Don't complain if the types differ: that implies the programmer really wants two variables.
if types.Identical(obj.Type(), shadowed.Type()) {
f.Badf(ident.Pos(), "declaration of %s shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos()))
}
}
@@ -1,37 +0,0 @@
// 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.
// This file contains the test for canonical struct tags.
package main
import (
"go/ast"
"reflect"
"strconv"
)
// checkField checks a struct field tag.
func (f *File) checkCanonicalFieldTag(field *ast.Field) {
if !vet("structtags") {
return
}
if field.Tag == nil {
return
}
tag, err := strconv.Unquote(field.Tag.Value)
if err != nil {
f.Badf(field.Pos(), "unable to read struct tag %s", field.Tag.Value)
return
}
// Check tag for validity by appending
// new key:value to end and checking that
// the tag parsing code can find it.
if reflect.StructTag(tag+` _gofix:"_magic"`).Get("_gofix") != "_magic" {
f.Badf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get", field.Tag.Value)
return
}
}
@@ -1,31 +0,0 @@
// 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.
// +build ignore
// This file contains declarations to test the assembly in test_asm.s.
package testdata
func arg1(x int8, y uint8)
func arg2(x int16, y uint16)
func arg4(x int32, y uint32)
func arg8(x int64, y uint64)
func argint(x int, y uint)
func argptr(x *byte, y *byte, c chan int, m map[int]int, f func())
func argstring(x, y string)
func argslice(x, y []string)
func argiface(x interface{}, y interface {
m()
})
func returnint() int
func returnbyte(x int) byte
func returnnamed(x byte) (r1 int, r2 int16, r3 string, r4 byte)
func noprof(x int)
func dupok(x int)
func nosplit(x int)
func rodata(x int)
func noptr(x int)
func wrapper(x int)
@@ -1,247 +0,0 @@
// 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.
// +build amd64
// +build vet_test
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), AX
MOVB y+1(FP), BX
MOVW x+0(FP), AX // ERROR "\[amd64\] invalid MOVW of x\+0\(FP\); int8 is 1-byte value"
MOVW y+1(FP), AX // ERROR "invalid MOVW of y\+1\(FP\); uint8 is 1-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int8 is 1-byte value"
MOVL y+1(FP), AX // ERROR "invalid MOVL of y\+1\(FP\); uint8 is 1-byte value"
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int8 is 1-byte value"
MOVQ y+1(FP), AX // ERROR "invalid MOVQ of y\+1\(FP\); uint8 is 1-byte value"
MOVB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)"
MOVB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)"
TESTB x+0(FP), AX
TESTB y+1(FP), BX
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int8 is 1-byte value"
TESTW y+1(FP), AX // ERROR "invalid TESTW of y\+1\(FP\); uint8 is 1-byte value"
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int8 is 1-byte value"
TESTL y+1(FP), AX // ERROR "invalid TESTL of y\+1\(FP\); uint8 is 1-byte value"
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int8 is 1-byte value"
TESTQ y+1(FP), AX // ERROR "invalid TESTQ of y\+1\(FP\); uint8 is 1-byte value"
TESTB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)"
TESTB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)"
RET
TEXT ·arg2(SB),0,$0-4
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int16 is 2-byte value"
MOVB y+2(FP), AX // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value"
MOVW x+0(FP), AX
MOVW y+2(FP), BX
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int16 is 2-byte value"
MOVL y+2(FP), AX // ERROR "invalid MOVL of y\+2\(FP\); uint16 is 2-byte value"
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int16 is 2-byte value"
MOVQ y+2(FP), AX // ERROR "invalid MOVQ of y\+2\(FP\); uint16 is 2-byte value"
MOVW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)"
MOVW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int16 is 2-byte value"
TESTB y+2(FP), AX // ERROR "invalid TESTB of y\+2\(FP\); uint16 is 2-byte value"
TESTW x+0(FP), AX
TESTW y+2(FP), BX
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int16 is 2-byte value"
TESTL y+2(FP), AX // ERROR "invalid TESTL of y\+2\(FP\); uint16 is 2-byte value"
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int16 is 2-byte value"
TESTQ y+2(FP), AX // ERROR "invalid TESTQ of y\+2\(FP\); uint16 is 2-byte value"
TESTW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)"
TESTW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)"
RET
TEXT ·arg4(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value"
MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int32 is 4-byte value"
MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); uint32 is 4-byte value"
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int32 is 4-byte value"
MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); uint32 is 4-byte value"
MOVL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
MOVL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int32 is 4-byte value"
TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); uint32 is 4-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int32 is 4-byte value"
TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); uint32 is 4-byte value"
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int32 is 4-byte value"
TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); uint32 is 4-byte value"
TESTL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
TESTL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
RET
TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value"
MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value"
MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int64 is 8-byte value"
MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); uint64 is 8-byte value"
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int64 is 8-byte value"
TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); uint64 is 8-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int64 is 8-byte value"
TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); uint64 is 8-byte value"
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int64 is 8-byte value"
TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); uint64 is 8-byte value"
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
RET
TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int is 8-byte value"
MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint is 8-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int is 8-byte value"
MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint is 8-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int is 8-byte value"
MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); uint is 8-byte value"
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int is 8-byte value"
TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); uint is 8-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int is 8-byte value"
TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); uint is 8-byte value"
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int is 8-byte value"
TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); uint is 8-byte value"
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
RET
TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-40"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 8-byte value"
MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); \*byte is 8-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); \*byte is 8-byte value"
MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); \*byte is 8-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); \*byte is 8-byte value"
MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); \*byte is 8-byte value"
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); \*byte is 8-byte value"
TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); \*byte is 8-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); \*byte is 8-byte value"
TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); \*byte is 8-byte value"
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); \*byte is 8-byte value"
TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); \*byte is 8-byte value"
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
MOVL c+16(FP), AX // ERROR "invalid MOVL of c\+16\(FP\); chan int is 8-byte value"
MOVL m+24(FP), AX // ERROR "invalid MOVL of m\+24\(FP\); map\[int\]int is 8-byte value"
MOVL f+32(FP), AX // ERROR "invalid MOVL of f\+32\(FP\); func\(\) is 8-byte value"
RET
TEXT ·argstring(SB),0,$32 // ERROR "wrong argument size 0; expected \$\.\.\.-32"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); string base is 8-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); string base is 8-byte value"
MOVQ x+0(FP), AX
MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); string base is 8-byte value"
MOVL x_base+0(FP), AX // ERROR "invalid MOVL of x_base\+0\(FP\); string base is 8-byte value"
MOVQ x_base+0(FP), AX
MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)"
MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)"
MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)"
MOVW x_len+8(FP), AX // ERROR "invalid MOVW of x_len\+8\(FP\); string len is 8-byte value"
MOVL x_len+8(FP), AX // ERROR "invalid MOVL of x_len\+8\(FP\); string len is 8-byte value"
MOVQ x_len+8(FP), AX
MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+16\(FP\)"
MOVQ y_len+8(FP), AX // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)"
RET
TEXT ·argslice(SB),0,$48 // ERROR "wrong argument size 0; expected \$\.\.\.-48"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); slice base is 8-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); slice base is 8-byte value"
MOVQ x+0(FP), AX
MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value"
MOVL x_base+0(FP), AX // ERROR "invalid MOVL of x_base\+0\(FP\); slice base is 8-byte value"
MOVQ x_base+0(FP), AX
MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)"
MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)"
MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)"
MOVW x_len+8(FP), AX // ERROR "invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value"
MOVL x_len+8(FP), AX // ERROR "invalid MOVL of x_len\+8\(FP\); slice len is 8-byte value"
MOVQ x_len+8(FP), AX
MOVW x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)"
MOVL x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)"
MOVQ x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)"
MOVW x_cap+16(FP), AX // ERROR "invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value"
MOVL x_cap+16(FP), AX // ERROR "invalid MOVL of x_cap\+16\(FP\); slice cap is 8-byte value"
MOVQ x_cap+16(FP), AX
MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+24\(FP\)"
MOVQ y_len+8(FP), AX // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)"
MOVQ y_cap+16(FP), AX // ERROR "invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)"
RET
TEXT ·argiface(SB),0,$0-32
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); interface type is 8-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); interface type is 8-byte value"
MOVQ x+0(FP), AX
MOVW x_type+0(FP), AX // ERROR "invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value"
MOVL x_type+0(FP), AX // ERROR "invalid MOVL of x_type\+0\(FP\); interface type is 8-byte value"
MOVQ x_type+0(FP), AX
MOVQ x_itable+0(FP), AX // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)"
MOVQ x_itable+1(FP), AX // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)"
MOVW x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)"
MOVL x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)"
MOVQ x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)"
MOVW x_data+8(FP), AX // ERROR "invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value"
MOVL x_data+8(FP), AX // ERROR "invalid MOVL of x_data\+8\(FP\); interface data is 8-byte value"
MOVQ x_data+8(FP), AX
MOVW y+16(FP), AX // ERROR "invalid MOVW of y\+16\(FP\); interface itable is 8-byte value"
MOVL y+16(FP), AX // ERROR "invalid MOVL of y\+16\(FP\); interface itable is 8-byte value"
MOVQ y+16(FP), AX
MOVW y_itable+16(FP), AX // ERROR "invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value"
MOVL y_itable+16(FP), AX // ERROR "invalid MOVL of y_itable\+16\(FP\); interface itable is 8-byte value"
MOVQ y_itable+16(FP), AX
MOVQ y_type+16(FP), AX // ERROR "unknown variable y_type; offset 16 is y_itable\+16\(FP\)"
MOVW y_data+16(FP), AX // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)"
MOVL y_data+16(FP), AX // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)"
MOVQ y_data+16(FP), AX // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)"
MOVW y_data+24(FP), AX // ERROR "invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value"
MOVL y_data+24(FP), AX // ERROR "invalid MOVL of y_data\+24\(FP\); interface data is 8-byte value"
MOVQ y_data+24(FP), AX
RET
TEXT ·returnint(SB),0,$0-8
MOVB AX, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 8-byte value"
MOVW AX, ret+0(FP) // ERROR "invalid MOVW of ret\+0\(FP\); int is 8-byte value"
MOVL AX, ret+0(FP) // ERROR "invalid MOVL of ret\+0\(FP\); int is 8-byte value"
MOVQ AX, ret+0(FP)
MOVQ AX, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)"
MOVQ AX, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)"
RET
TEXT ·returnbyte(SB),0,$0-9
MOVQ x+0(FP), AX
MOVB AX, ret+8(FP)
MOVW AX, ret+8(FP) // ERROR "invalid MOVW of ret\+8\(FP\); byte is 1-byte value"
MOVL AX, ret+8(FP) // ERROR "invalid MOVL of ret\+8\(FP\); byte is 1-byte value"
MOVQ AX, ret+8(FP) // ERROR "invalid MOVQ of ret\+8\(FP\); byte is 1-byte value"
MOVB AX, ret+7(FP) // ERROR "invalid offset ret\+7\(FP\); expected ret\+8\(FP\)"
RET
TEXT ·returnnamed(SB),0,$0-41
MOVB x+0(FP), AX
MOVQ AX, r1+8(FP)
MOVW AX, r2+16(FP)
MOVQ AX, r3+24(FP)
MOVQ AX, r3_base+24(FP)
MOVQ AX, r3_len+32(FP)
MOVB AX, r4+40(FP)
MOVL AX, r1+8(FP) // ERROR "invalid MOVL of r1\+8\(FP\); int is 8-byte value"
RET
@@ -1,251 +0,0 @@
// 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.
// +build 386
// +build vet_test
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), AX
MOVB y+1(FP), BX
MOVW x+0(FP), AX // ERROR "\[386\] invalid MOVW of x\+0\(FP\); int8 is 1-byte value"
MOVW y+1(FP), AX // ERROR "invalid MOVW of y\+1\(FP\); uint8 is 1-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int8 is 1-byte value"
MOVL y+1(FP), AX // ERROR "invalid MOVL of y\+1\(FP\); uint8 is 1-byte value"
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int8 is 1-byte value"
MOVQ y+1(FP), AX // ERROR "invalid MOVQ of y\+1\(FP\); uint8 is 1-byte value"
MOVB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)"
MOVB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)"
TESTB x+0(FP), AX
TESTB y+1(FP), BX
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int8 is 1-byte value"
TESTW y+1(FP), AX // ERROR "invalid TESTW of y\+1\(FP\); uint8 is 1-byte value"
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int8 is 1-byte value"
TESTL y+1(FP), AX // ERROR "invalid TESTL of y\+1\(FP\); uint8 is 1-byte value"
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int8 is 1-byte value"
TESTQ y+1(FP), AX // ERROR "invalid TESTQ of y\+1\(FP\); uint8 is 1-byte value"
TESTB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)"
TESTB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)"
RET
TEXT ·arg2(SB),0,$0-4
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int16 is 2-byte value"
MOVB y+2(FP), AX // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value"
MOVW x+0(FP), AX
MOVW y+2(FP), BX
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int16 is 2-byte value"
MOVL y+2(FP), AX // ERROR "invalid MOVL of y\+2\(FP\); uint16 is 2-byte value"
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int16 is 2-byte value"
MOVQ y+2(FP), AX // ERROR "invalid MOVQ of y\+2\(FP\); uint16 is 2-byte value"
MOVW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)"
MOVW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int16 is 2-byte value"
TESTB y+2(FP), AX // ERROR "invalid TESTB of y\+2\(FP\); uint16 is 2-byte value"
TESTW x+0(FP), AX
TESTW y+2(FP), BX
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int16 is 2-byte value"
TESTL y+2(FP), AX // ERROR "invalid TESTL of y\+2\(FP\); uint16 is 2-byte value"
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int16 is 2-byte value"
TESTQ y+2(FP), AX // ERROR "invalid TESTQ of y\+2\(FP\); uint16 is 2-byte value"
TESTW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)"
TESTW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)"
RET
TEXT ·arg4(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value"
MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int32 is 4-byte value"
MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); uint32 is 4-byte value"
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int32 is 4-byte value"
MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); uint32 is 4-byte value"
MOVL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
MOVL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int32 is 4-byte value"
TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); uint32 is 4-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int32 is 4-byte value"
TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); uint32 is 4-byte value"
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int32 is 4-byte value"
TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); uint32 is 4-byte value"
TESTL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
TESTL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
RET
TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value"
MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value"
MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value"
MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)"
MOVL x_lo+0(FP), AX
MOVL x_hi+4(FP), AX
MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)"
MOVL y_lo+8(FP), AX
MOVL y_hi+12(FP), AX
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int64 is 8-byte value"
TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); uint64 is 8-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int64 is 8-byte value"
TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); uint64 is 8-byte value"
TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)"
TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)"
TESTQ x+0(FP), AX
TESTQ y+8(FP), AX
TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
RET
TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int is 4-byte value"
MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint is 4-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int is 4-byte value"
MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); uint is 4-byte value"
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int is 4-byte value"
MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); uint is 4-byte value"
MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int is 4-byte value"
TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); uint is 4-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int is 4-byte value"
TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); uint is 4-byte value"
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int is 4-byte value"
TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); uint is 4-byte value"
TESTQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
RET
TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-20"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 4-byte value"
MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); \*byte is 4-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); \*byte is 4-byte value"
MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); \*byte is 4-byte value"
MOVL x+0(FP), AX
MOVL y+4(FP), AX
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); \*byte is 4-byte value"
MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); \*byte is 4-byte value"
MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); \*byte is 4-byte value"
TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); \*byte is 4-byte value"
TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); \*byte is 4-byte value"
TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); \*byte is 4-byte value"
TESTL x+0(FP), AX
TESTL y+4(FP), AX
TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); \*byte is 4-byte value"
TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); \*byte is 4-byte value"
TESTQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
MOVW c+8(FP), AX // ERROR "invalid MOVW of c\+8\(FP\); chan int is 4-byte value"
MOVW m+12(FP), AX // ERROR "invalid MOVW of m\+12\(FP\); map\[int\]int is 4-byte value"
MOVW f+16(FP), AX // ERROR "invalid MOVW of f\+16\(FP\); func\(\) is 4-byte value"
RET
TEXT ·argstring(SB),0,$16 // ERROR "wrong argument size 0; expected \$\.\.\.-16"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); string base is 4-byte value"
MOVL x+0(FP), AX
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); string base is 4-byte value"
MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); string base is 4-byte value"
MOVL x_base+0(FP), AX
MOVQ x_base+0(FP), AX // ERROR "invalid MOVQ of x_base\+0\(FP\); string base is 4-byte value"
MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVW x_len+4(FP), AX // ERROR "invalid MOVW of x_len\+4\(FP\); string len is 4-byte value"
MOVL x_len+4(FP), AX
MOVQ x_len+4(FP), AX // ERROR "invalid MOVQ of x_len\+4\(FP\); string len is 4-byte value"
MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+8\(FP\)"
MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)"
RET
TEXT ·argslice(SB),0,$24 // ERROR "wrong argument size 0; expected \$\.\.\.-24"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); slice base is 4-byte value"
MOVL x+0(FP), AX
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); slice base is 4-byte value"
MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); slice base is 4-byte value"
MOVL x_base+0(FP), AX
MOVQ x_base+0(FP), AX // ERROR "invalid MOVQ of x_base\+0\(FP\); slice base is 4-byte value"
MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVW x_len+4(FP), AX // ERROR "invalid MOVW of x_len\+4\(FP\); slice len is 4-byte value"
MOVL x_len+4(FP), AX
MOVQ x_len+4(FP), AX // ERROR "invalid MOVQ of x_len\+4\(FP\); slice len is 4-byte value"
MOVW x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)"
MOVL x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)"
MOVQ x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)"
MOVW x_cap+8(FP), AX // ERROR "invalid MOVW of x_cap\+8\(FP\); slice cap is 4-byte value"
MOVL x_cap+8(FP), AX
MOVQ x_cap+8(FP), AX // ERROR "invalid MOVQ of x_cap\+8\(FP\); slice cap is 4-byte value"
MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+12\(FP\)"
MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)"
MOVQ y_cap+8(FP), AX // ERROR "invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)"
RET
TEXT ·argiface(SB),0,$0-16
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); interface type is 4-byte value"
MOVL x+0(FP), AX
MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); interface type is 4-byte value"
MOVW x_type+0(FP), AX // ERROR "invalid MOVW of x_type\+0\(FP\); interface type is 4-byte value"
MOVL x_type+0(FP), AX
MOVQ x_type+0(FP), AX // ERROR "invalid MOVQ of x_type\+0\(FP\); interface type is 4-byte value"
MOVQ x_itable+0(FP), AX // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)"
MOVQ x_itable+1(FP), AX // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)"
MOVW x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)"
MOVL x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)"
MOVQ x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)"
MOVW x_data+4(FP), AX // ERROR "invalid MOVW of x_data\+4\(FP\); interface data is 4-byte value"
MOVL x_data+4(FP), AX
MOVQ x_data+4(FP), AX // ERROR "invalid MOVQ of x_data\+4\(FP\); interface data is 4-byte value"
MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); interface itable is 4-byte value"
MOVL y+8(FP), AX
MOVQ y+8(FP), AX // ERROR "invalid MOVQ of y\+8\(FP\); interface itable is 4-byte value"
MOVW y_itable+8(FP), AX // ERROR "invalid MOVW of y_itable\+8\(FP\); interface itable is 4-byte value"
MOVL y_itable+8(FP), AX
MOVQ y_itable+8(FP), AX // ERROR "invalid MOVQ of y_itable\+8\(FP\); interface itable is 4-byte value"
MOVQ y_type+8(FP), AX // ERROR "unknown variable y_type; offset 8 is y_itable\+8\(FP\)"
MOVW y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)"
MOVL y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)"
MOVQ y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)"
MOVW y_data+12(FP), AX // ERROR "invalid MOVW of y_data\+12\(FP\); interface data is 4-byte value"
MOVL y_data+12(FP), AX
MOVQ y_data+12(FP), AX // ERROR "invalid MOVQ of y_data\+12\(FP\); interface data is 4-byte value"
RET
TEXT ·returnint(SB),0,$0-4
MOVB AX, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 4-byte value"
MOVW AX, ret+0(FP) // ERROR "invalid MOVW of ret\+0\(FP\); int is 4-byte value"
MOVL AX, ret+0(FP)
MOVQ AX, ret+0(FP) // ERROR "invalid MOVQ of ret\+0\(FP\); int is 4-byte value"
MOVQ AX, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)"
MOVQ AX, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)"
RET
TEXT ·returnbyte(SB),0,$0-5
MOVL x+0(FP), AX
MOVB AX, ret+4(FP)
MOVW AX, ret+4(FP) // ERROR "invalid MOVW of ret\+4\(FP\); byte is 1-byte value"
MOVL AX, ret+4(FP) // ERROR "invalid MOVL of ret\+4\(FP\); byte is 1-byte value"
MOVQ AX, ret+4(FP) // ERROR "invalid MOVQ of ret\+4\(FP\); byte is 1-byte value"
MOVB AX, ret+3(FP) // ERROR "invalid offset ret\+3\(FP\); expected ret\+4\(FP\)"
RET
TEXT ·returnnamed(SB),0,$0-21
MOVB x+0(FP), AX
MOVL AX, r1+4(FP)
MOVW AX, r2+8(FP)
MOVL AX, r3+12(FP)
MOVL AX, r3_base+12(FP)
MOVL AX, r3_len+16(FP)
MOVB AX, r4+20(FP)
MOVQ AX, r1+4(FP) // ERROR "invalid MOVQ of r1\+4\(FP\); int is 4-byte value"
RET
@@ -1,166 +0,0 @@
// 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.
// +build arm
// +build vet_test
TEXT ·arg1(SB),0,$0-2
MOVB x+0(FP), AX
MOVB y+1(FP), BX
MOVH x+0(FP), AX // ERROR "\[arm\] invalid MOVH of x\+0\(FP\); int8 is 1-byte value"
MOVH y+1(FP), AX // ERROR "invalid MOVH of y\+1\(FP\); uint8 is 1-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int8 is 1-byte value"
MOVW y+1(FP), AX // ERROR "invalid MOVW of y\+1\(FP\); uint8 is 1-byte value"
MOVB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)"
MOVB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)"
RET
TEXT ·arg2(SB),0,$0-4
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int16 is 2-byte value"
MOVB y+2(FP), AX // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value"
MOVH x+0(FP), AX
MOVH y+2(FP), BX
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int16 is 2-byte value"
MOVW y+2(FP), AX // ERROR "invalid MOVW of y\+2\(FP\); uint16 is 2-byte value"
MOVH x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)"
MOVH y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)"
RET
TEXT ·arg4(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value"
MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value"
MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); int32 is 4-byte value"
MOVH y+4(FP), AX // ERROR "invalid MOVH of y\+4\(FP\); uint32 is 4-byte value"
MOVW x+0(FP), AX
MOVW y+4(FP), AX
MOVW x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
MOVW y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
RET
TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value"
MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value"
MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); int64 is 8-byte value"
MOVH y+8(FP), AX // ERROR "invalid MOVH of y\+8\(FP\); uint64 is 8-byte value"
MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)"
MOVW x_lo+0(FP), AX
MOVW x_hi+4(FP), AX
MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)"
MOVW y_lo+8(FP), AX
MOVW y_hi+12(FP), AX
MOVQ x+0(FP), AX
MOVQ y+8(FP), AX
MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)"
RET
TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int is 4-byte value"
MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint is 4-byte value"
MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); int is 4-byte value"
MOVH y+4(FP), AX // ERROR "invalid MOVH of y\+4\(FP\); uint is 4-byte value"
MOVW x+0(FP), AX
MOVW y+4(FP), AX
MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
RET
TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-20"
MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 4-byte value"
MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); \*byte is 4-byte value"
MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); \*byte is 4-byte value"
MOVH y+4(FP), AX // ERROR "invalid MOVH of y\+4\(FP\); \*byte is 4-byte value"
MOVW x+0(FP), AX
MOVW y+4(FP), AX
MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)"
MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)"
MOVH c+8(FP), AX // ERROR "invalid MOVH of c\+8\(FP\); chan int is 4-byte value"
MOVH m+12(FP), AX // ERROR "invalid MOVH of m\+12\(FP\); map\[int\]int is 4-byte value"
MOVH f+16(FP), AX // ERROR "invalid MOVH of f\+16\(FP\); func\(\) is 4-byte value"
RET
TEXT ·argstring(SB),0,$16 // ERROR "wrong argument size 0; expected \$\.\.\.-16"
MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); string base is 4-byte value"
MOVW x+0(FP), AX
MOVH x_base+0(FP), AX // ERROR "invalid MOVH of x_base\+0\(FP\); string base is 4-byte value"
MOVW x_base+0(FP), AX
MOVH x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVH x_len+4(FP), AX // ERROR "invalid MOVH of x_len\+4\(FP\); string len is 4-byte value"
MOVW x_len+4(FP), AX
MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+8\(FP\)"
MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)"
RET
TEXT ·argslice(SB),0,$24 // ERROR "wrong argument size 0; expected \$\.\.\.-24"
MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); slice base is 4-byte value"
MOVW x+0(FP), AX
MOVH x_base+0(FP), AX // ERROR "invalid MOVH of x_base\+0\(FP\); slice base is 4-byte value"
MOVW x_base+0(FP), AX
MOVH x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)"
MOVH x_len+4(FP), AX // ERROR "invalid MOVH of x_len\+4\(FP\); slice len is 4-byte value"
MOVW x_len+4(FP), AX
MOVH x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)"
MOVW x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)"
MOVQ x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)"
MOVH x_cap+8(FP), AX // ERROR "invalid MOVH of x_cap\+8\(FP\); slice cap is 4-byte value"
MOVW x_cap+8(FP), AX
MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+12\(FP\)"
MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)"
MOVQ y_cap+8(FP), AX // ERROR "invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)"
RET
TEXT ·argiface(SB),0,$0-16
MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); interface type is 4-byte value"
MOVW x+0(FP), AX
MOVH x_type+0(FP), AX // ERROR "invalid MOVH of x_type\+0\(FP\); interface type is 4-byte value"
MOVW x_type+0(FP), AX
MOVQ x_itable+0(FP), AX // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)"
MOVQ x_itable+1(FP), AX // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)"
MOVH x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)"
MOVW x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)"
MOVQ x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)"
MOVH x_data+4(FP), AX // ERROR "invalid MOVH of x_data\+4\(FP\); interface data is 4-byte value"
MOVW x_data+4(FP), AX
MOVH y+8(FP), AX // ERROR "invalid MOVH of y\+8\(FP\); interface itable is 4-byte value"
MOVW y+8(FP), AX
MOVH y_itable+8(FP), AX // ERROR "invalid MOVH of y_itable\+8\(FP\); interface itable is 4-byte value"
MOVW y_itable+8(FP), AX
MOVQ y_type+8(FP), AX // ERROR "unknown variable y_type; offset 8 is y_itable\+8\(FP\)"
MOVH y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)"
MOVW y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)"
MOVQ y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)"
MOVH y_data+12(FP), AX // ERROR "invalid MOVH of y_data\+12\(FP\); interface data is 4-byte value"
MOVW y_data+12(FP), AX
RET
TEXT ·returnint(SB),0,$0-4
MOVB AX, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 4-byte value"
MOVH AX, ret+0(FP) // ERROR "invalid MOVH of ret\+0\(FP\); int is 4-byte value"
MOVW AX, ret+0(FP)
MOVQ AX, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)"
MOVQ AX, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)"
RET
TEXT ·returnbyte(SB),0,$0-5
MOVW x+0(FP), AX
MOVB AX, ret+4(FP)
MOVH AX, ret+4(FP) // ERROR "invalid MOVH of ret\+4\(FP\); byte is 1-byte value"
MOVW AX, ret+4(FP) // ERROR "invalid MOVW of ret\+4\(FP\); byte is 1-byte value"
MOVB AX, ret+3(FP) // ERROR "invalid offset ret\+3\(FP\); expected ret\+4\(FP\)"
RET
TEXT ·returnnamed(SB),0,$0-21
MOVB x+0(FP), AX
MOVW AX, r1+4(FP)
MOVH AX, r2+8(FP)
MOVW AX, r3+12(FP)
MOVW AX, r3_base+12(FP)
MOVW AX, r3_len+16(FP)
MOVB AX, r4+20(FP)
MOVB AX, r1+4(FP) // ERROR "invalid MOVB of r1\+4\(FP\); int is 4-byte value"
RET
@@ -1,26 +0,0 @@
// 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.
// +build amd64
// +build vet_test
// Test cases for symbolic NOSPLIT etc. on TEXT symbols.
TEXT ·noprof(SB),NOPROF,$0-8
RET
TEXT ·dupok(SB),DUPOK,$0-8
RET
TEXT ·nosplit(SB),NOSPLIT,$0
RET
TEXT ·rodata(SB),RODATA,$0-8
RET
TEXT ·noptr(SB),NOPTR|NOSPLIT,$0
RET
TEXT ·wrapper(SB),WRAPPER,$0-8
RET
@@ -1,18 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the useless-assignment checker.
package testdata
type ST struct {
x int
}
func (s *ST) SetX(x int) {
// Accidental self-assignment; it should be "s.x = x"
x = x // ERROR "self-assignment of x to x"
// Another mistake
s.x = s.x // ERROR "self-assignment of s.x to s.x"
}
@@ -1,41 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the atomic checker.
package testdata
import (
"sync/atomic"
)
type Counter uint64
func AtomicTests() {
x := uint64(1)
x = atomic.AddUint64(&x, 1) // ERROR "direct assignment to atomic value"
_, x = 10, atomic.AddUint64(&x, 1) // ERROR "direct assignment to atomic value"
x, _ = atomic.AddUint64(&x, 1), 10 // ERROR "direct assignment to atomic value"
y := &x
*y = atomic.AddUint64(y, 1) // ERROR "direct assignment to atomic value"
var su struct{ Counter uint64 }
su.Counter = atomic.AddUint64(&su.Counter, 1) // ERROR "direct assignment to atomic value"
z1 := atomic.AddUint64(&su.Counter, 1)
_ = z1 // Avoid err "z declared and not used"
var sp struct{ Counter *uint64 }
*sp.Counter = atomic.AddUint64(sp.Counter, 1) // ERROR "direct assignment to atomic value"
z2 := atomic.AddUint64(sp.Counter, 1)
_ = z2 // Avoid err "z declared and not used"
au := []uint64{10, 20}
au[0] = atomic.AddUint64(&au[0], 1) // ERROR "direct assignment to atomic value"
au[1] = atomic.AddUint64(&au[0], 1)
ap := []*uint64{&au[0], &au[1]}
*ap[0] = atomic.AddUint64(ap[0], 1) // ERROR "direct assignment to atomic value"
*ap[1] = atomic.AddUint64(ap[0], 1)
}
@@ -1,14 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the buildtag checker.
// +builder // ERROR "possible malformed \+build comment"
// +build !ignore
package testdata
// +build toolate // ERROR "build comment must appear before package clause and be followed by a blank line"
var _ = 3
@@ -1,15 +0,0 @@
// This file contains misplaced or malformed build constraints.
// The Go tool will skip it, because the constraints are invalid.
// It serves only to test the tag checker during make test.
// Mention +build // ERROR "possible malformed \+build comment"
// +build !!bang // ERROR "invalid double negative in build constraint"
// +build @#$ // ERROR "invalid non-alphanumeric build constraint"
// +build toolate // ERROR "build comment must appear before package clause and be followed by a blank line"
package bad
// This is package 'bad' rather than 'main' so the erroneous build
// tag doesn't end up looking like a package doc for the vet command
// when examined by godoc.
@@ -1,63 +0,0 @@
// 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.
// This file contains tests for the untagged struct literal checker.
// This file contains the test for untagged struct literals.
package testdata
import (
"flag"
"go/scanner"
)
var Okay1 = []string{
"Name",
"Usage",
"DefValue",
}
var Okay2 = map[string]bool{
"Name": true,
"Usage": true,
"DefValue": true,
}
var Okay3 = struct {
X string
Y string
Z string
}{
"Name",
"Usage",
"DefValue",
}
type MyStruct struct {
X string
Y string
Z string
}
var Okay4 = MyStruct{
"Name",
"Usage",
"DefValue",
}
// Testing is awkward because we need to reference things from a separate package
// to trigger the warnings.
var BadStructLiteralUsedInTests = flag.Flag{ // ERROR "unkeyed fields"
"Name",
"Usage",
nil, // Value
"DefValue",
}
// Used to test the check for slices and arrays: If that test is disabled and
// vet is run with --compositewhitelist=false, this line triggers an error.
// Clumsy but sufficient.
var scannerErrorListTest = scanner.ErrorList{nil, nil}
@@ -1,89 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the copylock checker.
package testdata
import "sync"
func OkFunc(*sync.Mutex) {}
func BadFunc(sync.Mutex) {} // ERROR "BadFunc passes Lock by value: sync.Mutex"
func OkRet() *sync.Mutex {}
func BadRet() sync.Mutex {} // ERROR "BadRet returns Lock by value: sync.Mutex"
type EmbeddedRWMutex struct {
sync.RWMutex
}
func (*EmbeddedRWMutex) OkMeth() {}
func (EmbeddedRWMutex) BadMeth() {} // ERROR "BadMeth passes Lock by value: testdata.EmbeddedRWMutex"
func OkFunc(e *EmbeddedRWMutex) {}
func BadFunc(EmbeddedRWMutex) {} // ERROR "BadFunc passes Lock by value: testdata.EmbeddedRWMutex"
func OkRet() *EmbeddedRWMutex {}
func BadRet() EmbeddedRWMutex {} // ERROR "BadRet returns Lock by value: testdata.EmbeddedRWMutex"
type FieldMutex struct {
s sync.Mutex
}
func (*FieldMutex) OkMeth() {}
func (FieldMutex) BadMeth() {} // ERROR "BadMeth passes Lock by value: testdata.FieldMutex contains sync.Mutex"
func OkFunc(*FieldMutex) {}
func BadFunc(FieldMutex, int) {} // ERROR "BadFunc passes Lock by value: testdata.FieldMutex contains sync.Mutex"
type L0 struct {
L1
}
type L1 struct {
l L2
}
type L2 struct {
sync.Mutex
}
func (*L0) Ok() {}
func (L0) Bad() {} // ERROR "Bad passes Lock by value: testdata.L0 contains testdata.L1 contains testdata.L2"
type EmbeddedMutexPointer struct {
s *sync.Mutex // safe to copy this pointer
}
func (*EmbeddedMutexPointer) Ok() {}
func (EmbeddedMutexPointer) AlsoOk() {}
func StillOk(EmbeddedMutexPointer) {}
func LookinGood() EmbeddedMutexPointer {}
type EmbeddedLocker struct {
sync.Locker // safe to copy interface values
}
func (*EmbeddedLocker) Ok() {}
func (EmbeddedLocker) AlsoOk() {}
type CustomLock struct{}
func (*CustomLock) Lock() {}
func (*CustomLock) Unlock() {}
func Ok(*CustomLock) {}
func Bad(CustomLock) {} // ERROR "Bad passes Lock by value: testdata.CustomLock"
// TODO: Unfortunate cases
// Non-ideal error message:
// Since we're looking for Lock methods, sync.Once's underlying
// sync.Mutex gets called out, but without any reference to the sync.Once.
type LocalOnce sync.Once
func (LocalOnce) Bad() {} // ERROR "Bad passes Lock by value: testdata.LocalOnce contains sync.Mutex"
// False negative:
// LocalMutex doesn't have a Lock method.
// Nevertheless, it is probably a bad idea to pass it by value.
type LocalMutex sync.Mutex
func (LocalMutex) Bad() {} // WANTED: An error here :(
File diff suppressed because it is too large Load Diff
@@ -1,22 +0,0 @@
// 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.
// This file contains tests for the canonical method checker.
// This file contains the code to check canonical methods.
package testdata
import (
"fmt"
)
type MethodTest int
func (t *MethodTest) Scan(x fmt.ScanState, c byte) { // ERROR "should have signature Scan"
}
type MethodTestInterface interface {
ReadByte() byte // ERROR "should have signature ReadByte"
}
@@ -1,35 +0,0 @@
// 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 testdata
func F() {}
type T struct {
F func()
}
func (T) M() {}
var Fv = F
func Comparison() {
var t T
var fn func()
if fn == nil || Fv == nil || t.F == nil {
// no error; these func vars or fields may be nil
}
if F == nil { // ERROR "comparison of function F == nil is always false"
panic("can't happen")
}
if t.M == nil { // ERROR "comparison of function M == nil is always false"
panic("can't happen")
}
if F != nil { // ERROR "comparison of function F != nil is always true"
if t.M != nil { // ERROR "comparison of function M != nil is always true"
return
}
}
panic("can't happen")
}
@@ -1,332 +0,0 @@
// 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.
// This file contains tests for the printf checker.
package testdata
import (
"fmt"
"os"
"unsafe" // just for test case printing unsafe.Pointer
)
func UnsafePointerPrintfTest() {
var up unsafe.Pointer
fmt.Printf("%p, %x %X", up, up, up)
}
// Error methods that do not satisfy the Error interface and should be checked.
type errorTest1 int
func (errorTest1) Error(...interface{}) string {
return "hi"
}
type errorTest2 int // Analogous to testing's *T type.
func (errorTest2) Error(...interface{}) {
}
type errorTest3 int
func (errorTest3) Error() { // No return value.
}
type errorTest4 int
func (errorTest4) Error() int { // Different return type.
return 3
}
type errorTest5 int
func (errorTest5) error() { // niladic; don't complain if no args (was bug)
}
// This function never executes, but it serves as a simple test for the program.
// Test with make test.
func PrintfTests() {
var b bool
var i int
var r rune
var s string
var x float64
var p *int
var imap map[int]int
var fslice []float64
var c complex64
// Some good format/argtypes
fmt.Printf("")
fmt.Printf("%b %b %b", 3, i, x)
fmt.Printf("%c %c %c %c", 3, i, 'x', r)
fmt.Printf("%d %d %d", 3, i, imap)
fmt.Printf("%e %e %e %e", 3e9, x, fslice, c)
fmt.Printf("%E %E %E %E", 3e9, x, fslice, c)
fmt.Printf("%f %f %f %f", 3e9, x, fslice, c)
fmt.Printf("%F %F %F %F", 3e9, x, fslice, c)
fmt.Printf("%g %g %g %g", 3e9, x, fslice, c)
fmt.Printf("%G %G %G %G", 3e9, x, fslice, c)
fmt.Printf("%b %b %b %b", 3e9, x, fslice, c)
fmt.Printf("%o %o", 3, i)
fmt.Printf("%p %p", p, nil)
fmt.Printf("%q %q %q %q", 3, i, 'x', r)
fmt.Printf("%s %s %s", "hi", s, []byte{65})
fmt.Printf("%t %t", true, b)
fmt.Printf("%T %T", 3, i)
fmt.Printf("%U %U", 3, i)
fmt.Printf("%v %v", 3, i)
fmt.Printf("%x %x %x %x", 3, i, "hi", s)
fmt.Printf("%X %X %X %X", 3, i, "hi", s)
fmt.Printf("%.*s %d %g", 3, "hi", 23, 2.3)
fmt.Printf("%s", &stringerv)
fmt.Printf("%v", &stringerv)
fmt.Printf("%T", &stringerv)
fmt.Printf("%v", notstringerv)
fmt.Printf("%T", notstringerv)
fmt.Printf("%q", stringerarrayv)
fmt.Printf("%v", stringerarrayv)
fmt.Printf("%s", stringerarrayv)
fmt.Printf("%v", notstringerarrayv)
fmt.Printf("%T", notstringerarrayv)
fmt.Printf("%d", new(Formatter))
fmt.Printf("%*%", 2) // Ridiculous but allowed.
fmt.Printf("%s", interface{}(nil)) // Nothing useful we can say.
fmt.Printf("%g", 1+2i)
// Some bad format/argTypes
fmt.Printf("%b", "hi") // ERROR "arg .hi. for printf verb %b of wrong type"
fmt.Printf("%t", c) // ERROR "arg c for printf verb %t of wrong type"
fmt.Printf("%t", 1+2i) // ERROR "arg 1 \+ 2i for printf verb %t of wrong type"
fmt.Printf("%c", 2.3) // ERROR "arg 2.3 for printf verb %c of wrong type"
fmt.Printf("%d", 2.3) // ERROR "arg 2.3 for printf verb %d of wrong type"
fmt.Printf("%e", "hi") // ERROR "arg .hi. for printf verb %e of wrong type"
fmt.Printf("%E", true) // ERROR "arg true for printf verb %E of wrong type"
fmt.Printf("%f", "hi") // ERROR "arg .hi. for printf verb %f of wrong type"
fmt.Printf("%F", 'x') // ERROR "arg 'x' for printf verb %F of wrong type"
fmt.Printf("%g", "hi") // ERROR "arg .hi. for printf verb %g of wrong type"
fmt.Printf("%g", imap) // ERROR "arg imap for printf verb %g of wrong type"
fmt.Printf("%G", i) // ERROR "arg i for printf verb %G of wrong type"
fmt.Printf("%o", x) // ERROR "arg x for printf verb %o of wrong type"
fmt.Printf("%p", 23) // ERROR "arg 23 for printf verb %p of wrong type"
fmt.Printf("%q", x) // ERROR "arg x for printf verb %q of wrong type"
fmt.Printf("%s", b) // ERROR "arg b for printf verb %s of wrong type"
fmt.Printf("%s", byte(65)) // ERROR "arg byte\(65\) for printf verb %s of wrong type"
fmt.Printf("%t", 23) // ERROR "arg 23 for printf verb %t of wrong type"
fmt.Printf("%U", x) // ERROR "arg x for printf verb %U of wrong type"
fmt.Printf("%x", nil) // ERROR "arg nil for printf verb %x of wrong type"
fmt.Printf("%X", 2.3) // ERROR "arg 2.3 for printf verb %X of wrong type"
fmt.Printf("%s", stringerv) // ERROR "arg stringerv for printf verb %s of wrong type"
fmt.Printf("%t", stringerv) // ERROR "arg stringerv for printf verb %t of wrong type"
fmt.Printf("%q", notstringerv) // ERROR "arg notstringerv for printf verb %q of wrong type"
fmt.Printf("%t", notstringerv) // ERROR "arg notstringerv for printf verb %t of wrong type"
fmt.Printf("%t", stringerarrayv) // ERROR "arg stringerarrayv for printf verb %t of wrong type"
fmt.Printf("%t", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %t of wrong type"
fmt.Printf("%q", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %q of wrong type"
fmt.Printf("%d", Formatter(true)) // correct (the type is responsible for formatting)
fmt.Printf("%s", nonemptyinterface) // correct (the dynamic type of nonemptyinterface may be a stringer)
fmt.Printf("%.*s %d %g", 3, "hi", 23, 'x') // ERROR "arg 'x' for printf verb %g of wrong type"
fmt.Println() // not an error
fmt.Println("%s", "hi") // ERROR "possible formatting directive in Println call"
fmt.Printf("%s", "hi", 3) // ERROR "wrong number of args for format in Printf call"
fmt.Sprintf("%"+("s"), "hi", 3) // ERROR "wrong number of args for format in Sprintf call"
fmt.Printf("%s%%%d", "hi", 3) // correct
fmt.Printf("%08s", "woo") // correct
fmt.Printf("% 8s", "woo") // correct
fmt.Printf("%.*d", 3, 3) // correct
fmt.Printf("%.*d", 3, 3, 3, 3) // ERROR "wrong number of args for format in Printf call.*4 args"
fmt.Printf("%.*d", "hi", 3) // ERROR "arg .hi. for \* in printf format not of type int"
fmt.Printf("%.*d", i, 3) // correct
fmt.Printf("%.*d", s, 3) // ERROR "arg s for \* in printf format not of type int"
fmt.Printf("%*%", 0.22) // ERROR "arg 0.22 for \* in printf format not of type int"
fmt.Printf("%q %q", multi()...) // ok
fmt.Printf("%#q", `blah`) // ok
printf("now is the time", "buddy") // ERROR "no formatting directive"
Printf("now is the time", "buddy") // ERROR "no formatting directive"
Printf("hi") // ok
const format = "%s %s\n"
Printf(format, "hi", "there")
Printf(format, "hi") // ERROR "missing argument for Printf..%s..: format reads arg 2, have only 1"
Printf("%s %d %.3v %q", "str", 4) // ERROR "missing argument for Printf..%.3v..: format reads arg 3, have only 2"
f := new(stringer)
f.Warn(0, "%s", "hello", 3) // ERROR "possible formatting directive in Warn call"
f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args for format in Warnf call"
f.Warnf(0, "%r", "hello") // ERROR "unrecognized printf verb"
f.Warnf(0, "%#s", "hello") // ERROR "unrecognized printf flag"
Printf("d%", 2) // ERROR "missing verb at end of format string in Printf call"
Printf("%d", percentDV)
Printf("%d", &percentDV)
Printf("%d", notPercentDV) // ERROR "arg notPercentDV for printf verb %d of wrong type"
Printf("%d", &notPercentDV) // ERROR "arg &notPercentDV for printf verb %d of wrong type"
Printf("%p", &notPercentDV) // Works regardless: we print it as a pointer.
Printf("%s", percentSV)
Printf("%s", &percentSV)
// Good argument reorderings.
Printf("%[1]d", 3)
Printf("%[1]*d", 3, 1)
Printf("%[2]*[1]d", 1, 3)
Printf("%[2]*.[1]*[3]d", 2, 3, 4)
fmt.Fprintf(os.Stderr, "%[2]*.[1]*[3]d", 2, 3, 4) // Use Fprintf to make sure we count arguments correctly.
// Bad argument reorderings.
Printf("%[xd", 3) // ERROR "illegal syntax for printf argument index"
Printf("%[x]d", 3) // ERROR "illegal syntax for printf argument index"
Printf("%[3]*s", "hi", 2) // ERROR "missing argument for Printf.* reads arg 3, have only 2"
fmt.Sprintf("%[3]d", 2) // ERROR "missing argument for Sprintf.* reads arg 3, have only 1"
Printf("%[2]*.[1]*[3]d", 2, "hi", 4) // ERROR "arg .hi. for \* in printf format not of type int"
// Something that satisfies the error interface.
var e error
fmt.Println(e.Error()) // ok
// Something that looks like an error interface but isn't, such as the (*T).Error method
// in the testing package.
var et1 errorTest1
fmt.Println(et1.Error()) // ERROR "no args in Error call"
fmt.Println(et1.Error("hi")) // ok
fmt.Println(et1.Error("%d", 3)) // ERROR "possible formatting directive in Error call"
var et2 errorTest2
et2.Error() // ERROR "no args in Error call"
et2.Error("hi") // ok, not an error method.
et2.Error("%d", 3) // ERROR "possible formatting directive in Error call"
var et3 errorTest3
et3.Error() // ok, not an error method.
var et4 errorTest4
et4.Error() // ok, not an error method.
var et5 errorTest5
et5.error() // ok, not an error method.
// Bug: used to recur forever.
Printf("%p %x", recursiveStructV, recursiveStructV.next)
Printf("%p %x", recursiveStruct1V, recursiveStruct1V.next)
Printf("%p %x", recursiveSliceV, recursiveSliceV)
Printf("%p %x", recursiveMapV, recursiveMapV)
}
// Printf is used by the test so we must declare it.
func Printf(format string, args ...interface{}) {
panic("don't call - testing only")
}
// printf is used by the test so we must declare it.
func printf(format string, args ...interface{}) {
panic("don't call - testing only")
}
// multi is used by the test.
func multi() []interface{} {
panic("don't call - testing only")
}
type stringer float64
var stringerv stringer
func (*stringer) String() string {
return "string"
}
func (*stringer) Warn(int, ...interface{}) string {
return "warn"
}
func (*stringer) Warnf(int, string, ...interface{}) string {
return "warnf"
}
type notstringer struct {
f float64
}
var notstringerv notstringer
type stringerarray [4]float64
func (stringerarray) String() string {
return "string"
}
var stringerarrayv stringerarray
type notstringerarray [4]float64
var notstringerarrayv notstringerarray
var nonemptyinterface = interface {
f()
}(nil)
// A data type we can print with "%d".
type percentDStruct struct {
a int
b []byte
c *float64
}
var percentDV percentDStruct
// A data type we cannot print correctly with "%d".
type notPercentDStruct struct {
a int
b []byte
c bool
}
var notPercentDV notPercentDStruct
// A data type we can print with "%s".
type percentSStruct struct {
a string
b []byte
c stringerarray
}
var percentSV percentSStruct
type recursiveStringer int
func (s recursiveStringer) String() string {
fmt.Sprintf("%d", s)
fmt.Sprintf("%#v", s)
fmt.Sprintf("%v", s) // ERROR "arg s for printf causes recursive call to String method"
fmt.Sprintf("%v", &s) // ERROR "arg &s for printf causes recursive call to String method"
fmt.Sprintf("%T", s) // ok; does not recursively call String
return fmt.Sprintln(s) // ERROR "arg s for print causes recursive call to String method"
}
type recursivePtrStringer int
func (p *recursivePtrStringer) String() string {
fmt.Sprintf("%v", *p)
return fmt.Sprintln(p) // ERROR "arg p for print causes recursive call to String method"
}
type Formatter bool
func (*Formatter) Format(fmt.State, rune) {
}
type RecursiveSlice []RecursiveSlice
var recursiveSliceV = &RecursiveSlice{}
type RecursiveMap map[int]RecursiveMap
var recursiveMapV = make(RecursiveMap)
type RecursiveStruct struct {
next *RecursiveStruct
}
var recursiveStructV = &RecursiveStruct{}
type RecursiveStruct1 struct {
next *Recursive2Struct
}
type RecursiveStruct2 struct {
next *Recursive1Struct
}
var recursiveStruct1V = &RecursiveStruct1{}
// Fix for issue 7149: Missing return type on String method caused fault.
func (int) String() {
return ""
}
@@ -1,59 +0,0 @@
// 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.
// This file contains tests for the rangeloop checker.
package testdata
func RangeLoopTests() {
var s []int
for i, v := range s {
go func() {
println(i) // ERROR "range variable i enclosed by function"
println(v) // ERROR "range variable v enclosed by function"
}()
}
for i, v := range s {
defer func() {
println(i) // ERROR "range variable i enclosed by function"
println(v) // ERROR "range variable v enclosed by function"
}()
}
for i := range s {
go func() {
println(i) // ERROR "range variable i enclosed by function"
}()
}
for _, v := range s {
go func() {
println(v) // ERROR "range variable v enclosed by function"
}()
}
for i, v := range s {
go func() {
println(i, v)
}()
println("unfortunately, we don't catch the error above because of this statement")
}
for i, v := range s {
go func(i, v int) {
println(i, v)
}(i, v)
}
for i, v := range s {
i, v := i, v
go func() {
println(i, v)
}()
}
// If the key of the range statement is not an identifier
// the code should not panic (it used to).
var x [2]int
var f int
for x[0], f = range s {
go func() {
_ = f // ERROR "range variable f enclosed by function"
}()
}
}
@@ -1,54 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file contains tests for the shadowed variable checker.
// Some of these errors are caught by the compiler (shadowed return parameters for example)
// but are nonetheless useful tests.
package testdata
import "os"
func ShadowRead(f *os.File, buf []byte) (err error) {
var x int
if f != nil {
err := 3 // OK - different type.
_ = err
}
if f != nil {
_, err := f.Read(buf) // ERROR "declaration of err shadows declaration at testdata/shadow.go:13"
if err != nil {
return err
}
i := 3 // OK
_ = i
}
if f != nil {
var _, err = f.Read(buf) // ERROR "declaration of err shadows declaration at testdata/shadow.go:13"
if err != nil {
return err
}
}
for i := 0; i < 10; i++ {
i := i // OK: obviously intentional idiomatic redeclaration
go func() {
println(i)
}()
}
var shadowTemp interface{}
switch shadowTemp := shadowTemp.(type) { // OK: obviously intentional idiomatic redeclaration
case int:
println("OK")
_ = shadowTemp
}
if shadowTemp := shadowTemp; true { // OK: obviously intentional idiomatic redeclaration
var f *os.File // OK because f is not mentioned later in the function.
// The declaration of x is a shadow because x is mentioned below.
var x int // ERROR "declaration of x shadows declaration at testdata/shadow.go:14"
_, _, _ = x, f, shadowTemp
}
// Use a couple of variables to trigger shadowing errors.
_, _ = err, x
return
}
@@ -1,13 +0,0 @@
// 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.
// This file contains tests for the structtag checker.
// This file contains the test for canonical struct tags.
package testdata
type StructTagTest struct {
X int "hello" // ERROR "not compatible with reflect.StructTag.Get"
}
@@ -1,322 +0,0 @@
// 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.
// This file contains the pieces of the tool that use typechecking from the go/types package.
package main
import (
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
)
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error {
pkg.defs = make(map[*ast.Ident]types.Object)
pkg.uses = make(map[*ast.Ident]types.Object)
pkg.spans = make(map[types.Object]Span)
pkg.types = make(map[ast.Expr]types.TypeAndValue)
// By providing a Config with our own error function, it will continue
// past the first error. There is no need for that function to do anything.
config := types.Config{
Error: func(error) {},
}
info := &types.Info{
Types: pkg.types,
Defs: pkg.defs,
Uses: pkg.uses,
}
typesPkg, err := config.Check(pkg.path, fs, astFiles, info)
pkg.typesPkg = typesPkg
// update spans
for id, obj := range pkg.defs {
pkg.growSpan(id, obj)
}
for id, obj := range pkg.uses {
pkg.growSpan(id, obj)
}
return err
}
// isStruct reports whether the composite literal c is a struct.
// If it is not (probably a struct), it returns a printable form of the type.
func (pkg *Package) isStruct(c *ast.CompositeLit) (bool, string) {
// Check that the CompositeLit's type is a slice or array (which needs no field keys), if possible.
typ := pkg.types[c].Type
// If it's a named type, pull out the underlying type. If it's not, the Underlying
// method returns the type itself.
actual := typ
if actual != nil {
actual = actual.Underlying()
}
if actual == nil {
// No type information available. Assume true, so we do the check.
return true, ""
}
switch actual.(type) {
case *types.Struct:
return true, typ.String()
default:
return false, ""
}
}
var (
stringerMethodType = types.New("func() string")
errorType = types.New("interface{ Error() string }").(*types.Interface)
stringerType = types.New("interface{ String() string }").(*types.Interface)
// One day this might work. See issue 6259.
// formatterType = types.New("interface{Format(f fmt.State, c rune)}")
)
// matchArgType reports an error if printf verb t is not appropriate
// for operand arg.
//
// typ is used only for recursive calls; external callers must supply nil.
//
// (Recursion arises from the compound types {map,chan,slice} which
// may be printed with %d etc. if that is appropriate for their element
// types.)
func (f *File) matchArgType(t printfArgType, typ types.Type, arg ast.Expr) bool {
return f.matchArgTypeInternal(t, typ, arg, make(map[types.Type]bool))
}
// matchArgTypeInternal is the internal version of matchArgType. It carries a map
// remembering what types are in progress so we don't recur when faced with recursive
// types or mutually recursive types.
func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool {
// %v, %T accept any argument type.
if t == anyType {
return true
}
if typ == nil {
// external call
typ = f.pkg.types[arg].Type
if typ == nil {
return true // probably a type check problem
}
}
// If the type implements fmt.Formatter, we have nothing to check.
// But (see issue 6259) that's not easy to verify, so instead we see
// if its method set contains a Format function. We could do better,
// even now, but we don't need to be 100% accurate. Wait for 6259 to
// be fixed instead. TODO.
if f.hasMethod(typ, "Format") {
return true
}
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
if t&argString != 0 {
if types.AssertableTo(errorType, typ) || types.AssertableTo(stringerType, typ) {
return true
}
}
typ = typ.Underlying()
if inProgress[typ] {
// We're already looking at this type. The call that started it will take care of it.
return true
}
inProgress[typ] = true
switch typ := typ.(type) {
case *types.Signature:
return t&argPointer != 0
case *types.Map:
// Recur: map[int]int matches %d.
return t&argPointer != 0 ||
(f.matchArgTypeInternal(t, typ.Key(), arg, inProgress) && f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress))
case *types.Chan:
return t&argPointer != 0
case *types.Array:
// Same as slice.
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
return true // %s matches []byte
}
// Recur: []int matches %d.
return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem().Underlying(), arg, inProgress)
case *types.Slice:
// Same as array.
if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 {
return true // %s matches []byte
}
// Recur: []int matches %d. But watch out for
// type T []T
// If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below.
return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress)
case *types.Pointer:
// Ugly, but dealing with an edge case: a known pointer to an invalid type,
// probably something from a failed import.
if typ.Elem().String() == "invalid type" {
if *verbose {
f.Warnf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", f.gofmt(arg))
}
return true // special case
}
// If it's actually a pointer with %p, it prints as one.
if t == argPointer {
return true
}
// If it's pointer to struct, that's equivalent in our analysis to whether we can print the struct.
if str, ok := typ.Elem().Underlying().(*types.Struct); ok {
return f.matchStructArgType(t, str, arg, inProgress)
}
// The rest can print with %p as pointers, or as integers with %x etc.
return t&(argInt|argPointer) != 0
case *types.Struct:
return f.matchStructArgType(t, typ, arg, inProgress)
case *types.Interface:
// If the static type of the argument is empty interface, there's little we can do.
// Example:
// func f(x interface{}) { fmt.Printf("%s", x) }
// Whether x is valid for %s depends on the type of the argument to f. One day
// we will be able to do better. For now, we assume that empty interface is OK
// but non-empty interfaces, with Stringer and Error handled above, are errors.
return typ.NumMethods() == 0
case *types.Basic:
switch typ.Kind() {
case types.UntypedBool,
types.Bool:
return t&argBool != 0
case types.UntypedInt,
types.Int,
types.Int8,
types.Int16,
types.Int32,
types.Int64,
types.Uint,
types.Uint8,
types.Uint16,
types.Uint32,
types.Uint64,
types.Uintptr:
return t&argInt != 0
case types.UntypedFloat,
types.Float32,
types.Float64:
return t&argFloat != 0
case types.UntypedComplex,
types.Complex64,
types.Complex128:
return t&argComplex != 0
case types.UntypedString,
types.String:
return t&argString != 0
case types.UnsafePointer:
return t&(argPointer|argInt) != 0
case types.UntypedRune:
return t&(argInt|argRune) != 0
case types.UntypedNil:
return t&argPointer != 0 // TODO?
case types.Invalid:
if *verbose {
f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", f.gofmt(arg))
}
return true // Probably a type check problem.
}
panic("unreachable")
}
return false
}
// matchStructArgType reports whether all the elements of the struct match the expected
// type. For instance, with "%d" all the elements must be printable with the "%d" format.
func (f *File) matchStructArgType(t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool {
for i := 0; i < typ.NumFields(); i++ {
if !f.matchArgTypeInternal(t, typ.Field(i).Type(), arg, inProgress) {
return false
}
}
return true
}
// numArgsInSignature tells how many formal arguments the function type
// being called has.
func (f *File) numArgsInSignature(call *ast.CallExpr) int {
// Check the type of the function or method declaration
typ := f.pkg.types[call.Fun].Type
if typ == nil {
return 0
}
// The type must be a signature, but be sure for safety.
sig, ok := typ.(*types.Signature)
if !ok {
return 0
}
return sig.Params().Len()
}
// isErrorMethodCall reports whether the call is of a method with signature
// func Error() string
// where "string" is the universe's string type. We know the method is called "Error".
func (f *File) isErrorMethodCall(call *ast.CallExpr) bool {
typ := f.pkg.types[call].Type
if typ != nil {
// We know it's called "Error", so just check the function signature.
return types.Identical(f.pkg.types[call.Fun].Type, stringerMethodType)
}
// Without types, we can still check by hand.
// Is it a selector expression? Otherwise it's a function call, not a method call.
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return false
}
// The package is type-checked, so if there are no arguments, we're done.
if len(call.Args) > 0 {
return false
}
// Check the type of the method declaration
typ = f.pkg.types[sel].Type
if typ == nil {
return false
}
// The type must be a signature, but be sure for safety.
sig, ok := typ.(*types.Signature)
if !ok {
return false
}
// There must be a receiver for it to be a method call. Otherwise it is
// a function, not something that satisfies the error interface.
if sig.Recv() == nil {
return false
}
// There must be no arguments. Already verified by type checking, but be thorough.
if sig.Params().Len() > 0 {
return false
}
// Finally the real questions.
// There must be one result.
if sig.Results().Len() != 1 {
return false
}
// It must have return type "string" from the universe.
return sig.Results().At(0).Type() == types.Typ[types.String]
}
// hasMethod reports whether the type contains a method with the given name.
// It is part of the workaround for Formatters and should be deleted when
// that workaround is no longer necessary.
// TODO: This could be better once issue 6259 is fixed.
func (f *File) hasMethod(typ types.Type, name string) bool {
obj, _, _ := types.LookupFieldOrMethod(typ, f.pkg.typesPkg, name)
_, ok := obj.(*types.Func)
return ok
}
@@ -1,74 +0,0 @@
// 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"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
)
const (
dataDir = "testdata"
binary = "testvet"
)
// Run this shell script, but do it in Go so it can be run by "go test".
// go build -o testvet
// $(GOROOT)/test/errchk ./testvet -shadow -printfuncs='Warn:1,Warnf:1' testdata/*.go testdata/*.s
// rm testvet
//
func TestVet(t *testing.T) {
// Plan 9 and Windows systems can't be guaranteed to have Perl and so can't run errchk.
switch runtime.GOOS {
case "plan9", "windows":
t.Skip("skipping test; no Perl on %q", runtime.GOOS)
}
// go build
cmd := exec.Command("go", "build", "-o", binary)
run(cmd, t)
// defer removal of vet
defer os.Remove(binary)
// errchk ./testvet
gos, err := filepath.Glob(filepath.Join(dataDir, "*.go"))
if err != nil {
t.Fatal(err)
}
asms, err := filepath.Glob(filepath.Join(dataDir, "*.s"))
if err != nil {
t.Fatal(err)
}
files := append(gos, asms...)
errchk := filepath.Join(runtime.GOROOT(), "test", "errchk")
flags := []string{
"./" + binary,
"-printfuncs=Warn:1,Warnf:1",
"-test", // TODO: Delete once -shadow is part of -all.
}
cmd = exec.Command(errchk, append(flags, files...)...)
if !run(cmd, t) {
t.Fatal("vet command failed")
}
}
func run(c *exec.Cmd, t *testing.T) bool {
output, err := c.CombinedOutput()
os.Stderr.Write(output)
if err != nil {
t.Fatal(err)
}
// Errchk delights by not returning non-zero status if it finds errors, so we look at the output.
// It prints "BUG" if there is a failure.
if !c.ProcessState.Success() {
return false
}
return !bytes.Contains(output, []byte("BUG"))
}
@@ -1,52 +0,0 @@
// 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 whitelist defines exceptions for the vet tool.
package whitelist
// UnkeyedLiteral are types that are actually slices, but
// syntactically, we cannot tell whether the Typ in pkg.Typ{1, 2, 3}
// is a slice or a struct, so we whitelist all the standard package
// library's exported slice types.
var UnkeyedLiteral = map[string]bool{
/*
find $GOROOT/src/pkg -type f | grep -v _test.go | xargs grep '^type.*\[\]' | \
grep -v ' map\[' | sed 's,/[^/]*go.type,,' | sed 's,.*src/pkg/,,' | \
sed 's, ,.,' | sed 's, .*,,' | grep -v '\.[a-z]' | \
sort | awk '{ print "\"" $0 "\": true," }'
*/
"crypto/x509/pkix.RDNSequence": true,
"crypto/x509/pkix.RelativeDistinguishedNameSET": true,
"database/sql.RawBytes": true,
"debug/macho.LoadBytes": true,
"encoding/asn1.ObjectIdentifier": true,
"encoding/asn1.RawContent": true,
"encoding/json.RawMessage": true,
"encoding/xml.CharData": true,
"encoding/xml.Comment": true,
"encoding/xml.Directive": true,
"go/scanner.ErrorList": true,
"image/color.Palette": true,
"net.HardwareAddr": true,
"net.IP": true,
"net.IPMask": true,
"sort.Float64Slice": true,
"sort.IntSlice": true,
"sort.StringSlice": true,
"unicode.SpecialCase": true,
// These image and image/color struct types are frozen. We will never add fields to them.
"image/color.Alpha16": true,
"image/color.Alpha": true,
"image/color.Gray16": true,
"image/color.Gray": true,
"image/color.NRGBA64": true,
"image/color.NRGBA": true,
"image/color.RGBA64": true,
"image/color.RGBA": true,
"image/color.YCbCr": true,
"image.Point": true,
"image.Rectangle": true,
"image.Uniform": true,
}
@@ -1,2 +0,0 @@
defaultcc: golang-codereviews@googlegroups.com
contributors: http://go.googlecode.com/hg/CONTRIBUTORS
@@ -1,190 +0,0 @@
// 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 cover provides support for parsing coverage profiles
// generated by "go test -coverprofile=cover.out".
package cover
import (
"bufio"
"fmt"
"math"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
// Profile represents the profiling data for a specific file.
type Profile struct {
FileName string
Mode string
Blocks []ProfileBlock
}
// ProfileBlock represents a single block of profiling data.
type ProfileBlock struct {
StartLine, StartCol int
EndLine, EndCol int
NumStmt, Count int
}
type byFileName []*Profile
func (p byFileName) Len() int { return len(p) }
func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName }
func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// ParseProfiles parses profile data in the specified file and returns a
// Profile for each source file described therein.
func ParseProfiles(fileName string) ([]*Profile, error) {
pf, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer pf.Close()
files := make(map[string]*Profile)
buf := bufio.NewReader(pf)
// First line is "mode: foo", where foo is "set", "count", or "atomic".
// Rest of file is in the format
// encoding/base64/base64.go:34.44,37.40 3 1
// where the fields are: name.go:line.column,line.column numberOfStatements count
s := bufio.NewScanner(buf)
mode := ""
for s.Scan() {
line := s.Text()
if mode == "" {
const p = "mode: "
if !strings.HasPrefix(line, p) || line == p {
return nil, fmt.Errorf("bad mode line: %v", line)
}
mode = line[len(p):]
continue
}
m := lineRe.FindStringSubmatch(line)
if m == nil {
return nil, fmt.Errorf("line %q doesn't match expected format: %v", m, lineRe)
}
fn := m[1]
p := files[fn]
if p == nil {
p = &Profile{
FileName: fn,
Mode: mode,
}
files[fn] = p
}
p.Blocks = append(p.Blocks, ProfileBlock{
StartLine: toInt(m[2]),
StartCol: toInt(m[3]),
EndLine: toInt(m[4]),
EndCol: toInt(m[5]),
NumStmt: toInt(m[6]),
Count: toInt(m[7]),
})
}
if err := s.Err(); err != nil {
return nil, err
}
for _, p := range files {
sort.Sort(blocksByStart(p.Blocks))
}
// Generate a sorted slice.
profiles := make([]*Profile, 0, len(files))
for _, profile := range files {
profiles = append(profiles, profile)
}
sort.Sort(byFileName(profiles))
return profiles, nil
}
type blocksByStart []ProfileBlock
func (b blocksByStart) Len() int { return len(b) }
func (b blocksByStart) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b blocksByStart) Less(i, j int) bool {
bi, bj := b[i], b[j]
return bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol
}
var lineRe = regexp.MustCompile(`^(.+):([0-9]+).([0-9]+),([0-9]+).([0-9]+) ([0-9]+) ([0-9]+)$`)
func toInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}
// Boundary represents the position in a source file of the beginning or end of a
// block as reported by the coverage profile. In HTML mode, it will correspond to
// the opening or closing of a <span> tag and will be used to colorize the source
type Boundary struct {
Offset int // Location as a byte offset in the source file.
Start bool // Is this the start of a block?
Count int // Event count from the cover profile.
Norm float64 // Count normalized to [0..1].
}
// Boundaries returns a Profile as a set of Boundary objects within the provided src.
func (p *Profile) Boundaries(src []byte) (boundaries []Boundary) {
// Find maximum count.
max := 0
for _, b := range p.Blocks {
if b.Count > max {
max = b.Count
}
}
// Divisor for normalization.
divisor := math.Log(float64(max))
// boundary returns a Boundary, populating the Norm field with a normalized Count.
boundary := func(offset int, start bool, count int) Boundary {
b := Boundary{Offset: offset, Start: start, Count: count}
if !start || count == 0 {
return b
}
if max <= 1 {
b.Norm = 0.8 // Profile is in"set" mode; we want a heat map. Use cov8 in the CSS.
} else if count > 0 {
b.Norm = math.Log(float64(count)) / divisor
}
return b
}
line, col := 1, 2 // TODO: Why is this 2?
for si, bi := 0, 0; si < len(src) && bi < len(p.Blocks); {
b := p.Blocks[bi]
if b.StartLine == line && b.StartCol == col {
boundaries = append(boundaries, boundary(si, true, b.Count))
}
if b.EndLine == line && b.EndCol == col {
boundaries = append(boundaries, boundary(si, false, 0))
bi++
continue // Don't advance through src; maybe the next block starts here.
}
if src[si] == '\n' {
line++
col = 0
}
col++
si++
}
sort.Sort(boundariesByPos(boundaries))
return
}
type boundariesByPos []Boundary
func (b boundariesByPos) Len() int { return len(b) }
func (b boundariesByPos) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b boundariesByPos) Less(i, j int) bool {
if b[i].Offset == b[j].Offset {
return !b[i].Start && b[j].Start
}
return b[i].Offset < b[j].Offset
}
@@ -1,26 +0,0 @@
// 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.
The files in this directory constitute the continuous builder:
app/: an AppEngine server
builder/: gobuilder, a Go continuous build client
If you wish to run a Go builder, please email golang-dev@googlegroups.com
To run a builder:
* Write the key ~gobuild/.gobuildkey
You need to get it from someone who knows the key.
You may also use a filename of the form .gobuildkey-$BUILDER if you
wish to run builders for multiple targets.
* Append your username and password googlecode.com credentials from
https://code.google.com/hosting/settings
to the buildkey file in the format "Username\nPassword\n".
(This is for uploading tarballs to the project downloads section,
and is an optional step.)
* Build and run gobuilder (see its documentation for command-line options).
@@ -1,20 +0,0 @@
# Update with
# google_appengine/appcfg.py [-V test-build] update .
#
# Using -V test-build will run as test-build.golang.org.
application: golang-org
version: build
runtime: go
api_version: go1
handlers:
- url: /static
static_dir: static
- url: /(|gccgo/)log/.+
script: _go_app
- url: /(|gccgo/)(|commit|packages|result|tag|todo)
script: _go_app
- url: /(|gccgo/)(init|buildtest|key|_ah/queue/go/delay)
script: _go_app
login: admin
@@ -1,365 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine
package build
import (
"bytes"
"compress/gzip"
"crypto/sha1"
"errors"
"fmt"
"io"
"io/ioutil"
"strings"
"time"
"appengine"
"appengine/datastore"
)
const maxDatastoreStringLen = 500
// A Package describes a package that is listed on the dashboard.
type Package struct {
Kind string // "subrepo", "external", or empty for the main Go tree
Name string
Path string // (empty for the main Go tree)
NextNum int // Num of the next head Commit
}
func (p *Package) String() string {
return fmt.Sprintf("%s: %q", p.Path, p.Name)
}
func (p *Package) Key(c appengine.Context) *datastore.Key {
key := p.Path
if key == "" {
key = "go"
}
return datastore.NewKey(c, "Package", key, 0, nil)
}
// LastCommit returns the most recent Commit for this Package.
func (p *Package) LastCommit(c appengine.Context) (*Commit, error) {
var commits []*Commit
_, err := datastore.NewQuery("Commit").
Ancestor(p.Key(c)).
Order("-Time").
Limit(1).
GetAll(c, &commits)
if _, ok := err.(*datastore.ErrFieldMismatch); ok {
// Some fields have been removed, so it's okay to ignore this error.
err = nil
}
if err != nil {
return nil, err
}
if len(commits) != 1 {
return nil, datastore.ErrNoSuchEntity
}
return commits[0], nil
}
// GetPackage fetches a Package by path from the datastore.
func GetPackage(c appengine.Context, path string) (*Package, error) {
p := &Package{Path: path}
err := datastore.Get(c, p.Key(c), p)
if err == datastore.ErrNoSuchEntity {
return nil, fmt.Errorf("package %q not found", path)
}
if _, ok := err.(*datastore.ErrFieldMismatch); ok {
// Some fields have been removed, so it's okay to ignore this error.
err = nil
}
return p, err
}
// A Commit describes an individual commit in a package.
//
// Each Commit entity is a descendant of its associated Package entity.
// In other words, all Commits with the same PackagePath belong to the same
// datastore entity group.
type Commit struct {
PackagePath string // (empty for main repo commits)
Hash string
ParentHash string
Num int // Internal monotonic counter unique to this package.
User string
Desc string `datastore:",noindex"`
Time time.Time
// ResultData is the Data string of each build Result for this Commit.
// For non-Go commits, only the Results for the current Go tip, weekly,
// and release Tags are stored here. This is purely de-normalized data.
// The complete data set is stored in Result entities.
ResultData []string `datastore:",noindex"`
FailNotificationSent bool
}
func (com *Commit) Key(c appengine.Context) *datastore.Key {
if com.Hash == "" {
panic("tried Key on Commit with empty Hash")
}
p := Package{Path: com.PackagePath}
key := com.PackagePath + "|" + com.Hash
return datastore.NewKey(c, "Commit", key, 0, p.Key(c))
}
func (c *Commit) Valid() error {
if !validHash(c.Hash) {
return errors.New("invalid Hash")
}
if c.ParentHash != "" && !validHash(c.ParentHash) { // empty is OK
return errors.New("invalid ParentHash")
}
return nil
}
// each result line is approx 105 bytes. This constant is a tradeoff between
// build history and the AppEngine datastore limit of 1mb.
const maxResults = 1000
// AddResult adds the denormalized Result data to the Commit's Result field.
// It must be called from inside a datastore transaction.
func (com *Commit) AddResult(c appengine.Context, r *Result) error {
if err := datastore.Get(c, com.Key(c), com); err != nil {
return fmt.Errorf("getting Commit: %v", err)
}
com.ResultData = trim(append(com.ResultData, r.Data()), maxResults)
if _, err := datastore.Put(c, com.Key(c), com); err != nil {
return fmt.Errorf("putting Commit: %v", err)
}
return nil
}
func trim(s []string, n int) []string {
l := min(len(s), n)
return s[len(s)-l:]
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Result returns the build Result for this Commit for the given builder/goHash.
func (c *Commit) Result(builder, goHash string) *Result {
for _, r := range c.ResultData {
p := strings.SplitN(r, "|", 4)
if len(p) != 4 || p[0] != builder || p[3] != goHash {
continue
}
return partsToHash(c, p)
}
return nil
}
// Results returns the build Results for this Commit.
func (c *Commit) Results() (results []*Result) {
for _, r := range c.ResultData {
p := strings.SplitN(r, "|", 4)
if len(p) != 4 {
continue
}
results = append(results, partsToHash(c, p))
}
return
}
func (c *Commit) ResultGoHashes() []string {
var hashes []string
for _, r := range c.ResultData {
p := strings.SplitN(r, "|", 4)
if len(p) != 4 {
continue
}
// Append only new results (use linear scan to preserve order).
if !contains(hashes, p[3]) {
hashes = append(hashes, p[3])
}
}
// Return results in reverse order (newest first).
reverse(hashes)
return hashes
}
func contains(t []string, s string) bool {
for _, s2 := range t {
if s2 == s {
return true
}
}
return false
}
func reverse(s []string) {
for i := 0; i < len(s)/2; i++ {
j := len(s) - i - 1
s[i], s[j] = s[j], s[i]
}
}
// partsToHash converts a Commit and ResultData substrings to a Result.
func partsToHash(c *Commit, p []string) *Result {
return &Result{
Builder: p[0],
Hash: c.Hash,
PackagePath: c.PackagePath,
GoHash: p[3],
OK: p[1] == "true",
LogHash: p[2],
}
}
// A Result describes a build result for a Commit on an OS/architecture.
//
// Each Result entity is a descendant of its associated Package entity.
type Result struct {
Builder string // "os-arch[-note]"
Hash string
PackagePath string // (empty for Go commits)
// The Go Commit this was built against (empty for Go commits).
GoHash string
OK bool
Log string `datastore:"-"` // for JSON unmarshaling only
LogHash string `datastore:",noindex"` // Key to the Log record.
RunTime int64 // time to build+test in nanoseconds
}
func (r *Result) Key(c appengine.Context) *datastore.Key {
p := Package{Path: r.PackagePath}
key := r.Builder + "|" + r.PackagePath + "|" + r.Hash + "|" + r.GoHash
return datastore.NewKey(c, "Result", key, 0, p.Key(c))
}
func (r *Result) Valid() error {
if !validHash(r.Hash) {
return errors.New("invalid Hash")
}
if r.PackagePath != "" && !validHash(r.GoHash) {
return errors.New("invalid GoHash")
}
return nil
}
// Data returns the Result in string format
// to be stored in Commit's ResultData field.
func (r *Result) Data() string {
return fmt.Sprintf("%v|%v|%v|%v", r.Builder, r.OK, r.LogHash, r.GoHash)
}
// A Log is a gzip-compressed log file stored under the SHA1 hash of the
// uncompressed log text.
type Log struct {
CompressedLog []byte
}
func (l *Log) Text() ([]byte, error) {
d, err := gzip.NewReader(bytes.NewBuffer(l.CompressedLog))
if err != nil {
return nil, fmt.Errorf("reading log data: %v", err)
}
b, err := ioutil.ReadAll(d)
if err != nil {
return nil, fmt.Errorf("reading log data: %v", err)
}
return b, nil
}
func PutLog(c appengine.Context, text string) (hash string, err error) {
h := sha1.New()
io.WriteString(h, text)
b := new(bytes.Buffer)
z, _ := gzip.NewWriterLevel(b, gzip.BestCompression)
io.WriteString(z, text)
z.Close()
hash = fmt.Sprintf("%x", h.Sum(nil))
key := datastore.NewKey(c, "Log", hash, 0, nil)
_, err = datastore.Put(c, key, &Log{b.Bytes()})
return
}
// A Tag is used to keep track of the most recent Go weekly and release tags.
// Typically there will be one Tag entity for each kind of hg tag.
type Tag struct {
Kind string // "weekly", "release", or "tip"
Name string // the tag itself (for example: "release.r60")
Hash string
}
func (t *Tag) Key(c appengine.Context) *datastore.Key {
p := &Package{}
return datastore.NewKey(c, "Tag", t.Kind, 0, p.Key(c))
}
func (t *Tag) Valid() error {
if t.Kind != "weekly" && t.Kind != "release" && t.Kind != "tip" {
return errors.New("invalid Kind")
}
if !validHash(t.Hash) {
return errors.New("invalid Hash")
}
return nil
}
// Commit returns the Commit that corresponds with this Tag.
func (t *Tag) Commit(c appengine.Context) (*Commit, error) {
com := &Commit{Hash: t.Hash}
err := datastore.Get(c, com.Key(c), com)
return com, err
}
// GetTag fetches a Tag by name from the datastore.
func GetTag(c appengine.Context, tag string) (*Tag, error) {
t := &Tag{Kind: tag}
if err := datastore.Get(c, t.Key(c), t); err != nil {
if err == datastore.ErrNoSuchEntity {
return nil, errors.New("tag not found: " + tag)
}
return nil, err
}
if err := t.Valid(); err != nil {
return nil, err
}
return t, nil
}
// Packages returns packages of the specified kind.
// Kind must be one of "external" or "subrepo".
func Packages(c appengine.Context, kind string) ([]*Package, error) {
switch kind {
case "external", "subrepo":
default:
return nil, errors.New(`kind must be one of "external" or "subrepo"`)
}
var pkgs []*Package
q := datastore.NewQuery("Package").Filter("Kind=", kind)
for t := q.Run(c); ; {
pkg := new(Package)
_, err := t.Next(pkg)
if _, ok := err.(*datastore.ErrFieldMismatch); ok {
// Some fields have been removed, so it's okay to ignore this error.
err = nil
}
if err == datastore.Done {
break
} else if err != nil {
return nil, err
}
if pkg.Path != "" {
pkgs = append(pkgs, pkg)
}
}
return pkgs, nil
}
@@ -1,113 +0,0 @@
// 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.
// +build appengine
package build
import (
"net/http"
"strings"
"appengine"
)
// Dashboard describes a unique build dashboard.
type Dashboard struct {
Name string // This dashboard's name and namespace
RelPath string // The relative url path
Packages []*Package // The project's packages to build
}
// dashboardForRequest returns the appropriate dashboard for a given URL path.
func dashboardForRequest(r *http.Request) *Dashboard {
if strings.HasPrefix(r.URL.Path, gccgoDash.RelPath) {
return gccgoDash
}
return goDash
}
// Context returns a namespaced context for this dashboard, or panics if it
// fails to create a new context.
func (d *Dashboard) Context(c appengine.Context) appengine.Context {
// No namespace needed for the original Go dashboard.
if d.Name == "Go" {
return c
}
n, err := appengine.Namespace(c, d.Name)
if err != nil {
panic(err)
}
return n
}
// the currently known dashboards.
var dashboards = []*Dashboard{goDash, gccgoDash}
// goDash is the dashboard for the main go repository.
var goDash = &Dashboard{
Name: "Go",
RelPath: "/",
Packages: goPackages,
}
// goPackages is a list of all of the packages built by the main go repository.
var goPackages = []*Package{
{
Kind: "go",
Name: "Go",
},
{
Kind: "subrepo",
Name: "go.blog",
Path: "code.google.com/p/go.blog",
},
{
Kind: "subrepo",
Name: "go.codereview",
Path: "code.google.com/p/go.codereview",
},
{
Kind: "subrepo",
Name: "go.crypto",
Path: "code.google.com/p/go.crypto",
},
{
Kind: "subrepo",
Name: "go.exp",
Path: "code.google.com/p/go.exp",
},
{
Kind: "subrepo",
Name: "go.image",
Path: "code.google.com/p/go.image",
},
{
Kind: "subrepo",
Name: "go.net",
Path: "code.google.com/p/go.net",
},
{
Kind: "subrepo",
Name: "go.talks",
Path: "code.google.com/p/go.talks",
},
{
Kind: "subrepo",
Name: "go.tools",
Path: "code.google.com/p/go.tools",
},
}
// gccgoDash is the dashboard for gccgo.
var gccgoDash = &Dashboard{
Name: "Gccgo",
RelPath: "/gccgo/",
Packages: []*Package{
{
Kind: "gccgo",
Name: "Gccgo",
},
},
}
@@ -1,477 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine
package build
import (
"crypto/hmac"
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"unicode/utf8"
"appengine"
"appengine/datastore"
"cache"
)
const commitsPerPage = 30
// commitHandler retrieves commit data or records a new commit.
//
// For GET requests it returns a Commit value for the specified
// packagePath and hash.
//
// For POST requests it reads a JSON-encoded Commit value from the request
// body and creates a new Commit entity. It also updates the "tip" Tag for
// each new commit at tip.
//
// This handler is used by a gobuilder process in -commit mode.
func commitHandler(r *http.Request) (interface{}, error) {
c := contextForRequest(r)
com := new(Commit)
if r.Method == "GET" {
com.PackagePath = r.FormValue("packagePath")
com.Hash = r.FormValue("hash")
if err := datastore.Get(c, com.Key(c), com); err != nil {
return nil, fmt.Errorf("getting Commit: %v", err)
}
return com, nil
}
if r.Method != "POST" {
return nil, errBadMethod(r.Method)
}
if !isMasterKey(c, r.FormValue("key")) {
return nil, errors.New("can only POST commits with master key")
}
// POST request
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(com); err != nil {
return nil, fmt.Errorf("decoding Body: %v", err)
}
com.Desc = limitStringLength(com.Desc, maxDatastoreStringLen)
if err := com.Valid(); err != nil {
return nil, fmt.Errorf("validating Commit: %v", err)
}
defer cache.Tick(c)
tx := func(c appengine.Context) error {
return addCommit(c, com)
}
return nil, datastore.RunInTransaction(c, tx, nil)
}
// addCommit adds the Commit entity to the datastore and updates the tip Tag.
// It must be run inside a datastore transaction.
func addCommit(c appengine.Context, com *Commit) error {
var tc Commit // temp value so we don't clobber com
err := datastore.Get(c, com.Key(c), &tc)
if err != datastore.ErrNoSuchEntity {
// if this commit is already in the datastore, do nothing
if err == nil {
return nil
}
return fmt.Errorf("getting Commit: %v", err)
}
// get the next commit number
p, err := GetPackage(c, com.PackagePath)
if err != nil {
return fmt.Errorf("GetPackage: %v", err)
}
com.Num = p.NextNum
p.NextNum++
if _, err := datastore.Put(c, p.Key(c), p); err != nil {
return fmt.Errorf("putting Package: %v", err)
}
// if this isn't the first Commit test the parent commit exists
if com.Num > 0 {
n, err := datastore.NewQuery("Commit").
Filter("Hash =", com.ParentHash).
Ancestor(p.Key(c)).
Count(c)
if err != nil {
return fmt.Errorf("testing for parent Commit: %v", err)
}
if n == 0 {
return errors.New("parent commit not found")
}
}
// update the tip Tag if this is the Go repo and this isn't on a release branch
if p.Path == "" && !strings.HasPrefix(com.Desc, "[release-branch") {
t := &Tag{Kind: "tip", Hash: com.Hash}
if _, err = datastore.Put(c, t.Key(c), t); err != nil {
return fmt.Errorf("putting Tag: %v", err)
}
}
// put the Commit
if _, err = datastore.Put(c, com.Key(c), com); err != nil {
return fmt.Errorf("putting Commit: %v", err)
}
return nil
}
// tagHandler records a new tag. It reads a JSON-encoded Tag value from the
// request body and updates the Tag entity for the Kind of tag provided.
//
// This handler is used by a gobuilder process in -commit mode.
func tagHandler(r *http.Request) (interface{}, error) {
if r.Method != "POST" {
return nil, errBadMethod(r.Method)
}
t := new(Tag)
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(t); err != nil {
return nil, err
}
if err := t.Valid(); err != nil {
return nil, err
}
c := contextForRequest(r)
defer cache.Tick(c)
_, err := datastore.Put(c, t.Key(c), t)
return nil, err
}
// Todo is a todoHandler response.
type Todo struct {
Kind string // "build-go-commit" or "build-package"
Data interface{}
}
// todoHandler returns the next action to be performed by a builder.
// It expects "builder" and "kind" query parameters and returns a *Todo value.
// Multiple "kind" parameters may be specified.
func todoHandler(r *http.Request) (interface{}, error) {
c := contextForRequest(r)
now := cache.Now(c)
key := "build-todo-" + r.Form.Encode()
var todo *Todo
if cache.Get(r, now, key, &todo) {
return todo, nil
}
var err error
builder := r.FormValue("builder")
for _, kind := range r.Form["kind"] {
var com *Commit
switch kind {
case "build-go-commit":
com, err = buildTodo(c, builder, "", "")
case "build-package":
packagePath := r.FormValue("packagePath")
goHash := r.FormValue("goHash")
com, err = buildTodo(c, builder, packagePath, goHash)
}
if com != nil || err != nil {
if com != nil {
// ResultData can be large and not needed on builder.
com.ResultData = []string{}
}
todo = &Todo{Kind: kind, Data: com}
break
}
}
if err == nil {
cache.Set(r, now, key, todo)
}
return todo, err
}
// buildTodo returns the next Commit to be built (or nil if none available).
//
// If packagePath and goHash are empty, it scans the first 20 Go Commits in
// Num-descending order and returns the first one it finds that doesn't have a
// Result for this builder.
//
// If provided with non-empty packagePath and goHash args, it scans the first
// 20 Commits in Num-descending order for the specified packagePath and
// returns the first that doesn't have a Result for this builder and goHash.
func buildTodo(c appengine.Context, builder, packagePath, goHash string) (*Commit, error) {
p, err := GetPackage(c, packagePath)
if err != nil {
return nil, err
}
t := datastore.NewQuery("Commit").
Ancestor(p.Key(c)).
Limit(commitsPerPage).
Order("-Num").
Run(c)
for {
com := new(Commit)
if _, err := t.Next(com); err == datastore.Done {
break
} else if err != nil {
return nil, err
}
if com.Result(builder, goHash) == nil {
return com, nil
}
}
// Nothing left to do if this is a package (not the Go tree).
if packagePath != "" {
return nil, nil
}
// If there are no Go tree commits left to build,
// see if there are any subrepo commits that need to be built at tip.
// If so, ask the builder to build a go tree at the tip commit.
// TODO(adg): do the same for "weekly" and "release" tags.
tag, err := GetTag(c, "tip")
if err != nil {
return nil, err
}
// Check that this Go commit builds OK for this builder.
// If not, don't re-build as the subrepos will never get built anyway.
com, err := tag.Commit(c)
if err != nil {
return nil, err
}
if r := com.Result(builder, ""); r != nil && !r.OK {
return nil, nil
}
pkgs, err := Packages(c, "subrepo")
if err != nil {
return nil, err
}
for _, pkg := range pkgs {
com, err := pkg.LastCommit(c)
if err != nil {
c.Warningf("%v: no Commit found: %v", pkg, err)
continue
}
if com.Result(builder, tag.Hash) == nil {
return tag.Commit(c)
}
}
return nil, nil
}
// packagesHandler returns a list of the non-Go Packages monitored
// by the dashboard.
func packagesHandler(r *http.Request) (interface{}, error) {
kind := r.FormValue("kind")
c := contextForRequest(r)
now := cache.Now(c)
key := "build-packages-" + kind
var p []*Package
if cache.Get(r, now, key, &p) {
return p, nil
}
p, err := Packages(c, kind)
if err != nil {
return nil, err
}
cache.Set(r, now, key, p)
return p, nil
}
// resultHandler records a build result.
// It reads a JSON-encoded Result value from the request body,
// creates a new Result entity, and updates the relevant Commit entity.
// If the Log field is not empty, resultHandler creates a new Log entity
// and updates the LogHash field before putting the Commit entity.
func resultHandler(r *http.Request) (interface{}, error) {
if r.Method != "POST" {
return nil, errBadMethod(r.Method)
}
c := contextForRequest(r)
res := new(Result)
defer r.Body.Close()
if err := json.NewDecoder(r.Body).Decode(res); err != nil {
return nil, fmt.Errorf("decoding Body: %v", err)
}
if err := res.Valid(); err != nil {
return nil, fmt.Errorf("validating Result: %v", err)
}
defer cache.Tick(c)
// store the Log text if supplied
if len(res.Log) > 0 {
hash, err := PutLog(c, res.Log)
if err != nil {
return nil, fmt.Errorf("putting Log: %v", err)
}
res.LogHash = hash
}
tx := func(c appengine.Context) error {
// check Package exists
if _, err := GetPackage(c, res.PackagePath); err != nil {
return fmt.Errorf("GetPackage: %v", err)
}
// put Result
if _, err := datastore.Put(c, res.Key(c), res); err != nil {
return fmt.Errorf("putting Result: %v", err)
}
// add Result to Commit
com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
if err := com.AddResult(c, res); err != nil {
return fmt.Errorf("AddResult: %v", err)
}
// Send build failure notifications, if necessary.
// Note this must run after the call AddResult, which
// populates the Commit's ResultData field.
return notifyOnFailure(c, com, res.Builder)
}
return nil, datastore.RunInTransaction(c, tx, nil)
}
// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "text/plain; charset=utf-8")
c := contextForRequest(r)
hash := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
key := datastore.NewKey(c, "Log", hash, 0, nil)
l := new(Log)
if err := datastore.Get(c, key, l); err != nil {
logErr(w, r, err)
return
}
b, err := l.Text()
if err != nil {
logErr(w, r, err)
return
}
w.Write(b)
}
type dashHandler func(*http.Request) (interface{}, error)
type dashResponse struct {
Response interface{}
Error string
}
// errBadMethod is returned by a dashHandler when
// the request has an unsuitable method.
type errBadMethod string
func (e errBadMethod) Error() string {
return "bad method: " + string(e)
}
// AuthHandler wraps a http.HandlerFunc with a handler that validates the
// supplied key and builder query parameters.
func AuthHandler(h dashHandler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
c := contextForRequest(r)
// Put the URL Query values into r.Form to avoid parsing the
// request body when calling r.FormValue.
r.Form = r.URL.Query()
var err error
var resp interface{}
// Validate key query parameter for POST requests only.
key := r.FormValue("key")
builder := r.FormValue("builder")
if r.Method == "POST" && !validKey(c, key, builder) {
err = fmt.Errorf("invalid key %q for builder %q", key, builder)
}
// Call the original HandlerFunc and return the response.
if err == nil {
resp, err = h(r)
}
// Write JSON response.
dashResp := &dashResponse{Response: resp}
if err != nil {
c.Errorf("%v", err)
dashResp.Error = err.Error()
}
w.Header().Set("Content-Type", "application/json")
if err = json.NewEncoder(w).Encode(dashResp); err != nil {
c.Criticalf("encoding response: %v", err)
}
}
}
func keyHandler(w http.ResponseWriter, r *http.Request) {
builder := r.FormValue("builder")
if builder == "" {
logErr(w, r, errors.New("must supply builder in query string"))
return
}
c := contextForRequest(r)
fmt.Fprint(w, builderKey(c, builder))
}
func init() {
for _, d := range dashboards {
// admin handlers
http.HandleFunc(d.RelPath+"init", initHandler)
http.HandleFunc(d.RelPath+"key", keyHandler)
// authenticated handlers
http.HandleFunc(d.RelPath+"commit", AuthHandler(commitHandler))
http.HandleFunc(d.RelPath+"packages", AuthHandler(packagesHandler))
http.HandleFunc(d.RelPath+"result", AuthHandler(resultHandler))
http.HandleFunc(d.RelPath+"tag", AuthHandler(tagHandler))
http.HandleFunc(d.RelPath+"todo", AuthHandler(todoHandler))
// public handlers
http.HandleFunc(d.RelPath+"log/", logHandler)
}
}
func validHash(hash string) bool {
// TODO(adg): correctly validate a hash
return hash != ""
}
func validKey(c appengine.Context, key, builder string) bool {
return isMasterKey(c, key) || key == builderKey(c, builder)
}
func isMasterKey(c appengine.Context, key string) bool {
return appengine.IsDevAppServer() || key == secretKey(c)
}
func builderKey(c appengine.Context, builder string) string {
h := hmac.New(md5.New, []byte(secretKey(c)))
h.Write([]byte(builder))
return fmt.Sprintf("%x", h.Sum(nil))
}
func logErr(w http.ResponseWriter, r *http.Request, err error) {
contextForRequest(r).Errorf("Error: %v", err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "Error: ", err)
}
func contextForRequest(r *http.Request) appengine.Context {
return dashboardForRequest(r).Context(appengine.NewContext(r))
}
// limitStringLength essentially does return s[:max],
// but it ensures that we dot not split UTF-8 rune in half.
// Otherwise appengine python scripts will break badly.
func limitStringLength(s string, max int) string {
if len(s) <= max {
return s
}
for {
s = s[:max]
r, size := utf8.DecodeLastRuneInString(s)
if r != utf8.RuneError || size != 1 {
return s
}
max--
}
}

Some files were not shown because too many files have changed in this diff Show More