mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-15 07:00:52 +00:00
Try and use packaged go.tools
This commit is contained in:
Vendored
+7
@@ -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:
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
Vendored
+1
-17
@@ -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.
|
||||
-625
@@ -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))
|
||||
}
|
||||
-195
@@ -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)
|
||||
}
|
||||
-683
@@ -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) }
|
||||
-160
@@ -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)
|
||||
}
|
||||
-148
@@ -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) }
|
||||
-131
@@ -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
|
||||
-127
@@ -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
|
||||
}
|
||||
-154
@@ -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")
|
||||
}
|
||||
-87
@@ -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(">")
|
||||
case '<':
|
||||
dst.WriteString("<")
|
||||
case '&':
|
||||
dst.WriteString("&")
|
||||
case '\t':
|
||||
dst.WriteString(" ")
|
||||
default:
|
||||
dst.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return dst.Flush()
|
||||
}
|
||||
|
||||
// startBrowser tries to open the URL in a browser
|
||||
// and reports whether it succeeds.
|
||||
func startBrowser(url string) bool {
|
||||
// try to start the browser
|
||||
var args []string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
args = []string{"open"}
|
||||
case "windows":
|
||||
args = []string{"cmd", "/c", "start"}
|
||||
default:
|
||||
args = []string{"xdg-open"}
|
||||
}
|
||||
cmd := exec.Command(args[0], append(args[1:], url)...)
|
||||
return cmd.Start() == nil
|
||||
}
|
||||
|
||||
// rgb returns an rgb value for the specified coverage value
|
||||
// between 0 (no coverage) and 10 (max coverage).
|
||||
func rgb(n int) string {
|
||||
if n == 0 {
|
||||
return "rgb(192, 0, 0)" // Red
|
||||
}
|
||||
// Gradient from gray to green.
|
||||
r := 128 - 12*(n-1)
|
||||
g := 128 + 12*(n-1)
|
||||
b := 128 + 3*(n-1)
|
||||
return fmt.Sprintf("rgb(%v, %v, %v)", r, g, b)
|
||||
}
|
||||
|
||||
// colors generates the CSS rules for coverage colors.
|
||||
func colors() template.CSS {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < 11; i++ {
|
||||
fmt.Fprintf(&buf, ".cov%v { color: %v }\n", i, rgb(i))
|
||||
}
|
||||
return template.CSS(buf.String())
|
||||
}
|
||||
|
||||
var htmlTemplate = template.Must(template.New("html").Funcs(template.FuncMap{
|
||||
"colors": colors,
|
||||
}).Parse(tmplHTML))
|
||||
|
||||
type templateData struct {
|
||||
Files []*templateFile
|
||||
Set bool
|
||||
}
|
||||
|
||||
type templateFile struct {
|
||||
Name string
|
||||
Body template.HTML
|
||||
}
|
||||
|
||||
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>
|
||||
`
|
||||
Vendored
-93
@@ -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
|
||||
}
|
||||
Vendored
-177
@@ -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")
|
||||
}
|
||||
-242
@@ -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)
|
||||
}
|
||||
-56
@@ -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 .
|
||||
-63
@@ -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)
|
||||
-523
@@ -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
|
||||
-278
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
-83
@@ -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.")
|
||||
}
|
||||
-72
@@ -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
|
||||
}
|
||||
-134
@@ -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"
|
||||
-33
@@ -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
|
||||
-195
@@ -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
|
||||
-262
@@ -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))
|
||||
}
|
||||
}
|
||||
-348
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
-50
@@ -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)
|
||||
}
|
||||
}
|
||||
-223
@@ -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)
|
||||
-107
@@ -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>)
|
||||
-210
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
-121
@@ -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 ""
|
||||
}
|
||||
-101
@@ -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
|
||||
}
|
||||
-280
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -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()))
|
||||
}
|
||||
}
|
||||
-37
@@ -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
|
||||
}
|
||||
}
|
||||
-31
@@ -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)
|
||||
-247
@@ -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
|
||||
-251
@@ -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
|
||||
-166
@@ -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
|
||||
-26
@@ -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
|
||||
Vendored
-18
@@ -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"
|
||||
}
|
||||
Vendored
-41
@@ -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)
|
||||
}
|
||||
Vendored
-14
@@ -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
|
||||
Vendored
-15
@@ -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.
|
||||
Vendored
-63
@@ -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}
|
||||
Vendored
-89
@@ -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 :(
|
||||
Vendored
-2120
File diff suppressed because it is too large
Load Diff
Vendored
-22
@@ -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"
|
||||
}
|
||||
Vendored
-35
@@ -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")
|
||||
}
|
||||
Vendored
-332
@@ -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", ¬PercentDV) // ERROR "arg ¬PercentDV for printf verb %d of wrong type"
|
||||
Printf("%p", ¬PercentDV) // 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 ""
|
||||
}
|
||||
Vendored
-59
@@ -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"
|
||||
}()
|
||||
}
|
||||
}
|
||||
Vendored
-54
@@ -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
|
||||
}
|
||||
Vendored
-13
@@ -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"))
|
||||
}
|
||||
-52
@@ -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).
|
||||
|
||||
-20
@@ -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
|
||||
-365
@@ -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
|
||||
}
|
||||
-113
@@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
-477
@@ -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
Reference in New Issue
Block a user