🐳 docker build
This commit is contained in:
parent
1fcc40af30
commit
1d1b548a68
|
@ -7,7 +7,7 @@ RUN apt-get update && apt-get install bzip2 zip unzip && cp -r /usr/local/go /us
|
|||
ENV GOROOT_BOOTSTRAP=/usr/local/gobt
|
||||
|
||||
ADD . /wide/gogogo/src/github.com/b3log/wide
|
||||
ADD vendor/* /go/src/
|
||||
ADD vendor/ /go/src/
|
||||
RUN go install github.com/visualfc/gotools github.com/nsf/gocode github.com/bradfitz/goimports
|
||||
|
||||
RUN useradd wide && useradd runner
|
||||
|
|
|
@ -0,0 +1,685 @@
|
|||
// 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.
|
||||
|
||||
// Package doc extracts source code documentation from a Go AST.
|
||||
package astview
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type typeDoc struct {
|
||||
// len(decl.Specs) == 1, and the element type is *ast.TypeSpec
|
||||
// if the type declaration hasn't been seen yet, decl is nil
|
||||
decl *ast.GenDecl
|
||||
// values, factory functions, and methods associated with the type
|
||||
values []*ast.GenDecl // consts and vars
|
||||
factories map[string]*ast.FuncDecl
|
||||
methods map[string]*ast.FuncDecl
|
||||
}
|
||||
|
||||
// docReader accumulates documentation for a single package.
|
||||
// It modifies the AST: Comments (declaration documentation)
|
||||
// that have been collected by the DocReader are set to nil
|
||||
// in the respective AST nodes so that they are not printed
|
||||
// twice (once when printing the documentation and once when
|
||||
// printing the corresponding AST node).
|
||||
//
|
||||
type docReader struct {
|
||||
doc *ast.CommentGroup // package documentation, if any
|
||||
pkgName string
|
||||
showAll bool
|
||||
values []*ast.GenDecl // consts and vars
|
||||
types map[string]*typeDoc
|
||||
funcs map[string]*ast.FuncDecl
|
||||
imports map[string]int
|
||||
bugs []*ast.CommentGroup
|
||||
}
|
||||
|
||||
func (doc *docReader) init(pkgName string, showAll bool) {
|
||||
doc.pkgName = pkgName
|
||||
doc.showAll = showAll
|
||||
doc.imports = make(map[string]int)
|
||||
doc.types = make(map[string]*typeDoc)
|
||||
doc.funcs = make(map[string]*ast.FuncDecl)
|
||||
}
|
||||
|
||||
func (doc *docReader) addDoc(comments *ast.CommentGroup) {
|
||||
if doc.doc == nil {
|
||||
// common case: just one package comment
|
||||
doc.doc = comments
|
||||
return
|
||||
}
|
||||
|
||||
// More than one package comment: Usually there will be only
|
||||
// one file with a package comment, but it's better to collect
|
||||
// all comments than drop them on the floor.
|
||||
// (This code isn't particularly clever - no amortized doubling is
|
||||
// used - but this situation occurs rarely and is not time-critical.)
|
||||
n1 := len(doc.doc.List)
|
||||
n2 := len(comments.List)
|
||||
list := make([]*ast.Comment, n1+1+n2) // + 1 for separator line
|
||||
copy(list, doc.doc.List)
|
||||
list[n1] = &ast.Comment{token.NoPos, "//"} // separator line
|
||||
copy(list[n1+1:], comments.List)
|
||||
doc.doc = &ast.CommentGroup{list}
|
||||
}
|
||||
|
||||
func (doc *docReader) addType(decl *ast.GenDecl) {
|
||||
spec := decl.Specs[0].(*ast.TypeSpec)
|
||||
typ := doc.lookupTypeDoc(spec.Name.Name)
|
||||
// typ should always be != nil since declared types
|
||||
// are always named - be conservative and check
|
||||
if typ != nil {
|
||||
// a type should be added at most once, so typ.decl
|
||||
// should be nil - if it isn't, simply overwrite it
|
||||
typ.decl = decl
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
|
||||
if name == "" {
|
||||
return nil // no type docs for anonymous types
|
||||
}
|
||||
if tdoc, found := doc.types[name]; found {
|
||||
return tdoc
|
||||
}
|
||||
// type wasn't found - add one without declaration
|
||||
tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl)}
|
||||
doc.types[name] = tdoc
|
||||
return tdoc
|
||||
}
|
||||
|
||||
func docBaseTypeName(typ ast.Expr, showAll bool) string {
|
||||
switch t := typ.(type) {
|
||||
case *ast.Ident:
|
||||
// if the type is not exported, the effect to
|
||||
// a client is as if there were no type name
|
||||
if showAll || t.IsExported() {
|
||||
return t.Name
|
||||
}
|
||||
case *ast.StarExpr:
|
||||
return docBaseTypeName(t.X, showAll)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (doc *docReader) addValue(decl *ast.GenDecl) {
|
||||
// determine if decl should be associated with a type
|
||||
// Heuristic: For each typed entry, determine the type name, if any.
|
||||
// If there is exactly one type name that is sufficiently
|
||||
// frequent, associate the decl with the respective type.
|
||||
domName := ""
|
||||
domFreq := 0
|
||||
prev := ""
|
||||
for _, s := range decl.Specs {
|
||||
if v, ok := s.(*ast.ValueSpec); ok {
|
||||
name := ""
|
||||
switch {
|
||||
case v.Type != nil:
|
||||
// a type is present; determine its name
|
||||
name = docBaseTypeName(v.Type, doc.showAll)
|
||||
case decl.Tok == token.CONST:
|
||||
// no type is present but we have a constant declaration;
|
||||
// use the previous type name (w/o more type information
|
||||
// we cannot handle the case of unnamed variables with
|
||||
// initializer expressions except for some trivial cases)
|
||||
name = prev
|
||||
}
|
||||
if name != "" {
|
||||
// entry has a named type
|
||||
if domName != "" && domName != name {
|
||||
// more than one type name - do not associate
|
||||
// with any type
|
||||
domName = ""
|
||||
break
|
||||
}
|
||||
domName = name
|
||||
domFreq++
|
||||
}
|
||||
prev = name
|
||||
}
|
||||
}
|
||||
|
||||
// determine values list
|
||||
const threshold = 0.75
|
||||
values := &doc.values
|
||||
if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
|
||||
// typed entries are sufficiently frequent
|
||||
typ := doc.lookupTypeDoc(domName)
|
||||
if typ != nil {
|
||||
values = &typ.values // associate with that type
|
||||
}
|
||||
}
|
||||
|
||||
*values = append(*values, decl)
|
||||
}
|
||||
|
||||
// Helper function to set the table entry for function f. Makes sure that
|
||||
// at least one f with associated documentation is stored in table, if there
|
||||
// are multiple f's with the same name.
|
||||
func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {
|
||||
name := f.Name.Name
|
||||
if g, exists := table[name]; exists && g.Doc != nil {
|
||||
// a function with the same name has already been registered;
|
||||
// since it has documentation, assume f is simply another
|
||||
// implementation and ignore it
|
||||
// TODO(gri) consider collecting all functions, or at least
|
||||
// all comments
|
||||
return
|
||||
}
|
||||
// function doesn't exist or has no documentation; use f
|
||||
table[name] = f
|
||||
}
|
||||
|
||||
func (doc *docReader) addFunc(fun *ast.FuncDecl) {
|
||||
name := fun.Name.Name
|
||||
|
||||
// determine if it should be associated with a type
|
||||
if fun.Recv != nil {
|
||||
// method
|
||||
typ := doc.lookupTypeDoc(docBaseTypeName(fun.Recv.List[0].Type, doc.showAll))
|
||||
if typ != nil {
|
||||
// exported receiver type
|
||||
setFunc(typ.methods, fun)
|
||||
}
|
||||
// otherwise don't show the method
|
||||
// TODO(gri): There may be exported methods of non-exported types
|
||||
// that can be called because of exported values (consts, vars, or
|
||||
// function results) of that type. Could determine if that is the
|
||||
// case and then show those methods in an appropriate section.
|
||||
return
|
||||
}
|
||||
|
||||
// perhaps a factory function
|
||||
// determine result type, if any
|
||||
if fun.Type.Results.NumFields() >= 1 {
|
||||
res := fun.Type.Results.List[0]
|
||||
if len(res.Names) <= 1 {
|
||||
// exactly one (named or anonymous) result associated
|
||||
// with the first type in result signature (there may
|
||||
// be more than one result)
|
||||
tname := docBaseTypeName(res.Type, doc.showAll)
|
||||
typ := doc.lookupTypeDoc(tname)
|
||||
if typ != nil {
|
||||
// named and exported result type
|
||||
|
||||
// Work-around for failure of heuristic: In package os
|
||||
// too many functions are considered factory functions
|
||||
// for the Error type. Eliminate manually for now as
|
||||
// this appears to be the only important case in the
|
||||
// current library where the heuristic fails.
|
||||
if doc.pkgName == "os" && tname == "Error" &&
|
||||
name != "NewError" && name != "NewSyscallError" {
|
||||
// not a factory function for os.Error
|
||||
setFunc(doc.funcs, fun) // treat as ordinary function
|
||||
return
|
||||
}
|
||||
|
||||
setFunc(typ.factories, fun)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ordinary function
|
||||
setFunc(doc.funcs, fun)
|
||||
}
|
||||
|
||||
func (doc *docReader) addDecl(decl ast.Decl) {
|
||||
switch d := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
if len(d.Specs) > 0 {
|
||||
switch d.Tok {
|
||||
case token.IMPORT:
|
||||
// imports are handled individually
|
||||
for _, spec := range d.Specs {
|
||||
if s, ok := spec.(*ast.ImportSpec); ok {
|
||||
if import_, err := strconv.Unquote(s.Path.Value); err == nil {
|
||||
doc.imports[import_] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
case token.CONST, token.VAR:
|
||||
// constants and variables are always handled as a group
|
||||
doc.addValue(d)
|
||||
case token.TYPE:
|
||||
// types are handled individually
|
||||
for _, spec := range d.Specs {
|
||||
// make a (fake) GenDecl node for this TypeSpec
|
||||
// (we need to do this here - as opposed to just
|
||||
// for printing - so we don't lose the GenDecl
|
||||
// documentation)
|
||||
//
|
||||
// TODO(gri): Consider just collecting the TypeSpec
|
||||
// node (and copy in the GenDecl.doc if there is no
|
||||
// doc in the TypeSpec - this is currently done in
|
||||
// makeTypeDocs below). Simpler data structures, but
|
||||
// would lose GenDecl documentation if the TypeSpec
|
||||
// has documentation as well.
|
||||
doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})
|
||||
// A new GenDecl node is created, no need to nil out d.Doc.
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
doc.addFunc(d)
|
||||
}
|
||||
}
|
||||
|
||||
func copyCommentList(list []*ast.Comment) []*ast.Comment {
|
||||
return append([]*ast.Comment(nil), list...)
|
||||
}
|
||||
|
||||
var (
|
||||
bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BUG(uid):
|
||||
bug_content = regexp.MustCompile("[^ \n\r\t]+") // at least one non-whitespace char
|
||||
)
|
||||
|
||||
// addFile adds the AST for a source file to the docReader.
|
||||
// Adding the same AST multiple times is a no-op.
|
||||
//
|
||||
func (doc *docReader) addFile(src *ast.File) {
|
||||
// add package documentation
|
||||
if src.Doc != nil {
|
||||
doc.addDoc(src.Doc)
|
||||
src.Doc = nil // doc consumed - remove from ast.File node
|
||||
}
|
||||
|
||||
// add all declarations
|
||||
for _, decl := range src.Decls {
|
||||
doc.addDecl(decl)
|
||||
}
|
||||
|
||||
// collect BUG(...) comments
|
||||
for _, c := range src.Comments {
|
||||
text := c.List[0].Text
|
||||
if m := bug_markers.FindStringIndex(text); m != nil {
|
||||
// found a BUG comment; maybe empty
|
||||
if btxt := text[m[1]:]; bug_content.MatchString(btxt) {
|
||||
// non-empty BUG comment; collect comment without BUG prefix
|
||||
list := copyCommentList(c.List)
|
||||
list[0].Text = text[m[1]:]
|
||||
doc.bugs = append(doc.bugs, &ast.CommentGroup{list})
|
||||
}
|
||||
}
|
||||
}
|
||||
src.Comments = nil // consumed unassociated comments - remove from ast.File node
|
||||
}
|
||||
|
||||
func NewFileDoc(file *ast.File, showAll bool) *PackageDoc {
|
||||
var r docReader
|
||||
r.init(file.Name.Name, showAll)
|
||||
r.addFile(file)
|
||||
return r.newDoc("", nil)
|
||||
}
|
||||
|
||||
func NewPackageDoc(pkg *ast.Package, importpath string, showAll bool) *PackageDoc {
|
||||
var r docReader
|
||||
r.init(pkg.Name, showAll)
|
||||
filenames := make([]string, len(pkg.Files))
|
||||
i := 0
|
||||
for filename, f := range pkg.Files {
|
||||
r.addFile(f)
|
||||
filenames[i] = filename
|
||||
i++
|
||||
}
|
||||
return r.newDoc(importpath, filenames)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Conversion to external representation
|
||||
|
||||
// ValueDoc is the documentation for a group of declared
|
||||
// values, either vars or consts.
|
||||
//
|
||||
type ValueDoc struct {
|
||||
Doc string
|
||||
Decl *ast.GenDecl
|
||||
order int
|
||||
}
|
||||
|
||||
type sortValueDoc []*ValueDoc
|
||||
|
||||
func (p sortValueDoc) Len() int { return len(p) }
|
||||
func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
func declName(d *ast.GenDecl) string {
|
||||
if len(d.Specs) != 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch v := d.Specs[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
return v.Names[0].Name
|
||||
case *ast.TypeSpec:
|
||||
return v.Name.Name
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p sortValueDoc) Less(i, j int) bool {
|
||||
// sort by name
|
||||
// pull blocks (name = "") up to top
|
||||
// in original order
|
||||
if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj {
|
||||
return ni < nj
|
||||
}
|
||||
return p[i].order < p[j].order
|
||||
}
|
||||
|
||||
func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc {
|
||||
d := make([]*ValueDoc, len(list)) // big enough in any case
|
||||
n := 0
|
||||
for i, decl := range list {
|
||||
if decl.Tok == tok {
|
||||
d[n] = &ValueDoc{decl.Doc.Text(), decl, i}
|
||||
n++
|
||||
decl.Doc = nil // doc consumed - removed from AST
|
||||
}
|
||||
}
|
||||
d = d[0:n]
|
||||
sort.Sort(sortValueDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
// FuncDoc is the documentation for a func declaration,
|
||||
// either a top-level function or a method function.
|
||||
//
|
||||
type FuncDoc struct {
|
||||
Doc string
|
||||
Recv ast.Expr // TODO(rsc): Would like string here
|
||||
Name string
|
||||
Decl *ast.FuncDecl
|
||||
}
|
||||
|
||||
type sortFuncDoc []*FuncDoc
|
||||
|
||||
func (p sortFuncDoc) Len() int { return len(p) }
|
||||
func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name }
|
||||
|
||||
func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
|
||||
d := make([]*FuncDoc, len(m))
|
||||
i := 0
|
||||
for _, f := range m {
|
||||
doc := new(FuncDoc)
|
||||
doc.Doc = f.Doc.Text()
|
||||
f.Doc = nil // doc consumed - remove from ast.FuncDecl node
|
||||
if f.Recv != nil {
|
||||
doc.Recv = f.Recv.List[0].Type
|
||||
}
|
||||
doc.Name = f.Name.Name
|
||||
doc.Decl = f
|
||||
d[i] = doc
|
||||
i++
|
||||
}
|
||||
sort.Sort(sortFuncDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
// TypeDoc is the documentation for a declared type.
|
||||
// Consts and Vars are sorted lists of constants and variables of (mostly) that type.
|
||||
// Factories is a sorted list of factory functions that return that type.
|
||||
// Methods is a sorted list of method functions on that type.
|
||||
type TypeDoc struct {
|
||||
Doc string
|
||||
Type *ast.TypeSpec
|
||||
Consts []*ValueDoc
|
||||
Vars []*ValueDoc
|
||||
Funcs []*FuncDoc
|
||||
Methods []*FuncDoc
|
||||
Decl *ast.GenDecl
|
||||
order int
|
||||
}
|
||||
|
||||
type sortTypeDoc []*TypeDoc
|
||||
|
||||
func (p sortTypeDoc) Len() int { return len(p) }
|
||||
func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p sortTypeDoc) Less(i, j int) bool {
|
||||
// sort by name
|
||||
// pull blocks (name = "") up to top
|
||||
// in original order
|
||||
if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj {
|
||||
return ni < nj
|
||||
}
|
||||
return p[i].order < p[j].order
|
||||
}
|
||||
|
||||
// NOTE(rsc): This would appear not to be correct for type ( )
|
||||
// blocks, but the doc extractor above has split them into
|
||||
// individual declarations.
|
||||
func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
|
||||
d := make([]*TypeDoc, len(m))
|
||||
i := 0
|
||||
for _, old := range m {
|
||||
// all typeDocs should have a declaration associated with
|
||||
// them after processing an entire package - be conservative
|
||||
// and check
|
||||
if decl := old.decl; decl != nil {
|
||||
typespec := decl.Specs[0].(*ast.TypeSpec)
|
||||
t := new(TypeDoc)
|
||||
doc := typespec.Doc
|
||||
typespec.Doc = nil // doc consumed - remove from ast.TypeSpec node
|
||||
if doc == nil {
|
||||
// no doc associated with the spec, use the declaration doc, if any
|
||||
doc = decl.Doc
|
||||
}
|
||||
decl.Doc = nil // doc consumed - remove from ast.Decl node
|
||||
t.Doc = doc.Text()
|
||||
t.Type = typespec
|
||||
t.Consts = makeValueDocs(old.values, token.CONST)
|
||||
t.Vars = makeValueDocs(old.values, token.VAR)
|
||||
t.Funcs = makeFuncDocs(old.factories)
|
||||
t.Methods = makeFuncDocs(old.methods)
|
||||
t.Decl = old.decl
|
||||
t.order = i
|
||||
d[i] = t
|
||||
i++
|
||||
} else {
|
||||
// no corresponding type declaration found - move any associated
|
||||
// values, factory functions, and methods back to the top-level
|
||||
// so that they are not lost (this should only happen if a package
|
||||
// file containing the explicit type declaration is missing or if
|
||||
// an unqualified type name was used after a "." import)
|
||||
// 1) move values
|
||||
doc.values = append(doc.values, old.values...)
|
||||
// 2) move factory functions
|
||||
for name, f := range old.factories {
|
||||
doc.funcs[name] = f
|
||||
}
|
||||
// 3) move methods
|
||||
for name, f := range old.methods {
|
||||
// don't overwrite functions with the same name
|
||||
if _, found := doc.funcs[name]; !found {
|
||||
doc.funcs[name] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
d = d[0:i] // some types may have been ignored
|
||||
sort.Sort(sortTypeDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
func makeBugDocs(list []*ast.CommentGroup) []string {
|
||||
d := make([]string, len(list))
|
||||
for i, g := range list {
|
||||
d[i] = g.Text()
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// PackageDoc is the documentation for an entire package.
|
||||
//
|
||||
type PackageDoc struct {
|
||||
PackageName string
|
||||
ImportPath string
|
||||
Imports []string
|
||||
Filenames []string
|
||||
Doc string
|
||||
Consts []*ValueDoc
|
||||
Types []*TypeDoc
|
||||
Vars []*ValueDoc
|
||||
Funcs []*FuncDoc
|
||||
Factorys []*FuncDoc
|
||||
Bugs []string
|
||||
}
|
||||
|
||||
// newDoc returns the accumulated documentation for the package.
|
||||
//
|
||||
func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc {
|
||||
p := new(PackageDoc)
|
||||
p.PackageName = doc.pkgName
|
||||
p.ImportPath = importpath
|
||||
sort.Strings(filenames)
|
||||
p.Filenames = filenames
|
||||
p.Doc = doc.doc.Text()
|
||||
p.Imports = sortedKeys(doc.imports)
|
||||
// makeTypeDocs may extend the list of doc.values and
|
||||
// doc.funcs and thus must be called before any other
|
||||
// function consuming those lists
|
||||
p.Types = doc.makeTypeDocs(doc.types)
|
||||
p.Consts = makeValueDocs(doc.values, token.CONST)
|
||||
p.Vars = makeValueDocs(doc.values, token.VAR)
|
||||
p.Funcs = makeFuncDocs(doc.funcs)
|
||||
p.Bugs = makeBugDocs(doc.bugs)
|
||||
|
||||
for _, d := range p.Types {
|
||||
switch d.Type.Type.(type) {
|
||||
case *ast.StructType:
|
||||
p.Factorys = append(p.Factorys, d.Funcs...)
|
||||
d.Funcs = make([]*FuncDoc, 0)
|
||||
case *ast.InterfaceType:
|
||||
p.Factorys = append(p.Factorys, d.Funcs...)
|
||||
d.Funcs = make([]*FuncDoc, 0)
|
||||
default:
|
||||
p.Vars = append(p.Vars, d.Vars...)
|
||||
d.Vars = make([]*ValueDoc, 0)
|
||||
p.Consts = append(p.Consts, d.Consts...)
|
||||
d.Consts = make([]*ValueDoc, 0)
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func sortedKeys(m map[string]int) []string {
|
||||
list := make([]string, len(m))
|
||||
i := 0
|
||||
for key := range m {
|
||||
list[i] = key
|
||||
i++
|
||||
}
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Filtering by name
|
||||
|
||||
type Filter func(string) bool
|
||||
|
||||
func matchFields(fields *ast.FieldList, f Filter) bool {
|
||||
if fields != nil {
|
||||
for _, field := range fields.List {
|
||||
for _, name := range field.Names {
|
||||
if f(name.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchDecl(d *ast.GenDecl, f Filter) bool {
|
||||
for _, d := range d.Specs {
|
||||
switch v := d.(type) {
|
||||
case *ast.ValueSpec:
|
||||
for _, name := range v.Names {
|
||||
if f(name.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
if f(v.Name.Name) {
|
||||
return true
|
||||
}
|
||||
switch t := v.Type.(type) {
|
||||
case *ast.StructType:
|
||||
if matchFields(t.Fields, f) {
|
||||
return true
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
if matchFields(t.Methods, f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func filterValueDocs(a []*ValueDoc, f Filter) []*ValueDoc {
|
||||
w := 0
|
||||
for _, vd := range a {
|
||||
if matchDecl(vd.Decl, f) {
|
||||
a[w] = vd
|
||||
w++
|
||||
}
|
||||
}
|
||||
return a[0:w]
|
||||
}
|
||||
|
||||
func filterFuncDocs(a []*FuncDoc, f Filter) []*FuncDoc {
|
||||
w := 0
|
||||
for _, fd := range a {
|
||||
if f(fd.Name) {
|
||||
a[w] = fd
|
||||
w++
|
||||
}
|
||||
}
|
||||
return a[0:w]
|
||||
}
|
||||
|
||||
func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
|
||||
w := 0
|
||||
for _, td := range a {
|
||||
n := 0 // number of matches
|
||||
if matchDecl(td.Decl, f) {
|
||||
n = 1
|
||||
} else {
|
||||
// type name doesn't match, but we may have matching consts, vars, factories or methods
|
||||
td.Consts = filterValueDocs(td.Consts, f)
|
||||
td.Vars = filterValueDocs(td.Vars, f)
|
||||
td.Funcs = filterFuncDocs(td.Funcs, f)
|
||||
td.Methods = filterFuncDocs(td.Methods, f)
|
||||
n += len(td.Consts) + len(td.Vars) + len(td.Funcs) + len(td.Methods)
|
||||
}
|
||||
if n > 0 {
|
||||
a[w] = td
|
||||
w++
|
||||
}
|
||||
}
|
||||
return a[0:w]
|
||||
}
|
||||
|
||||
// Filter eliminates documentation for names that don't pass through the filter f.
|
||||
// TODO: Recognize "Type.Method" as a name.
|
||||
//
|
||||
func (p *PackageDoc) Filter(f Filter) {
|
||||
p.Consts = filterValueDocs(p.Consts, f)
|
||||
p.Vars = filterValueDocs(p.Vars, f)
|
||||
p.Types = filterTypeDocs(p.Types, f)
|
||||
p.Funcs = filterFuncDocs(p.Funcs, f)
|
||||
p.Doc = "" // don't show top-level package doc
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package astview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runAstView,
|
||||
UsageLine: "astview [-stdin] files...",
|
||||
Short: "print go files astview",
|
||||
Long: `print go files astview`,
|
||||
}
|
||||
|
||||
var astViewStdin bool
|
||||
|
||||
func init() {
|
||||
Command.Flag.BoolVar(&astViewStdin, "stdin", false, "input from stdin")
|
||||
}
|
||||
|
||||
func runAstView(cmd *command.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
cmd.Usage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if astViewStdin {
|
||||
view, err := NewFilePackageSource(args[0], os.Stdin, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "astview: %s", err)
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
}
|
||||
view.PrintTree(os.Stdout)
|
||||
} else {
|
||||
err := PrintFilesTree(args, os.Stdout, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "astview:%s", err)
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
tag_package = "p"
|
||||
tag_imports_folder = "+m"
|
||||
tag_import = "mm"
|
||||
tag_type = "t"
|
||||
tag_struct = "s"
|
||||
tag_interface = "i"
|
||||
tag_value = "v"
|
||||
tag_const = "c"
|
||||
tag_func = "f"
|
||||
tag_value_folder = "+v"
|
||||
tag_const_folder = "+c"
|
||||
tag_func_folder = "+f"
|
||||
tag_factor_folder = "+tf"
|
||||
tag_type_method = "tm"
|
||||
tag_type_factor = "tf"
|
||||
tag_type_value = "tv"
|
||||
)
|
||||
|
||||
type PackageView struct {
|
||||
fset *token.FileSet
|
||||
pdoc *PackageDoc
|
||||
pkg *ast.Package
|
||||
expr bool
|
||||
}
|
||||
|
||||
var AllFiles []string
|
||||
|
||||
func (p *PackageView) posFileIndex(pos token.Position) int {
|
||||
var index = -1
|
||||
for i := 0; i < len(AllFiles); i++ {
|
||||
if AllFiles[i] == pos.Filename {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
AllFiles = append(AllFiles, pos.Filename)
|
||||
index = len(AllFiles) - 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func (p *PackageView) posText(pos token.Position) (s string) {
|
||||
index := p.posFileIndex(pos)
|
||||
return fmt.Sprintf("%d:%d:%d", index, pos.Line, pos.Column)
|
||||
}
|
||||
|
||||
func NewFilePackage(filename string) (*PackageView, error) {
|
||||
p := new(PackageView)
|
||||
p.fset = token.NewFileSet()
|
||||
file, err := parser.ParseFile(p.fset, filename, nil, parser.AllErrors)
|
||||
if file == nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]*ast.File)
|
||||
m[filename] = file
|
||||
pkg, err := ast.NewPackage(p.fset, m, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.pkg = pkg
|
||||
p.pdoc = NewPackageDoc(pkg, pkg.Name, true)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func NewPackageView(pkg *ast.Package, fset *token.FileSet, expr bool) (*PackageView, error) {
|
||||
p := new(PackageView)
|
||||
p.fset = fset
|
||||
p.pkg = pkg
|
||||
p.pdoc = NewPackageDoc(pkg, pkg.Name, true)
|
||||
p.expr = expr
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func ParseFiles(fset *token.FileSet, filenames []string, mode parser.Mode) (pkgs map[string]*ast.Package, pkgsfiles []string, first error) {
|
||||
pkgs = make(map[string]*ast.Package)
|
||||
for _, filename := range filenames {
|
||||
if src, err := parser.ParseFile(fset, filename, nil, mode); src != nil {
|
||||
name := src.Name.Name
|
||||
pkg, found := pkgs[name]
|
||||
if !found {
|
||||
pkg = &ast.Package{
|
||||
Name: name,
|
||||
Files: make(map[string]*ast.File),
|
||||
}
|
||||
pkgs[name] = pkg
|
||||
}
|
||||
pkg.Files[filename] = src
|
||||
pkgsfiles = append(pkgsfiles, filename)
|
||||
} else {
|
||||
first = err
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func PrintFilesTree(filenames []string, w io.Writer, expr bool) error {
|
||||
fset := token.NewFileSet()
|
||||
pkgs, pkgsfiles, err := ParseFiles(fset, filenames, parser.AllErrors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
AllFiles = pkgsfiles
|
||||
for i := 0; i < len(AllFiles); i++ {
|
||||
fmt.Fprintf(w, "@%s\n", AllFiles[i])
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
view, err := NewPackageView(pkg, fset, expr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
view.PrintTree(w)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFilePackageSource(filename string, f *os.File, expr bool) (*PackageView, error) {
|
||||
src, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := new(PackageView)
|
||||
p.fset = token.NewFileSet()
|
||||
p.expr = expr
|
||||
file, err := parser.ParseFile(p.fset, filename, src, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]*ast.File)
|
||||
m[filename] = file
|
||||
pkg, err := ast.NewPackage(p.fset, m, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.pdoc = NewPackageDoc(pkg, pkg.Name, true)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *PackageView) printFuncsHelper(w io.Writer, funcs []*FuncDoc, level int, tag string, tag_folder string) {
|
||||
for _, f := range funcs {
|
||||
pos := p.fset.Position(f.Decl.Pos())
|
||||
if p.expr {
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s@%s\n", level, tag, f.Name, p.posText(pos), types.ExprString(f.Decl.Type))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, f.Name, p.posText(pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PackageView) PrintVars(w io.Writer, vars []*ValueDoc, level int, tag string, tag_folder string) {
|
||||
if len(tag_folder) > 0 && len(vars) > 0 {
|
||||
if tag_folder == tag_value_folder {
|
||||
fmt.Fprintf(w, "%d,%s,Variables\n", level, tag_folder)
|
||||
} else if tag_folder == tag_const_folder {
|
||||
fmt.Fprintf(w, "%d,%s,Constants\n", level, tag_folder)
|
||||
}
|
||||
level++
|
||||
}
|
||||
for _, v := range vars {
|
||||
if v.Decl == nil {
|
||||
continue
|
||||
}
|
||||
for _, s := range v.Decl.Specs {
|
||||
if m, ok := s.(*ast.ValueSpec); ok {
|
||||
pos := p.fset.Position(m.Pos())
|
||||
for i := 0; i < len(m.Names); i++ {
|
||||
if p.expr && m.Type != nil {
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s@%s\n", level, tag, m.Names[i], p.posText(pos), types.ExprString(m.Type))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, m.Names[i], p.posText(pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func (p *PackageView) PrintTypes(w io.Writer, types []*TypeDoc, level int) {
|
||||
for _, d := range types {
|
||||
if d.Decl == nil {
|
||||
continue
|
||||
}
|
||||
typespec := d.Decl.Specs[0].(*ast.TypeSpec)
|
||||
var tag = tag_type
|
||||
if _, ok := typespec.Type.(*ast.InterfaceType); ok {
|
||||
tag = tag_interface
|
||||
} else if _, ok := typespec.Type.(*ast.StructType); ok {
|
||||
tag = tag_struct
|
||||
}
|
||||
pos := p.fset.Position(d.Decl.Pos())
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, d.Type.Name, p.posText(pos))
|
||||
p.printFuncsHelper(w, d.Funcs, level+1, tag_type_factor, "")
|
||||
p.printFuncsHelper(w, d.Methods, level+1, tag_type_method, "")
|
||||
p.PrintTypeFields(w, d.Decl, level+1)
|
||||
//p.PrintVars(w, d.Consts, level+1, tag_const, "")
|
||||
//p.PrintVars(w, d.Vars, level+1, tag_value, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PackageView) PrintTypeFields(w io.Writer, decl *ast.GenDecl, level int) {
|
||||
spec, ok := decl.Specs[0].(*ast.TypeSpec)
|
||||
if ok == false {
|
||||
return
|
||||
}
|
||||
switch d := spec.Type.(type) {
|
||||
case *ast.StructType:
|
||||
for _, list := range d.Fields.List {
|
||||
if list.Names == nil {
|
||||
continue
|
||||
}
|
||||
for _, m := range list.Names {
|
||||
pos := p.fset.Position(m.Pos())
|
||||
if list.Type != nil {
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s@%s\n", level, tag_type_value, m.Name, p.posText(pos), types.ExprString(list.Type))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag_type_value, m.Name, p.posText(pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
for _, list := range d.Methods.List {
|
||||
if list.Names == nil {
|
||||
continue
|
||||
}
|
||||
for _, m := range list.Names {
|
||||
pos := p.fset.Position(m.Pos())
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag_type_method, m.Name, p.posText(pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PackageView) PrintHeader(w io.Writer, level int) {
|
||||
fmt.Fprintf(w, "%d,%s,%s\n", level, tag_package, p.pdoc.PackageName)
|
||||
}
|
||||
|
||||
func (p *PackageView) PrintImports(w io.Writer, level int, tag, tag_folder string) {
|
||||
if tag_folder != "" && len(p.pdoc.Imports) > 0 {
|
||||
fmt.Fprintf(w, "%d,%s,%s\n", level, tag_folder, "Imports")
|
||||
level++
|
||||
}
|
||||
for _, name := range p.pdoc.Imports {
|
||||
vname := "\"" + name + "\""
|
||||
var ps []string
|
||||
for _, file := range p.pkg.Files {
|
||||
for _, v := range file.Imports {
|
||||
if v.Path.Value == vname {
|
||||
pos := p.fset.Position(v.Pos())
|
||||
ps = append(ps, p.posText(pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%d,%s,%s,%s\n", level, tag, name, strings.Join(ps, ";"))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PackageView) PrintFuncs(w io.Writer, level int, tag_folder string) {
|
||||
hasFolder := false
|
||||
if len(p.pdoc.Funcs) > 0 || len(p.pdoc.Factorys) > 0 {
|
||||
hasFolder = true
|
||||
}
|
||||
if !hasFolder {
|
||||
return
|
||||
}
|
||||
if len(tag_folder) > 0 {
|
||||
fmt.Fprintf(w, "%d,%s,Functions\n", level, tag_folder)
|
||||
level++
|
||||
}
|
||||
p.printFuncsHelper(w, p.pdoc.Factorys, level, tag_type_factor, tag_func_folder)
|
||||
p.printFuncsHelper(w, p.pdoc.Funcs, level, tag_func, tag_func_folder)
|
||||
}
|
||||
|
||||
func (p *PackageView) PrintPackage(w io.Writer, level int) {
|
||||
p.PrintHeader(w, level)
|
||||
level++
|
||||
p.PrintImports(w, level, tag_import, tag_imports_folder)
|
||||
p.PrintVars(w, p.pdoc.Vars, level, tag_value, tag_value_folder)
|
||||
p.PrintVars(w, p.pdoc.Consts, level, tag_const, tag_const_folder)
|
||||
p.PrintFuncs(w, level, tag_func_folder)
|
||||
p.PrintTypes(w, p.pdoc.Types, level)
|
||||
}
|
||||
|
||||
// level,tag,pos@info
|
||||
func (p *PackageView) PrintTree(w io.Writer) {
|
||||
p.PrintPackage(w, 0)
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
// 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.
|
||||
|
||||
//modify 2013-2014 visualfc
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Command is an implementation of a go command
|
||||
// like go build or go fix.
|
||||
type Command struct {
|
||||
// Run runs the command.
|
||||
// The args are the arguments after the command name.
|
||||
Run func(cmd *Command, args []string) error
|
||||
|
||||
// UsageLine is the one-line usage message.
|
||||
// The first word in the line is taken to be the command name.
|
||||
UsageLine string
|
||||
|
||||
// Short is the short description shown in the 'go help' output.
|
||||
Short string
|
||||
|
||||
// Long is the long message shown in the 'go help <this-command>' output.
|
||||
Long string
|
||||
|
||||
// Flag is a set of flags specific to this command.
|
||||
Flag flag.FlagSet
|
||||
|
||||
// CustomFlags indicates that the command will do its own
|
||||
// flag parsing.
|
||||
CustomFlags bool
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// Name returns the command's name: the first word in the usage line.
|
||||
func (c *Command) Name() string {
|
||||
name := c.UsageLine
|
||||
i := strings.Index(name, " ")
|
||||
if i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (c *Command) Usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s %s\n", AppName, c.UsageLine)
|
||||
c.Flag.SetOutput(os.Stderr)
|
||||
c.Flag.PrintDefaults()
|
||||
//fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func (c *Command) PrintUsage() {
|
||||
fmt.Fprintf(Stderr, "usage: %s %s\n", AppName, c.UsageLine)
|
||||
c.Flag.SetOutput(Stderr)
|
||||
c.Flag.PrintDefaults()
|
||||
}
|
||||
|
||||
// Runnable reports whether the command can be run; otherwise
|
||||
// it is a documentation pseudo-command such as importpath.
|
||||
func (c *Command) Runnable() bool {
|
||||
return c.Run != nil
|
||||
}
|
||||
|
||||
func (c *Command) Println(args ...interface{}) {
|
||||
fmt.Fprintln(c.Stdout, args...)
|
||||
}
|
||||
|
||||
func (c *Command) Printf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(c.Stdout, format, args...)
|
||||
}
|
||||
|
||||
var commands []*Command
|
||||
|
||||
func Register(cmd *Command) {
|
||||
commands = append(commands, cmd)
|
||||
}
|
||||
|
||||
func CommandList() (cmds []string) {
|
||||
for _, cmd := range commands {
|
||||
cmds = append(cmds, cmd.Name())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var exitStatus = 0
|
||||
var exitMu sync.Mutex
|
||||
|
||||
func SetExitStatus(n int) {
|
||||
exitMu.Lock()
|
||||
if exitStatus < n {
|
||||
exitStatus = n
|
||||
}
|
||||
exitMu.Unlock()
|
||||
}
|
||||
|
||||
var (
|
||||
Stdout io.Writer = os.Stdout
|
||||
Stderr io.Writer = os.Stderr
|
||||
Stdin io.Reader = os.Stdin
|
||||
)
|
||||
|
||||
func RunArgs(arguments []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
|
||||
flag.CommandLine.Parse(arguments)
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
printUsage(os.Stderr)
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
if len(args) == 1 && strings.TrimSpace(args[0]) == "" {
|
||||
printUsage(os.Stderr)
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
if args[0] == "help" {
|
||||
if !help(args[1:]) {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name() == args[0] && cmd.Run != nil {
|
||||
cmd.Flag.Usage = func() { cmd.Usage() }
|
||||
if cmd.CustomFlags {
|
||||
args = args[1:]
|
||||
} else {
|
||||
cmd.Flag.Parse(args[1:])
|
||||
args = cmd.Flag.Args()
|
||||
}
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
return cmd.Run(cmd, args)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s: unknown subcommand %q\nRun '%s help' for usage.\n",
|
||||
AppName, args[0], AppName)
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func Main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
log.SetFlags(0)
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) < 1 {
|
||||
usage()
|
||||
}
|
||||
|
||||
if len(args) == 1 && strings.TrimSpace(args[0]) == "" {
|
||||
usage()
|
||||
}
|
||||
|
||||
if args[0] == "help" {
|
||||
if !help(args[1:]) {
|
||||
os.Exit(2)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name() == args[0] && cmd.Run != nil {
|
||||
cmd.Flag.Usage = func() { cmd.Usage() }
|
||||
if cmd.CustomFlags {
|
||||
args = args[1:]
|
||||
} else {
|
||||
cmd.Flag.Parse(args[1:])
|
||||
args = cmd.Flag.Args()
|
||||
}
|
||||
cmd.Stdin = Stdin
|
||||
cmd.Stdout = Stdout
|
||||
cmd.Stderr = Stderr
|
||||
cmd.Run(cmd, args)
|
||||
Exit()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s: unknown subcommand %q\nRun '%s help' for usage.\n",
|
||||
AppName, args[0], AppName)
|
||||
SetExitStatus(2)
|
||||
Exit()
|
||||
}
|
||||
|
||||
var AppInfo string = "LiteIDE golang tool."
|
||||
var AppName string = "tools"
|
||||
|
||||
var usageTemplate = `
|
||||
Usage:
|
||||
|
||||
{{AppName}} command [arguments]
|
||||
|
||||
The commands are:
|
||||
{{range .}}{{if .Runnable}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use "{{AppName}} help [command]" for more information about a command.
|
||||
|
||||
Additional help topics:
|
||||
{{range .}}{{if not .Runnable}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
||||
|
||||
Use "{{AppName}} help [topic]" for more information about that topic.
|
||||
|
||||
`
|
||||
|
||||
var helpTemplate = `{{if .Runnable}}usage: {{AppName}} {{.UsageLine}}
|
||||
|
||||
{{end}}{{.Long | trim}}
|
||||
`
|
||||
|
||||
var documentationTemplate = `//
|
||||
/*
|
||||
{{range .}}{{if .Short}}{{.Short | capitalize}}
|
||||
|
||||
{{end}}{{if .Runnable}}Usage:
|
||||
|
||||
{{AppName}} {{.UsageLine}}
|
||||
|
||||
{{end}}{{.Long | trim}}
|
||||
|
||||
|
||||
{{end}}*/
|
||||
package main
|
||||
`
|
||||
|
||||
// tmpl executes the given template text on data, writing the result to w.
|
||||
func tmpl(w io.Writer, text string, data interface{}) {
|
||||
t := template.New("top")
|
||||
t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
|
||||
template.Must(t.Parse(text))
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func capitalize(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(unicode.ToTitle(r)) + s[n:]
|
||||
}
|
||||
|
||||
func printUsage(w io.Writer) {
|
||||
if len(AppInfo) > 0 {
|
||||
fmt.Fprintln(w, AppInfo)
|
||||
}
|
||||
tmpl(w, strings.Replace(usageTemplate, "{{AppName}}", AppName, -1), commands)
|
||||
}
|
||||
|
||||
func usage() {
|
||||
printUsage(os.Stderr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// help implements the 'help' command.
|
||||
func help(args []string) bool {
|
||||
if len(args) == 0 {
|
||||
printUsage(os.Stdout)
|
||||
// not exit 2: succeeded at 'go help'.
|
||||
return true
|
||||
}
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintf(os.Stderr, "usage: %s help command\n\nToo many arguments given.\n", AppName)
|
||||
return false
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
|
||||
// 'go help documentation' generates doc.go.
|
||||
if arg == "documentation" {
|
||||
buf := new(bytes.Buffer)
|
||||
printUsage(buf)
|
||||
usage := &Command{Long: buf.String()}
|
||||
tmpl(os.Stdout, strings.Replace(documentationTemplate, "{{AppName}}", AppName, -1), append([]*Command{usage}, commands...))
|
||||
return false
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name() == arg {
|
||||
tmpl(os.Stdout, strings.Replace(helpTemplate, "{{AppName}}", AppName, -1), cmd)
|
||||
// not exit 2: succeeded at 'go help cmd'.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run '%s help'.\n", arg, AppName)
|
||||
//os.Exit(2) // failed at 'go help cmd'
|
||||
return false
|
||||
}
|
||||
|
||||
var atexitFuncs []func()
|
||||
|
||||
func Atexit(f func()) {
|
||||
atexitFuncs = append(atexitFuncs, f)
|
||||
}
|
||||
|
||||
func Exit() {
|
||||
for _, f := range atexitFuncs {
|
||||
f()
|
||||
}
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
Errorf(format, args...)
|
||||
Exit()
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
log.Printf(format, args...)
|
||||
SetExitStatus(1)
|
||||
}
|
||||
|
||||
var logf = log.Printf
|
||||
|
||||
func ExitIfErrors() {
|
||||
if exitStatus != 0 {
|
||||
Exit()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register(cmdVersion)
|
||||
}
|
||||
|
||||
var AppVersion string = "1.0"
|
||||
|
||||
var cmdVersion = &Command{
|
||||
Run: runVersion,
|
||||
UsageLine: "version",
|
||||
Short: "print tool version",
|
||||
Long: `Version prints the version.`,
|
||||
}
|
||||
|
||||
func runVersion(cmd *Command, args []string) error {
|
||||
if len(args) != 0 {
|
||||
cmd.PrintUsage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
cmd.Printf("%s version %s [%s %s/%s]\n", AppName, AppVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,352 @@
|
|||
// 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 dealing with package directory trees.
|
||||
|
||||
package docview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/doc"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Directory struct {
|
||||
Depth int
|
||||
Path string // includes Name
|
||||
Name string
|
||||
Text string // package documentation, if any
|
||||
Dirs []*Directory // subdirectories
|
||||
}
|
||||
|
||||
//func isGoFile(fi os.FileInfo) bool {
|
||||
// name := fi.Name()
|
||||
// return !fi.IsDir() &&
|
||||
// len(name) > 0 && name[0] != '.' && // ignore .files
|
||||
// filepath.Ext(name) == ".go"
|
||||
//}
|
||||
|
||||
func isGoFile(f os.FileInfo) bool {
|
||||
// ignore non-Go files
|
||||
name := f.Name()
|
||||
return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
||||
}
|
||||
|
||||
func isPkgFile(fi os.FileInfo) bool {
|
||||
return isGoFile(fi) &&
|
||||
!strings.HasSuffix(fi.Name(), "_test.go") // ignore test files
|
||||
}
|
||||
|
||||
func isPkgDir(fi os.FileInfo) bool {
|
||||
name := fi.Name()
|
||||
return fi.IsDir() && len(name) > 0 &&
|
||||
name[0] != '_' && name[0] != '.' // ignore _files and .files
|
||||
}
|
||||
|
||||
func firstSentence(s string) string {
|
||||
i := -1 // index+1 of first terminator (punctuation ending a sentence)
|
||||
j := -1 // index+1 of first terminator followed by white space
|
||||
prev := 'A'
|
||||
for k, ch := range s {
|
||||
k1 := k + 1
|
||||
if ch == '.' || ch == '!' || ch == '?' {
|
||||
if i < 0 {
|
||||
i = k1 // first terminator
|
||||
}
|
||||
if k1 < len(s) && s[k1] <= ' ' {
|
||||
if j < 0 {
|
||||
j = k1 // first terminator followed by white space
|
||||
}
|
||||
if !unicode.IsUpper(prev) {
|
||||
j = k1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = ch
|
||||
}
|
||||
|
||||
if j < 0 {
|
||||
// use the next best terminator
|
||||
j = i
|
||||
if j < 0 {
|
||||
// no terminator at all, use the entire string
|
||||
j = len(s)
|
||||
}
|
||||
}
|
||||
|
||||
return s[0:j]
|
||||
}
|
||||
|
||||
type treeBuilder struct {
|
||||
pathFilter func(string) bool
|
||||
maxDepth int
|
||||
}
|
||||
|
||||
func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory {
|
||||
if b.pathFilter != nil && !b.pathFilter(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if depth >= b.maxDepth {
|
||||
// return a dummy directory so that the parent directory
|
||||
// doesn't get discarded just because we reached the max
|
||||
// directory depth
|
||||
return &Directory{depth, path, name, "", nil}
|
||||
}
|
||||
|
||||
list, err := fs.ReadDir(path)
|
||||
if err != nil {
|
||||
// newDirTree is called with a path that should be a package
|
||||
// directory; errors here should not happen, but if they do,
|
||||
// we want to know about them
|
||||
log.Printf("ReadDir(%s): %s", path, err)
|
||||
}
|
||||
|
||||
// determine number of subdirectories and if there are package files
|
||||
ndirs := 0
|
||||
hasPkgFiles := false
|
||||
var synopses [4]string // prioritized package documentation (0 == highest priority)
|
||||
for _, d := range list {
|
||||
switch {
|
||||
case isPkgDir(d):
|
||||
ndirs++
|
||||
case isPkgFile(d):
|
||||
// looks like a package file, but may just be a file ending in ".go";
|
||||
// don't just count it yet (otherwise we may end up with hasPkgFiles even
|
||||
// though the directory doesn't contain any real package files - was bug)
|
||||
if synopses[0] == "" {
|
||||
// no "optimal" package synopsis yet; continue to collect synopses
|
||||
//file, err := parseFile(fset, filepath.Join(path, d.Name()),
|
||||
//parser.ParseComments|parser.PackageClauseOnly)
|
||||
file, err := parser.ParseFile(fset, filepath.Join(path, d.Name()), nil,
|
||||
parser.ParseComments|parser.PackageClauseOnly)
|
||||
|
||||
if err == nil {
|
||||
hasPkgFiles = true
|
||||
if file.Doc != nil {
|
||||
// prioritize documentation
|
||||
i := -1
|
||||
switch file.Name.Name {
|
||||
case name:
|
||||
i = 0 // normal case: directory name matches package name
|
||||
case fakePkgName:
|
||||
i = 1 // synopses for commands
|
||||
case "main":
|
||||
i = 2 // directory contains a main package
|
||||
default:
|
||||
i = 3 // none of the above
|
||||
}
|
||||
if 0 <= i && i < len(synopses) && synopses[i] == "" {
|
||||
synopses[i] = doc.Synopsis(file.Doc.Text())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create subdirectory tree
|
||||
var dirs []*Directory
|
||||
if ndirs > 0 {
|
||||
dirs = make([]*Directory, ndirs)
|
||||
i := 0
|
||||
for _, d := range list {
|
||||
if isPkgDir(d) {
|
||||
name := d.Name()
|
||||
dd := b.newDirTree(fset, filepath.Join(path, name), name, depth+1)
|
||||
if dd != nil {
|
||||
dirs[i] = dd
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
dirs = dirs[0:i]
|
||||
}
|
||||
|
||||
// if there are no package files and no subdirectories
|
||||
// containing package files, ignore the directory
|
||||
if !hasPkgFiles && len(dirs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// select the highest-priority synopsis for the directory entry, if any
|
||||
synopsis := ""
|
||||
for _, synopsis = range synopses {
|
||||
if synopsis != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &Directory{depth, path, name, synopsis, dirs}
|
||||
}
|
||||
|
||||
// newDirectory creates a new package directory tree with at most maxDepth
|
||||
// levels, anchored at root. The result tree is pruned such that it only
|
||||
// contains directories that contain package files or that contain
|
||||
// subdirectories containing package files (transitively). If a non-nil
|
||||
// pathFilter is provided, directory paths additionally must be accepted
|
||||
// by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is
|
||||
// provided for maxDepth, nodes at larger depths are pruned as well; they
|
||||
// are assumed to contain package files even if their contents are not known
|
||||
// (i.e., in this case the tree may contain directories w/o any package files).
|
||||
//
|
||||
func newDirectory(root string, pathFilter func(string) bool, maxDepth int) *Directory {
|
||||
// The root could be a symbolic link so use Stat not Lstat.
|
||||
d, err := fs.Stat(root)
|
||||
// If we fail here, report detailed error messages; otherwise
|
||||
// is is hard to see why a directory tree was not built.
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Printf("newDirectory(%s): %s", root, err)
|
||||
return nil
|
||||
case !isPkgDir(d):
|
||||
log.Printf("newDirectory(%s): not a package directory", root)
|
||||
return nil
|
||||
}
|
||||
if maxDepth < 0 {
|
||||
maxDepth = 1e6 // "infinity"
|
||||
}
|
||||
b := treeBuilder{pathFilter, maxDepth}
|
||||
// the file set provided is only for local parsing, no position
|
||||
// information escapes and thus we don't need to save the set
|
||||
return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
|
||||
}
|
||||
|
||||
func (dir *Directory) writeLeafs(buf *bytes.Buffer) {
|
||||
if dir != nil {
|
||||
if len(dir.Dirs) == 0 {
|
||||
buf.WriteString(dir.Path)
|
||||
buf.WriteByte('\n')
|
||||
return
|
||||
}
|
||||
|
||||
for _, d := range dir.Dirs {
|
||||
d.writeLeafs(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) {
|
||||
if dir != nil {
|
||||
if !skipRoot {
|
||||
c <- dir
|
||||
}
|
||||
for _, d := range dir.Dirs {
|
||||
d.walk(c, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dir *Directory) iter(skipRoot bool) <-chan *Directory {
|
||||
c := make(chan *Directory)
|
||||
go func() {
|
||||
dir.walk(c, skipRoot)
|
||||
close(c)
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
func (dir *Directory) lookupLocal(name string) *Directory {
|
||||
for _, d := range dir.Dirs {
|
||||
if d.Name == name {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookup looks for the *Directory for a given path, relative to dir.
|
||||
func (dir *Directory) lookup(path string) *Directory {
|
||||
d := strings.Split(dir.Path, string(filepath.Separator))
|
||||
p := strings.Split(path, string(filepath.Separator))
|
||||
i := 0
|
||||
for i < len(d) {
|
||||
if i >= len(p) || d[i] != p[i] {
|
||||
return nil
|
||||
}
|
||||
i++
|
||||
}
|
||||
for dir != nil && i < len(p) {
|
||||
dir = dir.lookupLocal(p[i])
|
||||
i++
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// DirEntry describes a directory entry. The Depth and Height values
|
||||
// are useful for presenting an entry in an indented fashion.
|
||||
//
|
||||
type DirEntry struct {
|
||||
Depth int // >= 0
|
||||
Height int // = DirList.MaxHeight - Depth, > 0
|
||||
Path string // includes Name, relative to DirList root
|
||||
Name string
|
||||
Synopsis string
|
||||
}
|
||||
|
||||
type DirList struct {
|
||||
MaxHeight int // directory tree height, > 0
|
||||
List []DirEntry
|
||||
}
|
||||
|
||||
// listing creates a (linear) directory listing from a directory tree.
|
||||
// If skipRoot is set, the root directory itself is excluded from the list.
|
||||
//
|
||||
func (root *Directory) listing(skipRoot bool) *DirList {
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// determine number of entries n and maximum height
|
||||
n := 0
|
||||
minDepth := 1 << 30 // infinity
|
||||
maxDepth := 0
|
||||
for d := range root.iter(skipRoot) {
|
||||
n++
|
||||
if minDepth > d.Depth {
|
||||
minDepth = d.Depth
|
||||
}
|
||||
if maxDepth < d.Depth {
|
||||
maxDepth = d.Depth
|
||||
}
|
||||
}
|
||||
maxHeight := maxDepth - minDepth + 1
|
||||
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create list
|
||||
list := make([]DirEntry, n)
|
||||
i := 0
|
||||
for d := range root.iter(skipRoot) {
|
||||
p := &list[i]
|
||||
p.Depth = d.Depth - minDepth
|
||||
p.Height = maxHeight - p.Depth
|
||||
// the path is relative to root.Path - remove the root.Path
|
||||
// prefix (the prefix should always be present but avoid
|
||||
// crashes and check)
|
||||
path := d.Path
|
||||
if strings.HasPrefix(d.Path, root.Path) {
|
||||
path = d.Path[len(root.Path):]
|
||||
}
|
||||
// remove trailing separator if any - path must be relative
|
||||
if len(path) > 0 && path[0] == filepath.Separator {
|
||||
path = path[1:]
|
||||
}
|
||||
p.Path = filepath.ToSlash(path)
|
||||
p.Name = d.Name
|
||||
p.Synopsis = d.Text
|
||||
i++
|
||||
}
|
||||
|
||||
return &DirList{maxHeight, list}
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
)
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runDocView,
|
||||
UsageLine: "docview [-mode] [-list|-find]",
|
||||
Short: "golang docview util",
|
||||
Long: `golang docview util`,
|
||||
}
|
||||
|
||||
var goroot = runtime.GOROOT()
|
||||
|
||||
var docViewFind string
|
||||
var docViewList string
|
||||
var docViewMode string
|
||||
|
||||
func init() {
|
||||
Command.Flag.StringVar(&docViewFind, "find", "", "find package list, :pkg flag is best match")
|
||||
Command.Flag.StringVar(&docViewList, "list", "", "Print go packages list [pkg|cmd]")
|
||||
Command.Flag.StringVar(&docViewMode, "mode", "text", "Print mode [text|html|lite]")
|
||||
}
|
||||
|
||||
func runDocView(cmd *command.Command, args []string) error {
|
||||
if docViewFind == "" && docViewList == "" {
|
||||
cmd.Usage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
var template string
|
||||
var info *Info
|
||||
if len(docViewList) > 0 {
|
||||
pkgPath := filepath.Join(goroot, "src", docViewList)
|
||||
if docViewList == "pkg" {
|
||||
_, err := os.Stat(pkgPath)
|
||||
if err != nil {
|
||||
pkgPath = filepath.Join(goroot, "src")
|
||||
}
|
||||
}
|
||||
info = NewListInfo(pkgPath)
|
||||
if info != nil {
|
||||
if docViewList == "pkg" {
|
||||
var filterList []DirEntry
|
||||
for _, v := range info.Dirs.List {
|
||||
if v.Path == "cmd" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(v.Path, "cmd/") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(v.Path, "/testdata") {
|
||||
continue
|
||||
}
|
||||
filterList = append(filterList, v)
|
||||
}
|
||||
info.Dirs.List = filterList
|
||||
} else if docViewList == "cmd" {
|
||||
var filterList []DirEntry
|
||||
for _, v := range info.Dirs.List {
|
||||
if strings.Contains(v.Path, "/") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(v.Path, "internal") {
|
||||
continue
|
||||
}
|
||||
filterList = append(filterList, v)
|
||||
}
|
||||
info.Dirs.List = filterList
|
||||
}
|
||||
}
|
||||
switch docViewMode {
|
||||
case "html":
|
||||
template = listHTML
|
||||
case "lite":
|
||||
template = listLite
|
||||
case "text":
|
||||
template = listText
|
||||
default:
|
||||
template = listText
|
||||
}
|
||||
} else if len(docViewFind) > 0 {
|
||||
dir := NewSourceDir(goroot)
|
||||
info = dir.FindInfo(docViewFind)
|
||||
switch docViewMode {
|
||||
case "html":
|
||||
template = findHTML
|
||||
case "lite":
|
||||
template = findLite
|
||||
case "text":
|
||||
template = findText
|
||||
default:
|
||||
template = findText
|
||||
}
|
||||
}
|
||||
if info == nil {
|
||||
fmt.Fprintf(os.Stderr, "<error>\n")
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
}
|
||||
contents := info.GetPkgList(docViewMode, template)
|
||||
fmt.Fprintf(os.Stdout, "%s", contents)
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
fs FileSystem = OS // the underlying file system
|
||||
)
|
||||
|
||||
// Fake package file and name for commands. Contains the command documentation.
|
||||
const fakePkgFile = "doc.go"
|
||||
const fakePkgName = "documentation"
|
||||
|
||||
func textFmt(w io.Writer, format string, x ...interface{}) {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, x)
|
||||
template.HTMLEscape(w, buf.Bytes())
|
||||
}
|
||||
|
||||
func pathEscFmt(w io.Writer, format string, x ...interface{}) {
|
||||
switch v := x[0].(type) {
|
||||
case []byte:
|
||||
template.HTMLEscape(w, v)
|
||||
case string:
|
||||
template.HTMLEscape(w, []byte(filepath.ToSlash(v)))
|
||||
default:
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, x)
|
||||
template.HTMLEscape(w, buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func htmlEscFmt(w io.Writer, format string, x ...interface{}) {
|
||||
switch v := x[0].(type) {
|
||||
case int:
|
||||
template.HTMLEscape(w, []byte(strconv.Itoa(v)))
|
||||
case []byte:
|
||||
template.HTMLEscape(w, v)
|
||||
case string:
|
||||
template.HTMLEscape(w, []byte(v))
|
||||
default:
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, x)
|
||||
template.HTMLEscape(w, buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// Template formatter for "padding" format.
|
||||
func paddingFmt(w io.Writer, format string, x ...interface{}) {
|
||||
for i := x[0].(int); i > 0; i-- {
|
||||
fmt.Fprint(w, `<td width="25"></td>`)
|
||||
}
|
||||
}
|
||||
|
||||
// Template formatter for "time" format.
|
||||
func timeFmt(w io.Writer, format string, x ...interface{}) {
|
||||
template.HTMLEscape(w, []byte(time.Unix(x[0].(int64)/1e9, 0).String()))
|
||||
}
|
||||
|
||||
var fmap = template.FuncMap{
|
||||
"repeat": strings.Repeat,
|
||||
}
|
||||
|
||||
func readTemplateData(name, data string) *template.Template {
|
||||
return template.Must(template.New(name).Funcs(fmap).Parse(data))
|
||||
}
|
||||
|
||||
func readTemplateFile(name, path string) *template.Template {
|
||||
return template.Must(template.New(name).Funcs(fmap).ParseFiles(path))
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
Find string
|
||||
Best *DirEntry
|
||||
Dirs *DirList
|
||||
}
|
||||
|
||||
type GodocDir struct {
|
||||
pkg *Directory
|
||||
cmd *Directory
|
||||
gopath []*Directory
|
||||
}
|
||||
|
||||
func NewSourceDir(goroot string) *GodocDir {
|
||||
pkgPath := filepath.Join(goroot, "src/pkg")
|
||||
_, err := os.Stat(pkgPath)
|
||||
var cmd *Directory
|
||||
if err != nil {
|
||||
pkgPath = filepath.Join(goroot, "src")
|
||||
} else {
|
||||
cmd = newDirectory(filepath.Join(goroot, "src", "cmd"), nil, -1)
|
||||
}
|
||||
pkg := newDirectory(pkgPath, nil, -1)
|
||||
ctx := build.Default
|
||||
ctx.GOROOT = ""
|
||||
var gopath []*Directory
|
||||
for _, v := range ctx.SrcDirs() {
|
||||
gopath = append(gopath, newDirectory(v, nil, -1))
|
||||
}
|
||||
return &GodocDir{pkg, cmd, gopath}
|
||||
}
|
||||
|
||||
func (dir *GodocDir) FindInfo(name string) *Info {
|
||||
max1, best1, list1 := FindDir(dir.pkg, name)
|
||||
max2, best2, list2 := FindDir(dir.cmd, name)
|
||||
var maxHeight int
|
||||
if max1 >= max2 {
|
||||
maxHeight = max1
|
||||
} else {
|
||||
maxHeight = max2
|
||||
}
|
||||
var best *DirEntry
|
||||
if best1 != nil {
|
||||
best = best1
|
||||
if best2 != nil {
|
||||
list2 = append(list2, *best2)
|
||||
}
|
||||
} else {
|
||||
best = best2
|
||||
}
|
||||
var list []DirEntry
|
||||
list = append(list, list1...)
|
||||
list = append(list, list2...)
|
||||
for _, v := range dir.gopath {
|
||||
max3, best3, list3 := FindDir(v, name)
|
||||
if max3 > maxHeight {
|
||||
maxHeight = max3
|
||||
}
|
||||
if best == nil {
|
||||
best = best3
|
||||
}
|
||||
list = append(list, list3...)
|
||||
}
|
||||
return &Info{name, best, &DirList{maxHeight, list}}
|
||||
}
|
||||
|
||||
func FindDir(dir *Directory, pkgname string) (maxHeight int, best *DirEntry, list []DirEntry) {
|
||||
if dir == nil {
|
||||
return
|
||||
}
|
||||
dirList := dir.listing(true)
|
||||
max := len(dirList.List)
|
||||
maxHeight = dirList.MaxHeight
|
||||
|
||||
for i := 0; i < max; i++ {
|
||||
name := dirList.List[i].Name
|
||||
path := filepath.ToSlash(dirList.List[i].Path)
|
||||
if name == pkgname || path == pkgname {
|
||||
best = &dirList.List[i]
|
||||
} else if strings.Contains(path, pkgname) {
|
||||
list = append(list, dirList.List[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func appendList(list1, list2 []DirEntry) []DirEntry {
|
||||
list := list1
|
||||
max := len(list2)
|
||||
for i := 0; i < max; i++ {
|
||||
list = append(list, list2[i])
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func NewListInfo(root string) *Info {
|
||||
dir := newDirectory(root, nil, -1)
|
||||
if dir == nil {
|
||||
return nil
|
||||
}
|
||||
return &Info{"", nil, dir.listing(true)}
|
||||
}
|
||||
|
||||
func FindPkgInfo(root string, pkgname string) *Info {
|
||||
dir := newDirectory(root, nil, -1)
|
||||
if dir == nil {
|
||||
return nil
|
||||
}
|
||||
dirList := dir.listing(true)
|
||||
if pkgname == "*" {
|
||||
return &Info{pkgname, nil, dirList}
|
||||
}
|
||||
var best DirEntry
|
||||
var list []DirEntry
|
||||
max := len(dirList.List)
|
||||
for i := 0; i < max; i++ {
|
||||
name := dirList.List[i].Name
|
||||
path := filepath.ToSlash(dirList.List[i].Path)
|
||||
if name == pkgname || path == pkgname {
|
||||
best = dirList.List[i]
|
||||
} else if strings.Contains(path, pkgname) {
|
||||
list = append(list, dirList.List[i])
|
||||
}
|
||||
}
|
||||
return &Info{pkgname, &best, &DirList{dirList.MaxHeight, list}}
|
||||
}
|
||||
|
||||
func (info *Info) GetPkgList(name, templateData string) []byte {
|
||||
data := readTemplateData(name, templateData)
|
||||
return applyTemplate(data, "pkglist", info)
|
||||
}
|
||||
|
||||
var listHTML = `<!-- Golang Package List -->
|
||||
<p class="detail">
|
||||
Need more packages? The
|
||||
<a href="http://godashboard.appspot.com/package">Package Dashboard</a>
|
||||
provides a list of <a href="/cmd/goinstall/">goinstallable</a> packages.
|
||||
</p>
|
||||
<h2 id="Subdirectories">Subdirectories</h2>
|
||||
<p>
|
||||
{{with .Dirs}}
|
||||
<p>
|
||||
<table class="layout">
|
||||
<tr>
|
||||
<th align="left" colspan="{{html .MaxHeight}}">Name</th>
|
||||
<td width="25"> </td>
|
||||
<th align="left">Synopsis</th>
|
||||
</tr>
|
||||
{{range .List}}
|
||||
<tr>
|
||||
{{repeat "<td width=\"25\"></td>" .Depth}}
|
||||
<td align="left" colspan="{{html .Height}}"><a href="{{.Path}}">{{html .Name}}</a></td>
|
||||
<td></td>
|
||||
<td align="left">{{html .Synopsis}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</p>
|
||||
{{end}}`
|
||||
|
||||
var listText = `$list
|
||||
{{with .Dirs}}
|
||||
{{range .List}}{{.Path }}
|
||||
{{end}}
|
||||
{{end}}`
|
||||
|
||||
var listLite = `$list{{with .Dirs}}{{range .List}},{{.Path}}{{end}}{{end}}`
|
||||
|
||||
var findHTML = `<!-- Golang Package List -->
|
||||
<p class="detail">
|
||||
Need more packages? The
|
||||
<a href="http://godashboard.appspot.com/package">Package Dashboard</a>
|
||||
provides a list of <a href="/cmd/goinstall/">goinstallable</a> packages.
|
||||
</p>
|
||||
<h2 id="Subdirectories">Subdirectories</h2>
|
||||
<table class="layout">
|
||||
<tr>
|
||||
<th align="left">Best</th>
|
||||
<td width="25"> </td>
|
||||
<th align="left">Synopsis</th>
|
||||
{{with .Best}}
|
||||
<tr>
|
||||
<td align="left"><a href="{{html .Path}}">{{.Path}}</a></td>
|
||||
<td></td>
|
||||
<td align="left">{{html .Synopsis}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{with .Dirs}}
|
||||
<tr>
|
||||
<th align="left">Match</th>
|
||||
<td width="25"> </td>
|
||||
<th align="left">Synopsis</th>
|
||||
</tr>
|
||||
{{range .List}}
|
||||
<tr>
|
||||
<td align="left"><a href="{{html .Path}}">{{.Path}}</a></td>
|
||||
<td></td>
|
||||
<td align="left">{{html .Synopsis}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</p>
|
||||
{{end}}`
|
||||
|
||||
var findText = `$best
|
||||
{{with .Best}}{{.Path}}{{end}}
|
||||
$list
|
||||
{{with .Dirs}}{{range .List}}{{.Path}}
|
||||
{{end}}{{end}}`
|
||||
|
||||
var findLite = `$find,{{with .Best}}{{.Path}}{{end}}{{with .Dirs}}{{range .List}},{{.Path}}{{end}}{{end}}`
|
|
@ -0,0 +1,668 @@
|
|||
// 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.
|
||||
|
||||
// Package doc extracts source code documentation from a Go AST.
|
||||
package docview
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type typeDoc struct {
|
||||
// len(decl.Specs) == 1, and the element type is *ast.TypeSpec
|
||||
// if the type declaration hasn't been seen yet, decl is nil
|
||||
decl *ast.GenDecl
|
||||
// values, factory functions, and methods associated with the type
|
||||
values []*ast.GenDecl // consts and vars
|
||||
factories map[string]*ast.FuncDecl
|
||||
methods map[string]*ast.FuncDecl
|
||||
}
|
||||
|
||||
// docReader accumulates documentation for a single package.
|
||||
// It modifies the AST: Comments (declaration documentation)
|
||||
// that have been collected by the DocReader are set to nil
|
||||
// in the respective AST nodes so that they are not printed
|
||||
// twice (once when printing the documentation and once when
|
||||
// printing the corresponding AST node).
|
||||
//
|
||||
type docReader struct {
|
||||
doc *ast.CommentGroup // package documentation, if any
|
||||
pkgName string
|
||||
showAll bool
|
||||
values []*ast.GenDecl // consts and vars
|
||||
types map[string]*typeDoc
|
||||
funcs map[string]*ast.FuncDecl
|
||||
imports map[string]int
|
||||
bugs []*ast.CommentGroup
|
||||
}
|
||||
|
||||
func (doc *docReader) init(pkgName string, showAll bool) {
|
||||
doc.pkgName = pkgName
|
||||
doc.showAll = showAll
|
||||
doc.imports = make(map[string]int)
|
||||
doc.types = make(map[string]*typeDoc)
|
||||
doc.funcs = make(map[string]*ast.FuncDecl)
|
||||
}
|
||||
|
||||
func (doc *docReader) addDoc(comments *ast.CommentGroup) {
|
||||
if doc.doc == nil {
|
||||
// common case: just one package comment
|
||||
doc.doc = comments
|
||||
return
|
||||
}
|
||||
|
||||
// More than one package comment: Usually there will be only
|
||||
// one file with a package comment, but it's better to collect
|
||||
// all comments than drop them on the floor.
|
||||
// (This code isn't particularly clever - no amortized doubling is
|
||||
// used - but this situation occurs rarely and is not time-critical.)
|
||||
n1 := len(doc.doc.List)
|
||||
n2 := len(comments.List)
|
||||
list := make([]*ast.Comment, n1+1+n2) // + 1 for separator line
|
||||
copy(list, doc.doc.List)
|
||||
list[n1] = &ast.Comment{token.NoPos, "//"} // separator line
|
||||
copy(list[n1+1:], comments.List)
|
||||
doc.doc = &ast.CommentGroup{list}
|
||||
}
|
||||
|
||||
func (doc *docReader) addType(decl *ast.GenDecl) {
|
||||
spec := decl.Specs[0].(*ast.TypeSpec)
|
||||
typ := doc.lookupTypeDoc(spec.Name.Name)
|
||||
// typ should always be != nil since declared types
|
||||
// are always named - be conservative and check
|
||||
if typ != nil {
|
||||
// a type should be added at most once, so typ.decl
|
||||
// should be nil - if it isn't, simply overwrite it
|
||||
typ.decl = decl
|
||||
}
|
||||
}
|
||||
|
||||
func (doc *docReader) lookupTypeDoc(name string) *typeDoc {
|
||||
if name == "" {
|
||||
return nil // no type docs for anonymous types
|
||||
}
|
||||
if tdoc, found := doc.types[name]; found {
|
||||
return tdoc
|
||||
}
|
||||
// type wasn't found - add one without declaration
|
||||
tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[string]*ast.FuncDecl)}
|
||||
doc.types[name] = tdoc
|
||||
return tdoc
|
||||
}
|
||||
|
||||
func docBaseTypeName(typ ast.Expr, showAll bool) string {
|
||||
switch t := typ.(type) {
|
||||
case *ast.Ident:
|
||||
// if the type is not exported, the effect to
|
||||
// a client is as if there were no type name
|
||||
if showAll || t.IsExported() {
|
||||
return t.Name
|
||||
}
|
||||
case *ast.StarExpr:
|
||||
return docBaseTypeName(t.X, showAll)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (doc *docReader) addValue(decl *ast.GenDecl) {
|
||||
// determine if decl should be associated with a type
|
||||
// Heuristic: For each typed entry, determine the type name, if any.
|
||||
// If there is exactly one type name that is sufficiently
|
||||
// frequent, associate the decl with the respective type.
|
||||
domName := ""
|
||||
domFreq := 0
|
||||
prev := ""
|
||||
for _, s := range decl.Specs {
|
||||
if v, ok := s.(*ast.ValueSpec); ok {
|
||||
name := ""
|
||||
switch {
|
||||
case v.Type != nil:
|
||||
// a type is present; determine its name
|
||||
name = docBaseTypeName(v.Type, doc.showAll)
|
||||
case decl.Tok == token.CONST:
|
||||
// no type is present but we have a constant declaration;
|
||||
// use the previous type name (w/o more type information
|
||||
// we cannot handle the case of unnamed variables with
|
||||
// initializer expressions except for some trivial cases)
|
||||
name = prev
|
||||
}
|
||||
if name != "" {
|
||||
// entry has a named type
|
||||
if domName != "" && domName != name {
|
||||
// more than one type name - do not associate
|
||||
// with any type
|
||||
domName = ""
|
||||
break
|
||||
}
|
||||
domName = name
|
||||
domFreq++
|
||||
}
|
||||
prev = name
|
||||
}
|
||||
}
|
||||
|
||||
// determine values list
|
||||
const threshold = 0.75
|
||||
values := &doc.values
|
||||
if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) {
|
||||
// typed entries are sufficiently frequent
|
||||
typ := doc.lookupTypeDoc(domName)
|
||||
if typ != nil {
|
||||
values = &typ.values // associate with that type
|
||||
}
|
||||
}
|
||||
|
||||
*values = append(*values, decl)
|
||||
}
|
||||
|
||||
// Helper function to set the table entry for function f. Makes sure that
|
||||
// at least one f with associated documentation is stored in table, if there
|
||||
// are multiple f's with the same name.
|
||||
func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) {
|
||||
name := f.Name.Name
|
||||
if g, exists := table[name]; exists && g.Doc != nil {
|
||||
// a function with the same name has already been registered;
|
||||
// since it has documentation, assume f is simply another
|
||||
// implementation and ignore it
|
||||
// TODO(gri) consider collecting all functions, or at least
|
||||
// all comments
|
||||
return
|
||||
}
|
||||
// function doesn't exist or has no documentation; use f
|
||||
table[name] = f
|
||||
}
|
||||
|
||||
func (doc *docReader) addFunc(fun *ast.FuncDecl) {
|
||||
name := fun.Name.Name
|
||||
|
||||
// determine if it should be associated with a type
|
||||
if fun.Recv != nil {
|
||||
// method
|
||||
typ := doc.lookupTypeDoc(docBaseTypeName(fun.Recv.List[0].Type, doc.showAll))
|
||||
if typ != nil {
|
||||
// exported receiver type
|
||||
setFunc(typ.methods, fun)
|
||||
}
|
||||
// otherwise don't show the method
|
||||
// TODO(gri): There may be exported methods of non-exported types
|
||||
// that can be called because of exported values (consts, vars, or
|
||||
// function results) of that type. Could determine if that is the
|
||||
// case and then show those methods in an appropriate section.
|
||||
return
|
||||
}
|
||||
|
||||
// perhaps a factory function
|
||||
// determine result type, if any
|
||||
if fun.Type.Results.NumFields() >= 1 {
|
||||
res := fun.Type.Results.List[0]
|
||||
if len(res.Names) <= 1 {
|
||||
// exactly one (named or anonymous) result associated
|
||||
// with the first type in result signature (there may
|
||||
// be more than one result)
|
||||
tname := docBaseTypeName(res.Type, doc.showAll)
|
||||
typ := doc.lookupTypeDoc(tname)
|
||||
if typ != nil {
|
||||
// named and exported result type
|
||||
|
||||
// Work-around for failure of heuristic: In package os
|
||||
// too many functions are considered factory functions
|
||||
// for the Error type. Eliminate manually for now as
|
||||
// this appears to be the only important case in the
|
||||
// current library where the heuristic fails.
|
||||
if doc.pkgName == "os" && tname == "Error" &&
|
||||
name != "NewError" && name != "NewSyscallError" {
|
||||
// not a factory function for os.Error
|
||||
setFunc(doc.funcs, fun) // treat as ordinary function
|
||||
return
|
||||
}
|
||||
|
||||
setFunc(typ.factories, fun)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ordinary function
|
||||
setFunc(doc.funcs, fun)
|
||||
}
|
||||
|
||||
func (doc *docReader) addDecl(decl ast.Decl) {
|
||||
switch d := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
if len(d.Specs) > 0 {
|
||||
switch d.Tok {
|
||||
case token.IMPORT:
|
||||
// imports are handled individually
|
||||
for _, spec := range d.Specs {
|
||||
if s, ok := spec.(*ast.ImportSpec); ok {
|
||||
if import_, err := strconv.Unquote(s.Path.Value); err == nil {
|
||||
doc.imports[import_] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
case token.CONST, token.VAR:
|
||||
// constants and variables are always handled as a group
|
||||
doc.addValue(d)
|
||||
case token.TYPE:
|
||||
// types are handled individually
|
||||
for _, spec := range d.Specs {
|
||||
// make a (fake) GenDecl node for this TypeSpec
|
||||
// (we need to do this here - as opposed to just
|
||||
// for printing - so we don't lose the GenDecl
|
||||
// documentation)
|
||||
//
|
||||
// TODO(gri): Consider just collecting the TypeSpec
|
||||
// node (and copy in the GenDecl.doc if there is no
|
||||
// doc in the TypeSpec - this is currently done in
|
||||
// makeTypeDocs below). Simpler data structures, but
|
||||
// would lose GenDecl documentation if the TypeSpec
|
||||
// has documentation as well.
|
||||
doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos})
|
||||
// A new GenDecl node is created, no need to nil out d.Doc.
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
doc.addFunc(d)
|
||||
}
|
||||
}
|
||||
|
||||
func copyCommentList(list []*ast.Comment) []*ast.Comment {
|
||||
return append([]*ast.Comment(nil), list...)
|
||||
}
|
||||
|
||||
var (
|
||||
bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BUG(uid):
|
||||
bug_content = regexp.MustCompile("[^ \n\r\t]+") // at least one non-whitespace char
|
||||
)
|
||||
|
||||
// addFile adds the AST for a source file to the docReader.
|
||||
// Adding the same AST multiple times is a no-op.
|
||||
//
|
||||
func (doc *docReader) addFile(src *ast.File) {
|
||||
// add package documentation
|
||||
if src.Doc != nil {
|
||||
doc.addDoc(src.Doc)
|
||||
src.Doc = nil // doc consumed - remove from ast.File node
|
||||
}
|
||||
|
||||
// add all declarations
|
||||
for _, decl := range src.Decls {
|
||||
doc.addDecl(decl)
|
||||
}
|
||||
|
||||
// collect BUG(...) comments
|
||||
for _, c := range src.Comments {
|
||||
text := c.List[0].Text
|
||||
if m := bug_markers.FindStringIndex(text); m != nil {
|
||||
// found a BUG comment; maybe empty
|
||||
if btxt := text[m[1]:]; bug_content.MatchString(btxt) {
|
||||
// non-empty BUG comment; collect comment without BUG prefix
|
||||
list := copyCommentList(c.List)
|
||||
list[0].Text = text[m[1]:]
|
||||
doc.bugs = append(doc.bugs, &ast.CommentGroup{list})
|
||||
}
|
||||
}
|
||||
}
|
||||
src.Comments = nil // consumed unassociated comments - remove from ast.File node
|
||||
}
|
||||
|
||||
func NewFileDoc(file *ast.File, showAll bool) *PackageDoc {
|
||||
var r docReader
|
||||
r.init(file.Name.Name, showAll)
|
||||
r.addFile(file)
|
||||
return r.newDoc("", nil)
|
||||
}
|
||||
|
||||
func NewPackageDoc(pkg *ast.Package, importpath string, showAll bool) *PackageDoc {
|
||||
var r docReader
|
||||
r.init(pkg.Name, showAll)
|
||||
filenames := make([]string, len(pkg.Files))
|
||||
i := 0
|
||||
for filename, f := range pkg.Files {
|
||||
r.addFile(f)
|
||||
filenames[i] = filename
|
||||
i++
|
||||
}
|
||||
return r.newDoc(importpath, filenames)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Conversion to external representation
|
||||
|
||||
// ValueDoc is the documentation for a group of declared
|
||||
// values, either vars or consts.
|
||||
//
|
||||
type ValueDoc struct {
|
||||
Doc string
|
||||
Decl *ast.GenDecl
|
||||
order int
|
||||
}
|
||||
|
||||
type sortValueDoc []*ValueDoc
|
||||
|
||||
func (p sortValueDoc) Len() int { return len(p) }
|
||||
func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
func declName(d *ast.GenDecl) string {
|
||||
if len(d.Specs) != 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch v := d.Specs[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
return v.Names[0].Name
|
||||
case *ast.TypeSpec:
|
||||
return v.Name.Name
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p sortValueDoc) Less(i, j int) bool {
|
||||
// sort by name
|
||||
// pull blocks (name = "") up to top
|
||||
// in original order
|
||||
if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj {
|
||||
return ni < nj
|
||||
}
|
||||
return p[i].order < p[j].order
|
||||
}
|
||||
|
||||
func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc {
|
||||
d := make([]*ValueDoc, len(list)) // big enough in any case
|
||||
n := 0
|
||||
for i, decl := range list {
|
||||
if decl.Tok == tok {
|
||||
d[n] = &ValueDoc{decl.Doc.Text(), decl, i}
|
||||
n++
|
||||
decl.Doc = nil // doc consumed - removed from AST
|
||||
}
|
||||
}
|
||||
d = d[0:n]
|
||||
sort.Sort(sortValueDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
// FuncDoc is the documentation for a func declaration,
|
||||
// either a top-level function or a method function.
|
||||
//
|
||||
type FuncDoc struct {
|
||||
Doc string
|
||||
Recv ast.Expr // TODO(rsc): Would like string here
|
||||
Name string
|
||||
Decl *ast.FuncDecl
|
||||
}
|
||||
|
||||
type sortFuncDoc []*FuncDoc
|
||||
|
||||
func (p sortFuncDoc) Len() int { return len(p) }
|
||||
func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name }
|
||||
|
||||
func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc {
|
||||
d := make([]*FuncDoc, len(m))
|
||||
i := 0
|
||||
for _, f := range m {
|
||||
doc := new(FuncDoc)
|
||||
doc.Doc = f.Doc.Text()
|
||||
f.Doc = nil // doc consumed - remove from ast.FuncDecl node
|
||||
if f.Recv != nil {
|
||||
doc.Recv = f.Recv.List[0].Type
|
||||
}
|
||||
doc.Name = f.Name.Name
|
||||
doc.Decl = f
|
||||
d[i] = doc
|
||||
i++
|
||||
}
|
||||
sort.Sort(sortFuncDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
// TypeDoc is the documentation for a declared type.
|
||||
// Consts and Vars are sorted lists of constants and variables of (mostly) that type.
|
||||
// Factories is a sorted list of factory functions that return that type.
|
||||
// Methods is a sorted list of method functions on that type.
|
||||
type TypeDoc struct {
|
||||
Doc string
|
||||
Type *ast.TypeSpec
|
||||
Consts []*ValueDoc
|
||||
Vars []*ValueDoc
|
||||
Funcs []*FuncDoc
|
||||
Methods []*FuncDoc
|
||||
Decl *ast.GenDecl
|
||||
order int
|
||||
}
|
||||
|
||||
type sortTypeDoc []*TypeDoc
|
||||
|
||||
func (p sortTypeDoc) Len() int { return len(p) }
|
||||
func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p sortTypeDoc) Less(i, j int) bool {
|
||||
// sort by name
|
||||
// pull blocks (name = "") up to top
|
||||
// in original order
|
||||
if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj {
|
||||
return ni < nj
|
||||
}
|
||||
return p[i].order < p[j].order
|
||||
}
|
||||
|
||||
// NOTE(rsc): This would appear not to be correct for type ( )
|
||||
// blocks, but the doc extractor above has split them into
|
||||
// individual declarations.
|
||||
func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc {
|
||||
d := make([]*TypeDoc, len(m))
|
||||
i := 0
|
||||
for _, old := range m {
|
||||
// all typeDocs should have a declaration associated with
|
||||
// them after processing an entire package - be conservative
|
||||
// and check
|
||||
if decl := old.decl; decl != nil {
|
||||
typespec := decl.Specs[0].(*ast.TypeSpec)
|
||||
t := new(TypeDoc)
|
||||
doc := typespec.Doc
|
||||
typespec.Doc = nil // doc consumed - remove from ast.TypeSpec node
|
||||
if doc == nil {
|
||||
// no doc associated with the spec, use the declaration doc, if any
|
||||
doc = decl.Doc
|
||||
}
|
||||
decl.Doc = nil // doc consumed - remove from ast.Decl node
|
||||
t.Doc = doc.Text()
|
||||
t.Type = typespec
|
||||
t.Consts = makeValueDocs(old.values, token.CONST)
|
||||
t.Vars = makeValueDocs(old.values, token.VAR)
|
||||
t.Funcs = makeFuncDocs(old.factories)
|
||||
t.Methods = makeFuncDocs(old.methods)
|
||||
t.Decl = old.decl
|
||||
t.order = i
|
||||
d[i] = t
|
||||
i++
|
||||
} else {
|
||||
// no corresponding type declaration found - move any associated
|
||||
// values, factory functions, and methods back to the top-level
|
||||
// so that they are not lost (this should only happen if a package
|
||||
// file containing the explicit type declaration is missing or if
|
||||
// an unqualified type name was used after a "." import)
|
||||
// 1) move values
|
||||
doc.values = append(doc.values, old.values...)
|
||||
// 2) move factory functions
|
||||
for name, f := range old.factories {
|
||||
doc.funcs[name] = f
|
||||
}
|
||||
// 3) move methods
|
||||
for name, f := range old.methods {
|
||||
// don't overwrite functions with the same name
|
||||
if _, found := doc.funcs[name]; !found {
|
||||
doc.funcs[name] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
d = d[0:i] // some types may have been ignored
|
||||
sort.Sort(sortTypeDoc(d))
|
||||
return d
|
||||
}
|
||||
|
||||
func makeBugDocs(list []*ast.CommentGroup) []string {
|
||||
d := make([]string, len(list))
|
||||
for i, g := range list {
|
||||
d[i] = g.Text()
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// PackageDoc is the documentation for an entire package.
|
||||
//
|
||||
type PackageDoc struct {
|
||||
PackageName string
|
||||
ImportPath string
|
||||
Imports []string
|
||||
Filenames []string
|
||||
Doc string
|
||||
Consts []*ValueDoc
|
||||
Types []*TypeDoc
|
||||
Vars []*ValueDoc
|
||||
Funcs []*FuncDoc
|
||||
Bugs []string
|
||||
}
|
||||
|
||||
// newDoc returns the accumulated documentation for the package.
|
||||
//
|
||||
func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc {
|
||||
p := new(PackageDoc)
|
||||
p.PackageName = doc.pkgName
|
||||
p.ImportPath = importpath
|
||||
sort.Strings(filenames)
|
||||
p.Filenames = filenames
|
||||
p.Doc = doc.doc.Text()
|
||||
p.Imports = sortedKeys(doc.imports)
|
||||
// makeTypeDocs may extend the list of doc.values and
|
||||
// doc.funcs and thus must be called before any other
|
||||
// function consuming those lists
|
||||
p.Types = doc.makeTypeDocs(doc.types)
|
||||
p.Consts = makeValueDocs(doc.values, token.CONST)
|
||||
p.Vars = makeValueDocs(doc.values, token.VAR)
|
||||
p.Funcs = makeFuncDocs(doc.funcs)
|
||||
p.Bugs = makeBugDocs(doc.bugs)
|
||||
return p
|
||||
}
|
||||
|
||||
func sortedKeys(m map[string]int) []string {
|
||||
list := make([]string, len(m))
|
||||
i := 0
|
||||
for key := range m {
|
||||
list[i] = key
|
||||
i++
|
||||
}
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Filtering by name
|
||||
|
||||
type Filter func(string) bool
|
||||
|
||||
func matchFields(fields *ast.FieldList, f Filter) bool {
|
||||
if fields != nil {
|
||||
for _, field := range fields.List {
|
||||
for _, name := range field.Names {
|
||||
if f(name.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchDecl(d *ast.GenDecl, f Filter) bool {
|
||||
for _, d := range d.Specs {
|
||||
switch v := d.(type) {
|
||||
case *ast.ValueSpec:
|
||||
for _, name := range v.Names {
|
||||
if f(name.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
if f(v.Name.Name) {
|
||||
return true
|
||||
}
|
||||
switch t := v.Type.(type) {
|
||||
case *ast.StructType:
|
||||
if matchFields(t.Fields, f) {
|
||||
return true
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
if matchFields(t.Methods, f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func filterValueDocs(a []*ValueDoc, f Filter) []*ValueDoc {
|
||||
w := 0
|
||||
for _, vd := range a {
|
||||
if matchDecl(vd.Decl, f) {
|
||||
a[w] = vd
|
||||
w++
|
||||
}
|
||||
}
|
||||
return a[0:w]
|
||||
}
|
||||
|
||||
func filterFuncDocs(a []*FuncDoc, f Filter) []*FuncDoc {
|
||||
w := 0
|
||||
for _, fd := range a {
|
||||
if f(fd.Name) {
|
||||
a[w] = fd
|
||||
w++
|
||||
}
|
||||
}
|
||||
return a[0:w]
|
||||
}
|
||||
|
||||
func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc {
|
||||
w := 0
|
||||
for _, td := range a {
|
||||
n := 0 // number of matches
|
||||
if matchDecl(td.Decl, f) {
|
||||
n = 1
|
||||
} else {
|
||||
// type name doesn't match, but we may have matching consts, vars, factories or methods
|
||||
td.Consts = filterValueDocs(td.Consts, f)
|
||||
td.Vars = filterValueDocs(td.Vars, f)
|
||||
td.Funcs = filterFuncDocs(td.Funcs, f)
|
||||
td.Methods = filterFuncDocs(td.Methods, f)
|
||||
n += len(td.Consts) + len(td.Vars) + len(td.Funcs) + len(td.Methods)
|
||||
}
|
||||
if n > 0 {
|
||||
a[w] = td
|
||||
w++
|
||||
}
|
||||
}
|
||||
return a[0:w]
|
||||
}
|
||||
|
||||
// Filter eliminates documentation for names that don't pass through the filter f.
|
||||
// TODO: Recognize "Type.Method" as a name.
|
||||
//
|
||||
func (p *PackageDoc) Filter(f Filter) {
|
||||
p.Consts = filterValueDocs(p.Consts, f)
|
||||
p.Vars = filterValueDocs(p.Vars, f)
|
||||
p.Types = filterTypeDocs(p.Types, f)
|
||||
p.Funcs = filterFuncDocs(p.Funcs, f)
|
||||
p.Doc = "" // don't show top-level package doc
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// 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 file defines types for abstract file system access and
|
||||
// provides an implementation accessing the file system of the
|
||||
// underlying OS.
|
||||
|
||||
package docview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
// The FileSystem interface specifies the methods godoc is using
|
||||
// to access the file system for which it serves documentation.
|
||||
type FileSystem interface {
|
||||
Open(path string) (io.ReadCloser, error)
|
||||
Lstat(path string) (os.FileInfo, error)
|
||||
Stat(path string) (os.FileInfo, error)
|
||||
ReadDir(path string) ([]os.FileInfo, error)
|
||||
}
|
||||
|
||||
// ReadFile reads the file named by path from fs and returns the contents.
|
||||
func ReadFile(fs FileSystem, path string) ([]byte, error) {
|
||||
rc, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rc.Close()
|
||||
return ioutil.ReadAll(rc)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// OS-specific FileSystem implementation
|
||||
|
||||
var OS FileSystem = osFS{}
|
||||
|
||||
// osFS is the OS-specific implementation of FileSystem
|
||||
type osFS struct{}
|
||||
|
||||
func (osFS) Open(path string) (io.ReadCloser, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return nil, fmt.Errorf("Open: %s is a directory", path)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (osFS) Lstat(path string) (os.FileInfo, error) {
|
||||
return os.Lstat(path)
|
||||
}
|
||||
|
||||
func (osFS) Stat(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
|
||||
func (osFS) ReadDir(path string) ([]os.FileInfo, error) {
|
||||
return ioutil.ReadDir(path) // is sorted
|
||||
}
|
|
@ -0,0 +1,609 @@
|
|||
// Copyright 2013 The rspace Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Doc is a simple document printer that produces the doc comments for its
|
||||
// argument symbols, plus a link to the full documentation and a pointer to
|
||||
// the source. It has a more Go-like UI than godoc. It can also search for
|
||||
// symbols by looking in all packages, and case is ignored. For instance:
|
||||
// doc isupper
|
||||
// will find unicode.IsUpper.
|
||||
//
|
||||
// The -pkg flag retrieves package-level doc comments only.
|
||||
//
|
||||
// Usage:
|
||||
// doc pkg.name # "doc io.Writer"
|
||||
// doc pkg name # "doc fmt Printf"
|
||||
// doc name # "doc isupper" (finds unicode.IsUpper)
|
||||
// doc -pkg pkg # "doc fmt"
|
||||
//
|
||||
// The pkg is the last element of the package path;
|
||||
// no slashes (ast.Node not go/ast.Node).
|
||||
//
|
||||
// Flags
|
||||
// -c(onst) -f(unc) -i(nterface) -m(ethod) -s(truct) -t(ype) -v(ar)
|
||||
// restrict hits to declarations of the corresponding kind.
|
||||
// Flags
|
||||
// -doc -src -url
|
||||
// restrict printing to the documentation, source path, or godoc URL.
|
||||
package finddoc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
_ "golang.org/x/tools/go/gcimporter"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
const usageDoc = `Find documentation for names.
|
||||
usage:
|
||||
doc pkg.name # "doc io.Writer"
|
||||
doc pkg name # "doc fmt Printf"
|
||||
doc name # "doc isupper" finds unicode.IsUpper
|
||||
doc -pkg pkg # "doc fmt"
|
||||
doc -r expr # "doc -r '.*exported'"
|
||||
pkg is the last component of any package, e.g. fmt, parser
|
||||
name is the name of an exported symbol; case is ignored in matches.
|
||||
|
||||
The name may also be a regular expression to select which names
|
||||
to match. In regular expression searches, case is ignored and
|
||||
the pattern must match the entire name, so ".?print" will match
|
||||
Print, Fprint and Sprint but not Fprintf.
|
||||
|
||||
Flags
|
||||
-c(onst) -f(unc) -i(nterface) -m(ethod) -s(truct) -t(ype) -v(ar)
|
||||
restrict hits to declarations of the corresponding kind.
|
||||
Flags
|
||||
-doc -src -url
|
||||
restrict printing to the documentation, source path, or godoc URL.
|
||||
Flag
|
||||
-r
|
||||
takes a single argument (no package), a name or regular expression
|
||||
to search for in all packages.
|
||||
`
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runDoc,
|
||||
UsageLine: "finddoc [pkg.name|pkg name|-pkg name]",
|
||||
Short: "golang doc lookup",
|
||||
Long: usageDoc,
|
||||
}
|
||||
|
||||
var (
|
||||
// If none is set, all are set.
|
||||
docFlag bool
|
||||
srcFlag bool
|
||||
urlFlag bool
|
||||
regexpFlag bool
|
||||
matchWordFlag bool
|
||||
matchCaseFlag bool
|
||||
constantFlag bool
|
||||
functionFlag bool
|
||||
interfaceFlag bool
|
||||
methodFlag bool
|
||||
packageFlag bool
|
||||
structFlag bool
|
||||
typeFlag bool
|
||||
variableFlag bool
|
||||
urlHeadTag string
|
||||
)
|
||||
|
||||
func init() {
|
||||
Command.Flag.BoolVar(&docFlag, "doc", false, "restrict output to documentation only")
|
||||
Command.Flag.BoolVar(&srcFlag, "src", false, "restrict output to source file only")
|
||||
Command.Flag.BoolVar(&urlFlag, "url", false, "restrict output to godoc URL only")
|
||||
Command.Flag.BoolVar(®expFlag, "r", false, "single argument is a regular expression for a name")
|
||||
Command.Flag.BoolVar(&matchWordFlag, "word", false, "search match whole word")
|
||||
Command.Flag.BoolVar(&matchCaseFlag, "case", false, "search match case")
|
||||
|
||||
Command.Flag.BoolVar(&constantFlag, "const", false, "show doc for consts only")
|
||||
Command.Flag.BoolVar(&functionFlag, "func", false, "show doc for funcs only")
|
||||
Command.Flag.BoolVar(&interfaceFlag, "interface", false, "show doc for interfaces only")
|
||||
Command.Flag.BoolVar(&methodFlag, "method", false, "show doc for methods only")
|
||||
Command.Flag.BoolVar(&packageFlag, "package", false, "show top-level package doc only")
|
||||
Command.Flag.BoolVar(&structFlag, "struct", false, "show doc for structs only")
|
||||
Command.Flag.BoolVar(&typeFlag, "type", false, "show doc for types only")
|
||||
Command.Flag.BoolVar(&variableFlag, "var", false, "show doc for vars only")
|
||||
|
||||
Command.Flag.BoolVar(&constantFlag, "c", false, "alias for -const")
|
||||
Command.Flag.BoolVar(&functionFlag, "f", false, "alias for -func")
|
||||
Command.Flag.BoolVar(&interfaceFlag, "i", false, "alias for -interface")
|
||||
Command.Flag.BoolVar(&methodFlag, "m", false, "alias for -method")
|
||||
Command.Flag.BoolVar(&packageFlag, "pkg", false, "alias for -package")
|
||||
Command.Flag.BoolVar(&structFlag, "s", false, "alias for -struct")
|
||||
Command.Flag.BoolVar(&typeFlag, "t", false, "alias for -type")
|
||||
Command.Flag.BoolVar(&variableFlag, "v", false, "alias for -var")
|
||||
|
||||
Command.Flag.StringVar(&urlHeadTag, "urltag", "", "url head tag, liteide provate")
|
||||
}
|
||||
|
||||
func runDoc(cmd *command.Command, args []string) error {
|
||||
if !(constantFlag || functionFlag || interfaceFlag || methodFlag || packageFlag || structFlag || typeFlag || variableFlag) { // none set
|
||||
constantFlag = true
|
||||
functionFlag = true
|
||||
methodFlag = true
|
||||
// Not package! It's special.
|
||||
typeFlag = true
|
||||
variableFlag = true
|
||||
}
|
||||
if !(docFlag || srcFlag || urlFlag) {
|
||||
docFlag = true
|
||||
srcFlag = true
|
||||
urlFlag = true
|
||||
}
|
||||
var pkg, name string
|
||||
switch len(args) {
|
||||
case 1:
|
||||
if packageFlag {
|
||||
pkg = args[0]
|
||||
} else if regexpFlag {
|
||||
name = args[0]
|
||||
} else if strings.Contains(args[0], ".") {
|
||||
pkg, name = split(args[0])
|
||||
} else {
|
||||
name = args[0]
|
||||
}
|
||||
case 2:
|
||||
if packageFlag {
|
||||
cmd.Usage()
|
||||
}
|
||||
pkg, name = args[0], args[1]
|
||||
default:
|
||||
cmd.Usage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if strings.Contains(pkg, "/") {
|
||||
fmt.Fprintf(os.Stderr, "doc: package name cannot contain slash (TODO)\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
for _, path := range Paths(pkg) {
|
||||
lookInDirectory(path, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var slash = string(filepath.Separator)
|
||||
var slashDot = string(filepath.Separator) + "."
|
||||
var goRootSrcPkg = filepath.Join(runtime.GOROOT(), "src", "pkg")
|
||||
var goRootSrcCmd = filepath.Join(runtime.GOROOT(), "src", "cmd")
|
||||
var goPaths = SplitGopath()
|
||||
|
||||
func split(arg string) (pkg, name string) {
|
||||
dot := strings.IndexRune(arg, '.') // We know there's one there.
|
||||
return arg[0:dot], arg[dot+1:]
|
||||
}
|
||||
|
||||
func Paths(pkg string) []string {
|
||||
pkgs := pathsFor(runtime.GOROOT(), pkg)
|
||||
for _, root := range goPaths {
|
||||
pkgs = append(pkgs, pathsFor(root, pkg)...)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func SplitGopath() []string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(gopath, string(os.PathListSeparator))
|
||||
}
|
||||
|
||||
// pathsFor recursively walks the tree looking for possible directories for the package:
|
||||
// those whose basename is pkg.
|
||||
func pathsFor(root, pkg string) []string {
|
||||
root = path.Join(root, "src")
|
||||
pkgPaths := make([]string, 0, 10)
|
||||
visit := func(pathName string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// One package per directory. Ignore the files themselves.
|
||||
if !f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
// No .hg or other dot nonsense please.
|
||||
if strings.Contains(pathName, slashDot) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
// Is the last element of the path correct
|
||||
if pkg == "" || filepath.Base(pathName) == pkg {
|
||||
pkgPaths = append(pkgPaths, pathName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
filepath.Walk(root, visit)
|
||||
return pkgPaths
|
||||
}
|
||||
|
||||
// lookInDirectory looks in the package (if any) in the directory for the named exported identifier.
|
||||
func lookInDirectory(directory, name string) {
|
||||
fset := token.NewFileSet()
|
||||
pkgs, _ := parser.ParseDir(fset, directory, nil, parser.ParseComments) // Ignore the error.
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Name == "main" || strings.HasSuffix(pkg.Name, "_test") {
|
||||
continue
|
||||
}
|
||||
doPackage(pkg, fset, name)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
fset *token.FileSet
|
||||
name string // Name of file.
|
||||
ident string // Identifier we are searching for.
|
||||
lowerIdent string // lower ident
|
||||
regexp *regexp.Regexp
|
||||
pathPrefix string // Prefix from GOROOT/GOPATH.
|
||||
urlPrefix string // Start of corresponding URL for golang.org or godoc.org.
|
||||
file *ast.File
|
||||
comments ast.CommentMap
|
||||
defs map[*ast.Ident]types.Object
|
||||
doPrint bool
|
||||
found bool
|
||||
allFiles []*File // All files in the package.
|
||||
}
|
||||
|
||||
// doPackage analyzes the single package constructed from the named files, looking for
|
||||
// the definition of ident.
|
||||
func doPackage(pkg *ast.Package, fset *token.FileSet, ident string) {
|
||||
var files []*File
|
||||
found := false
|
||||
for name, astFile := range pkg.Files {
|
||||
if packageFlag && astFile.Doc == nil {
|
||||
continue
|
||||
}
|
||||
file := &File{
|
||||
fset: fset,
|
||||
name: name,
|
||||
ident: ident,
|
||||
lowerIdent: strings.ToLower(ident),
|
||||
file: astFile,
|
||||
comments: ast.NewCommentMap(fset, astFile, astFile.Comments),
|
||||
}
|
||||
if regexpFlag && regexp.QuoteMeta(ident) != ident {
|
||||
// It's a regular expression.
|
||||
var err error
|
||||
file.regexp, err = regexp.Compile("^(?i:" + ident + ")$")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "regular expression `%s`:", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(name, goRootSrcPkg):
|
||||
file.urlPrefix = "http://golang.org/pkg"
|
||||
file.pathPrefix = goRootSrcPkg
|
||||
case strings.HasPrefix(name, goRootSrcCmd):
|
||||
file.urlPrefix = "http://golang.org/cmd"
|
||||
file.pathPrefix = goRootSrcCmd
|
||||
default:
|
||||
file.urlPrefix = "http://godoc.org"
|
||||
for _, path := range goPaths {
|
||||
p := filepath.Join(path, "src")
|
||||
if strings.HasPrefix(name, p) {
|
||||
file.pathPrefix = p
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
file.urlPrefix = urlHeadTag + file.urlPrefix
|
||||
files = append(files, file)
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
file.doPrint = false
|
||||
if packageFlag {
|
||||
file.pkgComments()
|
||||
} else {
|
||||
ast.Walk(file, file.file)
|
||||
if file.found {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
// By providing the Context 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{
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
}
|
||||
path := ""
|
||||
var astFiles []*ast.File
|
||||
for name, astFile := range pkg.Files {
|
||||
if path == "" {
|
||||
path = name
|
||||
}
|
||||
astFiles = append(astFiles, astFile)
|
||||
}
|
||||
config.Check(path, fset, astFiles, info) // Ignore errors.
|
||||
|
||||
// We need to search all files for methods, so record the full list in each file.
|
||||
for _, file := range files {
|
||||
file.allFiles = files
|
||||
}
|
||||
for _, file := range files {
|
||||
file.doPrint = true
|
||||
file.defs = info.Defs
|
||||
if packageFlag {
|
||||
file.pkgComments()
|
||||
} else {
|
||||
ast.Walk(file, file.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements the ast.Visitor interface.
|
||||
func (f *File) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
// Variables, constants, types.
|
||||
for _, spec := range n.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.ValueSpec:
|
||||
if constantFlag && n.Tok == token.CONST || variableFlag && n.Tok == token.VAR {
|
||||
for _, ident := range spec.Names {
|
||||
if f.match(ident.Name) {
|
||||
f.printNode(n, ident, f.nameURL(ident.Name))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
// If there is only one Spec, there are probably no parens and the
|
||||
// comment we want appears before the type keyword, bound to
|
||||
// the GenDecl. If the Specs are parenthesized, the comment we want
|
||||
// is bound to the Spec. Hence we dig into the GenDecl to the Spec,
|
||||
// but only if there are no parens.
|
||||
node := ast.Node(n)
|
||||
if n.Lparen.IsValid() {
|
||||
node = spec
|
||||
}
|
||||
if f.match(spec.Name.Name) {
|
||||
if typeFlag {
|
||||
f.printNode(node, spec.Name, f.nameURL(spec.Name.Name))
|
||||
} else {
|
||||
switch spec.Type.(type) {
|
||||
case *ast.InterfaceType:
|
||||
if interfaceFlag {
|
||||
f.printNode(node, spec.Name, f.nameURL(spec.Name.Name))
|
||||
}
|
||||
case *ast.StructType:
|
||||
if structFlag {
|
||||
f.printNode(node, spec.Name, f.nameURL(spec.Name.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
if f.doPrint && f.defs[spec.Name] != nil && f.defs[spec.Name].Type() != nil {
|
||||
ms := types.NewMethodSet(f.defs[spec.Name].Type()) //.Type().MethodSet()
|
||||
if ms.Len() == 0 {
|
||||
ms = types.NewMethodSet(types.NewPointer(f.defs[spec.Name].Type())) //.MethodSet()
|
||||
}
|
||||
f.methodSet(ms)
|
||||
}
|
||||
}
|
||||
case *ast.ImportSpec:
|
||||
continue // Don't care.
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
// Methods, top-level functions.
|
||||
if f.match(n.Name.Name) {
|
||||
n.Body = nil // Do not print the function body.
|
||||
if methodFlag && n.Recv != nil {
|
||||
f.printNode(n, n.Name, f.methodURL(n.Recv.List[0].Type, n.Name.Name))
|
||||
} else if functionFlag && n.Recv == nil {
|
||||
f.printNode(n, n.Name, f.nameURL(n.Name.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *File) match(name string) bool {
|
||||
// name must be exported.
|
||||
if !ast.IsExported(name) {
|
||||
return false
|
||||
}
|
||||
if f.regexp == nil {
|
||||
if matchWordFlag {
|
||||
if matchCaseFlag {
|
||||
return name == f.ident
|
||||
}
|
||||
return strings.ToLower(name) == f.lowerIdent
|
||||
} else {
|
||||
if matchCaseFlag {
|
||||
return strings.Contains(name, f.ident)
|
||||
}
|
||||
return strings.Contains(strings.ToLower(name), f.lowerIdent)
|
||||
}
|
||||
}
|
||||
return f.regexp.MatchString(name)
|
||||
}
|
||||
|
||||
func (f *File) printNode(node, ident ast.Node, url string) {
|
||||
if !f.doPrint {
|
||||
f.found = true
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s%s%s", url, f.sourcePos(f.fset.Position(ident.Pos())), f.docs(node))
|
||||
}
|
||||
|
||||
func (f *File) docs(node ast.Node) []byte {
|
||||
if !docFlag {
|
||||
return nil
|
||||
}
|
||||
commentedNode := printer.CommentedNode{Node: node}
|
||||
if comments := f.comments.Filter(node).Comments(); comments != nil {
|
||||
commentedNode.Comments = comments
|
||||
}
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, f.fset, &commentedNode)
|
||||
b.Write([]byte("\n\n")) // Add a blank line between entries if we print documentation.
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func (f *File) pkgComments() {
|
||||
doc := f.file.Doc
|
||||
if doc == nil {
|
||||
return
|
||||
}
|
||||
url := ""
|
||||
if urlFlag {
|
||||
url = f.packageURL() + "\n"
|
||||
}
|
||||
docText := ""
|
||||
if docFlag {
|
||||
docText = fmt.Sprintf("package %s\n%s\n\n", f.file.Name.Name, doc.Text())
|
||||
}
|
||||
fmt.Printf("%s%s%s", url, f.sourcePos(f.fset.Position(doc.Pos())), docText)
|
||||
}
|
||||
|
||||
func (f *File) packageURL() string {
|
||||
s := strings.TrimPrefix(f.name, f.pathPrefix)
|
||||
// Now we have a path with a final file name. Drop it.
|
||||
if i := strings.LastIndex(s, slash); i > 0 {
|
||||
s = s[:i+1]
|
||||
}
|
||||
return f.urlPrefix + s
|
||||
}
|
||||
|
||||
func (f *File) packageName() string {
|
||||
s := strings.TrimPrefix(f.name, f.pathPrefix)
|
||||
// Now we have a path with a final file name. Drop it.
|
||||
if i := strings.LastIndex(s, slash); i > 0 {
|
||||
s = s[:i+1]
|
||||
}
|
||||
s = strings.Trim(s, slash)
|
||||
return filepath.ToSlash(s)
|
||||
}
|
||||
|
||||
func (f *File) sourcePos(posn token.Position) string {
|
||||
if !srcFlag {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s:%d:\n", posn.Filename, posn.Line)
|
||||
}
|
||||
|
||||
func (f *File) nameURL(name string) string {
|
||||
if !urlFlag {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s#%s\n", f.packageURL(), name)
|
||||
}
|
||||
|
||||
func (f *File) methodURL(typ ast.Expr, name string) string {
|
||||
if !urlFlag {
|
||||
return ""
|
||||
}
|
||||
var b bytes.Buffer
|
||||
printer.Fprint(&b, f.fset, typ)
|
||||
typeName := b.Bytes()
|
||||
if len(typeName) > 0 && typeName[0] == '*' {
|
||||
typeName = typeName[1:]
|
||||
}
|
||||
return fmt.Sprintf("%s#%s.%s\n", f.packageURL(), typeName, name)
|
||||
}
|
||||
|
||||
// Here follows the code to find and print a method (actually a method set, because
|
||||
// we want to do only one redundant tree walk, not one per method).
|
||||
// It should be much easier than walking the whole tree again, but that's what we must do.
|
||||
// TODO.
|
||||
|
||||
type method struct {
|
||||
index int // Which doc to write. (Keeps the results sorted)
|
||||
*types.Selection
|
||||
}
|
||||
|
||||
type methodVisitor struct {
|
||||
*File
|
||||
methods []method
|
||||
docs []string
|
||||
}
|
||||
|
||||
func (f *File) methodSet(set *types.MethodSet) {
|
||||
// Build the set of things we're looking for.
|
||||
methods := make([]method, 0, set.Len())
|
||||
docs := make([]string, set.Len())
|
||||
for i := 0; i < set.Len(); i++ {
|
||||
if ast.IsExported(set.At(i).Obj().Name()) {
|
||||
m := method{
|
||||
i,
|
||||
set.At(i),
|
||||
}
|
||||
methods = append(methods, m)
|
||||
}
|
||||
}
|
||||
if len(methods) == 0 {
|
||||
return
|
||||
}
|
||||
// Collect the docs.
|
||||
for _, file := range f.allFiles {
|
||||
visitor := &methodVisitor{
|
||||
File: file,
|
||||
methods: methods,
|
||||
docs: docs,
|
||||
}
|
||||
ast.Walk(visitor, file.file)
|
||||
methods = visitor.methods
|
||||
}
|
||||
// Print them in order. The incoming method set is sorted by name.
|
||||
for _, doc := range docs {
|
||||
if doc != "" {
|
||||
fmt.Print(doc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements the ast.Visitor interface.
|
||||
func (visitor *methodVisitor) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
for i, method := range visitor.methods {
|
||||
// If this is the right one, the position of the name of its identifier will match.
|
||||
if method.Obj().Pos() == n.Name.Pos() {
|
||||
n.Body = nil // TODO. Ugly - don't print the function body.
|
||||
visitor.docs[method.index] = fmt.Sprintf("%s", visitor.File.docs(n))
|
||||
// If this was the last method, we're done.
|
||||
if len(visitor.methods) == 1 {
|
||||
return nil
|
||||
}
|
||||
// Drop this one from the list.
|
||||
visitor.methods = append(visitor.methods[:i], visitor.methods[i+1:]...)
|
||||
return visitor
|
||||
}
|
||||
}
|
||||
}
|
||||
return visitor
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,394 @@
|
|||
// 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 goimports
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/visualfc/gotools/stdlib"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// importToGroup is a list of functions which map from an import path to
|
||||
// a group number.
|
||||
var importToGroup = []func(importPath string) (num int, ok bool){
|
||||
func(importPath string) (num int, ok bool) {
|
||||
if strings.HasPrefix(importPath, "appengine") {
|
||||
return 2, true
|
||||
}
|
||||
return
|
||||
},
|
||||
func(importPath string) (num int, ok bool) {
|
||||
if strings.Contains(importPath, ".") {
|
||||
return 1, true
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
|
||||
func importGroup(importPath string) int {
|
||||
for _, fn := range importToGroup {
|
||||
if n, ok := fn(importPath); ok {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func fixImports(fset *token.FileSet, f *ast.File) (added []string, err error) {
|
||||
// refs are a set of possible package references currently unsatisfied by imports.
|
||||
// first key: either base package (e.g. "fmt") or renamed package
|
||||
// second key: referenced package symbol (e.g. "Println")
|
||||
refs := make(map[string]map[string]bool)
|
||||
|
||||
// decls are the current package imports. key is base package or renamed package.
|
||||
decls := make(map[string]*ast.ImportSpec)
|
||||
|
||||
// collect potential uses of packages.
|
||||
var visitor visitFn
|
||||
visitor = visitFn(func(node ast.Node) ast.Visitor {
|
||||
if node == nil {
|
||||
return visitor
|
||||
}
|
||||
switch v := node.(type) {
|
||||
case *ast.ImportSpec:
|
||||
if v.Name != nil {
|
||||
decls[v.Name.Name] = v
|
||||
} else {
|
||||
local := importPathToName(strings.Trim(v.Path.Value, `\"`))
|
||||
decls[local] = v
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
xident, ok := v.X.(*ast.Ident)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if xident.Obj != nil {
|
||||
// if the parser can resolve it, it's not a package ref
|
||||
break
|
||||
}
|
||||
pkgName := xident.Name
|
||||
if refs[pkgName] == nil {
|
||||
refs[pkgName] = make(map[string]bool)
|
||||
}
|
||||
if decls[pkgName] == nil {
|
||||
refs[pkgName][v.Sel.Name] = true
|
||||
}
|
||||
}
|
||||
return visitor
|
||||
})
|
||||
ast.Walk(visitor, f)
|
||||
|
||||
// Search for imports matching potential package references.
|
||||
searches := 0
|
||||
type result struct {
|
||||
ipath string
|
||||
name string
|
||||
err error
|
||||
}
|
||||
results := make(chan result)
|
||||
for pkgName, symbols := range refs {
|
||||
if len(symbols) == 0 {
|
||||
continue // skip over packages already imported
|
||||
}
|
||||
go func(pkgName string, symbols map[string]bool) {
|
||||
ipath, rename, err := findImport(pkgName, symbols)
|
||||
r := result{ipath: ipath, err: err}
|
||||
if rename {
|
||||
r.name = pkgName
|
||||
}
|
||||
results <- r
|
||||
}(pkgName, symbols)
|
||||
searches++
|
||||
}
|
||||
for i := 0; i < searches; i++ {
|
||||
result := <-results
|
||||
if result.err != nil {
|
||||
return nil, result.err
|
||||
}
|
||||
if result.ipath != "" {
|
||||
if result.name != "" {
|
||||
astutil.AddNamedImport(fset, f, result.name, result.ipath)
|
||||
} else {
|
||||
astutil.AddImport(fset, f, result.ipath)
|
||||
}
|
||||
added = append(added, result.ipath)
|
||||
}
|
||||
}
|
||||
|
||||
// Nil out any unused ImportSpecs, to be removed in following passes
|
||||
unusedImport := map[string]bool{}
|
||||
for pkg, is := range decls {
|
||||
if refs[pkg] == nil && pkg != "_" && pkg != "." {
|
||||
unusedImport[strings.Trim(is.Path.Value, `"`)] = true
|
||||
}
|
||||
}
|
||||
for ipath := range unusedImport {
|
||||
if ipath == "C" {
|
||||
// Don't remove cgo stuff.
|
||||
continue
|
||||
}
|
||||
astutil.DeleteImport(fset, f, ipath)
|
||||
}
|
||||
|
||||
return added, nil
|
||||
}
|
||||
|
||||
// importPathToName returns the package name for the given import path.
|
||||
var importPathToName = importPathToNameGoPath
|
||||
|
||||
// importPathToNameBasic assumes the package name is the base of import path.
|
||||
func importPathToNameBasic(importPath string) (packageName string) {
|
||||
return path.Base(importPath)
|
||||
}
|
||||
|
||||
// importPathToNameGoPath finds out the actual package name, as declared in its .go files.
|
||||
// If there's a problem, it falls back to using importPathToNameBasic.
|
||||
func importPathToNameGoPath(importPath string) (packageName string) {
|
||||
if stdlib.IsStdPkg(importPath) {
|
||||
return path.Base(importPath)
|
||||
}
|
||||
if buildPkg, err := build.Import(importPath, "", 0); err == nil {
|
||||
return buildPkg.Name
|
||||
} else {
|
||||
return importPathToNameBasic(importPath)
|
||||
}
|
||||
}
|
||||
|
||||
type pkg struct {
|
||||
importpath string // full pkg import path, e.g. "net/http"
|
||||
dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt"
|
||||
}
|
||||
|
||||
var pkgIndexOnce sync.Once
|
||||
|
||||
var pkgIndex struct {
|
||||
sync.Mutex
|
||||
m map[string][]pkg // shortname => []pkg, e.g "http" => "net/http"
|
||||
}
|
||||
|
||||
// gate is a semaphore for limiting concurrency.
|
||||
type gate chan struct{}
|
||||
|
||||
func (g gate) enter() { g <- struct{}{} }
|
||||
func (g gate) leave() { <-g }
|
||||
|
||||
// fsgate protects the OS & filesystem from too much concurrency.
|
||||
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
|
||||
var fsgate = make(gate, 8)
|
||||
|
||||
func loadPkgIndex() {
|
||||
pkgIndex.Lock()
|
||||
pkgIndex.m = make(map[string][]pkg)
|
||||
pkgIndex.Unlock()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, path := range build.Default.SrcDirs() {
|
||||
fsgate.enter()
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
fsgate.leave()
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
children, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
fsgate.leave()
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
for _, child := range children {
|
||||
if child.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(path, name string) {
|
||||
defer wg.Done()
|
||||
loadPkg(&wg, path, name)
|
||||
}(path, child.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func loadPkg(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||
importpath := filepath.ToSlash(pkgrelpath)
|
||||
dir := filepath.Join(root, importpath)
|
||||
|
||||
fsgate.enter()
|
||||
defer fsgate.leave()
|
||||
pkgDir, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
children, err := pkgDir.Readdir(-1)
|
||||
pkgDir.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// hasGo tracks whether a directory actually appears to be a
|
||||
// Go source code directory. If $GOPATH == $HOME, and
|
||||
// $HOME/src has lots of other large non-Go projects in it,
|
||||
// then the calls to importPathToName below can be expensive.
|
||||
hasGo := false
|
||||
for _, child := range children {
|
||||
name := child.Name()
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(name, ".go") {
|
||||
hasGo = true
|
||||
}
|
||||
if child.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(root, name string) {
|
||||
defer wg.Done()
|
||||
loadPkg(wg, root, name)
|
||||
}(root, filepath.Join(importpath, name))
|
||||
}
|
||||
}
|
||||
if hasGo {
|
||||
shortName := importPathToName(importpath)
|
||||
pkgIndex.Lock()
|
||||
pkgIndex.m[shortName] = append(pkgIndex.m[shortName], pkg{
|
||||
importpath: importpath,
|
||||
dir: dir,
|
||||
})
|
||||
pkgIndex.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// loadExports returns a list exports for a package.
|
||||
var loadExports = loadExportsGoPath
|
||||
|
||||
func loadExportsGoPath(dir string) map[string]bool {
|
||||
exports := make(map[string]bool)
|
||||
buildPkg, err := build.ImportDir(dir, 0)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no buildable Go source files in") {
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "could not import %q: %v\n", dir, err)
|
||||
return nil
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
for _, files := range [...][]string{buildPkg.GoFiles, buildPkg.CgoFiles} {
|
||||
for _, file := range files {
|
||||
f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not parse %q: %v\n", file, err)
|
||||
continue
|
||||
}
|
||||
for name := range f.Scope.Objects {
|
||||
if ast.IsExported(name) {
|
||||
exports[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return exports
|
||||
}
|
||||
|
||||
// findImport searches for a package with the given symbols.
|
||||
// If no package is found, findImport returns "".
|
||||
// Declared as a variable rather than a function so goimports can be easily
|
||||
// extended by adding a file with an init function.
|
||||
var findImport = findImportGoPath
|
||||
|
||||
func findImportGoPath(pkgName string, symbols map[string]bool) (string, bool, error) {
|
||||
// Fast path for the standard library.
|
||||
// In the common case we hopefully never have to scan the GOPATH, which can
|
||||
// be slow with moving disks.
|
||||
if pkg, rename, ok := findImportStdlib(pkgName, symbols); ok {
|
||||
return pkg, rename, nil
|
||||
}
|
||||
|
||||
// TODO(sameer): look at the import lines for other Go files in the
|
||||
// local directory, since the user is likely to import the same packages
|
||||
// in the current Go file. Return rename=true when the other Go files
|
||||
// use a renamed package that's also used in the current file.
|
||||
|
||||
pkgIndexOnce.Do(loadPkgIndex)
|
||||
|
||||
// Collect exports for packages with matching names.
|
||||
var wg sync.WaitGroup
|
||||
var pkgsMu sync.Mutex // guards pkgs
|
||||
// full importpath => exported symbol => True
|
||||
// e.g. "net/http" => "Client" => True
|
||||
pkgs := make(map[string]map[string]bool)
|
||||
pkgIndex.Lock()
|
||||
for _, pkg := range pkgIndex.m[pkgName] {
|
||||
wg.Add(1)
|
||||
go func(importpath, dir string) {
|
||||
defer wg.Done()
|
||||
exports := loadExports(dir)
|
||||
if exports != nil {
|
||||
pkgsMu.Lock()
|
||||
pkgs[importpath] = exports
|
||||
pkgsMu.Unlock()
|
||||
}
|
||||
}(pkg.importpath, pkg.dir)
|
||||
}
|
||||
pkgIndex.Unlock()
|
||||
wg.Wait()
|
||||
|
||||
// Filter out packages missing required exported symbols.
|
||||
for symbol := range symbols {
|
||||
for importpath, exports := range pkgs {
|
||||
if !exports[symbol] {
|
||||
delete(pkgs, importpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(pkgs) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// If there are multiple candidate packages, the shortest one wins.
|
||||
// This is a heuristic to prefer the standard library (e.g. "bytes")
|
||||
// over e.g. "github.com/foo/bar/bytes".
|
||||
shortest := ""
|
||||
for importPath := range pkgs {
|
||||
if shortest == "" || len(importPath) < len(shortest) {
|
||||
shortest = importPath
|
||||
}
|
||||
}
|
||||
return shortest, false, nil
|
||||
}
|
||||
|
||||
type visitFn func(node ast.Node) ast.Visitor
|
||||
|
||||
func (fn visitFn) Visit(node ast.Node) ast.Visitor {
|
||||
return fn(node)
|
||||
}
|
||||
|
||||
func findImportStdlib(shortPkg string, symbols map[string]bool) (importPath string, rename, ok bool) {
|
||||
for symbol := range symbols {
|
||||
path := stdlib.Symbols[shortPkg+"."+symbol]
|
||||
if path == "" {
|
||||
return "", false, false
|
||||
}
|
||||
if importPath != "" && importPath != path {
|
||||
// Ambiguous. Symbols pointed to different things.
|
||||
return "", false, false
|
||||
}
|
||||
importPath = path
|
||||
}
|
||||
return importPath, false, importPath != ""
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
// 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 goimports
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
)
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runGoimports,
|
||||
UsageLine: "goimports [flags] [path ...]",
|
||||
Short: "updates go import lines",
|
||||
Long: `goimports updates your Go import lines, adding missing ones and removing unreferenced ones. `,
|
||||
}
|
||||
|
||||
var (
|
||||
goimportsList bool
|
||||
goimportsWrite bool
|
||||
goimportsDiff bool
|
||||
goimportsAllErrors bool
|
||||
|
||||
// layout control
|
||||
goimportsComments bool
|
||||
goimportsTabWidth int
|
||||
goimportsTabIndent bool
|
||||
)
|
||||
|
||||
//func init
|
||||
func init() {
|
||||
Command.Flag.BoolVar(&goimportsList, "l", false, "list files whose formatting differs from goimport's")
|
||||
Command.Flag.BoolVar(&goimportsWrite, "w", false, "write result to (source) file instead of stdout")
|
||||
Command.Flag.BoolVar(&goimportsDiff, "d", false, "display diffs instead of rewriting files")
|
||||
Command.Flag.BoolVar(&goimportsAllErrors, "e", false, "report all errors (not just the first 10 on different lines)")
|
||||
|
||||
// layout control
|
||||
Command.Flag.BoolVar(&goimportsComments, "comments", true, "print comments")
|
||||
Command.Flag.IntVar(&goimportsTabWidth, "tabwidth", 8, "tab width")
|
||||
Command.Flag.BoolVar(&goimportsTabIndent, "tabs", true, "indent with tabs")
|
||||
}
|
||||
|
||||
var (
|
||||
fileSet = token.NewFileSet() // per process FileSet
|
||||
exitCode = 0
|
||||
|
||||
initModesOnce sync.Once // guards calling initModes
|
||||
parserMode parser.Mode
|
||||
printerMode printer.Mode
|
||||
options *Options
|
||||
)
|
||||
|
||||
func report(err error) {
|
||||
scanner.PrintError(os.Stderr, err)
|
||||
exitCode = 2
|
||||
}
|
||||
|
||||
func runGoimports(cmd *command.Command, args []string) error {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
if goimportsTabWidth < 0 {
|
||||
fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", goimportsTabWidth)
|
||||
exitCode = 2
|
||||
os.Exit(exitCode)
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
options = &Options{
|
||||
TabWidth: goimportsTabWidth,
|
||||
TabIndent: goimportsTabIndent,
|
||||
Comments: goimportsComments,
|
||||
AllErrors: goimportsAllErrors,
|
||||
Fragment: true,
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
|
||||
report(err)
|
||||
}
|
||||
} else {
|
||||
for _, path := range args {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
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 := Process(filename, src, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, res) {
|
||||
// formatting has changed
|
||||
if goimportsList {
|
||||
fmt.Fprintln(out, filename)
|
||||
}
|
||||
if goimportsWrite {
|
||||
err = ioutil.WriteFile(filename, res, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if goimportsDiff {
|
||||
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 !goimportsList && !goimportsWrite && !goimportsDiff {
|
||||
_, 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 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
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
// 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 imports implements a Go pretty-printer (like package "go/format")
|
||||
// that also adds or removes import statements as necessary.
|
||||
package goimports
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// Options specifies options for processing files.
|
||||
type Options struct {
|
||||
Fragment bool // Accept fragment of a source file (no package statement)
|
||||
AllErrors bool // Report all errors (not just the first 10 on different lines)
|
||||
|
||||
Comments bool // Print comments (true if nil *Options provided)
|
||||
TabIndent bool // Use tabs for indent (true if nil *Options provided)
|
||||
Format bool
|
||||
TabWidth int // Tab width (8 if nil *Options provided)
|
||||
}
|
||||
|
||||
// Process formats and adjusts imports for the provided file.
|
||||
// If opt is nil the defaults are used.
|
||||
func Process(filename string, src []byte, opt *Options) ([]byte, error) {
|
||||
if opt == nil {
|
||||
opt = &Options{Comments: true, TabIndent: true, TabWidth: 8}
|
||||
}
|
||||
|
||||
fileSet := token.NewFileSet()
|
||||
file, adjust, err := goImportParse(fileSet, filename, src, opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = fixImports(fileSet, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sortImports(fileSet, file)
|
||||
imps := astutil.Imports(fileSet, file)
|
||||
|
||||
var spacesBefore []string // import paths we need spaces before
|
||||
for _, impSection := range imps {
|
||||
// Within each block of contiguous imports, see if any
|
||||
// import lines are in different group numbers. If so,
|
||||
// we'll need to put a space between them so it's
|
||||
// compatible with gofmt.
|
||||
lastGroup := -1
|
||||
for _, importSpec := range impSection {
|
||||
importPath, _ := strconv.Unquote(importSpec.Path.Value)
|
||||
groupNum := importGroup(importPath)
|
||||
if groupNum != lastGroup && lastGroup != -1 {
|
||||
spacesBefore = append(spacesBefore, importPath)
|
||||
}
|
||||
lastGroup = groupNum
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
printerMode := printer.UseSpaces
|
||||
if opt.TabIndent {
|
||||
printerMode |= printer.TabIndent
|
||||
}
|
||||
printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = printConfig.Fprint(&buf, fileSet, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := buf.Bytes()
|
||||
if adjust != nil {
|
||||
out = adjust(src, out)
|
||||
}
|
||||
if len(spacesBefore) > 0 {
|
||||
out = addImportSpaces(bytes.NewReader(out), spacesBefore)
|
||||
}
|
||||
if opt.Format {
|
||||
out, err = format.Source(out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// parse parses src, which was read from filename,
|
||||
// as a Go source file or statement list.
|
||||
func goImportParse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) {
|
||||
parserMode := parser.Mode(0)
|
||||
if opt.Comments {
|
||||
parserMode |= parser.ParseComments
|
||||
}
|
||||
if opt.AllErrors {
|
||||
parserMode |= parser.AllErrors
|
||||
}
|
||||
|
||||
// Try as whole source file.
|
||||
file, err := parser.ParseFile(fset, filename, src, parserMode)
|
||||
if err == nil {
|
||||
return file, nil, nil
|
||||
}
|
||||
// If the error is that the source file didn't begin with a
|
||||
// package line and we accept fragmented input, fall through to
|
||||
// try as a source fragment. Stop and return on any other error.
|
||||
if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If this is a declaration list, make it a source file
|
||||
// by inserting a package clause.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in psrc match the ones in src.
|
||||
psrc := append([]byte("package main;"), src...)
|
||||
file, err = parser.ParseFile(fset, filename, psrc, parserMode)
|
||||
if err == nil {
|
||||
// If a main function exists, we will assume this is a main
|
||||
// package and leave the file.
|
||||
if containsMainFunc(file) {
|
||||
return file, nil, nil
|
||||
}
|
||||
|
||||
adjust := func(orig, src []byte) []byte {
|
||||
// Remove the package clause.
|
||||
// Gofmt has turned the ; into a \n.
|
||||
src = src[len("package main\n"):]
|
||||
return matchSpace(orig, src)
|
||||
}
|
||||
return file, adjust, nil
|
||||
}
|
||||
// If the error is that the source file didn't begin with a
|
||||
// declaration, fall through to try as a statement list.
|
||||
// Stop and return on any other error.
|
||||
if !strings.Contains(err.Error(), "expected declaration") {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If this is a statement list, make it a source file
|
||||
// by inserting a package clause and turning the list
|
||||
// into a function body. This handles expressions too.
|
||||
// Insert using a ;, not a newline, so that the line numbers
|
||||
// in fsrc match the ones in src.
|
||||
fsrc := append(append([]byte("package p; func _() {"), src...), '}')
|
||||
file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
|
||||
if err == nil {
|
||||
adjust := func(orig, src []byte) []byte {
|
||||
// Remove the wrapping.
|
||||
// Gofmt has turned the ; into a \n\n.
|
||||
src = src[len("package p\n\nfunc _() {"):]
|
||||
src = src[:len(src)-len("}\n")]
|
||||
// Gofmt has also indented the function body one level.
|
||||
// Remove that indent.
|
||||
src = bytes.Replace(src, []byte("\n\t"), []byte("\n"), -1)
|
||||
return matchSpace(orig, src)
|
||||
}
|
||||
return file, adjust, nil
|
||||
}
|
||||
|
||||
// Failed, and out of options.
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// containsMainFunc checks if a file contains a function declaration with the
|
||||
// function signature 'func main()'
|
||||
func containsMainFunc(file *ast.File) bool {
|
||||
for _, decl := range file.Decls {
|
||||
if f, ok := decl.(*ast.FuncDecl); ok {
|
||||
if f.Name.Name != "main" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(f.Type.Params.List) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Type.Results != nil && len(f.Type.Results.List) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func cutSpace(b []byte) (before, middle, after []byte) {
|
||||
i := 0
|
||||
for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') {
|
||||
i++
|
||||
}
|
||||
j := len(b)
|
||||
for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') {
|
||||
j--
|
||||
}
|
||||
if i <= j {
|
||||
return b[:i], b[i:j], b[j:]
|
||||
}
|
||||
return nil, nil, b[j:]
|
||||
}
|
||||
|
||||
// matchSpace reformats src to use the same space context as orig.
|
||||
// 1) If orig begins with blank lines, matchSpace inserts them at the beginning of src.
|
||||
// 2) matchSpace copies the indentation of the first non-blank line in orig
|
||||
// to every non-blank line in src.
|
||||
// 3) matchSpace copies the trailing space from orig and uses it in place
|
||||
// of src's trailing space.
|
||||
func matchSpace(orig []byte, src []byte) []byte {
|
||||
before, _, after := cutSpace(orig)
|
||||
i := bytes.LastIndex(before, []byte{'\n'})
|
||||
before, indent := before[:i+1], before[i+1:]
|
||||
|
||||
_, src, _ = cutSpace(src)
|
||||
|
||||
var b bytes.Buffer
|
||||
b.Write(before)
|
||||
for len(src) > 0 {
|
||||
line := src
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, src = line[:i+1], line[i+1:]
|
||||
} else {
|
||||
src = nil
|
||||
}
|
||||
if len(line) > 0 && line[0] != '\n' { // not blank
|
||||
b.Write(indent)
|
||||
}
|
||||
b.Write(line)
|
||||
}
|
||||
b.Write(after)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+)"`)
|
||||
|
||||
func addImportSpaces(r io.Reader, breaks []string) []byte {
|
||||
var out bytes.Buffer
|
||||
sc := bufio.NewScanner(r)
|
||||
inImports := false
|
||||
done := false
|
||||
for sc.Scan() {
|
||||
s := sc.Text()
|
||||
|
||||
if !inImports && !done && strings.HasPrefix(s, "import") {
|
||||
inImports = true
|
||||
}
|
||||
if inImports && (strings.HasPrefix(s, "var") ||
|
||||
strings.HasPrefix(s, "func") ||
|
||||
strings.HasPrefix(s, "const") ||
|
||||
strings.HasPrefix(s, "type")) {
|
||||
done = true
|
||||
inImports = false
|
||||
}
|
||||
if inImports && len(breaks) > 0 {
|
||||
if m := impLine.FindStringSubmatch(s); m != nil {
|
||||
if m[1] == string(breaks[0]) {
|
||||
out.WriteByte('\n')
|
||||
breaks = breaks[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(&out, s)
|
||||
}
|
||||
return out.Bytes()
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
// +build go1.2
|
||||
|
||||
// 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.
|
||||
|
||||
// Hacked up copy of go/ast/import.go
|
||||
|
||||
package goimports
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// sortImports sorts runs of consecutive import lines in import blocks in f.
|
||||
// It also removes duplicate imports when it is possible to do so without data loss.
|
||||
func sortImports(fset *token.FileSet, f *ast.File) {
|
||||
for i, d := range f.Decls {
|
||||
d, ok := d.(*ast.GenDecl)
|
||||
if !ok || d.Tok != token.IMPORT {
|
||||
// Not an import declaration, so we're done.
|
||||
// Imports are always first.
|
||||
break
|
||||
}
|
||||
|
||||
if len(d.Specs) == 0 {
|
||||
// Empty import block, remove it.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
}
|
||||
|
||||
if !d.Lparen.IsValid() {
|
||||
// Not a block: sorted by default.
|
||||
continue
|
||||
}
|
||||
|
||||
// Identify and sort runs of specs on successive lines.
|
||||
i := 0
|
||||
specs := d.Specs[:0]
|
||||
for j, s := range d.Specs {
|
||||
if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line {
|
||||
// j begins a new run. End this one.
|
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...)
|
||||
i = j
|
||||
}
|
||||
}
|
||||
specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...)
|
||||
d.Specs = specs
|
||||
|
||||
// Deduping can leave a blank line before the rparen; clean that up.
|
||||
if len(d.Specs) > 0 {
|
||||
lastSpec := d.Specs[len(d.Specs)-1]
|
||||
lastLine := fset.Position(lastSpec.Pos()).Line
|
||||
if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 {
|
||||
fset.File(d.Rparen).MergeLine(rParenLine - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importPath(s ast.Spec) string {
|
||||
t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value)
|
||||
if err == nil {
|
||||
return t
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func importName(s ast.Spec) string {
|
||||
n := s.(*ast.ImportSpec).Name
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return n.Name
|
||||
}
|
||||
|
||||
func importComment(s ast.Spec) string {
|
||||
c := s.(*ast.ImportSpec).Comment
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return c.Text()
|
||||
}
|
||||
|
||||
// collapse indicates whether prev may be removed, leaving only next.
|
||||
func collapse(prev, next ast.Spec) bool {
|
||||
if importPath(next) != importPath(prev) || importName(next) != importName(prev) {
|
||||
return false
|
||||
}
|
||||
return prev.(*ast.ImportSpec).Comment == nil
|
||||
}
|
||||
|
||||
type posSpan struct {
|
||||
Start token.Pos
|
||||
End token.Pos
|
||||
}
|
||||
|
||||
func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec {
|
||||
// Can't short-circuit here even if specs are already sorted,
|
||||
// since they might yet need deduplication.
|
||||
// A lone import, however, may be safely ignored.
|
||||
if len(specs) <= 1 {
|
||||
return specs
|
||||
}
|
||||
|
||||
// Record positions for specs.
|
||||
pos := make([]posSpan, len(specs))
|
||||
for i, s := range specs {
|
||||
pos[i] = posSpan{s.Pos(), s.End()}
|
||||
}
|
||||
|
||||
// Identify comments in this range.
|
||||
// Any comment from pos[0].Start to the final line counts.
|
||||
lastLine := fset.Position(pos[len(pos)-1].End).Line
|
||||
cstart := len(f.Comments)
|
||||
cend := len(f.Comments)
|
||||
for i, g := range f.Comments {
|
||||
if g.Pos() < pos[0].Start {
|
||||
continue
|
||||
}
|
||||
if i < cstart {
|
||||
cstart = i
|
||||
}
|
||||
if fset.Position(g.End()).Line > lastLine {
|
||||
cend = i
|
||||
break
|
||||
}
|
||||
}
|
||||
comments := f.Comments[cstart:cend]
|
||||
|
||||
// Assign each comment to the import spec preceding it.
|
||||
importComment := map[*ast.ImportSpec][]*ast.CommentGroup{}
|
||||
specIndex := 0
|
||||
for _, g := range comments {
|
||||
for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() {
|
||||
specIndex++
|
||||
}
|
||||
s := specs[specIndex].(*ast.ImportSpec)
|
||||
importComment[s] = append(importComment[s], g)
|
||||
}
|
||||
|
||||
// Sort the import specs by import path.
|
||||
// Remove duplicates, when possible without data loss.
|
||||
// Reassign the import paths to have the same position sequence.
|
||||
// Reassign each comment to abut the end of its spec.
|
||||
// Sort the comments by new position.
|
||||
sort.Sort(byImportSpec(specs))
|
||||
|
||||
// Dedup. Thanks to our sorting, we can just consider
|
||||
// adjacent pairs of imports.
|
||||
deduped := specs[:0]
|
||||
for i, s := range specs {
|
||||
if i == len(specs)-1 || !collapse(s, specs[i+1]) {
|
||||
deduped = append(deduped, s)
|
||||
} else {
|
||||
p := s.Pos()
|
||||
fset.File(p).MergeLine(fset.Position(p).Line)
|
||||
}
|
||||
}
|
||||
specs = deduped
|
||||
|
||||
// Fix up comment positions
|
||||
for i, s := range specs {
|
||||
s := s.(*ast.ImportSpec)
|
||||
if s.Name != nil {
|
||||
s.Name.NamePos = pos[i].Start
|
||||
}
|
||||
s.Path.ValuePos = pos[i].Start
|
||||
s.EndPos = pos[i].End
|
||||
for _, g := range importComment[s] {
|
||||
for _, c := range g.List {
|
||||
c.Slash = pos[i].End
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byCommentPos(comments))
|
||||
|
||||
return specs
|
||||
}
|
||||
|
||||
type byImportSpec []ast.Spec // slice of *ast.ImportSpec
|
||||
|
||||
func (x byImportSpec) Len() int { return len(x) }
|
||||
func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x byImportSpec) Less(i, j int) bool {
|
||||
ipath := importPath(x[i])
|
||||
jpath := importPath(x[j])
|
||||
|
||||
igroup := importGroup(ipath)
|
||||
jgroup := importGroup(jpath)
|
||||
if igroup != jgroup {
|
||||
return igroup < jgroup
|
||||
}
|
||||
|
||||
if ipath != jpath {
|
||||
return ipath < jpath
|
||||
}
|
||||
iname := importName(x[i])
|
||||
jname := importName(x[j])
|
||||
|
||||
if iname != jname {
|
||||
return iname < jname
|
||||
}
|
||||
return importComment(x[i]) < importComment(x[j])
|
||||
}
|
||||
|
||||
type byCommentPos []*ast.CommentGroup
|
||||
|
||||
func (x byCommentPos) Len() int { return len(x) }
|
||||
func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() }
|
14
vendor/github.com/visualfc/gotools/goimports/sortimports_compat.go
generated
vendored
Normal file
14
vendor/github.com/visualfc/gotools/goimports/sortimports_compat.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
// +build !go1.2
|
||||
|
||||
// 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 goimports
|
||||
|
||||
import "go/ast"
|
||||
|
||||
// Go 1.1 users don't get fancy package grouping.
|
||||
// But this is still gofmt-compliant:
|
||||
|
||||
var sortImports = ast.SortImports
|
|
@ -0,0 +1,383 @@
|
|||
// 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.
|
||||
|
||||
//modify 2013-2014 visualfc
|
||||
|
||||
package gopresent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
"golang.org/x/tools/present"
|
||||
)
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runPresent,
|
||||
UsageLine: "gopresent",
|
||||
Short: "golang present util",
|
||||
Long: `golang present util`,
|
||||
}
|
||||
|
||||
var presentVerifyOnly bool
|
||||
var presentInput string
|
||||
var presentStdout bool
|
||||
var presentOutput string
|
||||
|
||||
func init() {
|
||||
Command.Flag.BoolVar(&presentVerifyOnly, "v", false, "verify present only")
|
||||
Command.Flag.BoolVar(&presentStdout, "stdout", false, "output use std output")
|
||||
Command.Flag.StringVar(&presentInput, "i", "", "input golang present file")
|
||||
Command.Flag.StringVar(&presentOutput, "o", "", "output html file name")
|
||||
}
|
||||
|
||||
func runPresent(cmd *command.Command, args []string) error {
|
||||
if presentInput == "" || !isDoc(presentInput) {
|
||||
cmd.Usage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
if presentVerifyOnly {
|
||||
err := VerifyDoc(presentInput)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "present:%s", err)
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
w := os.Stdout
|
||||
if !presentStdout {
|
||||
if presentOutput == "" {
|
||||
presentOutput = presentInput + ".html"
|
||||
}
|
||||
ext := filepath.Ext(presentOutput)
|
||||
if ext != ".htm" && ext != ".html" {
|
||||
presentOutput += ".html"
|
||||
}
|
||||
var err error
|
||||
w, err = os.Create(presentOutput)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "present:%s", err)
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
}
|
||||
}
|
||||
err := RenderDoc(w, presentInput)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "present:%s", err)
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var extensions = map[string]string{
|
||||
".slide": "slides.tmpl",
|
||||
".article": "article.tmpl",
|
||||
}
|
||||
|
||||
var extensions_tmpl = map[string]string{
|
||||
".slide": slides_tmpl,
|
||||
".article": article_tmpl,
|
||||
}
|
||||
|
||||
func isDoc(path string) bool {
|
||||
_, ok := extensions[filepath.Ext(path)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func VerifyDoc(docFile string) error {
|
||||
doc, err := parse(docFile, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(docFile)
|
||||
return verify_doc(dir, doc)
|
||||
}
|
||||
|
||||
// renderDoc reads the present file, builds its template representation,
|
||||
// and executes the template, sending output to w.
|
||||
func renderDoc(w io.Writer, base, docFile string) error {
|
||||
// Read the input and build the doc structure.
|
||||
doc, err := parse(docFile, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find which template should be executed.
|
||||
ext := filepath.Ext(docFile)
|
||||
contentTmpl, ok := extensions[ext]
|
||||
if !ok {
|
||||
return fmt.Errorf("no template for extension %v", ext)
|
||||
}
|
||||
|
||||
// Locate the template file.
|
||||
actionTmpl := filepath.Join(base, "templates/action.tmpl")
|
||||
contentTmpl = filepath.Join(base, "templates", contentTmpl)
|
||||
|
||||
// Read and parse the input.
|
||||
tmpl := present.Template()
|
||||
tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
|
||||
if _, err := tmpl.ParseFiles(actionTmpl, contentTmpl); err != nil {
|
||||
return err
|
||||
}
|
||||
// Execute the template.
|
||||
return doc.Render(w, tmpl)
|
||||
}
|
||||
|
||||
func RenderDoc(w io.Writer, docFile string) error {
|
||||
// Read the input and build the doc structure.
|
||||
doc, err := parse(docFile, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find which template should be executed.
|
||||
ext := filepath.Ext(docFile)
|
||||
contentTmpl, ok := extensions_tmpl[ext]
|
||||
if !ok {
|
||||
return fmt.Errorf("no template for extension %v", ext)
|
||||
}
|
||||
|
||||
// Locate the template file.
|
||||
actionTmpl := action_tmpl //filepath.Join(base, "templates/action.tmpl")
|
||||
// Read and parse the input.
|
||||
tmpl := present.Template()
|
||||
tmpl = tmpl.Funcs(template.FuncMap{"playable": playable})
|
||||
if tmpl, err = tmpl.New("action").Parse(actionTmpl); err != nil {
|
||||
return err
|
||||
}
|
||||
if tmpl, err = tmpl.New("content").Parse(contentTmpl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute the template.
|
||||
return doc.Render(w, tmpl)
|
||||
}
|
||||
|
||||
func parse(name string, mode present.ParseMode) (*present.Doc, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return present.Parse(f, name, 0)
|
||||
}
|
||||
|
||||
func playable(c present.Code) bool {
|
||||
return present.PlayEnabled && c.Play
|
||||
}
|
||||
|
||||
func isSkipURL(url string) bool {
|
||||
if filepath.HasPrefix(url, "http://") {
|
||||
return true
|
||||
}
|
||||
if filepath.HasPrefix(url, "https://") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func verify_path(root string, url string) error {
|
||||
if isSkipURL(url) {
|
||||
return nil
|
||||
}
|
||||
path := url
|
||||
if !filepath.IsAbs(url) {
|
||||
path = filepath.Join(root, path)
|
||||
}
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verify_doc(root string, doc *present.Doc) error {
|
||||
for _, section := range doc.Sections {
|
||||
for _, elem := range section.Elem {
|
||||
switch i := elem.(type) {
|
||||
case present.Image:
|
||||
if err := verify_path(root, i.URL); err != nil {
|
||||
return fmt.Errorf("! .image %s not exist", i.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var action_tmpl = `
|
||||
{/*
|
||||
This is the action template.
|
||||
It determines how the formatting actions are rendered.
|
||||
*/}
|
||||
|
||||
{{define "section"}}
|
||||
<h{{len .Number}} id="TOC_{{.FormattedNumber}}">{{.FormattedNumber}} {{.Title}}</h{{len .Number}}>
|
||||
{{range .Elem}}{{elem $.Template .}}{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "list"}}
|
||||
<ul>
|
||||
{{range .Bullet}}
|
||||
<li>{{style .}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{define "text"}}
|
||||
{{if .Pre}}
|
||||
<div class="code"><pre>{{range .Lines}}{{.}}{{end}}</pre></div>
|
||||
{{else}}
|
||||
<p>
|
||||
{{range $i, $l := .Lines}}{{if $i}}{{template "newline"}}
|
||||
{{end}}{{style $l}}{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "code"}}
|
||||
<div class="code{{if playable .}} playground{{end}}" contenteditable="true" spellcheck="false">{{.Text}}</div>
|
||||
{{end}}
|
||||
|
||||
{{define "image"}}
|
||||
<div class="image">
|
||||
<img src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}}>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "iframe"}}
|
||||
<iframe src="{{.URL}}"{{with .Height}} height="{{.}}"{{end}}{{with .Width}} width="{{.}}"{{end}}></iframe>
|
||||
{{end}}
|
||||
|
||||
{{define "link"}}<p class="link"><a href="{{.URL}}" target="_blank">{{style .Label}}</a></p>{{end}}
|
||||
|
||||
{{define "html"}}{{.HTML}}{{end}}
|
||||
`
|
||||
|
||||
var article_tmpl = `
|
||||
{/* This is the article template. It defines how articles are formatted. */}
|
||||
|
||||
{{define "root"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
<link type="text/css" rel="stylesheet" href="static/article.css">
|
||||
<meta charset='utf-8'>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="topbar" class="wide">
|
||||
<div class="container">
|
||||
<div id="heading">{{.Title}}
|
||||
{{with .Subtitle}}{{.}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="page" class="wide">
|
||||
<div class="container">
|
||||
{{with .Sections}}
|
||||
<div id="toc">
|
||||
{{template "TOC" .}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{range .Sections}}
|
||||
{{elem $.Template .}}
|
||||
{{end}}{{/* of Section block */}}
|
||||
|
||||
<h2>Authors</h2>
|
||||
{{range .Authors}}
|
||||
<div class="author">
|
||||
{{range .Elem}}{{elem $.Template .}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<script src='/play.js'></script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
{{define "TOC"}}
|
||||
<ul>
|
||||
{{range .}}
|
||||
<li><a href="#TOC_{{.FormattedNumber}}">{{.Title}}</a></li>
|
||||
{{with .Sections}}{{template "TOC" .}}{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{define "newline"}}
|
||||
{{/* No automatic line break. Paragraphs are free-form. */}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
var slides_tmpl = `
|
||||
{/* This is the slide template. It defines how presentations are formatted. */}
|
||||
|
||||
{{define "root"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
<meta charset='utf-8'>
|
||||
<script src='static/slides.js'></script>
|
||||
</head>
|
||||
|
||||
<body style='display: none'>
|
||||
|
||||
<section class='slides layout-widescreen'>
|
||||
|
||||
<article>
|
||||
<h1>{{.Title}}</h1>
|
||||
{{with .Subtitle}}<h3>{{.}}</h3>{{end}}
|
||||
{{if not .Time.IsZero}}<h3>{{.Time.Format "2 January 2006"}}</h3>{{end}}
|
||||
{{range .Authors}}
|
||||
<div class="presenter">
|
||||
{{range .TextElem}}{{elem $.Template .}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
|
||||
{{range $i, $s := .Sections}}
|
||||
<!-- start of slide {{$s.Number}} -->
|
||||
<article>
|
||||
{{if $s.Elem}}
|
||||
<h3>{{$s.Title}}</h3>
|
||||
{{range $s.Elem}}{{elem $.Template .}}{{end}}
|
||||
{{else}}
|
||||
<h2>{{$s.Title}}</h2>
|
||||
{{end}}
|
||||
</article>
|
||||
<!-- end of slide {{$i}} -->
|
||||
{{end}}{{/* of Slide block */}}
|
||||
|
||||
<article>
|
||||
<h3>Thank you</h1>
|
||||
{{range .Authors}}
|
||||
<div class="presenter">
|
||||
{{range .Elem}}{{elem $.Template .}}{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
|
||||
</body>
|
||||
{{if .PlayEnabled}}
|
||||
<script src='/play.js'></script>
|
||||
{{end}}
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
{{define "newline"}}
|
||||
<br>
|
||||
{{end}}
|
||||
`
|
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package jsonfmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
)
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runJsonFmt,
|
||||
UsageLine: "jsonfmt",
|
||||
Short: "json format util",
|
||||
Long: `json format util.`,
|
||||
}
|
||||
|
||||
var (
|
||||
jsonFmtList bool
|
||||
jsonFmtCompact bool
|
||||
jsonFmtWrite bool
|
||||
jsonFmtDiff bool
|
||||
jsonTabWidth int
|
||||
jsonTabIndent bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
Command.Flag.BoolVar(&jsonFmtList, "l", false, "list files whose formatting differs")
|
||||
Command.Flag.BoolVar(&jsonFmtCompact, "c", false, "compact json")
|
||||
Command.Flag.BoolVar(&jsonFmtWrite, "w", false, "write result to (source) file instead of stdout")
|
||||
Command.Flag.BoolVar(&jsonFmtDiff, "d", false, "display diffs instead of rewriting files")
|
||||
Command.Flag.IntVar(&jsonTabWidth, "tabwidth", 4, "tab width")
|
||||
Command.Flag.BoolVar(&jsonTabIndent, "tabs", false, "indent with tabs")
|
||||
}
|
||||
|
||||
func runJsonFmt(cmd *command.Command, args []string) error {
|
||||
opt := &JsonFmtOption{}
|
||||
opt.List = jsonFmtList
|
||||
opt.Compact = jsonFmtCompact
|
||||
opt.IndentTab = jsonTabIndent
|
||||
opt.TabWidth = jsonTabWidth
|
||||
opt.Write = jsonFmtWrite
|
||||
opt.Diff = jsonFmtDiff
|
||||
|
||||
if len(args) == 0 {
|
||||
if err := processJsonFile("<standard input>", os.Stdin, os.Stdout, true, opt); err != nil {
|
||||
reportJsonError(err)
|
||||
}
|
||||
} else {
|
||||
for _, path := range args {
|
||||
switch dir, err := os.Stat(path); {
|
||||
case err != nil:
|
||||
reportJsonError(err)
|
||||
case dir.IsDir():
|
||||
filepath.Walk(path, func(path string, f os.FileInfo, err error) error {
|
||||
if err == nil && isJsonFile(f) {
|
||||
err = processJsonFile(path, nil, os.Stdout, false, opt)
|
||||
}
|
||||
if err != nil {
|
||||
reportJsonError(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
default:
|
||||
if err := processJsonFile(path, nil, os.Stdout, false, opt); err != nil {
|
||||
reportJsonError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type JsonFmtOption struct {
|
||||
List bool
|
||||
Compact bool
|
||||
Format bool
|
||||
Write bool
|
||||
Diff bool
|
||||
IndentTab bool
|
||||
TabWidth int
|
||||
}
|
||||
|
||||
func isJsonFile(f os.FileInfo) bool {
|
||||
// ignore non-Go files
|
||||
name := f.Name()
|
||||
return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".json")
|
||||
}
|
||||
|
||||
func reportJsonError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func processJson(filename string, src []byte, opt *JsonFmtOption) ([]byte, error) {
|
||||
if opt.Compact {
|
||||
var out bytes.Buffer
|
||||
err := json.Compact(&out, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
} else {
|
||||
var out bytes.Buffer
|
||||
var err error
|
||||
if opt.IndentTab {
|
||||
err = json.Indent(&out, src, "", "\t")
|
||||
} else {
|
||||
var indent string
|
||||
for i := 0; i < opt.TabWidth; i++ {
|
||||
indent += " "
|
||||
}
|
||||
err = json.Indent(&out, src, "", indent)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func processJsonFile(filename string, in io.Reader, out io.Writer, stdin bool, opt *JsonFmtOption) error {
|
||||
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 := processJson(filename, src, opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, res) {
|
||||
// formatting has changed
|
||||
if opt.List {
|
||||
fmt.Fprintln(out, filename)
|
||||
}
|
||||
if opt.Write {
|
||||
err = ioutil.WriteFile(filename, res, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if opt.Diff {
|
||||
data, err := diffJson(src, res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("computing diff: %s", err)
|
||||
}
|
||||
fmt.Printf("diff %s json/%s\n", filename, filename)
|
||||
out.Write(data)
|
||||
}
|
||||
}
|
||||
|
||||
if !opt.List && !opt.Write && !opt.Diff {
|
||||
_, err = out.Write(res)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func diffJson(b1, b2 []byte) (data []byte, err error) {
|
||||
f1, err := ioutil.TempFile("", "json")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
defer f1.Close()
|
||||
|
||||
f2, err := ioutil.TempFile("", "json")
|
||||
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
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
|
||||
"golang.org/x/tools/oracle"
|
||||
)
|
||||
|
||||
//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 type
|
||||
// peers show send/receive corresponding to selected channel op
|
||||
// referrers show all refs to entity denoted by selected identifier
|
||||
// what show basic information about the selected syntax node
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runOracle,
|
||||
UsageLine: "oracle",
|
||||
Short: "golang oracle util",
|
||||
Long: `golang oracle util.`,
|
||||
}
|
||||
|
||||
var (
|
||||
oraclePos string
|
||||
oracleReflect bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
Command.Flag.StringVar(&oraclePos, "pos", "", "filename:#offset")
|
||||
Command.Flag.BoolVar(&oracleReflect, "reflect", false, "Analyze reflection soundly (slow).")
|
||||
}
|
||||
|
||||
func runOracle(cmd *command.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if os.Getenv("GOMAXPROCS") == "" {
|
||||
n := runtime.NumCPU()
|
||||
if n < 4 {
|
||||
n = 4
|
||||
}
|
||||
runtime.GOMAXPROCS(n)
|
||||
}
|
||||
mode := args[0]
|
||||
args = args[1:]
|
||||
if args[0] == "." {
|
||||
pkgPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
pkg, err := build.Default.ImportDir(pkgPath, 0)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
args = pkg.GoFiles
|
||||
//log.Println(pkg.ImportPath)
|
||||
if pkg.ImportPath != "." && pkg.ImportPath != "" {
|
||||
args = []string{pkg.ImportPath}
|
||||
}
|
||||
}
|
||||
query := oracle.Query{
|
||||
Mode: mode,
|
||||
Pos: oraclePos,
|
||||
Build: &build.Default,
|
||||
Scope: args,
|
||||
PTALog: nil,
|
||||
Reflection: oracleReflect,
|
||||
}
|
||||
|
||||
if err := oracle.Run(&query); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "oracle: %s.\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if mode == "referrers" {
|
||||
ref := query.Serial().Referrers
|
||||
if ref != nil {
|
||||
fmt.Fprintln(os.Stdout, ref.Desc)
|
||||
fmt.Fprintln(os.Stdout, ref.ObjPos)
|
||||
for _, v := range ref.Refs {
|
||||
fmt.Fprintln(os.Stdout, v)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query.WriteTo(os.Stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package pkgs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
"github.com/visualfc/gotools/goapi"
|
||||
)
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runPkgs,
|
||||
UsageLine: "pkgs",
|
||||
Short: "print liteide_stub version",
|
||||
Long: `Version prints the liteide_stub version.`,
|
||||
}
|
||||
|
||||
var (
|
||||
pkgsList bool
|
||||
pkgsJson bool
|
||||
pkgsFind string
|
||||
pkgsStd bool
|
||||
pkgsPkgOnly bool
|
||||
pkgsSkipGoroot bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
Command.Flag.BoolVar(&pkgsList, "list", false, "list all package")
|
||||
Command.Flag.BoolVar(&pkgsJson, "json", false, "json format")
|
||||
Command.Flag.BoolVar(&pkgsStd, "std", false, "std library")
|
||||
Command.Flag.BoolVar(&pkgsPkgOnly, "pkg", false, "pkg only")
|
||||
Command.Flag.BoolVar(&pkgsSkipGoroot, "skip_goroot", false, "skip goroot")
|
||||
Command.Flag.StringVar(&pkgsFind, "find", "", "find package by name")
|
||||
}
|
||||
|
||||
func runPkgs(cmd *command.Command, args []string) error {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
if len(args) != 0 {
|
||||
cmd.Usage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
//pkgIndexOnce.Do(loadPkgsList)
|
||||
var pp PathPkgsIndex
|
||||
pp.LoadIndex()
|
||||
pp.Sort()
|
||||
if pkgsList {
|
||||
for _, pi := range pp.indexs {
|
||||
for _, pkg := range pi.pkgs {
|
||||
if pkgsPkgOnly && pkg.IsCommand() {
|
||||
continue
|
||||
}
|
||||
if pkgsJson {
|
||||
var p GoPackage
|
||||
p.copyBuild(pkg)
|
||||
b, err := json.MarshalIndent(&p, "", "\t")
|
||||
if err == nil {
|
||||
cmd.Stdout.Write(b)
|
||||
cmd.Stdout.Write([]byte{'\n'})
|
||||
}
|
||||
} else {
|
||||
cmd.Println(pkg.ImportPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if pkgsFind != "" {
|
||||
for _, pi := range pp.indexs {
|
||||
for _, pkg := range pi.pkgs {
|
||||
if pkg.Name == pkgsFind {
|
||||
if pkgsPkgOnly && pkg.IsCommand() {
|
||||
continue
|
||||
}
|
||||
if pkgsJson {
|
||||
var p GoPackage
|
||||
p.copyBuild(pkg)
|
||||
b, err := json.MarshalIndent(p, "", "\t")
|
||||
if err == nil {
|
||||
cmd.Stdout.Write(b)
|
||||
cmd.Stdout.Write([]byte{'\n'})
|
||||
}
|
||||
} else {
|
||||
cmd.Println(pkg.Name)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A Package describes a single package found in a directory.
|
||||
type GoPackage struct {
|
||||
// Note: These fields are part of the go command's public API.
|
||||
// See list.go. It is okay to add fields, but not to change or
|
||||
// remove existing ones. Keep in sync with list.go
|
||||
Dir string `json:",omitempty"` // directory containing package sources
|
||||
ImportPath string `json:",omitempty"` // import path of package in dir
|
||||
Name string `json:",omitempty"` // package name
|
||||
Doc string `json:",omitempty"` // package documentation string
|
||||
Target string `json:",omitempty"` // install path
|
||||
Goroot bool `json:",omitempty"` // is this package found in the Go root?
|
||||
Standard bool `json:",omitempty"` // is this package part of the standard Go library?
|
||||
Stale bool `json:",omitempty"` // would 'go install' do anything for this package?
|
||||
Root string `json:",omitempty"` // Go root or Go path dir containing this package
|
||||
ConflictDir string `json:",omitempty"` // Dir is hidden by this other directory
|
||||
|
||||
// Source files
|
||||
GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
|
||||
CgoFiles []string `json:",omitempty"` // .go sources files that import "C"
|
||||
IgnoredGoFiles []string `json:",omitempty"` // .go sources ignored due to build constraints
|
||||
CFiles []string `json:",omitempty"` // .c source files
|
||||
CXXFiles []string `json:",omitempty"` // .cc, .cpp and .cxx source files
|
||||
MFiles []string `json:",omitempty"` // .m source files
|
||||
HFiles []string `json:",omitempty"` // .h, .hh, .hpp and .hxx source files
|
||||
SFiles []string `json:",omitempty"` // .s source files
|
||||
SwigFiles []string `json:",omitempty"` // .swig files
|
||||
SwigCXXFiles []string `json:",omitempty"` // .swigcxx files
|
||||
SysoFiles []string `json:",omitempty"` // .syso system object files added to package
|
||||
|
||||
// Cgo directives
|
||||
CgoCFLAGS []string `json:",omitempty"` // cgo: flags for C compiler
|
||||
CgoCPPFLAGS []string `json:",omitempty"` // cgo: flags for C preprocessor
|
||||
CgoCXXFLAGS []string `json:",omitempty"` // cgo: flags for C++ compiler
|
||||
CgoLDFLAGS []string `json:",omitempty"` // cgo: flags for linker
|
||||
CgoPkgConfig []string `json:",omitempty"` // cgo: pkg-config names
|
||||
|
||||
// Dependency information
|
||||
Imports []string `json:",omitempty"` // import paths used by this package
|
||||
Deps []string `json:",omitempty"` // all (recursively) imported dependencies
|
||||
|
||||
// Error information
|
||||
Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies?
|
||||
|
||||
// Test information
|
||||
TestGoFiles []string `json:",omitempty"` // _test.go files in package
|
||||
TestImports []string `json:",omitempty"` // imports from TestGoFiles
|
||||
XTestGoFiles []string `json:",omitempty"` // _test.go files outside package
|
||||
XTestImports []string `json:",omitempty"` // imports from XTestGoFiles
|
||||
|
||||
// Unexported fields are not part of the public API.
|
||||
build *build.Package
|
||||
pkgdir string // overrides build.PkgDir
|
||||
imports []*goapi.Package
|
||||
deps []*goapi.Package
|
||||
gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths
|
||||
sfiles []string
|
||||
allgofiles []string // gofiles + IgnoredGoFiles, absolute paths
|
||||
target string // installed file for this package (may be executable)
|
||||
fake bool // synthesized package
|
||||
forceBuild bool // this package must be rebuilt
|
||||
forceLibrary bool // this package is a library (even if named "main")
|
||||
cmdline bool // defined by files listed on command line
|
||||
local bool // imported via local path (./ or ../)
|
||||
localPrefix string // interpret ./ and ../ imports relative to this prefix
|
||||
exeName string // desired name for temporary executable
|
||||
coverMode string // preprocess Go source files with the coverage tool in this mode
|
||||
coverVars map[string]*CoverVar // variables created by coverage analysis
|
||||
omitDWARF bool // tell linker not to write DWARF information
|
||||
}
|
||||
|
||||
// CoverVar holds the name of the generated coverage variables targeting the named file.
|
||||
type CoverVar struct {
|
||||
File string // local file name
|
||||
Var string // name of count struct
|
||||
}
|
||||
|
||||
func (p *GoPackage) copyBuild(pp *build.Package) {
|
||||
p.build = pp
|
||||
|
||||
p.Dir = pp.Dir
|
||||
p.ImportPath = pp.ImportPath
|
||||
p.Name = pp.Name
|
||||
p.Doc = pp.Doc
|
||||
p.Root = pp.Root
|
||||
p.ConflictDir = pp.ConflictDir
|
||||
// TODO? Target
|
||||
p.Goroot = pp.Goroot
|
||||
p.Standard = p.Goroot && p.ImportPath != "" && !strings.Contains(p.ImportPath, ".")
|
||||
p.GoFiles = pp.GoFiles
|
||||
p.CgoFiles = pp.CgoFiles
|
||||
p.IgnoredGoFiles = pp.IgnoredGoFiles
|
||||
p.CFiles = pp.CFiles
|
||||
p.CXXFiles = pp.CXXFiles
|
||||
p.MFiles = pp.MFiles
|
||||
p.HFiles = pp.HFiles
|
||||
p.SFiles = pp.SFiles
|
||||
p.SwigFiles = pp.SwigFiles
|
||||
p.SwigCXXFiles = pp.SwigCXXFiles
|
||||
p.SysoFiles = pp.SysoFiles
|
||||
p.CgoCFLAGS = pp.CgoCFLAGS
|
||||
p.CgoCPPFLAGS = pp.CgoCPPFLAGS
|
||||
p.CgoCXXFLAGS = pp.CgoCXXFLAGS
|
||||
p.CgoLDFLAGS = pp.CgoLDFLAGS
|
||||
p.CgoPkgConfig = pp.CgoPkgConfig
|
||||
p.Imports = pp.Imports
|
||||
p.TestGoFiles = pp.TestGoFiles
|
||||
p.TestImports = pp.TestImports
|
||||
p.XTestGoFiles = pp.XTestGoFiles
|
||||
p.XTestImports = pp.XTestImports
|
||||
}
|
||||
|
||||
type PathPkgsIndex struct {
|
||||
indexs []*PkgsIndex
|
||||
}
|
||||
|
||||
func (p *PathPkgsIndex) LoadIndex() {
|
||||
var wg sync.WaitGroup
|
||||
var context = build.Default
|
||||
if pkgsStd {
|
||||
context.GOPATH = ""
|
||||
}
|
||||
var srcDirs []string
|
||||
goroot := context.GOROOT
|
||||
gopath := context.GOPATH
|
||||
context.GOPATH = ""
|
||||
|
||||
if !pkgsSkipGoroot {
|
||||
//go1.4 go/src/
|
||||
//go1.3 go/src/pkg; go/src/cmd
|
||||
_, err := os.Stat(filepath.Join(goroot, "src/pkg/runtime"))
|
||||
if err == nil {
|
||||
for _, v := range context.SrcDirs() {
|
||||
if strings.HasSuffix(v, "pkg") {
|
||||
srcDirs = append(srcDirs, v[:len(v)-3]+"cmd")
|
||||
}
|
||||
srcDirs = append(srcDirs, v)
|
||||
}
|
||||
} else {
|
||||
srcDirs = append(srcDirs, filepath.Join(goroot, "src"))
|
||||
}
|
||||
}
|
||||
|
||||
context.GOPATH = gopath
|
||||
context.GOROOT = ""
|
||||
for _, v := range context.SrcDirs() {
|
||||
srcDirs = append(srcDirs, v)
|
||||
}
|
||||
context.GOROOT = goroot
|
||||
for _, path := range srcDirs {
|
||||
pi := &PkgsIndex{}
|
||||
p.indexs = append(p.indexs, pi)
|
||||
pkgsGate.enter()
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
pkgsGate.leave()
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
children, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
pkgsGate.leave()
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
continue
|
||||
}
|
||||
for _, child := range children {
|
||||
if child.IsDir() {
|
||||
wg.Add(1)
|
||||
go func(path, name string) {
|
||||
defer wg.Done()
|
||||
pi.loadPkgsPath(&wg, path, name)
|
||||
}(path, child.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (p *PathPkgsIndex) Sort() {
|
||||
for _, v := range p.indexs {
|
||||
v.sort()
|
||||
}
|
||||
}
|
||||
|
||||
type PkgsIndex struct {
|
||||
sync.Mutex
|
||||
pkgs []*build.Package
|
||||
}
|
||||
|
||||
func (p *PkgsIndex) sort() {
|
||||
sort.Sort(PkgSlice(p.pkgs))
|
||||
}
|
||||
|
||||
type PkgSlice []*build.Package
|
||||
|
||||
func (p PkgSlice) Len() int {
|
||||
return len([]*build.Package(p))
|
||||
}
|
||||
|
||||
func (p PkgSlice) Less(i, j int) bool {
|
||||
if p[i].IsCommand() && !p[j].IsCommand() {
|
||||
return true
|
||||
} else if !p[i].IsCommand() && p[j].IsCommand() {
|
||||
return false
|
||||
}
|
||||
return p[i].ImportPath < p[j].ImportPath
|
||||
}
|
||||
|
||||
func (p PkgSlice) Swap(i, j int) {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
|
||||
// pkgsgate protects the OS & filesystem from too much concurrency.
|
||||
// Too much disk I/O -> too many threads -> swapping and bad scheduling.
|
||||
// gate is a semaphore for limiting concurrency.
|
||||
type gate chan struct{}
|
||||
|
||||
func (g gate) enter() { g <- struct{}{} }
|
||||
func (g gate) leave() { <-g }
|
||||
|
||||
var pkgsGate = make(gate, 8)
|
||||
|
||||
func (p *PkgsIndex) loadPkgsPath(wg *sync.WaitGroup, root, pkgrelpath string) {
|
||||
importpath := filepath.ToSlash(pkgrelpath)
|
||||
dir := filepath.Join(root, importpath)
|
||||
|
||||
pkgsGate.enter()
|
||||
defer pkgsGate.leave()
|
||||
pkgDir, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
children, err := pkgDir.Readdir(-1)
|
||||
pkgDir.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// hasGo tracks whether a directory actually appears to be a
|
||||
// Go source code directory. If $GOPATH == $HOME, and
|
||||
// $HOME/src has lots of other large non-Go projects in it,
|
||||
// then the calls to importPathToName below can be expensive.
|
||||
hasGo := false
|
||||
for _, child := range children {
|
||||
name := child.Name()
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if c := name[0]; c == '.' || ('0' <= c && c <= '9') {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(name, ".go") {
|
||||
hasGo = true
|
||||
}
|
||||
if child.IsDir() {
|
||||
if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") || name == "testdata" {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(root, name string) {
|
||||
defer wg.Done()
|
||||
p.loadPkgsPath(wg, root, name)
|
||||
}(root, filepath.Join(importpath, name))
|
||||
}
|
||||
}
|
||||
if hasGo {
|
||||
buildPkg, err := build.ImportDir(dir, 0)
|
||||
if err == nil {
|
||||
if buildPkg.ImportPath == "." {
|
||||
buildPkg.ImportPath = filepath.ToSlash(pkgrelpath)
|
||||
buildPkg.Root = root
|
||||
buildPkg.Goroot = true
|
||||
}
|
||||
p.Lock()
|
||||
p.pkgs = append(p.pkgs, buildPkg)
|
||||
p.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2011-2015 visualfc <visualfc@gmail.com>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package runcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/visualfc/gotools/command"
|
||||
)
|
||||
|
||||
var Command = &command.Command{
|
||||
Run: runCmd,
|
||||
UsageLine: "runcmd [-w work_path] <program_name> [arguments...]",
|
||||
Short: "run program",
|
||||
Long: `run program and arguments`,
|
||||
}
|
||||
|
||||
var execWorkPath string
|
||||
var execWaitEnter bool
|
||||
|
||||
func init() {
|
||||
Command.Flag.StringVar(&execWorkPath, "w", "", "work path")
|
||||
Command.Flag.BoolVar(&execWaitEnter, "e", true, "wait enter and continue")
|
||||
}
|
||||
|
||||
func runCmd(cmd *command.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
cmd.Usage()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if execWorkPath == "" {
|
||||
var err error
|
||||
execWorkPath, err = os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "liteide_stub exec: os.Getwd() false\n")
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
return err
|
||||
}
|
||||
}
|
||||
fileName := args[0]
|
||||
|
||||
filePath, err := exec.LookPath(fileName)
|
||||
if err != nil {
|
||||
filePath, err = exec.LookPath("./" + fileName)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "liteide_stub exec: file %s not found\n", fileName)
|
||||
command.SetExitStatus(3)
|
||||
command.Exit()
|
||||
}
|
||||
|
||||
fmt.Println("Starting Process", filePath, strings.Join(args[1:], " "), "...")
|
||||
|
||||
command := exec.Command(filePath, args[1:]...)
|
||||
command.Dir = execWorkPath
|
||||
command.Stdin = os.Stdin
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
|
||||
err = command.Run()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("\nEnd Process", err)
|
||||
} else {
|
||||
fmt.Println("\nEnd Process", "exit status 0")
|
||||
}
|
||||
|
||||
exitWaitEnter()
|
||||
return nil
|
||||
}
|
||||
|
||||
func exitWaitEnter() {
|
||||
if !execWaitEnter {
|
||||
return
|
||||
}
|
||||
fmt.Println("\nPress enter key to continue")
|
||||
var s = [256]byte{}
|
||||
os.Stdin.Read(s[:])
|
||||
command.SetExitStatus(0)
|
||||
command.Exit()
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// +build !go1.4
|
||||
|
||||
package stdlib
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ImportStdPkg(context *build.Context, path string, mode build.ImportMode) (*build.Package, error) {
|
||||
realpath := filepath.Join(context.GOROOT, "src", "pkg", path)
|
||||
if _, err := os.Stat(realpath); err != nil {
|
||||
realpath = filepath.Join(context.GOROOT, "src", path)
|
||||
}
|
||||
pkg, err := context.ImportDir(realpath, 0)
|
||||
pkg.ImportPath = path
|
||||
return pkg, err
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// +build go1.4
|
||||
|
||||
package stdlib
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ImportStdPkg(context *build.Context, path string, mode build.ImportMode) (*build.Package, error) {
|
||||
realpath := filepath.Join(context.GOROOT, "src", path)
|
||||
if _, err := os.Stat(realpath); err != nil {
|
||||
realpath = filepath.Join(context.GOROOT, "src/pkg", path)
|
||||
}
|
||||
pkg, err := context.ImportDir(realpath, 0)
|
||||
pkg.ImportPath = path
|
||||
return pkg, err
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var pkgList = `
|
||||
cmd/cgo
|
||||
cmd/fix
|
||||
cmd/go
|
||||
cmd/gofmt
|
||||
cmd/yacc
|
||||
archive/tar
|
||||
archive/zip
|
||||
bufio
|
||||
bytes
|
||||
compress/bzip2
|
||||
compress/flate
|
||||
compress/gzip
|
||||
compress/lzw
|
||||
compress/zlib
|
||||
container/heap
|
||||
container/list
|
||||
container/ring
|
||||
crypto
|
||||
crypto/aes
|
||||
crypto/cipher
|
||||
crypto/des
|
||||
crypto/dsa
|
||||
crypto/ecdsa
|
||||
crypto/elliptic
|
||||
crypto/hmac
|
||||
crypto/md5
|
||||
crypto/rand
|
||||
crypto/rc4
|
||||
crypto/rsa
|
||||
crypto/sha1
|
||||
crypto/sha256
|
||||
crypto/sha512
|
||||
crypto/subtle
|
||||
crypto/tls
|
||||
crypto/x509
|
||||
crypto/x509/pkix
|
||||
database/sql
|
||||
database/sql/driver
|
||||
debug/dwarf
|
||||
debug/elf
|
||||
debug/gosym
|
||||
debug/macho
|
||||
debug/pe
|
||||
encoding
|
||||
encoding/ascii85
|
||||
encoding/asn1
|
||||
encoding/base32
|
||||
encoding/base64
|
||||
encoding/binary
|
||||
encoding/csv
|
||||
encoding/gob
|
||||
encoding/hex
|
||||
encoding/json
|
||||
encoding/pem
|
||||
encoding/xml
|
||||
errors
|
||||
expvar
|
||||
flag
|
||||
fmt
|
||||
go/ast
|
||||
go/build
|
||||
go/doc
|
||||
go/format
|
||||
go/parser
|
||||
go/printer
|
||||
go/scanner
|
||||
go/token
|
||||
hash
|
||||
hash/adler32
|
||||
hash/crc32
|
||||
hash/crc64
|
||||
hash/fnv
|
||||
html
|
||||
html/template
|
||||
image
|
||||
image/color
|
||||
image/color/palette
|
||||
image/draw
|
||||
image/gif
|
||||
image/jpeg
|
||||
image/png
|
||||
index/suffixarray
|
||||
io
|
||||
io/ioutil
|
||||
log
|
||||
log/syslog
|
||||
math
|
||||
math/big
|
||||
math/cmplx
|
||||
math/rand
|
||||
mime
|
||||
mime/multipart
|
||||
net
|
||||
net/http
|
||||
net/http/cgi
|
||||
net/http/cookiejar
|
||||
net/http/fcgi
|
||||
net/http/httptest
|
||||
net/http/httputil
|
||||
net/http/pprof
|
||||
net/mail
|
||||
net/rpc
|
||||
net/rpc/jsonrpc
|
||||
net/smtp
|
||||
net/textproto
|
||||
net/url
|
||||
os
|
||||
os/exec
|
||||
os/signal
|
||||
os/user
|
||||
path
|
||||
path/filepath
|
||||
reflect
|
||||
regexp
|
||||
regexp/syntax
|
||||
runtime
|
||||
runtime/cgo
|
||||
runtime/debug
|
||||
runtime/pprof
|
||||
runtime/race
|
||||
sort
|
||||
strconv
|
||||
strings
|
||||
sync
|
||||
sync/atomic
|
||||
syscall
|
||||
testing
|
||||
testing/iotest
|
||||
testing/quick
|
||||
text/scanner
|
||||
text/tabwriter
|
||||
text/template
|
||||
text/template/parse
|
||||
time
|
||||
unicode
|
||||
unicode/utf16
|
||||
unicode/utf8
|
||||
unsafe
|
||||
`
|
||||
|
||||
func main() {
|
||||
//fmt.Println(pkgList)
|
||||
var list []string
|
||||
index := 0
|
||||
for _, v := range strings.Split(pkgList, "\n") {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
v = "\"" + v + "\""
|
||||
if index%4 == 0 && index != 0 {
|
||||
v = "\n" + v
|
||||
}
|
||||
list = append(list, v)
|
||||
index++
|
||||
}
|
||||
fmt.Println(strings.Join(list, ","))
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// +build ignore
|
||||
|
||||
// mkstdlib generates the zstdlib.go file, containing the Go standard
|
||||
// library API symbols. It's baked into the binary to avoid scanning
|
||||
// GOPATH in the common case.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func mustOpen(name string) io.Reader {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func api(base string) string {
|
||||
return filepath.Join(os.Getenv("GOROOT"), "api", base)
|
||||
}
|
||||
|
||||
var sym = regexp.MustCompile(`^pkg (\S+).*?, (?:var|func|type|const) ([A-Z]\w*)`)
|
||||
|
||||
func main() {
|
||||
var buf bytes.Buffer
|
||||
outf := func(format string, args ...interface{}) {
|
||||
fmt.Fprintf(&buf, format, args...)
|
||||
}
|
||||
outf("// AUTO-GENERATED BY mkstdlib.go\n\n")
|
||||
outf("package imports\n")
|
||||
outf("var stdlib = map[string]string{\n")
|
||||
f := io.MultiReader(
|
||||
mustOpen(api("go1.txt")),
|
||||
mustOpen(api("go1.1.txt")),
|
||||
mustOpen(api("go1.2.txt")),
|
||||
mustOpen(api("go1.3.txt")),
|
||||
)
|
||||
sc := bufio.NewScanner(f)
|
||||
fullImport := map[string]string{} // "zip.NewReader" => "archive/zip"
|
||||
ambiguous := map[string]bool{}
|
||||
var keys []string
|
||||
for sc.Scan() {
|
||||
l := sc.Text()
|
||||
has := func(v string) bool { return strings.Contains(l, v) }
|
||||
if has("struct, ") || has("interface, ") || has(", method (") {
|
||||
continue
|
||||
}
|
||||
if m := sym.FindStringSubmatch(l); m != nil {
|
||||
full := m[1]
|
||||
key := path.Base(full) + "." + m[2]
|
||||
if exist, ok := fullImport[key]; ok {
|
||||
if exist != full {
|
||||
ambiguous[key] = true
|
||||
}
|
||||
} else {
|
||||
fullImport[key] = full
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
if ambiguous[key] {
|
||||
outf("\t// %q is ambiguous\n", key)
|
||||
} else {
|
||||
outf("\t%q: %q,\n", key, fullImport[key])
|
||||
}
|
||||
}
|
||||
outf("}\n")
|
||||
fmtbuf, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Stdout.Write(fmtbuf)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package stdlib
|
||||
|
||||
var Packages = []string{
|
||||
"cmd/cgo", "cmd/fix", "cmd/go", "cmd/gofmt",
|
||||
"cmd/yacc", "archive/tar", "archive/zip", "bufio",
|
||||
"bytes", "compress/bzip2", "compress/flate", "compress/gzip",
|
||||
"compress/lzw", "compress/zlib", "container/heap", "container/list",
|
||||
"container/ring", "crypto", "crypto/aes", "crypto/cipher",
|
||||
"crypto/des", "crypto/dsa", "crypto/ecdsa", "crypto/elliptic",
|
||||
"crypto/hmac", "crypto/md5", "crypto/rand", "crypto/rc4",
|
||||
"crypto/rsa", "crypto/sha1", "crypto/sha256", "crypto/sha512",
|
||||
"crypto/subtle", "crypto/tls", "crypto/x509", "crypto/x509/pkix",
|
||||
"database/sql", "database/sql/driver", "debug/dwarf", "debug/elf",
|
||||
"debug/gosym", "debug/macho", "debug/pe", "encoding",
|
||||
"encoding/ascii85", "encoding/asn1", "encoding/base32", "encoding/base64",
|
||||
"encoding/binary", "encoding/csv", "encoding/gob", "encoding/hex",
|
||||
"encoding/json", "encoding/pem", "encoding/xml", "errors",
|
||||
"expvar", "flag", "fmt", "go/ast",
|
||||
"go/build", "go/doc", "go/format", "go/parser",
|
||||
"go/printer", "go/scanner", "go/token", "hash",
|
||||
"hash/adler32", "hash/crc32", "hash/crc64", "hash/fnv",
|
||||
"html", "html/template", "image", "image/color",
|
||||
"image/color/palette", "image/draw", "image/gif", "image/jpeg",
|
||||
"image/png", "index/suffixarray", "io", "io/ioutil",
|
||||
"log", "log/syslog", "math", "math/big",
|
||||
"math/cmplx", "math/rand", "mime", "mime/multipart",
|
||||
"net", "net/http", "net/http/cgi", "net/http/cookiejar",
|
||||
"net/http/fcgi", "net/http/httptest", "net/http/httputil", "net/http/pprof",
|
||||
"net/mail", "net/rpc", "net/rpc/jsonrpc", "net/smtp",
|
||||
"net/textproto", "net/url", "os", "os/exec",
|
||||
"os/signal", "os/user", "path", "path/filepath",
|
||||
"reflect", "regexp", "regexp/syntax", "runtime",
|
||||
"runtime/cgo", "runtime/debug", "runtime/pprof", "runtime/race",
|
||||
"sort", "strconv", "strings", "sync",
|
||||
"sync/atomic", "syscall", "testing", "testing/iotest",
|
||||
"testing/quick", "text/scanner", "text/tabwriter", "text/template",
|
||||
"text/template/parse", "time", "unicode", "unicode/utf16",
|
||||
"unicode/utf8", "unsafe",
|
||||
}
|
||||
|
||||
func IsStdPkg(pkg string) bool {
|
||||
for _, v := range Packages {
|
||||
if v == pkg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
|||
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.
|
|
@ -0,0 +1,22 @@
|
|||
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.
|
|
@ -0,0 +1,967 @@
|
|||
// 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 intsets provides Sparse, a compact and fast representation
|
||||
// for sparse sets of int values.
|
||||
//
|
||||
// The time complexity of the operations Len, Insert, Remove and Has
|
||||
// is in O(n) but in practice those methods are faster and more
|
||||
// space-efficient than equivalent operations on sets based on the Go
|
||||
// map type. The IsEmpty, Min, Max, Clear and TakeMin operations
|
||||
// require constant time.
|
||||
//
|
||||
package intsets // import "golang.org/x/tools/container/intsets"
|
||||
|
||||
// TODO(adonovan):
|
||||
// - Add InsertAll(...int), RemoveAll(...int)
|
||||
// - Add 'bool changed' results for {Intersection,Difference}With too.
|
||||
//
|
||||
// TODO(adonovan): implement Dense, a dense bit vector with a similar API.
|
||||
// The space usage would be proportional to Max(), not Len(), and the
|
||||
// implementation would be based upon big.Int.
|
||||
//
|
||||
// TODO(adonovan): experiment with making the root block indirect (nil
|
||||
// iff IsEmpty). This would reduce the memory usage when empty and
|
||||
// might simplify the aliasing invariants.
|
||||
//
|
||||
// TODO(adonovan): opt: make UnionWith and Difference faster.
|
||||
// These are the hot-spots for go/pointer.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A Sparse is a set of int values.
|
||||
// Sparse operations (even queries) are not concurrency-safe.
|
||||
//
|
||||
// The zero value for Sparse is a valid empty set.
|
||||
//
|
||||
// Sparse sets must be copied using the Copy method, not by assigning
|
||||
// a Sparse value.
|
||||
//
|
||||
type Sparse struct {
|
||||
// An uninitialized Sparse represents an empty set.
|
||||
// An empty set may also be represented by
|
||||
// root.next == root.prev == &root.
|
||||
// In a non-empty set, root.next points to the first block and
|
||||
// root.prev to the last.
|
||||
// root.offset and root.bits are unused.
|
||||
root block
|
||||
}
|
||||
|
||||
type word uintptr
|
||||
|
||||
const (
|
||||
_m = ^word(0)
|
||||
bitsPerWord = 8 << (_m>>8&1 + _m>>16&1 + _m>>32&1)
|
||||
bitsPerBlock = 256 // optimal value for go/pointer solver performance
|
||||
wordsPerBlock = bitsPerBlock / bitsPerWord
|
||||
)
|
||||
|
||||
// Limit values of implementation-specific int type.
|
||||
const (
|
||||
MaxInt = int(^uint(0) >> 1)
|
||||
MinInt = -MaxInt - 1
|
||||
)
|
||||
|
||||
// -- block ------------------------------------------------------------
|
||||
|
||||
// A set is represented as a circular doubly-linked list of blocks,
|
||||
// each containing an offset and a bit array of fixed size
|
||||
// bitsPerBlock; the blocks are ordered by increasing offset.
|
||||
//
|
||||
// The set contains an element x iff the block whose offset is x - (x
|
||||
// mod bitsPerBlock) has the bit (x mod bitsPerBlock) set, where mod
|
||||
// is the Euclidean remainder.
|
||||
//
|
||||
// A block may only be empty transiently.
|
||||
//
|
||||
type block struct {
|
||||
offset int // offset mod bitsPerBlock == 0
|
||||
bits [wordsPerBlock]word // contains at least one set bit
|
||||
next, prev *block // doubly-linked list of blocks
|
||||
}
|
||||
|
||||
// wordMask returns the word index (in block.bits)
|
||||
// and single-bit mask for the block's ith bit.
|
||||
func wordMask(i uint) (w uint, mask word) {
|
||||
w = i / bitsPerWord
|
||||
mask = 1 << (i % bitsPerWord)
|
||||
return
|
||||
}
|
||||
|
||||
// insert sets the block b's ith bit and
|
||||
// returns true if it was not already set.
|
||||
//
|
||||
func (b *block) insert(i uint) bool {
|
||||
w, mask := wordMask(i)
|
||||
if b.bits[w]&mask == 0 {
|
||||
b.bits[w] |= mask
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// remove clears the block's ith bit and
|
||||
// returns true if the bit was previously set.
|
||||
// NB: may leave the block empty.
|
||||
//
|
||||
func (b *block) remove(i uint) bool {
|
||||
w, mask := wordMask(i)
|
||||
if b.bits[w]&mask != 0 {
|
||||
b.bits[w] &^= mask
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// has reports whether the block's ith bit is set.
|
||||
func (b *block) has(i uint) bool {
|
||||
w, mask := wordMask(i)
|
||||
return b.bits[w]&mask != 0
|
||||
}
|
||||
|
||||
// empty reports whether b.len()==0, but more efficiently.
|
||||
func (b *block) empty() bool {
|
||||
for _, w := range b.bits {
|
||||
if w != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// len returns the number of set bits in block b.
|
||||
func (b *block) len() int {
|
||||
var l int
|
||||
for _, w := range b.bits {
|
||||
l += popcount(w)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// max returns the maximum element of the block.
|
||||
// The block must not be empty.
|
||||
//
|
||||
func (b *block) max() int {
|
||||
bi := b.offset + bitsPerBlock
|
||||
// Decrement bi by number of high zeros in last.bits.
|
||||
for i := len(b.bits) - 1; i >= 0; i-- {
|
||||
if w := b.bits[i]; w != 0 {
|
||||
return bi - nlz(w) - 1
|
||||
}
|
||||
bi -= bitsPerWord
|
||||
}
|
||||
panic("BUG: empty block")
|
||||
}
|
||||
|
||||
// min returns the minimum element of the block,
|
||||
// and also removes it if take is set.
|
||||
// The block must not be initially empty.
|
||||
// NB: may leave the block empty.
|
||||
//
|
||||
func (b *block) min(take bool) int {
|
||||
for i, w := range b.bits {
|
||||
if w != 0 {
|
||||
tz := ntz(w)
|
||||
if take {
|
||||
b.bits[i] = w &^ (1 << uint(tz))
|
||||
}
|
||||
return b.offset + int(i*bitsPerWord) + tz
|
||||
}
|
||||
}
|
||||
panic("BUG: empty block")
|
||||
}
|
||||
|
||||
// forEach calls f for each element of block b.
|
||||
// f must not mutate b's enclosing Sparse.
|
||||
func (b *block) forEach(f func(int)) {
|
||||
for i, w := range b.bits {
|
||||
offset := b.offset + i*bitsPerWord
|
||||
for bi := 0; w != 0 && bi < bitsPerWord; bi++ {
|
||||
if w&1 != 0 {
|
||||
f(offset)
|
||||
}
|
||||
offset++
|
||||
w >>= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// offsetAndBitIndex returns the offset of the block that would
|
||||
// contain x and the bit index of x within that block.
|
||||
//
|
||||
func offsetAndBitIndex(x int) (int, uint) {
|
||||
mod := x % bitsPerBlock
|
||||
if mod < 0 {
|
||||
// Euclidean (non-negative) remainder
|
||||
mod += bitsPerBlock
|
||||
}
|
||||
return x - mod, uint(mod)
|
||||
}
|
||||
|
||||
// -- Sparse --------------------------------------------------------------
|
||||
|
||||
// start returns the root's next block, which is the root block
|
||||
// (if s.IsEmpty()) or the first true block otherwise.
|
||||
// start has the side effect of ensuring that s is properly
|
||||
// initialized.
|
||||
//
|
||||
func (s *Sparse) start() *block {
|
||||
root := &s.root
|
||||
if root.next == nil {
|
||||
root.next = root
|
||||
root.prev = root
|
||||
} else if root.next.prev != root {
|
||||
// Copying a Sparse x leads to pernicious corruption: the
|
||||
// new Sparse y shares the old linked list, but iteration
|
||||
// on y will never encounter &y.root so it goes into a
|
||||
// loop. Fail fast before this occurs.
|
||||
panic("A Sparse has been copied without (*Sparse).Copy()")
|
||||
}
|
||||
|
||||
return root.next
|
||||
}
|
||||
|
||||
// IsEmpty reports whether the set s is empty.
|
||||
func (s *Sparse) IsEmpty() bool {
|
||||
return s.start() == &s.root
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the set s.
|
||||
func (s *Sparse) Len() int {
|
||||
var l int
|
||||
for b := s.start(); b != &s.root; b = b.next {
|
||||
l += b.len()
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Max returns the maximum element of the set s, or MinInt if s is empty.
|
||||
func (s *Sparse) Max() int {
|
||||
if s.IsEmpty() {
|
||||
return MinInt
|
||||
}
|
||||
return s.root.prev.max()
|
||||
}
|
||||
|
||||
// Min returns the minimum element of the set s, or MaxInt if s is empty.
|
||||
func (s *Sparse) Min() int {
|
||||
if s.IsEmpty() {
|
||||
return MaxInt
|
||||
}
|
||||
return s.root.next.min(false)
|
||||
}
|
||||
|
||||
// block returns the block that would contain offset,
|
||||
// or nil if s contains no such block.
|
||||
//
|
||||
func (s *Sparse) block(offset int) *block {
|
||||
b := s.start()
|
||||
for b != &s.root && b.offset <= offset {
|
||||
if b.offset == offset {
|
||||
return b
|
||||
}
|
||||
b = b.next
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert adds x to the set s, and reports whether the set grew.
|
||||
func (s *Sparse) Insert(x int) bool {
|
||||
offset, i := offsetAndBitIndex(x)
|
||||
b := s.start()
|
||||
for b != &s.root && b.offset <= offset {
|
||||
if b.offset == offset {
|
||||
return b.insert(i)
|
||||
}
|
||||
b = b.next
|
||||
}
|
||||
|
||||
// Insert new block before b.
|
||||
new := &block{offset: offset}
|
||||
new.next = b
|
||||
new.prev = b.prev
|
||||
new.prev.next = new
|
||||
new.next.prev = new
|
||||
return new.insert(i)
|
||||
}
|
||||
|
||||
func (s *Sparse) removeBlock(b *block) {
|
||||
b.prev.next = b.next
|
||||
b.next.prev = b.prev
|
||||
}
|
||||
|
||||
// Remove removes x from the set s, and reports whether the set shrank.
|
||||
func (s *Sparse) Remove(x int) bool {
|
||||
offset, i := offsetAndBitIndex(x)
|
||||
if b := s.block(offset); b != nil {
|
||||
if !b.remove(i) {
|
||||
return false
|
||||
}
|
||||
if b.empty() {
|
||||
s.removeBlock(b)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear removes all elements from the set s.
|
||||
func (s *Sparse) Clear() {
|
||||
s.root.next = &s.root
|
||||
s.root.prev = &s.root
|
||||
}
|
||||
|
||||
// If set s is non-empty, TakeMin sets *p to the minimum element of
|
||||
// the set s, removes that element from the set and returns true.
|
||||
// Otherwise, it returns false and *p is undefined.
|
||||
//
|
||||
// This method may be used for iteration over a worklist like so:
|
||||
//
|
||||
// var x int
|
||||
// for worklist.TakeMin(&x) { use(x) }
|
||||
//
|
||||
func (s *Sparse) TakeMin(p *int) bool {
|
||||
head := s.start()
|
||||
if head == &s.root {
|
||||
return false
|
||||
}
|
||||
*p = head.min(true)
|
||||
if head.empty() {
|
||||
s.removeBlock(head)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Has reports whether x is an element of the set s.
|
||||
func (s *Sparse) Has(x int) bool {
|
||||
offset, i := offsetAndBitIndex(x)
|
||||
if b := s.block(offset); b != nil {
|
||||
return b.has(i)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// forEach applies function f to each element of the set s in order.
|
||||
//
|
||||
// f must not mutate s. Consequently, forEach is not safe to expose
|
||||
// to clients. In any case, using "range s.AppendTo()" allows more
|
||||
// natural control flow with continue/break/return.
|
||||
//
|
||||
func (s *Sparse) forEach(f func(int)) {
|
||||
for b := s.start(); b != &s.root; b = b.next {
|
||||
b.forEach(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy sets s to the value of x.
|
||||
func (s *Sparse) Copy(x *Sparse) {
|
||||
if s == x {
|
||||
return
|
||||
}
|
||||
|
||||
xb := x.start()
|
||||
sb := s.start()
|
||||
for xb != &x.root {
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
sb.offset = xb.offset
|
||||
sb.bits = xb.bits
|
||||
xb = xb.next
|
||||
sb = sb.next
|
||||
}
|
||||
s.discardTail(sb)
|
||||
}
|
||||
|
||||
// insertBlockBefore returns a new block, inserting it before next.
|
||||
func (s *Sparse) insertBlockBefore(next *block) *block {
|
||||
b := new(block)
|
||||
b.next = next
|
||||
b.prev = next.prev
|
||||
b.prev.next = b
|
||||
next.prev = b
|
||||
return b
|
||||
}
|
||||
|
||||
// discardTail removes block b and all its successors from s.
|
||||
func (s *Sparse) discardTail(b *block) {
|
||||
if b != &s.root {
|
||||
b.prev.next = &s.root
|
||||
s.root.prev = b.prev
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectionWith sets s to the intersection s ∩ x.
|
||||
func (s *Sparse) IntersectionWith(x *Sparse) {
|
||||
if s == x {
|
||||
return
|
||||
}
|
||||
|
||||
xb := x.start()
|
||||
sb := s.start()
|
||||
for xb != &x.root && sb != &s.root {
|
||||
switch {
|
||||
case xb.offset < sb.offset:
|
||||
xb = xb.next
|
||||
|
||||
case xb.offset > sb.offset:
|
||||
sb = sb.next
|
||||
s.removeBlock(sb.prev)
|
||||
|
||||
default:
|
||||
var sum word
|
||||
for i := range sb.bits {
|
||||
r := xb.bits[i] & sb.bits[i]
|
||||
sb.bits[i] = r
|
||||
sum |= r
|
||||
}
|
||||
if sum != 0 {
|
||||
sb = sb.next
|
||||
} else {
|
||||
// sb will be overwritten or removed
|
||||
}
|
||||
|
||||
xb = xb.next
|
||||
}
|
||||
}
|
||||
|
||||
s.discardTail(sb)
|
||||
}
|
||||
|
||||
// Intersection sets s to the intersection x ∩ y.
|
||||
func (s *Sparse) Intersection(x, y *Sparse) {
|
||||
switch {
|
||||
case s == x:
|
||||
s.IntersectionWith(y)
|
||||
return
|
||||
case s == y:
|
||||
s.IntersectionWith(x)
|
||||
return
|
||||
case x == y:
|
||||
s.Copy(x)
|
||||
return
|
||||
}
|
||||
|
||||
xb := x.start()
|
||||
yb := y.start()
|
||||
sb := s.start()
|
||||
for xb != &x.root && yb != &y.root {
|
||||
switch {
|
||||
case xb.offset < yb.offset:
|
||||
xb = xb.next
|
||||
continue
|
||||
case xb.offset > yb.offset:
|
||||
yb = yb.next
|
||||
continue
|
||||
}
|
||||
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
sb.offset = xb.offset
|
||||
|
||||
var sum word
|
||||
for i := range sb.bits {
|
||||
r := xb.bits[i] & yb.bits[i]
|
||||
sb.bits[i] = r
|
||||
sum |= r
|
||||
}
|
||||
if sum != 0 {
|
||||
sb = sb.next
|
||||
} else {
|
||||
// sb will be overwritten or removed
|
||||
}
|
||||
|
||||
xb = xb.next
|
||||
yb = yb.next
|
||||
}
|
||||
|
||||
s.discardTail(sb)
|
||||
}
|
||||
|
||||
// Intersects reports whether s ∩ x ≠ ∅.
|
||||
func (s *Sparse) Intersects(x *Sparse) bool {
|
||||
sb := s.start()
|
||||
xb := x.start()
|
||||
for sb != &s.root && xb != &x.root {
|
||||
switch {
|
||||
case xb.offset < sb.offset:
|
||||
xb = xb.next
|
||||
case xb.offset > sb.offset:
|
||||
sb = sb.next
|
||||
default:
|
||||
for i := range sb.bits {
|
||||
if sb.bits[i]&xb.bits[i] != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
sb = sb.next
|
||||
xb = xb.next
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnionWith sets s to the union s ∪ x, and reports whether s grew.
|
||||
func (s *Sparse) UnionWith(x *Sparse) bool {
|
||||
if s == x {
|
||||
return false
|
||||
}
|
||||
|
||||
var changed bool
|
||||
xb := x.start()
|
||||
sb := s.start()
|
||||
for xb != &x.root {
|
||||
if sb != &s.root && sb.offset == xb.offset {
|
||||
for i := range xb.bits {
|
||||
if sb.bits[i] != xb.bits[i] {
|
||||
sb.bits[i] |= xb.bits[i]
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
xb = xb.next
|
||||
} else if sb == &s.root || sb.offset > xb.offset {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
sb.offset = xb.offset
|
||||
sb.bits = xb.bits
|
||||
changed = true
|
||||
|
||||
xb = xb.next
|
||||
}
|
||||
sb = sb.next
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
// Union sets s to the union x ∪ y.
|
||||
func (s *Sparse) Union(x, y *Sparse) {
|
||||
switch {
|
||||
case x == y:
|
||||
s.Copy(x)
|
||||
return
|
||||
case s == x:
|
||||
s.UnionWith(y)
|
||||
return
|
||||
case s == y:
|
||||
s.UnionWith(x)
|
||||
return
|
||||
}
|
||||
|
||||
xb := x.start()
|
||||
yb := y.start()
|
||||
sb := s.start()
|
||||
for xb != &x.root || yb != &y.root {
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
switch {
|
||||
case yb == &y.root || (xb != &x.root && xb.offset < yb.offset):
|
||||
sb.offset = xb.offset
|
||||
sb.bits = xb.bits
|
||||
xb = xb.next
|
||||
|
||||
case xb == &x.root || (yb != &y.root && yb.offset < xb.offset):
|
||||
sb.offset = yb.offset
|
||||
sb.bits = yb.bits
|
||||
yb = yb.next
|
||||
|
||||
default:
|
||||
sb.offset = xb.offset
|
||||
for i := range xb.bits {
|
||||
sb.bits[i] = xb.bits[i] | yb.bits[i]
|
||||
}
|
||||
xb = xb.next
|
||||
yb = yb.next
|
||||
}
|
||||
sb = sb.next
|
||||
}
|
||||
|
||||
s.discardTail(sb)
|
||||
}
|
||||
|
||||
// DifferenceWith sets s to the difference s ∖ x.
|
||||
func (s *Sparse) DifferenceWith(x *Sparse) {
|
||||
if s == x {
|
||||
s.Clear()
|
||||
return
|
||||
}
|
||||
|
||||
xb := x.start()
|
||||
sb := s.start()
|
||||
for xb != &x.root && sb != &s.root {
|
||||
switch {
|
||||
case xb.offset > sb.offset:
|
||||
sb = sb.next
|
||||
|
||||
case xb.offset < sb.offset:
|
||||
xb = xb.next
|
||||
|
||||
default:
|
||||
var sum word
|
||||
for i := range sb.bits {
|
||||
r := sb.bits[i] & ^xb.bits[i]
|
||||
sb.bits[i] = r
|
||||
sum |= r
|
||||
}
|
||||
sb = sb.next
|
||||
xb = xb.next
|
||||
|
||||
if sum == 0 {
|
||||
s.removeBlock(sb.prev)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Difference sets s to the difference x ∖ y.
|
||||
func (s *Sparse) Difference(x, y *Sparse) {
|
||||
switch {
|
||||
case x == y:
|
||||
s.Clear()
|
||||
return
|
||||
case s == x:
|
||||
s.DifferenceWith(y)
|
||||
return
|
||||
case s == y:
|
||||
var y2 Sparse
|
||||
y2.Copy(y)
|
||||
s.Difference(x, &y2)
|
||||
return
|
||||
}
|
||||
|
||||
xb := x.start()
|
||||
yb := y.start()
|
||||
sb := s.start()
|
||||
for xb != &x.root && yb != &y.root {
|
||||
if xb.offset > yb.offset {
|
||||
// y has block, x has none
|
||||
yb = yb.next
|
||||
continue
|
||||
}
|
||||
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
sb.offset = xb.offset
|
||||
|
||||
switch {
|
||||
case xb.offset < yb.offset:
|
||||
// x has block, y has none
|
||||
sb.bits = xb.bits
|
||||
|
||||
sb = sb.next
|
||||
|
||||
default:
|
||||
// x and y have corresponding blocks
|
||||
var sum word
|
||||
for i := range sb.bits {
|
||||
r := xb.bits[i] & ^yb.bits[i]
|
||||
sb.bits[i] = r
|
||||
sum |= r
|
||||
}
|
||||
if sum != 0 {
|
||||
sb = sb.next
|
||||
} else {
|
||||
// sb will be overwritten or removed
|
||||
}
|
||||
|
||||
yb = yb.next
|
||||
}
|
||||
xb = xb.next
|
||||
}
|
||||
|
||||
for xb != &x.root {
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
sb.offset = xb.offset
|
||||
sb.bits = xb.bits
|
||||
sb = sb.next
|
||||
|
||||
xb = xb.next
|
||||
}
|
||||
|
||||
s.discardTail(sb)
|
||||
}
|
||||
|
||||
// SymmetricDifferenceWith sets s to the symmetric difference s ∆ x.
|
||||
func (s *Sparse) SymmetricDifferenceWith(x *Sparse) {
|
||||
if s == x {
|
||||
s.Clear()
|
||||
return
|
||||
}
|
||||
|
||||
sb := s.start()
|
||||
xb := x.start()
|
||||
for xb != &x.root && sb != &s.root {
|
||||
switch {
|
||||
case sb.offset < xb.offset:
|
||||
sb = sb.next
|
||||
case xb.offset < sb.offset:
|
||||
nb := s.insertBlockBefore(sb)
|
||||
nb.offset = xb.offset
|
||||
nb.bits = xb.bits
|
||||
xb = xb.next
|
||||
default:
|
||||
var sum word
|
||||
for i := range sb.bits {
|
||||
r := sb.bits[i] ^ xb.bits[i]
|
||||
sb.bits[i] = r
|
||||
sum |= r
|
||||
}
|
||||
sb = sb.next
|
||||
xb = xb.next
|
||||
if sum == 0 {
|
||||
s.removeBlock(sb.prev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for xb != &x.root { // append the tail of x to s
|
||||
sb = s.insertBlockBefore(sb)
|
||||
sb.offset = xb.offset
|
||||
sb.bits = xb.bits
|
||||
sb = sb.next
|
||||
xb = xb.next
|
||||
}
|
||||
}
|
||||
|
||||
// SymmetricDifference sets s to the symmetric difference x ∆ y.
|
||||
func (s *Sparse) SymmetricDifference(x, y *Sparse) {
|
||||
switch {
|
||||
case x == y:
|
||||
s.Clear()
|
||||
return
|
||||
case s == x:
|
||||
s.SymmetricDifferenceWith(y)
|
||||
return
|
||||
case s == y:
|
||||
s.SymmetricDifferenceWith(x)
|
||||
return
|
||||
}
|
||||
|
||||
sb := s.start()
|
||||
xb := x.start()
|
||||
yb := y.start()
|
||||
for xb != &x.root && yb != &y.root {
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
switch {
|
||||
case yb.offset < xb.offset:
|
||||
sb.offset = yb.offset
|
||||
sb.bits = yb.bits
|
||||
sb = sb.next
|
||||
yb = yb.next
|
||||
case xb.offset < yb.offset:
|
||||
sb.offset = xb.offset
|
||||
sb.bits = xb.bits
|
||||
sb = sb.next
|
||||
xb = xb.next
|
||||
default:
|
||||
var sum word
|
||||
for i := range sb.bits {
|
||||
r := xb.bits[i] ^ yb.bits[i]
|
||||
sb.bits[i] = r
|
||||
sum |= r
|
||||
}
|
||||
if sum != 0 {
|
||||
sb.offset = xb.offset
|
||||
sb = sb.next
|
||||
}
|
||||
xb = xb.next
|
||||
yb = yb.next
|
||||
}
|
||||
}
|
||||
|
||||
for xb != &x.root { // append the tail of x to s
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
sb.offset = xb.offset
|
||||
sb.bits = xb.bits
|
||||
sb = sb.next
|
||||
xb = xb.next
|
||||
}
|
||||
|
||||
for yb != &y.root { // append the tail of y to s
|
||||
if sb == &s.root {
|
||||
sb = s.insertBlockBefore(sb)
|
||||
}
|
||||
sb.offset = yb.offset
|
||||
sb.bits = yb.bits
|
||||
sb = sb.next
|
||||
yb = yb.next
|
||||
}
|
||||
|
||||
s.discardTail(sb)
|
||||
}
|
||||
|
||||
// SubsetOf reports whether s ∖ x = ∅.
|
||||
func (s *Sparse) SubsetOf(x *Sparse) bool {
|
||||
if s == x {
|
||||
return true
|
||||
}
|
||||
|
||||
sb := s.start()
|
||||
xb := x.start()
|
||||
for sb != &s.root {
|
||||
switch {
|
||||
case xb == &x.root || xb.offset > sb.offset:
|
||||
return false
|
||||
case xb.offset < sb.offset:
|
||||
xb = xb.next
|
||||
default:
|
||||
for i := range sb.bits {
|
||||
if sb.bits[i]&^xb.bits[i] != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
sb = sb.next
|
||||
xb = xb.next
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Equals reports whether the sets s and t have the same elements.
|
||||
func (s *Sparse) Equals(t *Sparse) bool {
|
||||
if s == t {
|
||||
return true
|
||||
}
|
||||
sb := s.start()
|
||||
tb := t.start()
|
||||
for {
|
||||
switch {
|
||||
case sb == &s.root && tb == &t.root:
|
||||
return true
|
||||
case sb == &s.root || tb == &t.root:
|
||||
return false
|
||||
case sb.offset != tb.offset:
|
||||
return false
|
||||
case sb.bits != tb.bits:
|
||||
return false
|
||||
}
|
||||
|
||||
sb = sb.next
|
||||
tb = tb.next
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the set s.
|
||||
func (s *Sparse) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('{')
|
||||
s.forEach(func(x int) {
|
||||
if buf.Len() > 1 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
fmt.Fprintf(&buf, "%d", x)
|
||||
})
|
||||
buf.WriteByte('}')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// BitString returns the set as a string of 1s and 0s denoting the sum
|
||||
// of the i'th powers of 2, for each i in s. A radix point, always
|
||||
// preceded by a digit, appears if the sum is non-integral.
|
||||
//
|
||||
// Examples:
|
||||
// {}.BitString() = "0"
|
||||
// {4,5}.BitString() = "110000"
|
||||
// {-3}.BitString() = "0.001"
|
||||
// {-3,0,4,5}.BitString() = "110001.001"
|
||||
//
|
||||
func (s *Sparse) BitString() string {
|
||||
if s.IsEmpty() {
|
||||
return "0"
|
||||
}
|
||||
|
||||
min, max := s.Min(), s.Max()
|
||||
var nbytes int
|
||||
if max > 0 {
|
||||
nbytes = max
|
||||
}
|
||||
nbytes++ // zero bit
|
||||
radix := nbytes
|
||||
if min < 0 {
|
||||
nbytes += len(".") - min
|
||||
}
|
||||
|
||||
b := make([]byte, nbytes)
|
||||
for i := range b {
|
||||
b[i] = '0'
|
||||
}
|
||||
if radix < nbytes {
|
||||
b[radix] = '.'
|
||||
}
|
||||
s.forEach(func(x int) {
|
||||
if x >= 0 {
|
||||
x += len(".")
|
||||
}
|
||||
b[radix-x] = '1'
|
||||
})
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// GoString returns a string showing the internal representation of
|
||||
// the set s.
|
||||
//
|
||||
func (s *Sparse) GoString() string {
|
||||
var buf bytes.Buffer
|
||||
for b := s.start(); b != &s.root; b = b.next {
|
||||
fmt.Fprintf(&buf, "block %p {offset=%d next=%p prev=%p",
|
||||
b, b.offset, b.next, b.prev)
|
||||
for _, w := range b.bits {
|
||||
fmt.Fprintf(&buf, " 0%016x", w)
|
||||
}
|
||||
fmt.Fprintf(&buf, "}\n")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// AppendTo returns the result of appending the elements of s to slice
|
||||
// in order.
|
||||
func (s *Sparse) AppendTo(slice []int) []int {
|
||||
s.forEach(func(x int) {
|
||||
slice = append(slice, x)
|
||||
})
|
||||
return slice
|
||||
}
|
||||
|
||||
// -- Testing/debugging ------------------------------------------------
|
||||
|
||||
// check returns an error if the representation invariants of s are violated.
|
||||
func (s *Sparse) check() error {
|
||||
if !s.root.empty() {
|
||||
return fmt.Errorf("non-empty root block")
|
||||
}
|
||||
if s.root.offset != 0 {
|
||||
return fmt.Errorf("root block has non-zero offset %d", s.root.offset)
|
||||
}
|
||||
for b := s.start(); b != &s.root; b = b.next {
|
||||
if b.offset%bitsPerBlock != 0 {
|
||||
return fmt.Errorf("bad offset modulo: %d", b.offset)
|
||||
}
|
||||
if b.empty() {
|
||||
return fmt.Errorf("empty block")
|
||||
}
|
||||
if b.prev.next != b {
|
||||
return fmt.Errorf("bad prev.next link")
|
||||
}
|
||||
if b.next.prev != b {
|
||||
return fmt.Errorf("bad next.prev link")
|
||||
}
|
||||
if b.prev != &s.root {
|
||||
if b.offset <= b.prev.offset {
|
||||
return fmt.Errorf("bad offset order: b.offset=%d, prev.offset=%d",
|
||||
b.offset, b.prev.offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// 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 intsets
|
||||
|
||||
var a [1 << 8]byte
|
||||
|
||||
func init() {
|
||||
for i := range a {
|
||||
var n byte
|
||||
for x := i; x != 0; x >>= 1 {
|
||||
if x&1 != 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
a[i] = n
|
||||
}
|
||||
}
|
||||
|
||||
// popcount returns the population count (number of set bits) of x.
|
||||
func popcount(x word) int {
|
||||
return int(a[byte(x>>(0*8))] +
|
||||
a[byte(x>>(1*8))] +
|
||||
a[byte(x>>(2*8))] +
|
||||
a[byte(x>>(3*8))] +
|
||||
a[byte(x>>(4*8))] +
|
||||
a[byte(x>>(5*8))] +
|
||||
a[byte(x>>(6*8))] +
|
||||
a[byte(x>>(7*8))])
|
||||
}
|
||||
|
||||
// nlz returns the number of leading zeros of x.
|
||||
// From Hacker's Delight, fig 5.11.
|
||||
func nlz(x word) int {
|
||||
x |= (x >> 1)
|
||||
x |= (x >> 2)
|
||||
x |= (x >> 4)
|
||||
x |= (x >> 8)
|
||||
x |= (x >> 16)
|
||||
x |= (x >> 32)
|
||||
return popcount(^x)
|
||||
}
|
||||
|
||||
// ntz returns the number of trailing zeros of x.
|
||||
// From Hacker's Delight, fig 5.13.
|
||||
func ntz(x word) int {
|
||||
if x == 0 {
|
||||
return bitsPerWord
|
||||
}
|
||||
n := 1
|
||||
if bitsPerWord == 64 {
|
||||
if (x & 0xffffffff) == 0 {
|
||||
n = n + 32
|
||||
x = x >> 32
|
||||
}
|
||||
}
|
||||
if (x & 0x0000ffff) == 0 {
|
||||
n = n + 16
|
||||
x = x >> 16
|
||||
}
|
||||
if (x & 0x000000ff) == 0 {
|
||||
n = n + 8
|
||||
x = x >> 8
|
||||
}
|
||||
if (x & 0x0000000f) == 0 {
|
||||
n = n + 4
|
||||
x = x >> 4
|
||||
}
|
||||
if (x & 0x00000003) == 0 {
|
||||
n = n + 2
|
||||
x = x >> 2
|
||||
}
|
||||
return n - int(x&1)
|
||||
}
|
|
@ -0,0 +1,625 @@
|
|||
// 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))
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
// 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 "golang.org/x/tools/go/ast/astutil"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"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(fset, 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.
|
||||
// The goal is to find an existing import
|
||||
// whose import path has the longest shared
|
||||
// prefix with ipath.
|
||||
var (
|
||||
bestMatch = -1 // length of longest shared prefix
|
||||
lastImport = -1 // index in f.Decls of the file's final import decl
|
||||
impDecl *ast.GenDecl // import decl containing the best match
|
||||
impIndex = -1 // spec index in impDecl containing the best match
|
||||
)
|
||||
for i, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
lastImport = i
|
||||
// Do not add to import "C", to avoid disrupting the
|
||||
// association with its doc comment, breaking cgo.
|
||||
if declImports(gen, "C") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Match an empty import decl if that's all that is available.
|
||||
if len(gen.Specs) == 0 && bestMatch == -1 {
|
||||
impDecl = gen
|
||||
}
|
||||
|
||||
// Compute longest shared prefix with imports in this group.
|
||||
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 {
|
||||
impDecl = &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
}
|
||||
if lastImport >= 0 {
|
||||
impDecl.TokPos = f.Decls[lastImport].End()
|
||||
} else {
|
||||
// There are no existing imports.
|
||||
// Our new import goes after the package declaration and after
|
||||
// the comment, if any, that starts on the same line as the
|
||||
// package declaration.
|
||||
impDecl.TokPos = f.Package
|
||||
|
||||
file := fset.File(f.Package)
|
||||
pkgLine := file.Line(f.Package)
|
||||
for _, c := range f.Comments {
|
||||
if file.Line(c.Pos()) > pkgLine {
|
||||
break
|
||||
}
|
||||
impDecl.TokPos = c.End()
|
||||
}
|
||||
}
|
||||
f.Decls = append(f.Decls, nil)
|
||||
copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
|
||||
f.Decls[lastImport+1] = impDecl
|
||||
}
|
||||
|
||||
// Insert new import at insertAt.
|
||||
insertAt := 0
|
||||
if impIndex >= 0 {
|
||||
// insert after the found import
|
||||
insertAt = impIndex + 1
|
||||
}
|
||||
impDecl.Specs = append(impDecl.Specs, nil)
|
||||
copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
|
||||
impDecl.Specs[insertAt] = newImport
|
||||
pos := impDecl.Pos()
|
||||
if insertAt > 0 {
|
||||
// Assign same position as the previous import,
|
||||
// so that the sorter sees it as being in the same block.
|
||||
pos = impDecl.Specs[insertAt-1].Pos()
|
||||
}
|
||||
if newImport.Name != nil {
|
||||
newImport.Name.NamePos = pos
|
||||
}
|
||||
newImport.Path.ValuePos = pos
|
||||
newImport.EndPos = pos
|
||||
|
||||
// Clean up parens. impDecl contains at least one spec.
|
||||
if len(impDecl.Specs) == 1 {
|
||||
// Remove unneeded parens.
|
||||
impDecl.Lparen = token.NoPos
|
||||
} else if !impDecl.Lparen.IsValid() {
|
||||
// impDecl needs parens added.
|
||||
impDecl.Lparen = impDecl.Specs[0].Pos()
|
||||
}
|
||||
|
||||
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) {
|
||||
var delspecs []*ast.ImportSpec
|
||||
|
||||
// Find the import nodes that import path, if any.
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT {
|
||||
continue
|
||||
}
|
||||
for j := 0; j < len(gen.Specs); j++ {
|
||||
spec := gen.Specs[j]
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if importPath(impspec) != path {
|
||||
continue
|
||||
}
|
||||
|
||||
// We found an import spec that imports path.
|
||||
// Delete it.
|
||||
delspecs = append(delspecs, impspec)
|
||||
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]
|
||||
i--
|
||||
break
|
||||
} else if len(gen.Specs) == 1 {
|
||||
gen.Lparen = token.NoPos // drop parens
|
||||
}
|
||||
if j > 0 {
|
||||
lastImpspec := gen.Specs[j-1].(*ast.ImportSpec)
|
||||
lastLine := fset.Position(lastImpspec.Path.ValuePos).Line
|
||||
line := fset.Position(impspec.Path.ValuePos).Line
|
||||
|
||||
// We deleted an entry but now there may be
|
||||
// a blank line-sized hole where the import was.
|
||||
if line-lastLine > 1 {
|
||||
// There was a blank line immediately preceding the deleted import,
|
||||
// so there's no need to close the hole.
|
||||
// Do nothing.
|
||||
} else {
|
||||
// There was no blank line. Close the hole.
|
||||
fset.File(gen.Rparen).MergeLine(line)
|
||||
}
|
||||
}
|
||||
j--
|
||||
}
|
||||
}
|
||||
|
||||
// Delete them from f.Imports.
|
||||
for i := 0; i < len(f.Imports); i++ {
|
||||
imp := f.Imports[i]
|
||||
for j, del := range delspecs {
|
||||
if imp == del {
|
||||
copy(f.Imports[i:], f.Imports[i+1:])
|
||||
f.Imports = f.Imports[:len(f.Imports)-1]
|
||||
copy(delspecs[j:], delspecs[j+1:])
|
||||
delspecs = delspecs[:len(delspecs)-1]
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(delspecs) > 0 {
|
||||
panic(fmt.Sprintf("deleted specs from Decls but not Imports: %v", delspecs))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// matchLen returns the length of the longest path segment prefix shared by x and y.
|
||||
func matchLen(x, y string) int {
|
||||
n := 0
|
||||
for i := 0; i < len(x) && i < len(y) && x[i] == y[i]; i++ {
|
||||
if x[i] == '/' {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package astutil
|
||||
|
||||
import "go/ast"
|
||||
|
||||
// Unparen returns e with any enclosing parentheses stripped.
|
||||
func Unparen(e ast.Expr) ast.Expr {
|
||||
for {
|
||||
p, ok := e.(*ast.ParenExpr)
|
||||
if !ok {
|
||||
return e
|
||||
}
|
||||
e = p.X
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// 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 buildutil provides utilities related to the go/build
|
||||
// package in the standard library.
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface, which must
|
||||
// be concurrency-safe.
|
||||
package buildutil // import "golang.org/x/tools/go/buildutil"
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// AllPackages returns the import path of each Go package in any source
|
||||
// directory of the specified build context (e.g. $GOROOT or an element
|
||||
// of $GOPATH). Errors are ignored. The results are sorted.
|
||||
//
|
||||
// The result may include import paths for directories that contain no
|
||||
// *.go files, such as "archive" (in $GOROOT/src).
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface,
|
||||
// which must be concurrency-safe.
|
||||
//
|
||||
func AllPackages(ctxt *build.Context) []string {
|
||||
var list []string
|
||||
ForEachPackage(ctxt, func(pkg string, _ error) {
|
||||
list = append(list, pkg)
|
||||
})
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
// ForEachPackage calls the found function with the import path of
|
||||
// each Go package it finds in any source directory of the specified
|
||||
// build context (e.g. $GOROOT or an element of $GOPATH).
|
||||
//
|
||||
// If the package directory exists but could not be read, the second
|
||||
// argument to the found function provides the error.
|
||||
//
|
||||
// All I/O is done via the build.Context file system interface,
|
||||
// which must be concurrency-safe.
|
||||
//
|
||||
func ForEachPackage(ctxt *build.Context, found func(importPath string, err error)) {
|
||||
// We use a counting semaphore to limit
|
||||
// the number of parallel calls to ReadDir.
|
||||
sema := make(chan bool, 20)
|
||||
|
||||
ch := make(chan item)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, root := range ctxt.SrcDirs() {
|
||||
root := root
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
allPackages(ctxt, sema, root, ch)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// All calls to found occur in the caller's goroutine.
|
||||
for i := range ch {
|
||||
found(i.importPath, i.err)
|
||||
}
|
||||
}
|
||||
|
||||
type item struct {
|
||||
importPath string
|
||||
err error // (optional)
|
||||
}
|
||||
|
||||
func allPackages(ctxt *build.Context, sema chan bool, root string, ch chan<- item) {
|
||||
root = filepath.Clean(root) + string(os.PathSeparator)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
var walkDir func(dir string)
|
||||
walkDir = func(dir string) {
|
||||
// Avoid .foo, _foo, and testdata directory trees.
|
||||
base := filepath.Base(dir)
|
||||
if base == "" || base[0] == '.' || base[0] == '_' || base == "testdata" {
|
||||
return
|
||||
}
|
||||
|
||||
pkg := filepath.ToSlash(strings.TrimPrefix(dir, root))
|
||||
|
||||
// Prune search if we encounter any of these import paths.
|
||||
switch pkg {
|
||||
case "builtin":
|
||||
return
|
||||
}
|
||||
|
||||
sema <- true
|
||||
files, err := ReadDir(ctxt, dir)
|
||||
<-sema
|
||||
if pkg != "" || err != nil {
|
||||
ch <- item{pkg, err}
|
||||
}
|
||||
for _, fi := range files {
|
||||
fi := fi
|
||||
if fi.IsDir() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
walkDir(filepath.Join(dir, fi.Name()))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walkDir(root)
|
||||
wg.Wait()
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package buildutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FakeContext returns a build.Context for the fake file tree specified
|
||||
// by pkgs, which maps package import paths to a mapping from file base
|
||||
// names to contents.
|
||||
//
|
||||
// The fake Context has a GOROOT of "/go" and no GOPATH, and overrides
|
||||
// the necessary file access methods to read from memory instead of the
|
||||
// real file system.
|
||||
//
|
||||
// Unlike a real file tree, the fake one has only two levels---packages
|
||||
// and files---so ReadDir("/go/src/") returns all packages under
|
||||
// /go/src/ including, for instance, "math" and "math/big".
|
||||
// ReadDir("/go/src/math/big") would return all the files in the
|
||||
// "math/big" package.
|
||||
//
|
||||
func FakeContext(pkgs map[string]map[string]string) *build.Context {
|
||||
clean := func(filename string) string {
|
||||
f := path.Clean(filepath.ToSlash(filename))
|
||||
// Removing "/go/src" while respecting segment
|
||||
// boundaries has this unfortunate corner case:
|
||||
if f == "/go/src" {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(f, "/go/src/")
|
||||
}
|
||||
|
||||
ctxt := build.Default // copy
|
||||
ctxt.GOROOT = "/go"
|
||||
ctxt.GOPATH = ""
|
||||
ctxt.IsDir = func(dir string) bool {
|
||||
dir = clean(dir)
|
||||
if dir == "" {
|
||||
return true // needed by (*build.Context).SrcDirs
|
||||
}
|
||||
return pkgs[dir] != nil
|
||||
}
|
||||
ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
|
||||
dir = clean(dir)
|
||||
var fis []os.FileInfo
|
||||
if dir == "" {
|
||||
// enumerate packages
|
||||
for importPath := range pkgs {
|
||||
fis = append(fis, fakeDirInfo(importPath))
|
||||
}
|
||||
} else {
|
||||
// enumerate files of package
|
||||
for basename := range pkgs[dir] {
|
||||
fis = append(fis, fakeFileInfo(basename))
|
||||
}
|
||||
}
|
||||
sort.Sort(byName(fis))
|
||||
return fis, nil
|
||||
}
|
||||
ctxt.OpenFile = func(filename string) (io.ReadCloser, error) {
|
||||
filename = clean(filename)
|
||||
dir, base := path.Split(filename)
|
||||
content, ok := pkgs[path.Clean(dir)][base]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file not found: %s", filename)
|
||||
}
|
||||
return ioutil.NopCloser(strings.NewReader(content)), nil
|
||||
}
|
||||
ctxt.IsAbsPath = func(path string) bool {
|
||||
path = filepath.ToSlash(path)
|
||||
// Don't rely on the default (filepath.Path) since on
|
||||
// Windows, it reports virtual paths as non-absolute.
|
||||
return strings.HasPrefix(path, "/")
|
||||
}
|
||||
return &ctxt
|
||||
}
|
||||
|
||||
type byName []os.FileInfo
|
||||
|
||||
func (s byName) Len() int { return len(s) }
|
||||
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
|
||||
|
||||
type fakeFileInfo string
|
||||
|
||||
func (fi fakeFileInfo) Name() string { return string(fi) }
|
||||
func (fakeFileInfo) Sys() interface{} { return nil }
|
||||
func (fakeFileInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (fakeFileInfo) IsDir() bool { return false }
|
||||
func (fakeFileInfo) Size() int64 { return 0 }
|
||||
func (fakeFileInfo) Mode() os.FileMode { return 0644 }
|
||||
|
||||
type fakeDirInfo string
|
||||
|
||||
func (fd fakeDirInfo) Name() string { return string(fd) }
|
||||
func (fakeDirInfo) Sys() interface{} { return nil }
|
||||
func (fakeDirInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (fakeDirInfo) IsDir() bool { return true }
|
||||
func (fakeDirInfo) Size() int64 { return 0 }
|
||||
func (fakeDirInfo) Mode() os.FileMode { return 0755 }
|
|
@ -0,0 +1,73 @@
|
|||
package buildutil
|
||||
|
||||
// This logic was copied from stringsFlag from $GOROOT/src/cmd/go/build.go.
|
||||
|
||||
import "fmt"
|
||||
|
||||
const TagsFlagDoc = "a list of `build tags` to consider satisfied during the build. " +
|
||||
"For more information about build tags, see the description of " +
|
||||
"build constraints in the documentation for the go/build package"
|
||||
|
||||
// TagsFlag is an implementation of the flag.Value interface that parses
|
||||
// a flag value in the same manner as go build's -tags flag and
|
||||
// populates a []string slice.
|
||||
//
|
||||
// See $GOROOT/src/go/build/doc.go for description of build tags.
|
||||
// See $GOROOT/src/cmd/go/doc.go for description of 'go build -tags' flag.
|
||||
//
|
||||
// Example:
|
||||
// flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsDoc)
|
||||
type TagsFlag []string
|
||||
|
||||
func (v *TagsFlag) Set(s string) error {
|
||||
var err error
|
||||
*v, err = splitQuotedFields(s)
|
||||
if *v == nil {
|
||||
*v = []string{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func splitQuotedFields(s string) ([]string, error) {
|
||||
// Split fields allowing '' or "" around elements.
|
||||
// Quotes further inside the string do not count.
|
||||
var f []string
|
||||
for len(s) > 0 {
|
||||
for len(s) > 0 && isSpaceByte(s[0]) {
|
||||
s = s[1:]
|
||||
}
|
||||
if len(s) == 0 {
|
||||
break
|
||||
}
|
||||
// Accepted quoted string. No unescaping inside.
|
||||
if s[0] == '"' || s[0] == '\'' {
|
||||
quote := s[0]
|
||||
s = s[1:]
|
||||
i := 0
|
||||
for i < len(s) && s[i] != quote {
|
||||
i++
|
||||
}
|
||||
if i >= len(s) {
|
||||
return nil, fmt.Errorf("unterminated %c string", quote)
|
||||
}
|
||||
f = append(f, s[:i])
|
||||
s = s[i+1:]
|
||||
continue
|
||||
}
|
||||
i := 0
|
||||
for i < len(s) && !isSpaceByte(s[i]) {
|
||||
i++
|
||||
}
|
||||
f = append(f, s[:i])
|
||||
s = s[i:]
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (v *TagsFlag) String() string {
|
||||
return "<tagsFlag>"
|
||||
}
|
||||
|
||||
func isSpaceByte(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
// 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 buildutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseFile behaves like parser.ParseFile,
|
||||
// but uses the build context's file system interface, if any.
|
||||
//
|
||||
// If file is not absolute (as defined by IsAbsPath), the (dir, file)
|
||||
// components are joined using JoinPath; dir must be absolute.
|
||||
//
|
||||
// The displayPath function, if provided, is used to transform the
|
||||
// filename that will be attached to the ASTs.
|
||||
//
|
||||
// TODO(adonovan): call this from go/loader.parseFiles when the tree thaws.
|
||||
//
|
||||
func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) {
|
||||
if !IsAbsPath(ctxt, file) {
|
||||
file = JoinPath(ctxt, dir, file)
|
||||
}
|
||||
rd, err := OpenFile(ctxt, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rd.Close() // ignore error
|
||||
if displayPath != nil {
|
||||
file = displayPath(file)
|
||||
}
|
||||
return parser.ParseFile(fset, file, rd, mode)
|
||||
}
|
||||
|
||||
// ContainingPackage returns the package containing filename.
|
||||
//
|
||||
// If filename is not absolute, it is interpreted relative to working directory dir.
|
||||
// All I/O is via the build context's file system interface, if any.
|
||||
//
|
||||
// The '...Files []string' fields of the resulting build.Package are not
|
||||
// populated (build.FindOnly mode).
|
||||
//
|
||||
// TODO(adonovan): call this from oracle when the tree thaws.
|
||||
//
|
||||
func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) {
|
||||
if !IsAbsPath(ctxt, filename) {
|
||||
filename = JoinPath(ctxt, dir, filename)
|
||||
}
|
||||
|
||||
// We must not assume the file tree uses
|
||||
// "/" always,
|
||||
// `\` always,
|
||||
// or os.PathSeparator (which varies by platform),
|
||||
// but to make any progress, we are forced to assume that
|
||||
// paths will not use `\` unless the PathSeparator
|
||||
// is also `\`, thus we can rely on filepath.ToSlash for some sanity.
|
||||
|
||||
dirSlash := path.Dir(filepath.ToSlash(filename)) + "/"
|
||||
|
||||
// We assume that no source root (GOPATH[i] or GOROOT) contains any other.
|
||||
for _, srcdir := range ctxt.SrcDirs() {
|
||||
srcdirSlash := filepath.ToSlash(srcdir) + "/"
|
||||
if strings.HasPrefix(dirSlash, srcdirSlash) {
|
||||
importPath := dirSlash[len(srcdirSlash) : len(dirSlash)-len("/")]
|
||||
return ctxt.Import(importPath, dir, build.FindOnly)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't find package containing %s", filename)
|
||||
}
|
||||
|
||||
// -- Effective methods of file system interface -------------------------
|
||||
|
||||
// (go/build.Context defines these as methods, but does not export them.)
|
||||
|
||||
// TODO(adonovan): HasSubdir?
|
||||
|
||||
// FileExists returns true if the specified file exists,
|
||||
// using the build context's file system interface.
|
||||
func FileExists(ctxt *build.Context, path string) bool {
|
||||
if ctxt.OpenFile != nil {
|
||||
r, err := ctxt.OpenFile(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
r.Close() // ignore error
|
||||
return true
|
||||
}
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// OpenFile behaves like os.Open,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) {
|
||||
if ctxt.OpenFile != nil {
|
||||
return ctxt.OpenFile(path)
|
||||
}
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
// IsAbsPath behaves like filepath.IsAbs,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func IsAbsPath(ctxt *build.Context, path string) bool {
|
||||
if ctxt.IsAbsPath != nil {
|
||||
return ctxt.IsAbsPath(path)
|
||||
}
|
||||
return filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
// JoinPath behaves like filepath.Join,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func JoinPath(ctxt *build.Context, path ...string) string {
|
||||
if ctxt.JoinPath != nil {
|
||||
return ctxt.JoinPath(path...)
|
||||
}
|
||||
return filepath.Join(path...)
|
||||
}
|
||||
|
||||
// IsDir behaves like os.Stat plus IsDir,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func IsDir(ctxt *build.Context, path string) bool {
|
||||
if ctxt.IsDir != nil {
|
||||
return ctxt.IsDir(path)
|
||||
}
|
||||
fi, err := os.Stat(path)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
||||
// ReadDir behaves like ioutil.ReadDir,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func ReadDir(ctxt *build.Context, path string) ([]os.FileInfo, error) {
|
||||
if ctxt.ReadDir != nil {
|
||||
return ctxt.ReadDir(path)
|
||||
}
|
||||
return ioutil.ReadDir(path)
|
||||
}
|
||||
|
||||
// SplitPathList behaves like filepath.SplitList,
|
||||
// but uses the build context's file system interface, if any.
|
||||
func SplitPathList(ctxt *build.Context, s string) []string {
|
||||
if ctxt.SplitPathList != nil {
|
||||
return ctxt.SplitPathList(s)
|
||||
}
|
||||
return filepath.SplitList(s)
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
// 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 callgraph defines the call graph and various algorithms
|
||||
and utilities to operate on it.
|
||||
|
||||
A call graph is a labelled directed graph whose nodes represent
|
||||
functions and whose edge labels represent syntactic function call
|
||||
sites. The presence of a labelled edge (caller, site, callee)
|
||||
indicates that caller may call callee at the specified call site.
|
||||
|
||||
A call graph is a multigraph: it may contain multiple edges (caller,
|
||||
*, callee) connecting the same pair of nodes, so long as the edges
|
||||
differ by label; this occurs when one function calls another function
|
||||
from multiple call sites. Also, it may contain multiple edges
|
||||
(caller, site, *) that differ only by callee; this indicates a
|
||||
polymorphic call.
|
||||
|
||||
A SOUND call graph is one that overapproximates the dynamic calling
|
||||
behaviors of the program in all possible executions. One call graph
|
||||
is more PRECISE than another if it is a smaller overapproximation of
|
||||
the dynamic behavior.
|
||||
|
||||
All call graphs have a synthetic root node which is responsible for
|
||||
calling main() and init().
|
||||
|
||||
Calls to built-in functions (e.g. panic, println) are not represented
|
||||
in the call graph; they are treated like built-in operators of the
|
||||
language.
|
||||
|
||||
*/
|
||||
package callgraph // import "golang.org/x/tools/go/callgraph"
|
||||
|
||||
// TODO(adonovan): add a function to eliminate wrappers from the
|
||||
// callgraph, preserving topology.
|
||||
// More generally, we could eliminate "uninteresting" nodes such as
|
||||
// nodes from packages we don't care about.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
// A Graph represents a call graph.
|
||||
//
|
||||
// A graph may contain nodes that are not reachable from the root.
|
||||
// If the call graph is sound, such nodes indicate unreachable
|
||||
// functions.
|
||||
//
|
||||
type Graph struct {
|
||||
Root *Node // the distinguished root node
|
||||
Nodes map[*ssa.Function]*Node // all nodes by function
|
||||
}
|
||||
|
||||
// New returns a new Graph with the specified root node.
|
||||
func New(root *ssa.Function) *Graph {
|
||||
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
|
||||
g.Root = g.CreateNode(root)
|
||||
return g
|
||||
}
|
||||
|
||||
// CreateNode returns the Node for fn, creating it if not present.
|
||||
func (g *Graph) CreateNode(fn *ssa.Function) *Node {
|
||||
n, ok := g.Nodes[fn]
|
||||
if !ok {
|
||||
n = &Node{Func: fn, ID: len(g.Nodes)}
|
||||
g.Nodes[fn] = n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// A Node represents a node in a call graph.
|
||||
type Node struct {
|
||||
Func *ssa.Function // the function this node represents
|
||||
ID int // 0-based sequence number
|
||||
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
|
||||
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return fmt.Sprintf("n%d:%s", n.ID, n.Func)
|
||||
}
|
||||
|
||||
// A Edge represents an edge in the call graph.
|
||||
//
|
||||
// Site is nil for edges originating in synthetic or intrinsic
|
||||
// functions, e.g. reflect.Call or the root of the call graph.
|
||||
type Edge struct {
|
||||
Caller *Node
|
||||
Site ssa.CallInstruction
|
||||
Callee *Node
|
||||
}
|
||||
|
||||
func (e Edge) String() string {
|
||||
return fmt.Sprintf("%s --> %s", e.Caller, e.Callee)
|
||||
}
|
||||
|
||||
func (e Edge) Description() string {
|
||||
var prefix string
|
||||
switch e.Site.(type) {
|
||||
case nil:
|
||||
return "synthetic call"
|
||||
case *ssa.Go:
|
||||
prefix = "concurrent "
|
||||
case *ssa.Defer:
|
||||
prefix = "deferred "
|
||||
}
|
||||
return prefix + e.Site.Common().Description()
|
||||
}
|
||||
|
||||
func (e Edge) Pos() token.Pos {
|
||||
if e.Site == nil {
|
||||
return token.NoPos
|
||||
}
|
||||
return e.Site.Pos()
|
||||
}
|
||||
|
||||
// AddEdge adds the edge (caller, site, callee) to the call graph.
|
||||
// Elimination of duplicate edges is the caller's responsibility.
|
||||
func AddEdge(caller *Node, site ssa.CallInstruction, callee *Node) {
|
||||
e := &Edge{caller, site, callee}
|
||||
callee.In = append(callee.In, e)
|
||||
caller.Out = append(caller.Out, e)
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// 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 callgraph
|
||||
|
||||
import "golang.org/x/tools/go/ssa"
|
||||
|
||||
// This file provides various utilities over call graphs, such as
|
||||
// visitation and path search.
|
||||
|
||||
// CalleesOf returns a new set containing all direct callees of the
|
||||
// caller node.
|
||||
//
|
||||
func CalleesOf(caller *Node) map[*Node]bool {
|
||||
callees := make(map[*Node]bool)
|
||||
for _, e := range caller.Out {
|
||||
callees[e.Callee] = true
|
||||
}
|
||||
return callees
|
||||
}
|
||||
|
||||
// GraphVisitEdges visits all the edges in graph g in depth-first order.
|
||||
// The edge function is called for each edge in postorder. If it
|
||||
// returns non-nil, visitation stops and GraphVisitEdges returns that
|
||||
// value.
|
||||
//
|
||||
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
|
||||
seen := make(map[*Node]bool)
|
||||
var visit func(n *Node) error
|
||||
visit = func(n *Node) error {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
for _, e := range n.Out {
|
||||
if err := visit(e.Callee); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := edge(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, n := range g.Nodes {
|
||||
if err := visit(n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PathSearch finds an arbitrary path starting at node start and
|
||||
// ending at some node for which isEnd() returns true. On success,
|
||||
// PathSearch returns the path as an ordered list of edges; on
|
||||
// failure, it returns nil.
|
||||
//
|
||||
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
|
||||
stack := make([]*Edge, 0, 32)
|
||||
seen := make(map[*Node]bool)
|
||||
var search func(n *Node) []*Edge
|
||||
search = func(n *Node) []*Edge {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
if isEnd(n) {
|
||||
return stack
|
||||
}
|
||||
for _, e := range n.Out {
|
||||
stack = append(stack, e) // push
|
||||
if found := search(e.Callee); found != nil {
|
||||
return found
|
||||
}
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return search(start)
|
||||
}
|
||||
|
||||
// DeleteSyntheticNodes removes from call graph g all nodes for
|
||||
// synthetic functions (except g.Root and package initializers),
|
||||
// preserving the topology. In effect, calls to synthetic wrappers
|
||||
// are "inlined".
|
||||
//
|
||||
func (g *Graph) DeleteSyntheticNodes() {
|
||||
// Measurements on the standard library and go.tools show that
|
||||
// resulting graph has ~15% fewer nodes and 4-8% fewer edges
|
||||
// than the input.
|
||||
//
|
||||
// Inlining a wrapper of in-degree m, out-degree n adds m*n
|
||||
// and removes m+n edges. Since most wrappers are monomorphic
|
||||
// (n=1) this results in a slight reduction. Polymorphic
|
||||
// wrappers (n>1), e.g. from embedding an interface value
|
||||
// inside a struct to satisfy some interface, cause an
|
||||
// increase in the graph, but they seem to be uncommon.
|
||||
|
||||
// Hash all existing edges to avoid creating duplicates.
|
||||
edges := make(map[Edge]bool)
|
||||
for _, cgn := range g.Nodes {
|
||||
for _, e := range cgn.Out {
|
||||
edges[*e] = true
|
||||
}
|
||||
}
|
||||
for fn, cgn := range g.Nodes {
|
||||
if cgn == g.Root || fn.Synthetic == "" || isInit(cgn.Func) {
|
||||
continue // keep
|
||||
}
|
||||
for _, eIn := range cgn.In {
|
||||
for _, eOut := range cgn.Out {
|
||||
newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
|
||||
if edges[newEdge] {
|
||||
continue // don't add duplicate
|
||||
}
|
||||
AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
|
||||
edges[newEdge] = true
|
||||
}
|
||||
}
|
||||
g.DeleteNode(cgn)
|
||||
}
|
||||
}
|
||||
|
||||
func isInit(fn *ssa.Function) bool {
|
||||
return fn.Pkg != nil && fn.Pkg.Func("init") == fn
|
||||
}
|
||||
|
||||
// DeleteNode removes node n and its edges from the graph g.
|
||||
// (NB: not efficient for batch deletion.)
|
||||
func (g *Graph) DeleteNode(n *Node) {
|
||||
n.deleteIns()
|
||||
n.deleteOuts()
|
||||
delete(g.Nodes, n.Func)
|
||||
}
|
||||
|
||||
// deleteIns deletes all incoming edges to n.
|
||||
func (n *Node) deleteIns() {
|
||||
for _, e := range n.In {
|
||||
removeOutEdge(e)
|
||||
}
|
||||
n.In = nil
|
||||
}
|
||||
|
||||
// deleteOuts deletes all outgoing edges from n.
|
||||
func (n *Node) deleteOuts() {
|
||||
for _, e := range n.Out {
|
||||
removeInEdge(e)
|
||||
}
|
||||
n.Out = nil
|
||||
}
|
||||
|
||||
// removeOutEdge removes edge.Caller's outgoing edge 'edge'.
|
||||
func removeOutEdge(edge *Edge) {
|
||||
caller := edge.Caller
|
||||
n := len(caller.Out)
|
||||
for i, e := range caller.Out {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.Out[i] = caller.Out[n-1]
|
||||
caller.Out[n-1] = nil // aid GC
|
||||
caller.Out = caller.Out[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
||||
|
||||
// removeInEdge removes edge.Callee's incoming edge 'edge'.
|
||||
func removeInEdge(edge *Edge) {
|
||||
caller := edge.Callee
|
||||
n := len(caller.In)
|
||||
for i, e := range caller.In {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.In[i] = caller.In[n-1]
|
||||
caller.In[n-1] = nil // aid GC
|
||||
caller.In = caller.In[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
|
@ -0,0 +1,920 @@
|
|||
// 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 exact implements Values representing untyped
|
||||
// Go constants and the corresponding operations. Values
|
||||
// and operations have unlimited precision.
|
||||
//
|
||||
// A special Unknown value may be used when a value
|
||||
// is unknown due to an error. Operations on unknown
|
||||
// values produce unknown values unless specified
|
||||
// otherwise.
|
||||
//
|
||||
package exact // import "golang.org/x/tools/go/exact"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"math/big"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Kind specifies the kind of value represented by a Value.
|
||||
type Kind int
|
||||
|
||||
// Implementation note: Kinds must be enumerated in
|
||||
// order of increasing "complexity" (used by match).
|
||||
|
||||
const (
|
||||
// unknown values
|
||||
Unknown Kind = iota
|
||||
|
||||
// non-numeric values
|
||||
Bool
|
||||
String
|
||||
|
||||
// numeric values
|
||||
Int
|
||||
Float
|
||||
Complex
|
||||
)
|
||||
|
||||
// A Value represents a mathematically exact value of a given Kind.
|
||||
type Value interface {
|
||||
// Kind returns the value kind; it is always the smallest
|
||||
// kind in which the value can be represented exactly.
|
||||
Kind() Kind
|
||||
|
||||
// String returns a human-readable form of the value.
|
||||
String() string
|
||||
|
||||
// Prevent external implementations.
|
||||
implementsValue()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Implementations
|
||||
|
||||
type (
|
||||
unknownVal struct{}
|
||||
boolVal bool
|
||||
stringVal string
|
||||
int64Val int64
|
||||
intVal struct{ val *big.Int }
|
||||
floatVal struct{ val *big.Rat }
|
||||
complexVal struct{ re, im *big.Rat }
|
||||
)
|
||||
|
||||
func (unknownVal) Kind() Kind { return Unknown }
|
||||
func (boolVal) Kind() Kind { return Bool }
|
||||
func (stringVal) Kind() Kind { return String }
|
||||
func (int64Val) Kind() Kind { return Int }
|
||||
func (intVal) Kind() Kind { return Int }
|
||||
func (floatVal) Kind() Kind { return Float }
|
||||
func (complexVal) Kind() Kind { return Complex }
|
||||
|
||||
func (unknownVal) String() string { return "unknown" }
|
||||
func (x boolVal) String() string { return fmt.Sprintf("%v", bool(x)) }
|
||||
func (x stringVal) String() string { return strconv.Quote(string(x)) }
|
||||
func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) }
|
||||
func (x intVal) String() string { return x.val.String() }
|
||||
func (x floatVal) String() string { return x.val.String() }
|
||||
func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) }
|
||||
|
||||
func (unknownVal) implementsValue() {}
|
||||
func (boolVal) implementsValue() {}
|
||||
func (stringVal) implementsValue() {}
|
||||
func (int64Val) implementsValue() {}
|
||||
func (intVal) implementsValue() {}
|
||||
func (floatVal) implementsValue() {}
|
||||
func (complexVal) implementsValue() {}
|
||||
|
||||
// int64 bounds
|
||||
var (
|
||||
minInt64 = big.NewInt(-1 << 63)
|
||||
maxInt64 = big.NewInt(1<<63 - 1)
|
||||
)
|
||||
|
||||
func normInt(x *big.Int) Value {
|
||||
if minInt64.Cmp(x) <= 0 && x.Cmp(maxInt64) <= 0 {
|
||||
return int64Val(x.Int64())
|
||||
}
|
||||
return intVal{x}
|
||||
}
|
||||
|
||||
func normFloat(x *big.Rat) Value {
|
||||
if x.IsInt() {
|
||||
return normInt(x.Num())
|
||||
}
|
||||
return floatVal{x}
|
||||
}
|
||||
|
||||
func normComplex(re, im *big.Rat) Value {
|
||||
if im.Sign() == 0 {
|
||||
return normFloat(re)
|
||||
}
|
||||
return complexVal{re, im}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Factories
|
||||
|
||||
// MakeUnknown returns the Unknown value.
|
||||
func MakeUnknown() Value { return unknownVal{} }
|
||||
|
||||
// MakeBool returns the Bool value for x.
|
||||
func MakeBool(b bool) Value { return boolVal(b) }
|
||||
|
||||
// MakeString returns the String value for x.
|
||||
func MakeString(s string) Value { return stringVal(s) }
|
||||
|
||||
// MakeInt64 returns the Int value for x.
|
||||
func MakeInt64(x int64) Value { return int64Val(x) }
|
||||
|
||||
// MakeUint64 returns the Int value for x.
|
||||
func MakeUint64(x uint64) Value { return normInt(new(big.Int).SetUint64(x)) }
|
||||
|
||||
// MakeFloat64 returns the numeric value for x.
|
||||
// If x is not finite, the result is unknown.
|
||||
func MakeFloat64(x float64) Value {
|
||||
if f := new(big.Rat).SetFloat64(x); f != nil {
|
||||
return normFloat(f)
|
||||
}
|
||||
return unknownVal{}
|
||||
}
|
||||
|
||||
// MakeFromLiteral returns the corresponding integer, floating-point,
|
||||
// imaginary, character, or string value for a Go literal string. The
|
||||
// result is nil if the literal string is invalid.
|
||||
func MakeFromLiteral(lit string, tok token.Token) Value {
|
||||
switch tok {
|
||||
case token.INT:
|
||||
if x, err := strconv.ParseInt(lit, 0, 64); err == nil {
|
||||
return int64Val(x)
|
||||
}
|
||||
if x, ok := new(big.Int).SetString(lit, 0); ok {
|
||||
return intVal{x}
|
||||
}
|
||||
|
||||
case token.FLOAT:
|
||||
if x, ok := new(big.Rat).SetString(lit); ok {
|
||||
return normFloat(x)
|
||||
}
|
||||
|
||||
case token.IMAG:
|
||||
if n := len(lit); n > 0 && lit[n-1] == 'i' {
|
||||
if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok {
|
||||
return normComplex(big.NewRat(0, 1), im)
|
||||
}
|
||||
}
|
||||
|
||||
case token.CHAR:
|
||||
if n := len(lit); n >= 2 {
|
||||
if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil {
|
||||
return int64Val(code)
|
||||
}
|
||||
}
|
||||
|
||||
case token.STRING:
|
||||
if s, err := strconv.Unquote(lit); err == nil {
|
||||
return stringVal(s)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Accessors
|
||||
//
|
||||
// For unknown arguments the result is the zero value for the respective
|
||||
// accessor type, except for Sign, where the result is 1.
|
||||
|
||||
// BoolVal returns the Go boolean value of x, which must be a Bool or an Unknown.
|
||||
// If x is Unknown, the result is false.
|
||||
func BoolVal(x Value) bool {
|
||||
switch x := x.(type) {
|
||||
case boolVal:
|
||||
return bool(x)
|
||||
case unknownVal:
|
||||
return false
|
||||
}
|
||||
panic(fmt.Sprintf("%v not a Bool", x))
|
||||
}
|
||||
|
||||
// StringVal returns the Go string value of x, which must be a String or an Unknown.
|
||||
// If x is Unknown, the result is "".
|
||||
func StringVal(x Value) string {
|
||||
switch x := x.(type) {
|
||||
case stringVal:
|
||||
return string(x)
|
||||
case unknownVal:
|
||||
return ""
|
||||
}
|
||||
panic(fmt.Sprintf("%v not a String", x))
|
||||
}
|
||||
|
||||
// Int64Val returns the Go int64 value of x and whether the result is exact;
|
||||
// x must be an Int or an Unknown. If the result is not exact, its value is undefined.
|
||||
// If x is Unknown, the result is (0, false).
|
||||
func Int64Val(x Value) (int64, bool) {
|
||||
switch x := x.(type) {
|
||||
case int64Val:
|
||||
return int64(x), true
|
||||
case intVal:
|
||||
return x.val.Int64(), x.val.BitLen() <= 63
|
||||
case unknownVal:
|
||||
return 0, false
|
||||
}
|
||||
panic(fmt.Sprintf("%v not an Int", x))
|
||||
}
|
||||
|
||||
// Uint64Val returns the Go uint64 value of x and whether the result is exact;
|
||||
// x must be an Int or an Unknown. If the result is not exact, its value is undefined.
|
||||
// If x is Unknown, the result is (0, false).
|
||||
func Uint64Val(x Value) (uint64, bool) {
|
||||
switch x := x.(type) {
|
||||
case int64Val:
|
||||
return uint64(x), x >= 0
|
||||
case intVal:
|
||||
return x.val.Uint64(), x.val.Sign() >= 0 && x.val.BitLen() <= 64
|
||||
case unknownVal:
|
||||
return 0, false
|
||||
}
|
||||
panic(fmt.Sprintf("%v not an Int", x))
|
||||
}
|
||||
|
||||
// Float32Val is like Float64Val but for float32 instead of float64.
|
||||
func Float32Val(x Value) (float32, bool) {
|
||||
switch x := x.(type) {
|
||||
case int64Val:
|
||||
f := float32(x)
|
||||
return f, int64Val(f) == x
|
||||
case intVal:
|
||||
return ratToFloat32(new(big.Rat).SetFrac(x.val, int1))
|
||||
case floatVal:
|
||||
return ratToFloat32(x.val)
|
||||
case unknownVal:
|
||||
return 0, false
|
||||
}
|
||||
panic(fmt.Sprintf("%v not a Float", x))
|
||||
}
|
||||
|
||||
// Float64Val returns the nearest Go float64 value of x and whether the result is exact;
|
||||
// x must be numeric but not Complex, or Unknown. For values too small (too close to 0)
|
||||
// to represent as float64, Float64Val silently underflows to 0. The result sign always
|
||||
// matches the sign of x, even for 0.
|
||||
// If x is Unknown, the result is (0, false).
|
||||
func Float64Val(x Value) (float64, bool) {
|
||||
switch x := x.(type) {
|
||||
case int64Val:
|
||||
f := float64(int64(x))
|
||||
return f, int64Val(f) == x
|
||||
case intVal:
|
||||
return new(big.Rat).SetFrac(x.val, int1).Float64()
|
||||
case floatVal:
|
||||
return x.val.Float64()
|
||||
case unknownVal:
|
||||
return 0, false
|
||||
}
|
||||
panic(fmt.Sprintf("%v not a Float", x))
|
||||
}
|
||||
|
||||
// BitLen returns the number of bits required to represent
|
||||
// the absolute value x in binary representation; x must be an Int or an Unknown.
|
||||
// If x is Unknown, the result is 0.
|
||||
func BitLen(x Value) int {
|
||||
switch x := x.(type) {
|
||||
case int64Val:
|
||||
return new(big.Int).SetInt64(int64(x)).BitLen()
|
||||
case intVal:
|
||||
return x.val.BitLen()
|
||||
case unknownVal:
|
||||
return 0
|
||||
}
|
||||
panic(fmt.Sprintf("%v not an Int", x))
|
||||
}
|
||||
|
||||
// Sign returns -1, 0, or 1 depending on whether x < 0, x == 0, or x > 0;
|
||||
// x must be numeric or Unknown. For complex values x, the sign is 0 if x == 0,
|
||||
// otherwise it is != 0. If x is Unknown, the result is 1.
|
||||
func Sign(x Value) int {
|
||||
switch x := x.(type) {
|
||||
case int64Val:
|
||||
switch {
|
||||
case x < 0:
|
||||
return -1
|
||||
case x > 0:
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case intVal:
|
||||
return x.val.Sign()
|
||||
case floatVal:
|
||||
return x.val.Sign()
|
||||
case complexVal:
|
||||
return x.re.Sign() | x.im.Sign()
|
||||
case unknownVal:
|
||||
return 1 // avoid spurious division by zero errors
|
||||
}
|
||||
panic(fmt.Sprintf("%v not numeric", x))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Support for serializing/deserializing integers
|
||||
|
||||
const (
|
||||
// Compute the size of a Word in bytes.
|
||||
_m = ^big.Word(0)
|
||||
_log = _m>>8&1 + _m>>16&1 + _m>>32&1
|
||||
wordSize = 1 << _log
|
||||
)
|
||||
|
||||
// Bytes returns the bytes for the absolute value of x in little-
|
||||
// endian binary representation; x must be an Int.
|
||||
func Bytes(x Value) []byte {
|
||||
var val *big.Int
|
||||
switch x := x.(type) {
|
||||
case int64Val:
|
||||
val = new(big.Int).SetInt64(int64(x))
|
||||
case intVal:
|
||||
val = x.val
|
||||
default:
|
||||
panic(fmt.Sprintf("%v not an Int", x))
|
||||
}
|
||||
|
||||
words := val.Bits()
|
||||
bytes := make([]byte, len(words)*wordSize)
|
||||
|
||||
i := 0
|
||||
for _, w := range words {
|
||||
for j := 0; j < wordSize; j++ {
|
||||
bytes[i] = byte(w)
|
||||
w >>= 8
|
||||
i++
|
||||
}
|
||||
}
|
||||
// remove leading 0's
|
||||
for i > 0 && bytes[i-1] == 0 {
|
||||
i--
|
||||
}
|
||||
|
||||
return bytes[:i]
|
||||
}
|
||||
|
||||
// MakeFromBytes returns the Int value given the bytes of its little-endian
|
||||
// binary representation. An empty byte slice argument represents 0.
|
||||
func MakeFromBytes(bytes []byte) Value {
|
||||
words := make([]big.Word, (len(bytes)+(wordSize-1))/wordSize)
|
||||
|
||||
i := 0
|
||||
var w big.Word
|
||||
var s uint
|
||||
for _, b := range bytes {
|
||||
w |= big.Word(b) << s
|
||||
if s += 8; s == wordSize*8 {
|
||||
words[i] = w
|
||||
i++
|
||||
w = 0
|
||||
s = 0
|
||||
}
|
||||
}
|
||||
// store last word
|
||||
if i < len(words) {
|
||||
words[i] = w
|
||||
i++
|
||||
}
|
||||
// remove leading 0's
|
||||
for i > 0 && words[i-1] == 0 {
|
||||
i--
|
||||
}
|
||||
|
||||
return normInt(new(big.Int).SetBits(words[:i]))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Support for disassembling fractions
|
||||
|
||||
// Num returns the numerator of x; x must be Int, Float, or Unknown.
|
||||
// If x is Unknown, the result is Unknown, otherwise it is an Int
|
||||
// with the same sign as x.
|
||||
func Num(x Value) Value {
|
||||
switch x := x.(type) {
|
||||
case unknownVal, int64Val, intVal:
|
||||
return x
|
||||
case floatVal:
|
||||
return normInt(x.val.Num())
|
||||
}
|
||||
panic(fmt.Sprintf("%v not Int or Float", x))
|
||||
}
|
||||
|
||||
// Denom returns the denominator of x; x must be Int, Float, or Unknown.
|
||||
// If x is Unknown, the result is Unknown, otherwise it is an Int >= 1.
|
||||
func Denom(x Value) Value {
|
||||
switch x := x.(type) {
|
||||
case unknownVal:
|
||||
return x
|
||||
case int64Val, intVal:
|
||||
return int64Val(1)
|
||||
case floatVal:
|
||||
return normInt(x.val.Denom())
|
||||
}
|
||||
panic(fmt.Sprintf("%v not Int or Float", x))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Support for assembling/disassembling complex numbers
|
||||
|
||||
// MakeImag returns the numeric value x*i (possibly 0);
|
||||
// x must be Int, Float, or Unknown.
|
||||
// If x is Unknown, the result is Unknown.
|
||||
func MakeImag(x Value) Value {
|
||||
var im *big.Rat
|
||||
switch x := x.(type) {
|
||||
case unknownVal:
|
||||
return x
|
||||
case int64Val:
|
||||
im = big.NewRat(int64(x), 1)
|
||||
case intVal:
|
||||
im = new(big.Rat).SetFrac(x.val, int1)
|
||||
case floatVal:
|
||||
im = x.val
|
||||
default:
|
||||
panic(fmt.Sprintf("%v not Int or Float", x))
|
||||
}
|
||||
return normComplex(rat0, im)
|
||||
}
|
||||
|
||||
// Real returns the real part of x, which must be a numeric or unknown value.
|
||||
// If x is Unknown, the result is Unknown.
|
||||
func Real(x Value) Value {
|
||||
switch x := x.(type) {
|
||||
case unknownVal, int64Val, intVal, floatVal:
|
||||
return x
|
||||
case complexVal:
|
||||
return normFloat(x.re)
|
||||
}
|
||||
panic(fmt.Sprintf("%v not numeric", x))
|
||||
}
|
||||
|
||||
// Imag returns the imaginary part of x, which must be a numeric or unknown value.
|
||||
// If x is Unknown, the result is Unknown.
|
||||
func Imag(x Value) Value {
|
||||
switch x := x.(type) {
|
||||
case unknownVal:
|
||||
return x
|
||||
case int64Val, intVal, floatVal:
|
||||
return int64Val(0)
|
||||
case complexVal:
|
||||
return normFloat(x.im)
|
||||
}
|
||||
panic(fmt.Sprintf("%v not numeric", x))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Operations
|
||||
|
||||
// is32bit reports whether x can be represented using 32 bits.
|
||||
func is32bit(x int64) bool {
|
||||
const s = 32
|
||||
return -1<<(s-1) <= x && x <= 1<<(s-1)-1
|
||||
}
|
||||
|
||||
// is63bit reports whether x can be represented using 63 bits.
|
||||
func is63bit(x int64) bool {
|
||||
const s = 63
|
||||
return -1<<(s-1) <= x && x <= 1<<(s-1)-1
|
||||
}
|
||||
|
||||
// UnaryOp returns the result of the unary expression op y.
|
||||
// The operation must be defined for the operand.
|
||||
// If size >= 0 it specifies the ^ (xor) result size in bytes.
|
||||
// If y is Unknown, the result is Unknown.
|
||||
//
|
||||
func UnaryOp(op token.Token, y Value, size int) Value {
|
||||
switch op {
|
||||
case token.ADD:
|
||||
switch y.(type) {
|
||||
case unknownVal, int64Val, intVal, floatVal, complexVal:
|
||||
return y
|
||||
}
|
||||
|
||||
case token.SUB:
|
||||
switch y := y.(type) {
|
||||
case unknownVal:
|
||||
return y
|
||||
case int64Val:
|
||||
if z := -y; z != y {
|
||||
return z // no overflow
|
||||
}
|
||||
return normInt(new(big.Int).Neg(big.NewInt(int64(y))))
|
||||
case intVal:
|
||||
return normInt(new(big.Int).Neg(y.val))
|
||||
case floatVal:
|
||||
return normFloat(new(big.Rat).Neg(y.val))
|
||||
case complexVal:
|
||||
return normComplex(new(big.Rat).Neg(y.re), new(big.Rat).Neg(y.im))
|
||||
}
|
||||
|
||||
case token.XOR:
|
||||
var z big.Int
|
||||
switch y := y.(type) {
|
||||
case unknownVal:
|
||||
return y
|
||||
case int64Val:
|
||||
z.Not(big.NewInt(int64(y)))
|
||||
case intVal:
|
||||
z.Not(y.val)
|
||||
default:
|
||||
goto Error
|
||||
}
|
||||
// For unsigned types, the result will be negative and
|
||||
// thus "too large": We must limit the result size to
|
||||
// the type's size.
|
||||
if size >= 0 {
|
||||
s := uint(size) * 8
|
||||
z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s)) // z &^= (-1)<<s
|
||||
}
|
||||
return normInt(&z)
|
||||
|
||||
case token.NOT:
|
||||
switch y := y.(type) {
|
||||
case unknownVal:
|
||||
return y
|
||||
case boolVal:
|
||||
return !y
|
||||
}
|
||||
}
|
||||
|
||||
Error:
|
||||
panic(fmt.Sprintf("invalid unary operation %s%v", op, y))
|
||||
}
|
||||
|
||||
var (
|
||||
int1 = big.NewInt(1)
|
||||
rat0 = big.NewRat(0, 1)
|
||||
)
|
||||
|
||||
func ord(x Value) int {
|
||||
switch x.(type) {
|
||||
default:
|
||||
return 0
|
||||
case boolVal, stringVal:
|
||||
return 1
|
||||
case int64Val:
|
||||
return 2
|
||||
case intVal:
|
||||
return 3
|
||||
case floatVal:
|
||||
return 4
|
||||
case complexVal:
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
||||
// match returns the matching representation (same type) with the
|
||||
// smallest complexity for two values x and y. If one of them is
|
||||
// numeric, both of them must be numeric. If one of them is Unknown,
|
||||
// both results are Unknown.
|
||||
//
|
||||
func match(x, y Value) (_, _ Value) {
|
||||
if ord(x) > ord(y) {
|
||||
y, x = match(y, x)
|
||||
return x, y
|
||||
}
|
||||
// ord(x) <= ord(y)
|
||||
|
||||
switch x := x.(type) {
|
||||
case unknownVal:
|
||||
return x, x
|
||||
|
||||
case boolVal, stringVal, complexVal:
|
||||
return x, y
|
||||
|
||||
case int64Val:
|
||||
switch y := y.(type) {
|
||||
case int64Val:
|
||||
return x, y
|
||||
case intVal:
|
||||
return intVal{big.NewInt(int64(x))}, y
|
||||
case floatVal:
|
||||
return floatVal{big.NewRat(int64(x), 1)}, y
|
||||
case complexVal:
|
||||
return complexVal{big.NewRat(int64(x), 1), rat0}, y
|
||||
}
|
||||
|
||||
case intVal:
|
||||
switch y := y.(type) {
|
||||
case intVal:
|
||||
return x, y
|
||||
case floatVal:
|
||||
return floatVal{new(big.Rat).SetFrac(x.val, int1)}, y
|
||||
case complexVal:
|
||||
return complexVal{new(big.Rat).SetFrac(x.val, int1), rat0}, y
|
||||
}
|
||||
|
||||
case floatVal:
|
||||
switch y := y.(type) {
|
||||
case floatVal:
|
||||
return x, y
|
||||
case complexVal:
|
||||
return complexVal{x.val, rat0}, y
|
||||
}
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// BinaryOp returns the result of the binary expression x op y.
|
||||
// The operation must be defined for the operands. If one of the
|
||||
// operands is Unknown, the result is Unknown.
|
||||
// To force integer division of Int operands, use op == token.QUO_ASSIGN
|
||||
// instead of token.QUO; the result is guaranteed to be Int in this case.
|
||||
// Division by zero leads to a run-time panic.
|
||||
//
|
||||
func BinaryOp(x Value, op token.Token, y Value) Value {
|
||||
x, y = match(x, y)
|
||||
|
||||
switch x := x.(type) {
|
||||
case unknownVal:
|
||||
return x
|
||||
|
||||
case boolVal:
|
||||
y := y.(boolVal)
|
||||
switch op {
|
||||
case token.LAND:
|
||||
return x && y
|
||||
case token.LOR:
|
||||
return x || y
|
||||
}
|
||||
|
||||
case int64Val:
|
||||
a := int64(x)
|
||||
b := int64(y.(int64Val))
|
||||
var c int64
|
||||
switch op {
|
||||
case token.ADD:
|
||||
if !is63bit(a) || !is63bit(b) {
|
||||
return normInt(new(big.Int).Add(big.NewInt(a), big.NewInt(b)))
|
||||
}
|
||||
c = a + b
|
||||
case token.SUB:
|
||||
if !is63bit(a) || !is63bit(b) {
|
||||
return normInt(new(big.Int).Sub(big.NewInt(a), big.NewInt(b)))
|
||||
}
|
||||
c = a - b
|
||||
case token.MUL:
|
||||
if !is32bit(a) || !is32bit(b) {
|
||||
return normInt(new(big.Int).Mul(big.NewInt(a), big.NewInt(b)))
|
||||
}
|
||||
c = a * b
|
||||
case token.QUO:
|
||||
return normFloat(new(big.Rat).SetFrac(big.NewInt(a), big.NewInt(b)))
|
||||
case token.QUO_ASSIGN: // force integer division
|
||||
c = a / b
|
||||
case token.REM:
|
||||
c = a % b
|
||||
case token.AND:
|
||||
c = a & b
|
||||
case token.OR:
|
||||
c = a | b
|
||||
case token.XOR:
|
||||
c = a ^ b
|
||||
case token.AND_NOT:
|
||||
c = a &^ b
|
||||
default:
|
||||
goto Error
|
||||
}
|
||||
return int64Val(c)
|
||||
|
||||
case intVal:
|
||||
a := x.val
|
||||
b := y.(intVal).val
|
||||
var c big.Int
|
||||
switch op {
|
||||
case token.ADD:
|
||||
c.Add(a, b)
|
||||
case token.SUB:
|
||||
c.Sub(a, b)
|
||||
case token.MUL:
|
||||
c.Mul(a, b)
|
||||
case token.QUO:
|
||||
return normFloat(new(big.Rat).SetFrac(a, b))
|
||||
case token.QUO_ASSIGN: // force integer division
|
||||
c.Quo(a, b)
|
||||
case token.REM:
|
||||
c.Rem(a, b)
|
||||
case token.AND:
|
||||
c.And(a, b)
|
||||
case token.OR:
|
||||
c.Or(a, b)
|
||||
case token.XOR:
|
||||
c.Xor(a, b)
|
||||
case token.AND_NOT:
|
||||
c.AndNot(a, b)
|
||||
default:
|
||||
goto Error
|
||||
}
|
||||
return normInt(&c)
|
||||
|
||||
case floatVal:
|
||||
a := x.val
|
||||
b := y.(floatVal).val
|
||||
var c big.Rat
|
||||
switch op {
|
||||
case token.ADD:
|
||||
c.Add(a, b)
|
||||
case token.SUB:
|
||||
c.Sub(a, b)
|
||||
case token.MUL:
|
||||
c.Mul(a, b)
|
||||
case token.QUO:
|
||||
c.Quo(a, b)
|
||||
default:
|
||||
goto Error
|
||||
}
|
||||
return normFloat(&c)
|
||||
|
||||
case complexVal:
|
||||
y := y.(complexVal)
|
||||
a, b := x.re, x.im
|
||||
c, d := y.re, y.im
|
||||
var re, im big.Rat
|
||||
switch op {
|
||||
case token.ADD:
|
||||
// (a+c) + i(b+d)
|
||||
re.Add(a, c)
|
||||
im.Add(b, d)
|
||||
case token.SUB:
|
||||
// (a-c) + i(b-d)
|
||||
re.Sub(a, c)
|
||||
im.Sub(b, d)
|
||||
case token.MUL:
|
||||
// (ac-bd) + i(bc+ad)
|
||||
var ac, bd, bc, ad big.Rat
|
||||
ac.Mul(a, c)
|
||||
bd.Mul(b, d)
|
||||
bc.Mul(b, c)
|
||||
ad.Mul(a, d)
|
||||
re.Sub(&ac, &bd)
|
||||
im.Add(&bc, &ad)
|
||||
case token.QUO:
|
||||
// (ac+bd)/s + i(bc-ad)/s, with s = cc + dd
|
||||
var ac, bd, bc, ad, s, cc, dd big.Rat
|
||||
ac.Mul(a, c)
|
||||
bd.Mul(b, d)
|
||||
bc.Mul(b, c)
|
||||
ad.Mul(a, d)
|
||||
cc.Mul(c, c)
|
||||
dd.Mul(d, d)
|
||||
s.Add(&cc, &dd)
|
||||
re.Add(&ac, &bd)
|
||||
re.Quo(&re, &s)
|
||||
im.Sub(&bc, &ad)
|
||||
im.Quo(&im, &s)
|
||||
default:
|
||||
goto Error
|
||||
}
|
||||
return normComplex(&re, &im)
|
||||
|
||||
case stringVal:
|
||||
if op == token.ADD {
|
||||
return x + y.(stringVal)
|
||||
}
|
||||
}
|
||||
|
||||
Error:
|
||||
panic(fmt.Sprintf("invalid binary operation %v %s %v", x, op, y))
|
||||
}
|
||||
|
||||
// Shift returns the result of the shift expression x op s
|
||||
// with op == token.SHL or token.SHR (<< or >>). x must be
|
||||
// an Int or an Unknown. If x is Unknown, the result is x.
|
||||
//
|
||||
func Shift(x Value, op token.Token, s uint) Value {
|
||||
switch x := x.(type) {
|
||||
case unknownVal:
|
||||
return x
|
||||
|
||||
case int64Val:
|
||||
if s == 0 {
|
||||
return x
|
||||
}
|
||||
switch op {
|
||||
case token.SHL:
|
||||
z := big.NewInt(int64(x))
|
||||
return normInt(z.Lsh(z, s))
|
||||
case token.SHR:
|
||||
return x >> s
|
||||
}
|
||||
|
||||
case intVal:
|
||||
if s == 0 {
|
||||
return x
|
||||
}
|
||||
var z big.Int
|
||||
switch op {
|
||||
case token.SHL:
|
||||
return normInt(z.Lsh(x.val, s))
|
||||
case token.SHR:
|
||||
return normInt(z.Rsh(x.val, s))
|
||||
}
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("invalid shift %v %s %d", x, op, s))
|
||||
}
|
||||
|
||||
func cmpZero(x int, op token.Token) bool {
|
||||
switch op {
|
||||
case token.EQL:
|
||||
return x == 0
|
||||
case token.NEQ:
|
||||
return x != 0
|
||||
case token.LSS:
|
||||
return x < 0
|
||||
case token.LEQ:
|
||||
return x <= 0
|
||||
case token.GTR:
|
||||
return x > 0
|
||||
case token.GEQ:
|
||||
return x >= 0
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Compare returns the result of the comparison x op y.
|
||||
// The comparison must be defined for the operands.
|
||||
// If one of the operands is Unknown, the result is
|
||||
// false.
|
||||
//
|
||||
func Compare(x Value, op token.Token, y Value) bool {
|
||||
x, y = match(x, y)
|
||||
|
||||
switch x := x.(type) {
|
||||
case unknownVal:
|
||||
return false
|
||||
|
||||
case boolVal:
|
||||
y := y.(boolVal)
|
||||
switch op {
|
||||
case token.EQL:
|
||||
return x == y
|
||||
case token.NEQ:
|
||||
return x != y
|
||||
}
|
||||
|
||||
case int64Val:
|
||||
y := y.(int64Val)
|
||||
switch op {
|
||||
case token.EQL:
|
||||
return x == y
|
||||
case token.NEQ:
|
||||
return x != y
|
||||
case token.LSS:
|
||||
return x < y
|
||||
case token.LEQ:
|
||||
return x <= y
|
||||
case token.GTR:
|
||||
return x > y
|
||||
case token.GEQ:
|
||||
return x >= y
|
||||
}
|
||||
|
||||
case intVal:
|
||||
return cmpZero(x.val.Cmp(y.(intVal).val), op)
|
||||
|
||||
case floatVal:
|
||||
return cmpZero(x.val.Cmp(y.(floatVal).val), op)
|
||||
|
||||
case complexVal:
|
||||
y := y.(complexVal)
|
||||
re := x.re.Cmp(y.re)
|
||||
im := x.im.Cmp(y.im)
|
||||
switch op {
|
||||
case token.EQL:
|
||||
return re == 0 && im == 0
|
||||
case token.NEQ:
|
||||
return re != 0 || im != 0
|
||||
}
|
||||
|
||||
case stringVal:
|
||||
y := y.(stringVal)
|
||||
switch op {
|
||||
case token.EQL:
|
||||
return x == y
|
||||
case token.NEQ:
|
||||
return x != y
|
||||
case token.LSS:
|
||||
return x < y
|
||||
case token.LEQ:
|
||||
return x <= y
|
||||
case token.GTR:
|
||||
return x > y
|
||||
case token.GEQ:
|
||||
return x >= y
|
||||
}
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("invalid comparison %v %s %v", x, op, y))
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// 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.
|
||||
|
||||
// +build !go1.4
|
||||
|
||||
package exact
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func ratToFloat32(x *big.Rat) (float32, bool) {
|
||||
// Before 1.4, there's no Rat.Float32.
|
||||
// Emulate it, albeit at the cost of
|
||||
// imprecision in corner cases.
|
||||
x64, exact := x.Float64()
|
||||
x32 := float32(x64)
|
||||
if math.IsInf(float64(x32), 0) {
|
||||
exact = false
|
||||
}
|
||||
return x32, exact
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 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.
|
||||
|
||||
// +build go1.4
|
||||
|
||||
package exact
|
||||
|
||||
import "math/big"
|
||||
|
||||
func ratToFloat32(x *big.Rat) (float32, bool) {
|
||||
return x.Float32()
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// 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 file implements FindExportData.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func readGopackHeader(r *bufio.Reader) (name string, size int, err error) {
|
||||
// See $GOROOT/include/ar.h.
|
||||
hdr := make([]byte, 16+12+6+6+8+10+2)
|
||||
_, err = io.ReadFull(r, hdr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// leave for debugging
|
||||
if false {
|
||||
fmt.Printf("header: %s", hdr)
|
||||
}
|
||||
s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
|
||||
size, err = strconv.Atoi(s)
|
||||
if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
|
||||
err = errors.New("invalid archive header")
|
||||
return
|
||||
}
|
||||
name = strings.TrimSpace(string(hdr[:16]))
|
||||
return
|
||||
}
|
||||
|
||||
// FindExportData positions the reader r at the beginning of the
|
||||
// export data section of an underlying GC-created object/archive
|
||||
// file by reading from it. The reader must be positioned at the
|
||||
// start of the file before calling this function.
|
||||
//
|
||||
func FindExportData(r *bufio.Reader) (err error) {
|
||||
// Read first line to make sure this is an object file.
|
||||
line, err := r.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if string(line) == "!<arch>\n" {
|
||||
// Archive file. Scan to __.PKGDEF.
|
||||
var name string
|
||||
var size int
|
||||
if name, size, err = readGopackHeader(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Optional leading __.GOSYMDEF or __.SYMDEF.
|
||||
// Read and discard.
|
||||
if name == "__.SYMDEF" || name == "__.GOSYMDEF" {
|
||||
const block = 4096
|
||||
tmp := make([]byte, block)
|
||||
for size > 0 {
|
||||
n := size
|
||||
if n > block {
|
||||
n = block
|
||||
}
|
||||
if _, err = io.ReadFull(r, tmp[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
size -= n
|
||||
}
|
||||
|
||||
if name, size, err = readGopackHeader(r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// First real entry should be __.PKGDEF.
|
||||
if name != "__.PKGDEF" {
|
||||
err = errors.New("go archive is missing __.PKGDEF")
|
||||
return
|
||||
}
|
||||
|
||||
// Read first line of __.PKGDEF data, so that line
|
||||
// is once again the first line of the input.
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now at __.PKGDEF in archive or still at beginning of file.
|
||||
// Either way, line should begin with "go object ".
|
||||
if !strings.HasPrefix(string(line), "go object ") {
|
||||
err = errors.New("not a go object file")
|
||||
return
|
||||
}
|
||||
|
||||
// Skip over object header to export data.
|
||||
// Begins after first line with $$.
|
||||
for line[0] != '$' {
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,995 @@
|
|||
// 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 gcimporter implements Import for gc-generated object files.
|
||||
// Importing this package installs Import as go/types.DefaultImport.
|
||||
package gcimporter // import "golang.org/x/tools/go/gcimporter"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// debugging/development support
|
||||
const debug = false
|
||||
|
||||
func init() {
|
||||
types.DefaultImport = Import
|
||||
}
|
||||
|
||||
var pkgExts = [...]string{".a", ".5", ".6", ".7", ".8", ".9"}
|
||||
|
||||
// FindPkg returns the filename and unique package id for an import
|
||||
// path based on package information provided by build.Import (using
|
||||
// the build.Default build.Context).
|
||||
// If no file was found, an empty filename is returned.
|
||||
//
|
||||
func FindPkg(path, srcDir string) (filename, id string) {
|
||||
if len(path) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
id = path
|
||||
var noext string
|
||||
switch {
|
||||
default:
|
||||
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
|
||||
// Don't require the source files to be present.
|
||||
bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
|
||||
if bp.PkgObj == "" {
|
||||
return
|
||||
}
|
||||
noext = strings.TrimSuffix(bp.PkgObj, ".a")
|
||||
|
||||
case build.IsLocalImport(path):
|
||||
// "./x" -> "/this/directory/x.ext", "/this/directory/x"
|
||||
noext = filepath.Join(srcDir, path)
|
||||
id = noext
|
||||
|
||||
case filepath.IsAbs(path):
|
||||
// for completeness only - go/build.Import
|
||||
// does not support absolute imports
|
||||
// "/x" -> "/x.ext", "/x"
|
||||
noext = path
|
||||
}
|
||||
|
||||
// try extensions
|
||||
for _, ext := range pkgExts {
|
||||
filename = noext + ext
|
||||
if f, err := os.Stat(filename); err == nil && !f.IsDir() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filename = "" // not found
|
||||
return
|
||||
}
|
||||
|
||||
// ImportData imports a package by reading the gc-generated export data,
|
||||
// adds the corresponding package object to the packages map indexed by id,
|
||||
// and returns the object.
|
||||
//
|
||||
// The packages map must contains all packages already imported. The data
|
||||
// reader position must be the beginning of the export data section. The
|
||||
// filename is only used in error messages.
|
||||
//
|
||||
// If packages[id] contains the completely imported package, that package
|
||||
// can be used directly, and there is no need to call this function (but
|
||||
// there is also no harm but for extra time used).
|
||||
//
|
||||
func ImportData(packages map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) {
|
||||
// support for parser error handling
|
||||
defer func() {
|
||||
switch r := recover().(type) {
|
||||
case nil:
|
||||
// nothing to do
|
||||
case importError:
|
||||
err = r
|
||||
default:
|
||||
panic(r) // internal error
|
||||
}
|
||||
}()
|
||||
|
||||
var p parser
|
||||
p.init(filename, id, data, packages)
|
||||
pkg = p.parseExport()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Import imports a gc-generated package given its import path, adds the
|
||||
// corresponding package object to the packages map, and returns the object.
|
||||
// Local import paths are interpreted relative to the current working directory.
|
||||
// The packages map must contains all packages already imported.
|
||||
//
|
||||
func Import(packages map[string]*types.Package, path string) (pkg *types.Package, err error) {
|
||||
if path == "unsafe" {
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
|
||||
srcDir := "."
|
||||
if build.IsLocalImport(path) {
|
||||
srcDir, err = os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filename, id := FindPkg(path, srcDir)
|
||||
if filename == "" {
|
||||
err = fmt.Errorf("can't find import: %s", id)
|
||||
return
|
||||
}
|
||||
|
||||
// no need to re-import if the package was imported completely before
|
||||
if pkg = packages[id]; pkg != nil && pkg.Complete() {
|
||||
return
|
||||
}
|
||||
|
||||
// open file
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
if err != nil {
|
||||
// add file name to error
|
||||
err = fmt.Errorf("reading export data: %s: %v", filename, err)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := bufio.NewReader(f)
|
||||
if err = FindExportData(buf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pkg, err = ImportData(packages, filename, id, buf)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parser
|
||||
|
||||
// TODO(gri) Imported objects don't have position information.
|
||||
// Ideally use the debug table line info; alternatively
|
||||
// create some fake position (or the position of the
|
||||
// import). That way error messages referring to imported
|
||||
// objects can print meaningful information.
|
||||
|
||||
// parser parses the exports inside a gc compiler-produced
|
||||
// object/archive file and populates its scope with the results.
|
||||
type parser struct {
|
||||
scanner scanner.Scanner
|
||||
tok rune // current token
|
||||
lit string // literal string; only valid for Ident, Int, String tokens
|
||||
id string // package id of imported package
|
||||
sharedPkgs map[string]*types.Package // package id -> package object (across importer)
|
||||
localPkgs map[string]*types.Package // package id -> package object (just this package)
|
||||
}
|
||||
|
||||
func (p *parser) init(filename, id string, src io.Reader, packages map[string]*types.Package) {
|
||||
p.scanner.Init(src)
|
||||
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
|
||||
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
|
||||
p.scanner.Whitespace = 1<<'\t' | 1<<' '
|
||||
p.scanner.Filename = filename // for good error messages
|
||||
p.next()
|
||||
p.id = id
|
||||
p.sharedPkgs = packages
|
||||
if debug {
|
||||
// check consistency of packages map
|
||||
for _, pkg := range packages {
|
||||
if pkg.Name() == "" {
|
||||
fmt.Printf("no package name for %s\n", pkg.Path())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) next() {
|
||||
p.tok = p.scanner.Scan()
|
||||
switch p.tok {
|
||||
case scanner.Ident, scanner.Int, scanner.Char, scanner.String, '·':
|
||||
p.lit = p.scanner.TokenText()
|
||||
default:
|
||||
p.lit = ""
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("%s: %q -> %q\n", scanner.TokenString(p.tok), p.scanner.TokenText(), p.lit)
|
||||
}
|
||||
}
|
||||
|
||||
func declTypeName(pkg *types.Package, name string) *types.TypeName {
|
||||
scope := pkg.Scope()
|
||||
if obj := scope.Lookup(name); obj != nil {
|
||||
return obj.(*types.TypeName)
|
||||
}
|
||||
obj := types.NewTypeName(token.NoPos, pkg, name, nil)
|
||||
// a named type may be referred to before the underlying type
|
||||
// is known - set it up
|
||||
types.NewNamed(obj, nil, nil)
|
||||
scope.Insert(obj)
|
||||
return obj
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Error handling
|
||||
|
||||
// Internal errors are boxed as importErrors.
|
||||
type importError struct {
|
||||
pos scanner.Position
|
||||
err error
|
||||
}
|
||||
|
||||
func (e importError) Error() string {
|
||||
return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err)
|
||||
}
|
||||
|
||||
func (p *parser) error(err interface{}) {
|
||||
if s, ok := err.(string); ok {
|
||||
err = errors.New(s)
|
||||
}
|
||||
// panic with a runtime.Error if err is not an error
|
||||
panic(importError{p.scanner.Pos(), err.(error)})
|
||||
}
|
||||
|
||||
func (p *parser) errorf(format string, args ...interface{}) {
|
||||
p.error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (p *parser) expect(tok rune) string {
|
||||
lit := p.lit
|
||||
if p.tok != tok {
|
||||
p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit)
|
||||
}
|
||||
p.next()
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *parser) expectSpecial(tok string) {
|
||||
sep := 'x' // not white space
|
||||
i := 0
|
||||
for i < len(tok) && p.tok == rune(tok[i]) && sep > ' ' {
|
||||
sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token
|
||||
p.next()
|
||||
i++
|
||||
}
|
||||
if i < len(tok) {
|
||||
p.errorf("expected %q, got %q", tok, tok[0:i])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) expectKeyword(keyword string) {
|
||||
lit := p.expect(scanner.Ident)
|
||||
if lit != keyword {
|
||||
p.errorf("expected keyword %s, got %q", keyword, lit)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Qualified and unqualified names
|
||||
|
||||
// PackageId = string_lit .
|
||||
//
|
||||
func (p *parser) parsePackageId() string {
|
||||
id, err := strconv.Unquote(p.expect(scanner.String))
|
||||
if err != nil {
|
||||
p.error(err)
|
||||
}
|
||||
// id == "" stands for the imported package id
|
||||
// (only known at time of package installation)
|
||||
if id == "" {
|
||||
id = p.id
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// PackageName = ident .
|
||||
//
|
||||
func (p *parser) parsePackageName() string {
|
||||
return p.expect(scanner.Ident)
|
||||
}
|
||||
|
||||
// dotIdentifier = ( ident | '·' ) { ident | int | '·' } .
|
||||
func (p *parser) parseDotIdent() string {
|
||||
ident := ""
|
||||
if p.tok != scanner.Int {
|
||||
sep := 'x' // not white space
|
||||
for (p.tok == scanner.Ident || p.tok == scanner.Int || p.tok == '·') && sep > ' ' {
|
||||
ident += p.lit
|
||||
sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
if ident == "" {
|
||||
p.expect(scanner.Ident) // use expect() for error handling
|
||||
}
|
||||
return ident
|
||||
}
|
||||
|
||||
// QualifiedName = "@" PackageId "." ( "?" | dotIdentifier ) .
|
||||
//
|
||||
func (p *parser) parseQualifiedName() (id, name string) {
|
||||
p.expect('@')
|
||||
id = p.parsePackageId()
|
||||
p.expect('.')
|
||||
// Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields.
|
||||
if p.tok == '?' {
|
||||
p.next()
|
||||
} else {
|
||||
name = p.parseDotIdent()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getPkg returns the package for a given id. If the package is
|
||||
// not found but we have a package name, create the package and
|
||||
// add it to the p.localPkgs and p.sharedPkgs maps.
|
||||
//
|
||||
// id identifies a package, usually by a canonical package path like
|
||||
// "encoding/json" but possibly by a non-canonical import path like
|
||||
// "./json".
|
||||
//
|
||||
func (p *parser) getPkg(id, name string) *types.Package {
|
||||
// package unsafe is not in the packages maps - handle explicitly
|
||||
if id == "unsafe" {
|
||||
return types.Unsafe
|
||||
}
|
||||
|
||||
pkg := p.localPkgs[id]
|
||||
if pkg == nil && name != "" {
|
||||
// first import of id from this package
|
||||
pkg = p.sharedPkgs[id]
|
||||
if pkg == nil {
|
||||
// first import of id by this importer
|
||||
pkg = types.NewPackage(id, name)
|
||||
p.sharedPkgs[id] = pkg
|
||||
}
|
||||
|
||||
if p.localPkgs == nil {
|
||||
p.localPkgs = make(map[string]*types.Package)
|
||||
}
|
||||
p.localPkgs[id] = pkg
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
// parseExportedName is like parseQualifiedName, but
|
||||
// the package id is resolved to an imported *types.Package.
|
||||
//
|
||||
func (p *parser) parseExportedName() (pkg *types.Package, name string) {
|
||||
id, name := p.parseQualifiedName()
|
||||
pkg = p.getPkg(id, "")
|
||||
if pkg == nil {
|
||||
p.errorf("%s package not found", id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Types
|
||||
|
||||
// BasicType = identifier .
|
||||
//
|
||||
func (p *parser) parseBasicType() types.Type {
|
||||
id := p.expect(scanner.Ident)
|
||||
obj := types.Universe.Lookup(id)
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
return obj.Type()
|
||||
}
|
||||
p.errorf("not a basic type: %s", id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArrayType = "[" int_lit "]" Type .
|
||||
//
|
||||
func (p *parser) parseArrayType() types.Type {
|
||||
// "[" already consumed and lookahead known not to be "]"
|
||||
lit := p.expect(scanner.Int)
|
||||
p.expect(']')
|
||||
elem := p.parseType()
|
||||
n, err := strconv.ParseInt(lit, 10, 64)
|
||||
if err != nil {
|
||||
p.error(err)
|
||||
}
|
||||
return types.NewArray(elem, n)
|
||||
}
|
||||
|
||||
// MapType = "map" "[" Type "]" Type .
|
||||
//
|
||||
func (p *parser) parseMapType() types.Type {
|
||||
p.expectKeyword("map")
|
||||
p.expect('[')
|
||||
key := p.parseType()
|
||||
p.expect(']')
|
||||
elem := p.parseType()
|
||||
return types.NewMap(key, elem)
|
||||
}
|
||||
|
||||
// Name = identifier | "?" | QualifiedName .
|
||||
//
|
||||
// If materializePkg is set, the returned package is guaranteed to be set.
|
||||
// For fully qualified names, the returned package may be a fake package
|
||||
// (without name, scope, and not in the p.imports map), created for the
|
||||
// sole purpose of providing a package path. Fake packages are created
|
||||
// when the package id is not found in the p.imports map; in that case
|
||||
// we cannot create a real package because we don't have a package name.
|
||||
// For non-qualified names, the returned package is the imported package.
|
||||
//
|
||||
func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) {
|
||||
switch p.tok {
|
||||
case scanner.Ident:
|
||||
pkg = p.sharedPkgs[p.id]
|
||||
name = p.lit
|
||||
p.next()
|
||||
case '?':
|
||||
// anonymous
|
||||
pkg = p.sharedPkgs[p.id]
|
||||
p.next()
|
||||
case '@':
|
||||
// exported name prefixed with package path
|
||||
var id string
|
||||
id, name = p.parseQualifiedName()
|
||||
if materializePkg {
|
||||
// we don't have a package name - if the package
|
||||
// doesn't exist yet, create a fake package instead
|
||||
pkg = p.getPkg(id, "")
|
||||
if pkg == nil {
|
||||
pkg = types.NewPackage(id, "")
|
||||
}
|
||||
}
|
||||
default:
|
||||
p.error("name expected")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, _ := typ.(*types.Pointer); p != nil {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// Field = Name Type [ string_lit ] .
|
||||
//
|
||||
func (p *parser) parseField() (*types.Var, string) {
|
||||
pkg, name := p.parseName(true)
|
||||
typ := p.parseType()
|
||||
anonymous := false
|
||||
if name == "" {
|
||||
// anonymous field - typ must be T or *T and T must be a type name
|
||||
switch typ := deref(typ).(type) {
|
||||
case *types.Basic: // basic types are named types
|
||||
pkg = nil
|
||||
name = typ.Name()
|
||||
case *types.Named:
|
||||
name = typ.Obj().Name()
|
||||
default:
|
||||
p.errorf("anonymous field expected")
|
||||
}
|
||||
anonymous = true
|
||||
}
|
||||
tag := ""
|
||||
if p.tok == scanner.String {
|
||||
s := p.expect(scanner.String)
|
||||
var err error
|
||||
tag, err = strconv.Unquote(s)
|
||||
if err != nil {
|
||||
p.errorf("invalid struct tag %s: %s", s, err)
|
||||
}
|
||||
}
|
||||
return types.NewField(token.NoPos, pkg, name, typ, anonymous), tag
|
||||
}
|
||||
|
||||
// StructType = "struct" "{" [ FieldList ] "}" .
|
||||
// FieldList = Field { ";" Field } .
|
||||
//
|
||||
func (p *parser) parseStructType() types.Type {
|
||||
var fields []*types.Var
|
||||
var tags []string
|
||||
|
||||
p.expectKeyword("struct")
|
||||
p.expect('{')
|
||||
for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ {
|
||||
if i > 0 {
|
||||
p.expect(';')
|
||||
}
|
||||
fld, tag := p.parseField()
|
||||
if tag != "" && tags == nil {
|
||||
tags = make([]string, i)
|
||||
}
|
||||
if tags != nil {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
fields = append(fields, fld)
|
||||
}
|
||||
p.expect('}')
|
||||
|
||||
return types.NewStruct(fields, tags)
|
||||
}
|
||||
|
||||
// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] .
|
||||
//
|
||||
func (p *parser) parseParameter() (par *types.Var, isVariadic bool) {
|
||||
_, name := p.parseName(false)
|
||||
// remove gc-specific parameter numbering
|
||||
if i := strings.Index(name, "·"); i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
if p.tok == '.' {
|
||||
p.expectSpecial("...")
|
||||
isVariadic = true
|
||||
}
|
||||
typ := p.parseType()
|
||||
if isVariadic {
|
||||
typ = types.NewSlice(typ)
|
||||
}
|
||||
// ignore argument tag (e.g. "noescape")
|
||||
if p.tok == scanner.String {
|
||||
p.next()
|
||||
}
|
||||
// TODO(gri) should we provide a package?
|
||||
par = types.NewVar(token.NoPos, nil, name, typ)
|
||||
return
|
||||
}
|
||||
|
||||
// Parameters = "(" [ ParameterList ] ")" .
|
||||
// ParameterList = { Parameter "," } Parameter .
|
||||
//
|
||||
func (p *parser) parseParameters() (list []*types.Var, isVariadic bool) {
|
||||
p.expect('(')
|
||||
for p.tok != ')' && p.tok != scanner.EOF {
|
||||
if len(list) > 0 {
|
||||
p.expect(',')
|
||||
}
|
||||
par, variadic := p.parseParameter()
|
||||
list = append(list, par)
|
||||
if variadic {
|
||||
if isVariadic {
|
||||
p.error("... not on final argument")
|
||||
}
|
||||
isVariadic = true
|
||||
}
|
||||
}
|
||||
p.expect(')')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Signature = Parameters [ Result ] .
|
||||
// Result = Type | Parameters .
|
||||
//
|
||||
func (p *parser) parseSignature(recv *types.Var) *types.Signature {
|
||||
params, isVariadic := p.parseParameters()
|
||||
|
||||
// optional result type
|
||||
var results []*types.Var
|
||||
if p.tok == '(' {
|
||||
var variadic bool
|
||||
results, variadic = p.parseParameters()
|
||||
if variadic {
|
||||
p.error("... not permitted on result type")
|
||||
}
|
||||
}
|
||||
|
||||
return types.NewSignature(recv, types.NewTuple(params...), types.NewTuple(results...), isVariadic)
|
||||
}
|
||||
|
||||
// InterfaceType = "interface" "{" [ MethodList ] "}" .
|
||||
// MethodList = Method { ";" Method } .
|
||||
// Method = Name Signature .
|
||||
//
|
||||
// The methods of embedded interfaces are always "inlined"
|
||||
// by the compiler and thus embedded interfaces are never
|
||||
// visible in the export data.
|
||||
//
|
||||
func (p *parser) parseInterfaceType() types.Type {
|
||||
var methods []*types.Func
|
||||
|
||||
p.expectKeyword("interface")
|
||||
p.expect('{')
|
||||
for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ {
|
||||
if i > 0 {
|
||||
p.expect(';')
|
||||
}
|
||||
pkg, name := p.parseName(true)
|
||||
sig := p.parseSignature(nil)
|
||||
methods = append(methods, types.NewFunc(token.NoPos, pkg, name, sig))
|
||||
}
|
||||
p.expect('}')
|
||||
|
||||
// Complete requires the type's embedded interfaces to be fully defined,
|
||||
// but we do not define any
|
||||
return types.NewInterface(methods, nil).Complete()
|
||||
}
|
||||
|
||||
// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type .
|
||||
//
|
||||
func (p *parser) parseChanType() types.Type {
|
||||
dir := types.SendRecv
|
||||
if p.tok == scanner.Ident {
|
||||
p.expectKeyword("chan")
|
||||
if p.tok == '<' {
|
||||
p.expectSpecial("<-")
|
||||
dir = types.SendOnly
|
||||
}
|
||||
} else {
|
||||
p.expectSpecial("<-")
|
||||
p.expectKeyword("chan")
|
||||
dir = types.RecvOnly
|
||||
}
|
||||
elem := p.parseType()
|
||||
return types.NewChan(dir, elem)
|
||||
}
|
||||
|
||||
// Type =
|
||||
// BasicType | TypeName | ArrayType | SliceType | StructType |
|
||||
// PointerType | FuncType | InterfaceType | MapType | ChanType |
|
||||
// "(" Type ")" .
|
||||
//
|
||||
// BasicType = ident .
|
||||
// TypeName = ExportedName .
|
||||
// SliceType = "[" "]" Type .
|
||||
// PointerType = "*" Type .
|
||||
// FuncType = "func" Signature .
|
||||
//
|
||||
func (p *parser) parseType() types.Type {
|
||||
switch p.tok {
|
||||
case scanner.Ident:
|
||||
switch p.lit {
|
||||
default:
|
||||
return p.parseBasicType()
|
||||
case "struct":
|
||||
return p.parseStructType()
|
||||
case "func":
|
||||
// FuncType
|
||||
p.next()
|
||||
return p.parseSignature(nil)
|
||||
case "interface":
|
||||
return p.parseInterfaceType()
|
||||
case "map":
|
||||
return p.parseMapType()
|
||||
case "chan":
|
||||
return p.parseChanType()
|
||||
}
|
||||
case '@':
|
||||
// TypeName
|
||||
pkg, name := p.parseExportedName()
|
||||
return declTypeName(pkg, name).Type()
|
||||
case '[':
|
||||
p.next() // look ahead
|
||||
if p.tok == ']' {
|
||||
// SliceType
|
||||
p.next()
|
||||
return types.NewSlice(p.parseType())
|
||||
}
|
||||
return p.parseArrayType()
|
||||
case '*':
|
||||
// PointerType
|
||||
p.next()
|
||||
return types.NewPointer(p.parseType())
|
||||
case '<':
|
||||
return p.parseChanType()
|
||||
case '(':
|
||||
// "(" Type ")"
|
||||
p.next()
|
||||
typ := p.parseType()
|
||||
p.expect(')')
|
||||
return typ
|
||||
}
|
||||
p.errorf("expected type, got %s (%q)", scanner.TokenString(p.tok), p.lit)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Declarations
|
||||
|
||||
// ImportDecl = "import" PackageName PackageId .
|
||||
//
|
||||
func (p *parser) parseImportDecl() {
|
||||
p.expectKeyword("import")
|
||||
name := p.parsePackageName()
|
||||
p.getPkg(p.parsePackageId(), name)
|
||||
}
|
||||
|
||||
// int_lit = [ "+" | "-" ] { "0" ... "9" } .
|
||||
//
|
||||
func (p *parser) parseInt() string {
|
||||
s := ""
|
||||
switch p.tok {
|
||||
case '-':
|
||||
s = "-"
|
||||
p.next()
|
||||
case '+':
|
||||
p.next()
|
||||
}
|
||||
return s + p.expect(scanner.Int)
|
||||
}
|
||||
|
||||
// number = int_lit [ "p" int_lit ] .
|
||||
//
|
||||
func (p *parser) parseNumber() (typ *types.Basic, val exact.Value) {
|
||||
// mantissa
|
||||
mant := exact.MakeFromLiteral(p.parseInt(), token.INT)
|
||||
if mant == nil {
|
||||
panic("invalid mantissa")
|
||||
}
|
||||
|
||||
if p.lit == "p" {
|
||||
// exponent (base 2)
|
||||
p.next()
|
||||
exp, err := strconv.ParseInt(p.parseInt(), 10, 0)
|
||||
if err != nil {
|
||||
p.error(err)
|
||||
}
|
||||
if exp < 0 {
|
||||
denom := exact.MakeInt64(1)
|
||||
denom = exact.Shift(denom, token.SHL, uint(-exp))
|
||||
typ = types.Typ[types.UntypedFloat]
|
||||
val = exact.BinaryOp(mant, token.QUO, denom)
|
||||
return
|
||||
}
|
||||
if exp > 0 {
|
||||
mant = exact.Shift(mant, token.SHL, uint(exp))
|
||||
}
|
||||
typ = types.Typ[types.UntypedFloat]
|
||||
val = mant
|
||||
return
|
||||
}
|
||||
|
||||
typ = types.Typ[types.UntypedInt]
|
||||
val = mant
|
||||
return
|
||||
}
|
||||
|
||||
// ConstDecl = "const" ExportedName [ Type ] "=" Literal .
|
||||
// Literal = bool_lit | int_lit | float_lit | complex_lit | rune_lit | string_lit .
|
||||
// bool_lit = "true" | "false" .
|
||||
// complex_lit = "(" float_lit "+" float_lit "i" ")" .
|
||||
// rune_lit = "(" int_lit "+" int_lit ")" .
|
||||
// string_lit = `"` { unicode_char } `"` .
|
||||
//
|
||||
func (p *parser) parseConstDecl() {
|
||||
p.expectKeyword("const")
|
||||
pkg, name := p.parseExportedName()
|
||||
|
||||
var typ0 types.Type
|
||||
if p.tok != '=' {
|
||||
typ0 = p.parseType()
|
||||
}
|
||||
|
||||
p.expect('=')
|
||||
var typ types.Type
|
||||
var val exact.Value
|
||||
switch p.tok {
|
||||
case scanner.Ident:
|
||||
// bool_lit
|
||||
if p.lit != "true" && p.lit != "false" {
|
||||
p.error("expected true or false")
|
||||
}
|
||||
typ = types.Typ[types.UntypedBool]
|
||||
val = exact.MakeBool(p.lit == "true")
|
||||
p.next()
|
||||
|
||||
case '-', scanner.Int:
|
||||
// int_lit
|
||||
typ, val = p.parseNumber()
|
||||
|
||||
case '(':
|
||||
// complex_lit or rune_lit
|
||||
p.next()
|
||||
if p.tok == scanner.Char {
|
||||
p.next()
|
||||
p.expect('+')
|
||||
typ = types.Typ[types.UntypedRune]
|
||||
_, val = p.parseNumber()
|
||||
p.expect(')')
|
||||
break
|
||||
}
|
||||
_, re := p.parseNumber()
|
||||
p.expect('+')
|
||||
_, im := p.parseNumber()
|
||||
p.expectKeyword("i")
|
||||
p.expect(')')
|
||||
typ = types.Typ[types.UntypedComplex]
|
||||
val = exact.BinaryOp(re, token.ADD, exact.MakeImag(im))
|
||||
|
||||
case scanner.Char:
|
||||
// rune_lit
|
||||
typ = types.Typ[types.UntypedRune]
|
||||
val = exact.MakeFromLiteral(p.lit, token.CHAR)
|
||||
p.next()
|
||||
|
||||
case scanner.String:
|
||||
// string_lit
|
||||
typ = types.Typ[types.UntypedString]
|
||||
val = exact.MakeFromLiteral(p.lit, token.STRING)
|
||||
p.next()
|
||||
|
||||
default:
|
||||
p.errorf("expected literal got %s", scanner.TokenString(p.tok))
|
||||
}
|
||||
|
||||
if typ0 == nil {
|
||||
typ0 = typ
|
||||
}
|
||||
|
||||
pkg.Scope().Insert(types.NewConst(token.NoPos, pkg, name, typ0, val))
|
||||
}
|
||||
|
||||
// TypeDecl = "type" ExportedName Type .
|
||||
//
|
||||
func (p *parser) parseTypeDecl() {
|
||||
p.expectKeyword("type")
|
||||
pkg, name := p.parseExportedName()
|
||||
obj := declTypeName(pkg, name)
|
||||
|
||||
// The type object may have been imported before and thus already
|
||||
// have a type associated with it. We still need to parse the type
|
||||
// structure, but throw it away if the object already has a type.
|
||||
// This ensures that all imports refer to the same type object for
|
||||
// a given type declaration.
|
||||
typ := p.parseType()
|
||||
|
||||
if name := obj.Type().(*types.Named); name.Underlying() == nil {
|
||||
name.SetUnderlying(typ)
|
||||
}
|
||||
}
|
||||
|
||||
// VarDecl = "var" ExportedName Type .
|
||||
//
|
||||
func (p *parser) parseVarDecl() {
|
||||
p.expectKeyword("var")
|
||||
pkg, name := p.parseExportedName()
|
||||
typ := p.parseType()
|
||||
pkg.Scope().Insert(types.NewVar(token.NoPos, pkg, name, typ))
|
||||
}
|
||||
|
||||
// Func = Signature [ Body ] .
|
||||
// Body = "{" ... "}" .
|
||||
//
|
||||
func (p *parser) parseFunc(recv *types.Var) *types.Signature {
|
||||
sig := p.parseSignature(recv)
|
||||
if p.tok == '{' {
|
||||
p.next()
|
||||
for i := 1; i > 0; p.next() {
|
||||
switch p.tok {
|
||||
case '{':
|
||||
i++
|
||||
case '}':
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
// MethodDecl = "func" Receiver Name Func .
|
||||
// Receiver = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" .
|
||||
//
|
||||
func (p *parser) parseMethodDecl() {
|
||||
// "func" already consumed
|
||||
p.expect('(')
|
||||
recv, _ := p.parseParameter() // receiver
|
||||
p.expect(')')
|
||||
|
||||
// determine receiver base type object
|
||||
base := deref(recv.Type()).(*types.Named)
|
||||
|
||||
// parse method name, signature, and possibly inlined body
|
||||
_, name := p.parseName(true)
|
||||
sig := p.parseFunc(recv)
|
||||
|
||||
// methods always belong to the same package as the base type object
|
||||
pkg := base.Obj().Pkg()
|
||||
|
||||
// add method to type unless type was imported before
|
||||
// and method exists already
|
||||
// TODO(gri) This leads to a quadratic algorithm - ok for now because method counts are small.
|
||||
base.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig))
|
||||
}
|
||||
|
||||
// FuncDecl = "func" ExportedName Func .
|
||||
//
|
||||
func (p *parser) parseFuncDecl() {
|
||||
// "func" already consumed
|
||||
pkg, name := p.parseExportedName()
|
||||
typ := p.parseFunc(nil)
|
||||
pkg.Scope().Insert(types.NewFunc(token.NoPos, pkg, name, typ))
|
||||
}
|
||||
|
||||
// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" .
|
||||
//
|
||||
func (p *parser) parseDecl() {
|
||||
if p.tok == scanner.Ident {
|
||||
switch p.lit {
|
||||
case "import":
|
||||
p.parseImportDecl()
|
||||
case "const":
|
||||
p.parseConstDecl()
|
||||
case "type":
|
||||
p.parseTypeDecl()
|
||||
case "var":
|
||||
p.parseVarDecl()
|
||||
case "func":
|
||||
p.next() // look ahead
|
||||
if p.tok == '(' {
|
||||
p.parseMethodDecl()
|
||||
} else {
|
||||
p.parseFuncDecl()
|
||||
}
|
||||
}
|
||||
}
|
||||
p.expect('\n')
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Export
|
||||
|
||||
// Export = "PackageClause { Decl } "$$" .
|
||||
// PackageClause = "package" PackageName [ "safe" ] "\n" .
|
||||
//
|
||||
func (p *parser) parseExport() *types.Package {
|
||||
p.expectKeyword("package")
|
||||
name := p.parsePackageName()
|
||||
if p.tok == scanner.Ident && p.lit == "safe" {
|
||||
// package was compiled with -u option - ignore
|
||||
p.next()
|
||||
}
|
||||
p.expect('\n')
|
||||
|
||||
pkg := p.getPkg(p.id, name)
|
||||
|
||||
for p.tok != '$' && p.tok != scanner.EOF {
|
||||
p.parseDecl()
|
||||
}
|
||||
|
||||
if ch := p.scanner.Peek(); p.tok != '$' || ch != '$' {
|
||||
// don't call next()/expect() since reading past the
|
||||
// export data may cause scanner errors (e.g. NUL chars)
|
||||
p.errorf("expected '$$', got %s %c", scanner.TokenString(p.tok), ch)
|
||||
}
|
||||
|
||||
if n := p.scanner.ErrorCount; n != 0 {
|
||||
p.errorf("expected no scanner errors, got %d", n)
|
||||
}
|
||||
|
||||
// Record all referenced packages as imports.
|
||||
var imports []*types.Package
|
||||
for id, pkg2 := range p.localPkgs {
|
||||
if id == p.id {
|
||||
continue // avoid self-edge
|
||||
}
|
||||
imports = append(imports, pkg2)
|
||||
}
|
||||
sort.Sort(byPath(imports))
|
||||
pkg.SetImports(imports)
|
||||
|
||||
// package was imported completely and without errors
|
||||
pkg.MarkComplete()
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
type byPath []*types.Package
|
||||
|
||||
func (a byPath) Len() int { return len(a) }
|
||||
func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }
|
|
@ -0,0 +1,199 @@
|
|||
package loader
|
||||
|
||||
// This file handles cgo preprocessing of files containing `import "C"`.
|
||||
//
|
||||
// DESIGN
|
||||
//
|
||||
// The approach taken is to run the cgo processor on the package's
|
||||
// CgoFiles and parse the output, faking the filenames of the
|
||||
// resulting ASTs so that the synthetic file containing the C types is
|
||||
// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
|
||||
// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
|
||||
// not the names of the actual temporary files.
|
||||
//
|
||||
// The advantage of this approach is its fidelity to 'go build'. The
|
||||
// downside is that the token.Position.Offset for each AST node is
|
||||
// incorrect, being an offset within the temporary file. Line numbers
|
||||
// should still be correct because of the //line comments.
|
||||
//
|
||||
// The logic of this file is mostly plundered from the 'go build'
|
||||
// tool, which also invokes the cgo preprocessor.
|
||||
//
|
||||
//
|
||||
// REJECTED ALTERNATIVE
|
||||
//
|
||||
// An alternative approach that we explored is to extend go/types'
|
||||
// Importer mechanism to provide the identity of the importing package
|
||||
// so that each time `import "C"` appears it resolves to a different
|
||||
// synthetic package containing just the objects needed in that case.
|
||||
// The loader would invoke cgo but parse only the cgo_types.go file
|
||||
// defining the package-level objects, discarding the other files
|
||||
// resulting from preprocessing.
|
||||
//
|
||||
// The benefit of this approach would have been that source-level
|
||||
// syntax information would correspond exactly to the original cgo
|
||||
// file, with no preprocessing involved, making source tools like
|
||||
// godoc, oracle, and eg happy. However, the approach was rejected
|
||||
// due to the additional complexity it would impose on go/types. (It
|
||||
// made for a beautiful demo, though.)
|
||||
//
|
||||
// cgo files, despite their *.go extension, are not legal Go source
|
||||
// files per the specification since they may refer to unexported
|
||||
// members of package "C" such as C.int. Also, a function such as
|
||||
// C.getpwent has in effect two types, one matching its C type and one
|
||||
// which additionally returns (errno C.int). The cgo preprocessor
|
||||
// uses name mangling to distinguish these two functions in the
|
||||
// processed code, but go/types would need to duplicate this logic in
|
||||
// its handling of function calls, analogous to the treatment of map
|
||||
// lookups in which y=m[k] and y,ok=m[k] are both legal.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses
|
||||
// the output and returns the resulting ASTs.
|
||||
//
|
||||
func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
|
||||
tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
pkgdir := bp.Dir
|
||||
if DisplayPath != nil {
|
||||
pkgdir = DisplayPath(pkgdir)
|
||||
}
|
||||
|
||||
cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []*ast.File
|
||||
for i := range cgoFiles {
|
||||
rd, err := os.Open(cgoFiles[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rd.Close()
|
||||
display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
|
||||
f, err := parser.ParseFile(fset, display, rd, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
var cgoRe = regexp.MustCompile(`[/\\:]`)
|
||||
|
||||
// runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two
|
||||
// lists of files: the resulting processed files (in temporary
|
||||
// directory tmpdir) and the corresponding names of the unprocessed files.
|
||||
//
|
||||
// runCgo is adapted from (*builder).cgo in
|
||||
// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
|
||||
// pkg-config, Objective C, CGOPKGPATH, CGO_FLAGS.
|
||||
//
|
||||
func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) {
|
||||
cgoCPPFLAGS, _, _, _ := cflags(bp, true)
|
||||
_, cgoexeCFLAGS, _, _ := cflags(bp, false)
|
||||
|
||||
if len(bp.CgoPkgConfig) > 0 {
|
||||
return nil, nil, fmt.Errorf("cgo pkg-config not supported")
|
||||
}
|
||||
|
||||
// Allows including _cgo_export.h from .[ch] files in the package.
|
||||
cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
|
||||
|
||||
// _cgo_gotypes.go (displayed "C") contains the type definitions.
|
||||
files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
|
||||
displayFiles = append(displayFiles, "C")
|
||||
for _, fn := range bp.CgoFiles {
|
||||
// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
|
||||
f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
|
||||
files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
|
||||
displayFiles = append(displayFiles, fn)
|
||||
}
|
||||
|
||||
var cgoflags []string
|
||||
if bp.Goroot && bp.ImportPath == "runtime/cgo" {
|
||||
cgoflags = append(cgoflags, "-import_runtime_cgo=false")
|
||||
}
|
||||
if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
|
||||
cgoflags = append(cgoflags, "-import_syscall=false")
|
||||
}
|
||||
|
||||
args := stringList(
|
||||
"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
|
||||
cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles,
|
||||
)
|
||||
if false {
|
||||
log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
|
||||
}
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = pkgdir
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
|
||||
}
|
||||
|
||||
return files, displayFiles, nil
|
||||
}
|
||||
|
||||
// -- unmodified from 'go build' ---------------------------------------
|
||||
|
||||
// Return the flags to use when invoking the C or C++ compilers, or cgo.
|
||||
func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
|
||||
var defaults string
|
||||
if def {
|
||||
defaults = "-g -O2"
|
||||
}
|
||||
|
||||
cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
|
||||
cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
|
||||
cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
|
||||
ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
|
||||
return
|
||||
}
|
||||
|
||||
// envList returns the value of the given environment variable broken
|
||||
// into fields, using the default value when the variable is empty.
|
||||
func envList(key, def string) []string {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
v = def
|
||||
}
|
||||
return strings.Fields(v)
|
||||
}
|
||||
|
||||
// stringList's arguments should be a sequence of string or []string values.
|
||||
// stringList flattens them into a single []string.
|
||||
func stringList(args ...interface{}) []string {
|
||||
var x []string
|
||||
for _, arg := range args {
|
||||
switch arg := arg.(type) {
|
||||
case []string:
|
||||
x = append(x, arg...)
|
||||
case string:
|
||||
x = append(x, arg)
|
||||
default:
|
||||
panic("stringList: invalid argument")
|
||||
}
|
||||
}
|
||||
return x
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package loader loads a complete Go program from source code, parsing
|
||||
// and type-checking the initial packages plus their transitive closure
|
||||
// of dependencies. The ASTs and the derived facts are retained for
|
||||
// later use.
|
||||
//
|
||||
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
|
||||
//
|
||||
// The package defines two primary types: Config, which specifies a
|
||||
// set of initial packages to load and various other options; and
|
||||
// Program, which is the result of successfully loading the packages
|
||||
// specified by a configuration.
|
||||
//
|
||||
// The configuration can be set directly, but *Config provides various
|
||||
// convenience methods to simplify the common cases, each of which can
|
||||
// be called any number of times. Finally, these are followed by a
|
||||
// call to Load() to actually load and type-check the program.
|
||||
//
|
||||
// var conf loader.Config
|
||||
//
|
||||
// // Use the command-line arguments to specify
|
||||
// // a set of initial packages to load from source.
|
||||
// // See FromArgsUsage for help.
|
||||
// rest, err := conf.FromArgs(os.Args[1:], wantTests)
|
||||
//
|
||||
// // Parse the specified files and create an ad hoc package with path "foo".
|
||||
// // All files must have the same 'package' declaration.
|
||||
// conf.CreateFromFilenames("foo", "foo.go", "bar.go")
|
||||
//
|
||||
// // Create an ad hoc package with path "foo" from
|
||||
// // the specified already-parsed files.
|
||||
// // All ASTs must have the same 'package' declaration.
|
||||
// conf.CreateFromFiles("foo", parsedFiles)
|
||||
//
|
||||
// // Add "runtime" to the set of packages to be loaded.
|
||||
// conf.Import("runtime")
|
||||
//
|
||||
// // Adds "fmt" and "fmt_test" to the set of packages
|
||||
// // to be loaded. "fmt" will include *_test.go files.
|
||||
// conf.ImportWithTests("fmt")
|
||||
//
|
||||
// // Finally, load all the packages specified by the configuration.
|
||||
// prog, err := conf.Load()
|
||||
//
|
||||
// See examples_test.go for examples of API usage.
|
||||
//
|
||||
//
|
||||
// CONCEPTS AND TERMINOLOGY
|
||||
//
|
||||
// An AD HOC package is one specified as a set of source files on the
|
||||
// command line. In the simplest case, it may consist of a single file
|
||||
// such as $GOROOT/src/net/http/triv.go.
|
||||
//
|
||||
// EXTERNAL TEST packages are those comprised of a set of *_test.go
|
||||
// files all with the same 'package foo_test' declaration, all in the
|
||||
// same directory. (go/build.Package calls these files XTestFiles.)
|
||||
//
|
||||
// An IMPORTABLE package is one that can be referred to by some import
|
||||
// spec. The Path() of each importable package is unique within a
|
||||
// Program.
|
||||
//
|
||||
// ad hoc packages and external test packages are NON-IMPORTABLE. The
|
||||
// Path() of an ad hoc package is inferred from the package
|
||||
// declarations of its files and is therefore not a unique package key.
|
||||
// For example, Config.CreatePkgs may specify two initial ad hoc
|
||||
// packages both called "main".
|
||||
//
|
||||
// An AUGMENTED package is an importable package P plus all the
|
||||
// *_test.go files with same 'package foo' declaration as P.
|
||||
// (go/build.Package calls these files TestFiles.)
|
||||
//
|
||||
// The INITIAL packages are those specified in the configuration. A
|
||||
// DEPENDENCY is a package loaded to satisfy an import in an initial
|
||||
// package or another dependency.
|
||||
//
|
||||
package loader
|
||||
|
||||
// IMPLEMENTATION NOTES
|
||||
//
|
||||
// 'go test', in-package test files, and import cycles
|
||||
// ---------------------------------------------------
|
||||
//
|
||||
// An external test package may depend upon members of the augmented
|
||||
// package that are not in the unaugmented package, such as functions
|
||||
// that expose internals. (See bufio/export_test.go for an example.)
|
||||
// So, the loader must ensure that for each external test package
|
||||
// it loads, it also augments the corresponding non-test package.
|
||||
//
|
||||
// The import graph over n unaugmented packages must be acyclic; the
|
||||
// import graph over n-1 unaugmented packages plus one augmented
|
||||
// package must also be acyclic. ('go test' relies on this.) But the
|
||||
// import graph over n augmented packages may contain cycles.
|
||||
//
|
||||
// First, all the (unaugmented) non-test packages and their
|
||||
// dependencies are imported in the usual way; the loader reports an
|
||||
// error if it detects an import cycle.
|
||||
//
|
||||
// Then, each package P for which testing is desired is augmented by
|
||||
// the list P' of its in-package test files, by calling
|
||||
// (*types.Checker).Files. This arrangement ensures that P' may
|
||||
// reference definitions within P, but P may not reference definitions
|
||||
// within P'. Furthermore, P' may import any other package, including
|
||||
// ones that depend upon P, without an import cycle error.
|
||||
//
|
||||
// Consider two packages A and B, both of which have lists of
|
||||
// in-package test files we'll call A' and B', and which have the
|
||||
// following import graph edges:
|
||||
// B imports A
|
||||
// B' imports A
|
||||
// A' imports B
|
||||
// This last edge would be expected to create an error were it not
|
||||
// for the special type-checking discipline above.
|
||||
// Cycles of size greater than two are possible. For example:
|
||||
// compress/bzip2/bzip2_test.go (package bzip2) imports "io/ioutil"
|
||||
// io/ioutil/tempfile_test.go (package ioutil) imports "regexp"
|
||||
// regexp/exec_test.go (package regexp) imports "compress/bzip2"
|
||||
//
|
||||
//
|
||||
// Concurrency
|
||||
// -----------
|
||||
//
|
||||
// Let us define the import dependency graph as follows. Each node is a
|
||||
// list of files passed to (Checker).Files at once. Many of these lists
|
||||
// are the production code of an importable Go package, so those nodes
|
||||
// are labelled by the package's import path. The remaining nodes are
|
||||
// ad hoc packages and lists of in-package *_test.go files that augment
|
||||
// an importable package; those nodes have no label.
|
||||
//
|
||||
// The edges of the graph represent import statements appearing within a
|
||||
// file. An edge connects a node (a list of files) to the node it
|
||||
// imports, which is importable and thus always labelled.
|
||||
//
|
||||
// Loading is controlled by this dependency graph.
|
||||
//
|
||||
// To reduce I/O latency, we start loading a package's dependencies
|
||||
// asynchronously as soon as we've parsed its files and enumerated its
|
||||
// imports (scanImports). This performs a preorder traversal of the
|
||||
// import dependency graph.
|
||||
//
|
||||
// To exploit hardware parallelism, we type-check unrelated packages in
|
||||
// parallel, where "unrelated" means not ordered by the partial order of
|
||||
// the import dependency graph.
|
||||
//
|
||||
// We use a concurrency-safe blocking cache (importer.imported) to
|
||||
// record the results of type-checking, whether success or failure. An
|
||||
// entry is created in this cache by startLoad the first time the
|
||||
// package is imported. The first goroutine to request an entry becomes
|
||||
// responsible for completing the task and broadcasting completion to
|
||||
// subsequent requestors, which block until then.
|
||||
//
|
||||
// Type checking occurs in (parallel) postorder: we cannot type-check a
|
||||
// set of files until we have loaded and type-checked all of their
|
||||
// immediate dependencies (and thus all of their transitive
|
||||
// dependencies). If the input were guaranteed free of import cycles,
|
||||
// this would be trivial: we could simply wait for completion of the
|
||||
// dependencies and then invoke the typechecker.
|
||||
//
|
||||
// But as we saw in the 'go test' section above, some cycles in the
|
||||
// import graph over packages are actually legal, so long as the
|
||||
// cycle-forming edge originates in the in-package test files that
|
||||
// augment the package. This explains why the nodes of the import
|
||||
// dependency graph are not packages, but lists of files: the unlabelled
|
||||
// nodes avoid the cycles. Consider packages A and B where B imports A
|
||||
// and A's in-package tests AT import B. The naively constructed import
|
||||
// graph over packages would contain a cycle (A+AT) --> B --> (A+AT) but
|
||||
// the graph over lists of files is AT --> B --> A, where AT is an
|
||||
// unlabelled node.
|
||||
//
|
||||
// Awaiting completion of the dependencies in a cyclic graph would
|
||||
// deadlock, so we must materialize the import dependency graph (as
|
||||
// importer.graph) and check whether each import edge forms a cycle. If
|
||||
// x imports y, and the graph already contains a path from y to x, then
|
||||
// there is an import cycle, in which case the processing of x must not
|
||||
// wait for the completion of processing of y.
|
||||
//
|
||||
// When the type-checker makes a callback (doImport) to the loader for a
|
||||
// given import edge, there are two possible cases. In the normal case,
|
||||
// the dependency has already been completely type-checked; doImport
|
||||
// does a cache lookup and returns it. In the cyclic case, the entry in
|
||||
// the cache is still necessarily incomplete, indicating a cycle. We
|
||||
// perform the cycle check again to obtain the error message, and return
|
||||
// the error.
|
||||
//
|
||||
// The result of using concurrency is about a 2.5x speedup for stdlib_test.
|
||||
|
||||
// TODO(adonovan): overhaul the package documentation.
|
|
@ -0,0 +1,966 @@
|
|||
// 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 loader
|
||||
|
||||
// See doc.go for package documentation and implementation notes.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
const trace = false // show timing info for type-checking
|
||||
|
||||
// Config specifies the configuration for loading a whole program from
|
||||
// Go source code.
|
||||
// The zero value for Config is a ready-to-use default configuration.
|
||||
type Config struct {
|
||||
// Fset is the file set for the parser to use when loading the
|
||||
// program. If nil, it may be lazily initialized by any
|
||||
// method of Config.
|
||||
Fset *token.FileSet
|
||||
|
||||
// ParserMode specifies the mode to be used by the parser when
|
||||
// loading source packages.
|
||||
ParserMode parser.Mode
|
||||
|
||||
// TypeChecker contains options relating to the type checker.
|
||||
//
|
||||
// The supplied IgnoreFuncBodies is not used; the effective
|
||||
// value comes from the TypeCheckFuncBodies func below.
|
||||
// The supplied Import function is not used either.
|
||||
TypeChecker types.Config
|
||||
|
||||
// TypeCheckFuncBodies is a predicate over package import
|
||||
// paths. A package for which the predicate is false will
|
||||
// have its package-level declarations type checked, but not
|
||||
// its function bodies; this can be used to quickly load
|
||||
// dependencies from source. If nil, all func bodies are type
|
||||
// checked.
|
||||
TypeCheckFuncBodies func(string) bool
|
||||
|
||||
// If Build is non-nil, it is used to locate source packages.
|
||||
// Otherwise &build.Default is used.
|
||||
//
|
||||
// By default, cgo is invoked to preprocess Go files that
|
||||
// import the fake package "C". This behaviour can be
|
||||
// disabled by setting CGO_ENABLED=0 in the environment prior
|
||||
// to startup, or by setting Build.CgoEnabled=false.
|
||||
Build *build.Context
|
||||
|
||||
// The current directory, used for resolving relative package
|
||||
// references such as "./go/loader". If empty, os.Getwd will be
|
||||
// used instead.
|
||||
Cwd string
|
||||
|
||||
// If DisplayPath is non-nil, it is used to transform each
|
||||
// file name obtained from Build.Import(). This can be used
|
||||
// to prevent a virtualized build.Config's file names from
|
||||
// leaking into the user interface.
|
||||
DisplayPath func(path string) string
|
||||
|
||||
// If AllowErrors is true, Load will return a Program even
|
||||
// if some of the its packages contained I/O, parser or type
|
||||
// errors; such errors are accessible via PackageInfo.Errors. If
|
||||
// false, Load will fail if any package had an error.
|
||||
AllowErrors bool
|
||||
|
||||
// CreatePkgs specifies a list of non-importable initial
|
||||
// packages to create. The resulting packages will appear in
|
||||
// the corresponding elements of the Program.Created slice.
|
||||
CreatePkgs []PkgSpec
|
||||
|
||||
// ImportPkgs specifies a set of initial packages to load from
|
||||
// source. The map keys are package import paths, used to
|
||||
// locate the package relative to $GOROOT.
|
||||
//
|
||||
// The map value indicates whether to load tests. If true, Load
|
||||
// will add and type-check two lists of files to the package:
|
||||
// non-test files followed by in-package *_test.go files. In
|
||||
// addition, it will append the external test package (if any)
|
||||
// to Program.Created.
|
||||
ImportPkgs map[string]bool
|
||||
|
||||
// FindPackage is called during Load to create the build.Package
|
||||
// for a given import path. If nil, a default implementation
|
||||
// based on ctxt.Import is used. A client may use this hook to
|
||||
// adapt to a proprietary build system that does not follow the
|
||||
// "go build" layout conventions, for example.
|
||||
//
|
||||
// It must be safe to call concurrently from multiple goroutines.
|
||||
FindPackage func(ctxt *build.Context, importPath string) (*build.Package, error)
|
||||
}
|
||||
|
||||
// A PkgSpec specifies a non-importable package to be created by Load.
|
||||
// Files are processed first, but typically only one of Files and
|
||||
// Filenames is provided. The path needn't be globally unique.
|
||||
//
|
||||
type PkgSpec struct {
|
||||
Path string // import path ("" => use package declaration)
|
||||
Files []*ast.File // ASTs of already-parsed files
|
||||
Filenames []string // names of files to be parsed
|
||||
}
|
||||
|
||||
// A Program is a Go program loaded from source as specified by a Config.
|
||||
type Program struct {
|
||||
Fset *token.FileSet // the file set for this program
|
||||
|
||||
// Created[i] contains the initial package whose ASTs or
|
||||
// filenames were supplied by Config.CreatePkgs[i], followed by
|
||||
// the external test package, if any, of each package in
|
||||
// Config.ImportPkgs ordered by ImportPath.
|
||||
Created []*PackageInfo
|
||||
|
||||
// Imported contains the initially imported packages,
|
||||
// as specified by Config.ImportPkgs.
|
||||
Imported map[string]*PackageInfo
|
||||
|
||||
// AllPackages contains the PackageInfo of every package
|
||||
// encountered by Load: all initial packages and all
|
||||
// dependencies, including incomplete ones.
|
||||
AllPackages map[*types.Package]*PackageInfo
|
||||
|
||||
// importMap is the canonical mapping of import paths to
|
||||
// packages. It contains all Imported initial packages, but not
|
||||
// Created ones, and all imported dependencies.
|
||||
importMap map[string]*types.Package
|
||||
}
|
||||
|
||||
// PackageInfo holds the ASTs and facts derived by the type-checker
|
||||
// for a single package.
|
||||
//
|
||||
// Not mutated once exposed via the API.
|
||||
//
|
||||
type PackageInfo struct {
|
||||
Pkg *types.Package
|
||||
Importable bool // true if 'import "Pkg.Path()"' would resolve to this
|
||||
TransitivelyErrorFree bool // true if Pkg and all its dependencies are free of errors
|
||||
Files []*ast.File // syntax trees for the package's files
|
||||
Errors []error // non-nil if the package had errors
|
||||
types.Info // type-checker deductions.
|
||||
|
||||
checker *types.Checker // transient type-checker state
|
||||
errorFunc func(error)
|
||||
}
|
||||
|
||||
func (info *PackageInfo) String() string { return info.Pkg.Path() }
|
||||
|
||||
func (info *PackageInfo) appendError(err error) {
|
||||
if info.errorFunc != nil {
|
||||
info.errorFunc(err)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
info.Errors = append(info.Errors, err)
|
||||
}
|
||||
|
||||
func (conf *Config) fset() *token.FileSet {
|
||||
if conf.Fset == nil {
|
||||
conf.Fset = token.NewFileSet()
|
||||
}
|
||||
return conf.Fset
|
||||
}
|
||||
|
||||
// ParseFile is a convenience function (intended for testing) that invokes
|
||||
// the parser using the Config's FileSet, which is initialized if nil.
|
||||
//
|
||||
// src specifies the parser input as a string, []byte, or io.Reader, and
|
||||
// filename is its apparent name. If src is nil, the contents of
|
||||
// filename are read from the file system.
|
||||
//
|
||||
func (conf *Config) ParseFile(filename string, src interface{}) (*ast.File, error) {
|
||||
// TODO(adonovan): use conf.build() etc like parseFiles does.
|
||||
return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode)
|
||||
}
|
||||
|
||||
// FromArgsUsage is a partial usage message that applications calling
|
||||
// FromArgs may wish to include in their -help output.
|
||||
const FromArgsUsage = `
|
||||
<args> is a list of arguments denoting a set of initial packages.
|
||||
It may take one of two forms:
|
||||
|
||||
1. A list of *.go source files.
|
||||
|
||||
All of the specified files are loaded, parsed and type-checked
|
||||
as a single package. All the files must belong to the same directory.
|
||||
|
||||
2. A list of import paths, each denoting a package.
|
||||
|
||||
The package's directory is found relative to the $GOROOT and
|
||||
$GOPATH using similar logic to 'go build', and the *.go files in
|
||||
that directory are loaded, parsed and type-checked as a single
|
||||
package.
|
||||
|
||||
In addition, all *_test.go files in the directory are then loaded
|
||||
and parsed. Those files whose package declaration equals that of
|
||||
the non-*_test.go files are included in the primary package. Test
|
||||
files whose package declaration ends with "_test" are type-checked
|
||||
as another package, the 'external' test package, so that a single
|
||||
import path may denote two packages. (Whether this behaviour is
|
||||
enabled is tool-specific, and may depend on additional flags.)
|
||||
|
||||
A '--' argument terminates the list of packages.
|
||||
`
|
||||
|
||||
// FromArgs interprets args as a set of initial packages to load from
|
||||
// source and updates the configuration. It returns the list of
|
||||
// unconsumed arguments.
|
||||
//
|
||||
// It is intended for use in command-line interfaces that require a
|
||||
// set of initial packages to be specified; see FromArgsUsage message
|
||||
// for details.
|
||||
//
|
||||
// Only superficial errors are reported at this stage; errors dependent
|
||||
// on I/O are detected during Load.
|
||||
//
|
||||
func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) {
|
||||
var rest []string
|
||||
for i, arg := range args {
|
||||
if arg == "--" {
|
||||
rest = args[i+1:]
|
||||
args = args[:i]
|
||||
break // consume "--" and return the remaining args
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 && strings.HasSuffix(args[0], ".go") {
|
||||
// Assume args is a list of a *.go files
|
||||
// denoting a single ad hoc package.
|
||||
for _, arg := range args {
|
||||
if !strings.HasSuffix(arg, ".go") {
|
||||
return nil, fmt.Errorf("named files must be .go files: %s", arg)
|
||||
}
|
||||
}
|
||||
conf.CreateFromFilenames("", args...)
|
||||
} else {
|
||||
// Assume args are directories each denoting a
|
||||
// package and (perhaps) an external test, iff xtest.
|
||||
for _, arg := range args {
|
||||
if xtest {
|
||||
conf.ImportWithTests(arg)
|
||||
} else {
|
||||
conf.Import(arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rest, nil
|
||||
}
|
||||
|
||||
// CreateFromFilenames is a convenience function that adds
|
||||
// a conf.CreatePkgs entry to create a package of the specified *.go
|
||||
// files.
|
||||
//
|
||||
func (conf *Config) CreateFromFilenames(path string, filenames ...string) {
|
||||
conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames})
|
||||
}
|
||||
|
||||
// CreateFromFiles is a convenience function that adds a conf.CreatePkgs
|
||||
// entry to create package of the specified path and parsed files.
|
||||
//
|
||||
func (conf *Config) CreateFromFiles(path string, files ...*ast.File) {
|
||||
conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files})
|
||||
}
|
||||
|
||||
// ImportWithTests is a convenience function that adds path to
|
||||
// ImportPkgs, the set of initial source packages located relative to
|
||||
// $GOPATH. The package will be augmented by any *_test.go files in
|
||||
// its directory that contain a "package x" (not "package x_test")
|
||||
// declaration.
|
||||
//
|
||||
// In addition, if any *_test.go files contain a "package x_test"
|
||||
// declaration, an additional package comprising just those files will
|
||||
// be added to CreatePkgs.
|
||||
//
|
||||
func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) }
|
||||
|
||||
// Import is a convenience function that adds path to ImportPkgs, the
|
||||
// set of initial packages that will be imported from source.
|
||||
//
|
||||
func (conf *Config) Import(path string) { conf.addImport(path, false) }
|
||||
|
||||
func (conf *Config) addImport(path string, tests bool) {
|
||||
if path == "C" || path == "unsafe" {
|
||||
return // ignore; not a real package
|
||||
}
|
||||
if conf.ImportPkgs == nil {
|
||||
conf.ImportPkgs = make(map[string]bool)
|
||||
}
|
||||
conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests
|
||||
}
|
||||
|
||||
// PathEnclosingInterval returns the PackageInfo and ast.Node that
|
||||
// contain source interval [start, end), and all the node's ancestors
|
||||
// up to the AST root. It searches all ast.Files of all packages in prog.
|
||||
// exact is defined as for astutil.PathEnclosingInterval.
|
||||
//
|
||||
// The zero value is returned if not found.
|
||||
//
|
||||
func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
|
||||
for _, info := range prog.AllPackages {
|
||||
for _, f := range info.Files {
|
||||
if f.Pos() == token.NoPos {
|
||||
// This can happen if the parser saw
|
||||
// too many errors and bailed out.
|
||||
// (Use parser.AllErrors to prevent that.)
|
||||
continue
|
||||
}
|
||||
if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) {
|
||||
continue
|
||||
}
|
||||
if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
|
||||
return info, path, exact
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// InitialPackages returns a new slice containing the set of initial
|
||||
// packages (Created + Imported) in unspecified order.
|
||||
//
|
||||
func (prog *Program) InitialPackages() []*PackageInfo {
|
||||
infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported))
|
||||
infos = append(infos, prog.Created...)
|
||||
for _, info := range prog.Imported {
|
||||
infos = append(infos, info)
|
||||
}
|
||||
return infos
|
||||
}
|
||||
|
||||
// Package returns the ASTs and results of type checking for the
|
||||
// specified package.
|
||||
func (prog *Program) Package(path string) *PackageInfo {
|
||||
if info, ok := prog.AllPackages[prog.importMap[path]]; ok {
|
||||
return info
|
||||
}
|
||||
for _, info := range prog.Created {
|
||||
if path == info.Pkg.Path() {
|
||||
return info
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------- Implementation ----------
|
||||
|
||||
// importer holds the working state of the algorithm.
|
||||
type importer struct {
|
||||
conf *Config // the client configuration
|
||||
start time.Time // for logging
|
||||
|
||||
progMu sync.Mutex // guards prog
|
||||
prog *Program // the resulting program
|
||||
|
||||
importedMu sync.Mutex // guards imported
|
||||
imported map[string]*importInfo // all imported packages (incl. failures) by import path
|
||||
|
||||
// import dependency graph: graph[x][y] => x imports y
|
||||
//
|
||||
// Since non-importable packages cannot be cyclic, we ignore
|
||||
// their imports, thus we only need the subgraph over importable
|
||||
// packages. Nodes are identified by their import paths.
|
||||
graphMu sync.Mutex
|
||||
graph map[string]map[string]bool
|
||||
}
|
||||
|
||||
// importInfo tracks the success or failure of a single import.
|
||||
//
|
||||
// Upon completion, exactly one of info and err is non-nil:
|
||||
// info on successful creation of a package, err otherwise.
|
||||
// A successful package may still contain type errors.
|
||||
//
|
||||
type importInfo struct {
|
||||
path string // import path
|
||||
mu sync.Mutex // guards the following fields prior to completion
|
||||
info *PackageInfo // results of typechecking (including errors)
|
||||
err error // reason for failure to create a package
|
||||
complete sync.Cond // complete condition is that one of info, err is non-nil.
|
||||
}
|
||||
|
||||
// awaitCompletion blocks until ii is complete,
|
||||
// i.e. the info and err fields are safe to inspect without a lock.
|
||||
// It is concurrency-safe and idempotent.
|
||||
func (ii *importInfo) awaitCompletion() {
|
||||
ii.mu.Lock()
|
||||
for ii.info == nil && ii.err == nil {
|
||||
ii.complete.Wait()
|
||||
}
|
||||
ii.mu.Unlock()
|
||||
}
|
||||
|
||||
// Complete marks ii as complete.
|
||||
// Its info and err fields will not be subsequently updated.
|
||||
func (ii *importInfo) Complete(info *PackageInfo, err error) {
|
||||
if info == nil && err == nil {
|
||||
panic("Complete(nil, nil)")
|
||||
}
|
||||
ii.mu.Lock()
|
||||
ii.info = info
|
||||
ii.err = err
|
||||
ii.complete.Broadcast()
|
||||
ii.mu.Unlock()
|
||||
}
|
||||
|
||||
// Load creates the initial packages specified by conf.{Create,Import}Pkgs,
|
||||
// loading their dependencies packages as needed.
|
||||
//
|
||||
// On success, Load returns a Program containing a PackageInfo for
|
||||
// each package. On failure, it returns an error.
|
||||
//
|
||||
// If AllowErrors is true, Load will return a Program even if some
|
||||
// packages contained I/O, parser or type errors, or if dependencies
|
||||
// were missing. (Such errors are accessible via PackageInfo.Errors. If
|
||||
// false, Load will fail if any package had an error.
|
||||
//
|
||||
// It is an error if no packages were loaded.
|
||||
//
|
||||
func (conf *Config) Load() (*Program, error) {
|
||||
// Create a simple default error handler for parse/type errors.
|
||||
if conf.TypeChecker.Error == nil {
|
||||
conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
|
||||
}
|
||||
|
||||
// Set default working directory for relative package references.
|
||||
if conf.Cwd == "" {
|
||||
var err error
|
||||
conf.Cwd, err = os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Install default FindPackage hook using go/build logic.
|
||||
if conf.FindPackage == nil {
|
||||
conf.FindPackage = func(ctxt *build.Context, path string) (*build.Package, error) {
|
||||
// TODO(adonovan): cache calls to build.Import
|
||||
// so we don't do it three times per test package.
|
||||
bp, err := ctxt.Import(path, conf.Cwd, 0)
|
||||
if _, ok := err.(*build.NoGoError); ok {
|
||||
return bp, nil // empty directory is not an error
|
||||
}
|
||||
return bp, err
|
||||
}
|
||||
}
|
||||
|
||||
prog := &Program{
|
||||
Fset: conf.fset(),
|
||||
Imported: make(map[string]*PackageInfo),
|
||||
importMap: make(map[string]*types.Package),
|
||||
AllPackages: make(map[*types.Package]*PackageInfo),
|
||||
}
|
||||
|
||||
imp := importer{
|
||||
conf: conf,
|
||||
prog: prog,
|
||||
imported: make(map[string]*importInfo),
|
||||
start: time.Now(),
|
||||
graph: make(map[string]map[string]bool),
|
||||
}
|
||||
|
||||
// -- loading proper (concurrent phase) --------------------------------
|
||||
|
||||
var errpkgs []string // packages that contained errors
|
||||
|
||||
// Load the initially imported packages and their dependencies,
|
||||
// in parallel.
|
||||
for _, ii := range imp.loadAll("", conf.ImportPkgs) {
|
||||
if ii.err != nil {
|
||||
conf.TypeChecker.Error(ii.err) // failed to create package
|
||||
errpkgs = append(errpkgs, ii.path)
|
||||
continue
|
||||
}
|
||||
prog.Imported[ii.info.Pkg.Path()] = ii.info
|
||||
}
|
||||
|
||||
// Augment the designated initial packages by their tests.
|
||||
// Dependencies are loaded in parallel.
|
||||
var xtestPkgs []*build.Package
|
||||
for path, augment := range conf.ImportPkgs {
|
||||
if !augment {
|
||||
continue
|
||||
}
|
||||
|
||||
bp, err := conf.FindPackage(conf.build(), path)
|
||||
if err != nil {
|
||||
// Package not found, or can't even parse package declaration.
|
||||
// Already reported by previous loop; ignore it.
|
||||
continue
|
||||
}
|
||||
|
||||
// Needs external test package?
|
||||
if len(bp.XTestGoFiles) > 0 {
|
||||
xtestPkgs = append(xtestPkgs, bp)
|
||||
}
|
||||
|
||||
imp.importedMu.Lock() // (unnecessary, we're sequential here)
|
||||
ii, ok := imp.imported[path]
|
||||
// Paranoid checks added due to issue #11012.
|
||||
if !ok {
|
||||
// Unreachable.
|
||||
// The previous loop called loadAll and thus
|
||||
// startLoad for each path in ImportPkgs, which
|
||||
// populates imp.imported[path] with a non-zero value.
|
||||
panic(fmt.Sprintf("imported[%q] not found", path))
|
||||
}
|
||||
if ii == nil {
|
||||
// Unreachable.
|
||||
// The ii values in this loop are the same as in
|
||||
// the previous loop, which enforced the invariant
|
||||
// that at least one of ii.err and ii.info is non-nil.
|
||||
panic(fmt.Sprintf("imported[%q] == nil", path))
|
||||
}
|
||||
if ii.err != nil {
|
||||
// The sole possible cause is failure of the
|
||||
// FindPackage call in (*importer).load,
|
||||
// but we rechecked that condition above.
|
||||
// Perhaps the state of the file system changed
|
||||
// in between? Seems unlikely.
|
||||
panic(fmt.Sprintf("imported[%q].err = %v", path, ii.err))
|
||||
}
|
||||
if ii.info == nil {
|
||||
// Unreachable.
|
||||
// Complete has this postcondition:
|
||||
// ii.err != nil || ii.info != nil
|
||||
// and we know that ii.err == nil here.
|
||||
panic(fmt.Sprintf("imported[%q].info = nil", path))
|
||||
}
|
||||
info := ii.info
|
||||
imp.importedMu.Unlock()
|
||||
|
||||
// Parse the in-package test files.
|
||||
files, errs := imp.conf.parsePackageFiles(bp, 't')
|
||||
for _, err := range errs {
|
||||
info.appendError(err)
|
||||
}
|
||||
|
||||
// The test files augmenting package P cannot be imported,
|
||||
// but may import packages that import P,
|
||||
// so we must disable the cycle check.
|
||||
imp.addFiles(info, files, false)
|
||||
}
|
||||
|
||||
createPkg := func(path string, files []*ast.File, errs []error) {
|
||||
info := imp.newPackageInfo(path)
|
||||
for _, err := range errs {
|
||||
info.appendError(err)
|
||||
}
|
||||
|
||||
// Ad hoc packages are non-importable,
|
||||
// so no cycle check is needed.
|
||||
// addFiles loads dependencies in parallel.
|
||||
imp.addFiles(info, files, false)
|
||||
prog.Created = append(prog.Created, info)
|
||||
}
|
||||
|
||||
// Create packages specified by conf.CreatePkgs.
|
||||
for _, cp := range conf.CreatePkgs {
|
||||
files, errs := parseFiles(conf.fset(), conf.build(), nil, ".", cp.Filenames, conf.ParserMode)
|
||||
files = append(files, cp.Files...)
|
||||
|
||||
path := cp.Path
|
||||
if path == "" {
|
||||
if len(files) > 0 {
|
||||
path = files[0].Name.Name
|
||||
} else {
|
||||
path = "(unnamed)"
|
||||
}
|
||||
}
|
||||
createPkg(path, files, errs)
|
||||
}
|
||||
|
||||
// Create external test packages.
|
||||
sort.Sort(byImportPath(xtestPkgs))
|
||||
for _, bp := range xtestPkgs {
|
||||
files, errs := imp.conf.parsePackageFiles(bp, 'x')
|
||||
createPkg(bp.ImportPath+"_test", files, errs)
|
||||
}
|
||||
|
||||
// -- finishing up (sequential) ----------------------------------------
|
||||
|
||||
if len(prog.Imported)+len(prog.Created) == 0 {
|
||||
return nil, errors.New("no initial packages were loaded")
|
||||
}
|
||||
|
||||
// Create infos for indirectly imported packages.
|
||||
// e.g. incomplete packages without syntax, loaded from export data.
|
||||
for _, obj := range prog.importMap {
|
||||
info := prog.AllPackages[obj]
|
||||
if info == nil {
|
||||
prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true}
|
||||
} else {
|
||||
// finished
|
||||
info.checker = nil
|
||||
info.errorFunc = nil
|
||||
}
|
||||
}
|
||||
|
||||
if !conf.AllowErrors {
|
||||
// Report errors in indirectly imported packages.
|
||||
for _, info := range prog.AllPackages {
|
||||
if len(info.Errors) > 0 {
|
||||
errpkgs = append(errpkgs, info.Pkg.Path())
|
||||
}
|
||||
}
|
||||
if errpkgs != nil {
|
||||
var more string
|
||||
if len(errpkgs) > 3 {
|
||||
more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
|
||||
errpkgs = errpkgs[:3]
|
||||
}
|
||||
return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
|
||||
strings.Join(errpkgs, ", "), more)
|
||||
}
|
||||
}
|
||||
|
||||
markErrorFreePackages(prog.AllPackages)
|
||||
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
type byImportPath []*build.Package
|
||||
|
||||
func (b byImportPath) Len() int { return len(b) }
|
||||
func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath }
|
||||
func (b byImportPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
|
||||
// markErrorFreePackages sets the TransitivelyErrorFree flag on all
|
||||
// applicable packages.
|
||||
func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) {
|
||||
// Build the transpose of the import graph.
|
||||
importedBy := make(map[*types.Package]map[*types.Package]bool)
|
||||
for P := range allPackages {
|
||||
for _, Q := range P.Imports() {
|
||||
clients, ok := importedBy[Q]
|
||||
if !ok {
|
||||
clients = make(map[*types.Package]bool)
|
||||
importedBy[Q] = clients
|
||||
}
|
||||
clients[P] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Find all packages reachable from some error package.
|
||||
reachable := make(map[*types.Package]bool)
|
||||
var visit func(*types.Package)
|
||||
visit = func(p *types.Package) {
|
||||
if !reachable[p] {
|
||||
reachable[p] = true
|
||||
for q := range importedBy[p] {
|
||||
visit(q)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, info := range allPackages {
|
||||
if len(info.Errors) > 0 {
|
||||
visit(info.Pkg)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the others as "transitively error-free".
|
||||
for _, info := range allPackages {
|
||||
if !reachable[info.Pkg] {
|
||||
info.TransitivelyErrorFree = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build returns the effective build context.
|
||||
func (conf *Config) build() *build.Context {
|
||||
if conf.Build != nil {
|
||||
return conf.Build
|
||||
}
|
||||
return &build.Default
|
||||
}
|
||||
|
||||
// parsePackageFiles enumerates the files belonging to package path,
|
||||
// then loads, parses and returns them, plus a list of I/O or parse
|
||||
// errors that were encountered.
|
||||
//
|
||||
// 'which' indicates which files to include:
|
||||
// 'g': include non-test *.go source files (GoFiles + processed CgoFiles)
|
||||
// 't': include in-package *_test.go source files (TestGoFiles)
|
||||
// 'x': include external *_test.go source files. (XTestGoFiles)
|
||||
//
|
||||
func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) {
|
||||
var filenames []string
|
||||
switch which {
|
||||
case 'g':
|
||||
filenames = bp.GoFiles
|
||||
case 't':
|
||||
filenames = bp.TestGoFiles
|
||||
case 'x':
|
||||
filenames = bp.XTestGoFiles
|
||||
default:
|
||||
panic(which)
|
||||
}
|
||||
|
||||
files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode)
|
||||
|
||||
// Preprocess CgoFiles and parse the outputs (sequentially).
|
||||
if which == 'g' && bp.CgoFiles != nil {
|
||||
cgofiles, err := processCgoFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
files = append(files, cgofiles...)
|
||||
}
|
||||
}
|
||||
|
||||
return files, errs
|
||||
}
|
||||
|
||||
// doImport imports the package denoted by path.
|
||||
// It implements the types.Importer signature.
|
||||
//
|
||||
// imports is the type-checker's package canonicalization map.
|
||||
//
|
||||
// It returns an error if a package could not be created
|
||||
// (e.g. go/build or parse error), but type errors are reported via
|
||||
// the types.Config.Error callback (the first of which is also saved
|
||||
// in the package's PackageInfo).
|
||||
//
|
||||
// Idempotent.
|
||||
//
|
||||
func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) {
|
||||
// Package unsafe is handled specially, and has no PackageInfo.
|
||||
// TODO(adonovan): move this check into go/types?
|
||||
if to == "unsafe" {
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
if to == "C" {
|
||||
// This should be unreachable, but ad hoc packages are
|
||||
// not currently subject to cgo preprocessing.
|
||||
// See https://github.com/golang/go/issues/11627.
|
||||
return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`,
|
||||
from.Pkg.Path())
|
||||
}
|
||||
|
||||
imp.importedMu.Lock()
|
||||
ii := imp.imported[to]
|
||||
imp.importedMu.Unlock()
|
||||
if ii == nil {
|
||||
panic("internal error: unexpected import: " + to)
|
||||
}
|
||||
if ii.err != nil {
|
||||
return nil, ii.err
|
||||
}
|
||||
if ii.info != nil {
|
||||
return ii.info.Pkg, nil
|
||||
}
|
||||
|
||||
// Import of incomplete package: this indicates a cycle.
|
||||
fromPath := from.Pkg.Path()
|
||||
if cycle := imp.findPath(to, fromPath); cycle != nil {
|
||||
cycle = append([]string{fromPath}, cycle...)
|
||||
return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> "))
|
||||
}
|
||||
|
||||
panic("internal error: import of incomplete (yet acyclic) package: " + fromPath)
|
||||
}
|
||||
|
||||
// loadAll loads, parses, and type-checks the specified packages in
|
||||
// parallel and returns their completed importInfos in unspecified order.
|
||||
//
|
||||
// fromPath is the import path of the importing package, if it is
|
||||
// importable, "" otherwise. It is used for cycle detection.
|
||||
//
|
||||
func (imp *importer) loadAll(fromPath string, paths map[string]bool) []*importInfo {
|
||||
result := make([]*importInfo, 0, len(paths))
|
||||
for path := range paths {
|
||||
result = append(result, imp.startLoad(path))
|
||||
}
|
||||
|
||||
if fromPath != "" {
|
||||
// We're loading a set of imports.
|
||||
//
|
||||
// We must record graph edges from the importing package
|
||||
// to its dependencies, and check for cycles.
|
||||
imp.graphMu.Lock()
|
||||
deps, ok := imp.graph[fromPath]
|
||||
if !ok {
|
||||
deps = make(map[string]bool)
|
||||
imp.graph[fromPath] = deps
|
||||
}
|
||||
for path := range paths {
|
||||
deps[path] = true
|
||||
}
|
||||
imp.graphMu.Unlock()
|
||||
}
|
||||
|
||||
for _, ii := range result {
|
||||
if fromPath != "" {
|
||||
if cycle := imp.findPath(ii.path, fromPath); cycle != nil {
|
||||
// Cycle-forming import: we must not await its
|
||||
// completion since it would deadlock.
|
||||
//
|
||||
// We don't record the error in ii since
|
||||
// the error is really associated with the
|
||||
// cycle-forming edge, not the package itself.
|
||||
// (Also it would complicate the
|
||||
// invariants of importPath completion.)
|
||||
if trace {
|
||||
fmt.Fprintln(os.Stderr, "import cycle: %q", cycle)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
ii.awaitCompletion()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// findPath returns an arbitrary path from 'from' to 'to' in the import
|
||||
// graph, or nil if there was none.
|
||||
func (imp *importer) findPath(from, to string) []string {
|
||||
imp.graphMu.Lock()
|
||||
defer imp.graphMu.Unlock()
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var search func(stack []string, importPath string) []string
|
||||
search = func(stack []string, importPath string) []string {
|
||||
if !seen[importPath] {
|
||||
seen[importPath] = true
|
||||
stack = append(stack, importPath)
|
||||
if importPath == to {
|
||||
return stack
|
||||
}
|
||||
for x := range imp.graph[importPath] {
|
||||
if p := search(stack, x); p != nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return search(make([]string, 0, 20), from)
|
||||
}
|
||||
|
||||
// startLoad initiates the loading, parsing and type-checking of the
|
||||
// specified package and its dependencies, if it has not already begun.
|
||||
//
|
||||
// It returns an importInfo, not necessarily in a completed state. The
|
||||
// caller must call awaitCompletion() before accessing its info and err
|
||||
// fields.
|
||||
//
|
||||
// startLoad is concurrency-safe and idempotent.
|
||||
//
|
||||
func (imp *importer) startLoad(path string) *importInfo {
|
||||
imp.importedMu.Lock()
|
||||
ii, ok := imp.imported[path]
|
||||
if !ok {
|
||||
ii = &importInfo{path: path}
|
||||
ii.complete.L = &ii.mu
|
||||
imp.imported[path] = ii
|
||||
go func() {
|
||||
ii.Complete(imp.load(path))
|
||||
}()
|
||||
}
|
||||
imp.importedMu.Unlock()
|
||||
|
||||
return ii
|
||||
}
|
||||
|
||||
// load implements package loading by parsing Go source files
|
||||
// located by go/build.
|
||||
//
|
||||
func (imp *importer) load(path string) (*PackageInfo, error) {
|
||||
bp, err := imp.conf.FindPackage(imp.conf.build(), path)
|
||||
if err != nil {
|
||||
return nil, err // package not found
|
||||
}
|
||||
info := imp.newPackageInfo(bp.ImportPath)
|
||||
info.Importable = true
|
||||
files, errs := imp.conf.parsePackageFiles(bp, 'g')
|
||||
for _, err := range errs {
|
||||
info.appendError(err)
|
||||
}
|
||||
|
||||
imp.addFiles(info, files, true)
|
||||
|
||||
imp.progMu.Lock()
|
||||
imp.prog.importMap[path] = info.Pkg
|
||||
imp.progMu.Unlock()
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// addFiles adds and type-checks the specified files to info, loading
|
||||
// their dependencies if needed. The order of files determines the
|
||||
// package initialization order. It may be called multiple times on the
|
||||
// same package. Errors are appended to the info.Errors field.
|
||||
//
|
||||
// cycleCheck determines whether the imports within files create
|
||||
// dependency edges that should be checked for potential cycles.
|
||||
//
|
||||
func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) {
|
||||
info.Files = append(info.Files, files...)
|
||||
|
||||
// Ensure the dependencies are loaded, in parallel.
|
||||
var fromPath string
|
||||
if cycleCheck {
|
||||
fromPath = info.Pkg.Path()
|
||||
}
|
||||
imp.loadAll(fromPath, scanImports(files))
|
||||
|
||||
if trace {
|
||||
fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n",
|
||||
time.Since(imp.start), info.Pkg.Path(), len(files))
|
||||
}
|
||||
|
||||
// Ignore the returned (first) error since we
|
||||
// already collect them all in the PackageInfo.
|
||||
info.checker.Files(files)
|
||||
|
||||
if trace {
|
||||
fmt.Fprintf(os.Stderr, "%s: stop %q\n",
|
||||
time.Since(imp.start), info.Pkg.Path())
|
||||
}
|
||||
}
|
||||
|
||||
func (imp *importer) newPackageInfo(path string) *PackageInfo {
|
||||
pkg := types.NewPackage(path, "")
|
||||
info := &PackageInfo{
|
||||
Pkg: pkg,
|
||||
Info: types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
},
|
||||
errorFunc: imp.conf.TypeChecker.Error,
|
||||
}
|
||||
|
||||
// Copy the types.Config so we can vary it across PackageInfos.
|
||||
tc := imp.conf.TypeChecker
|
||||
tc.IgnoreFuncBodies = false
|
||||
if f := imp.conf.TypeCheckFuncBodies; f != nil {
|
||||
tc.IgnoreFuncBodies = !f(path)
|
||||
}
|
||||
tc.Import = func(_ map[string]*types.Package, to string) (*types.Package, error) {
|
||||
return imp.doImport(info, to)
|
||||
}
|
||||
tc.Error = info.appendError // appendError wraps the user's Error function
|
||||
|
||||
info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info)
|
||||
imp.progMu.Lock()
|
||||
imp.prog.AllPackages[pkg] = info
|
||||
imp.progMu.Unlock()
|
||||
return info
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
// 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 loader
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
)
|
||||
|
||||
// We use a counting semaphore to limit
|
||||
// the number of parallel I/O calls per process.
|
||||
var sema = make(chan bool, 10)
|
||||
|
||||
// parseFiles parses the Go source files within directory dir and
|
||||
// returns the ASTs of the ones that could be at least partially parsed,
|
||||
// along with a list of I/O and parse errors encountered.
|
||||
//
|
||||
// I/O is done via ctxt, which may specify a virtual file system.
|
||||
// displayPath is used to transform the filenames attached to the ASTs.
|
||||
//
|
||||
func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) {
|
||||
if displayPath == nil {
|
||||
displayPath = func(path string) string { return path }
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
n := len(files)
|
||||
parsed := make([]*ast.File, n)
|
||||
errors := make([]error, n)
|
||||
for i, file := range files {
|
||||
if !buildutil.IsAbsPath(ctxt, file) {
|
||||
file = buildutil.JoinPath(ctxt, dir, file)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(i int, file string) {
|
||||
sema <- true // wait
|
||||
defer func() {
|
||||
wg.Done()
|
||||
<-sema // signal
|
||||
}()
|
||||
var rd io.ReadCloser
|
||||
var err error
|
||||
if ctxt.OpenFile != nil {
|
||||
rd, err = ctxt.OpenFile(file)
|
||||
} else {
|
||||
rd, err = os.Open(file)
|
||||
}
|
||||
if err != nil {
|
||||
errors[i] = err // open failed
|
||||
return
|
||||
}
|
||||
|
||||
// ParseFile may return both an AST and an error.
|
||||
parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode)
|
||||
rd.Close()
|
||||
}(i, file)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Eliminate nils, preserving order.
|
||||
var o int
|
||||
for _, f := range parsed {
|
||||
if f != nil {
|
||||
parsed[o] = f
|
||||
o++
|
||||
}
|
||||
}
|
||||
parsed = parsed[:o]
|
||||
|
||||
o = 0
|
||||
for _, err := range errors {
|
||||
if err != nil {
|
||||
errors[o] = err
|
||||
o++
|
||||
}
|
||||
}
|
||||
errors = errors[:o]
|
||||
|
||||
return parsed, errors
|
||||
}
|
||||
|
||||
// scanImports returns the set of all package import paths from all
|
||||
// import specs in the specified files.
|
||||
func scanImports(files []*ast.File) map[string]bool {
|
||||
imports := make(map[string]bool)
|
||||
for _, f := range files {
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.ImportSpec)
|
||||
|
||||
// NB: do not assume the program is well-formed!
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
continue // quietly ignore the error
|
||||
}
|
||||
if path == "C" || path == "unsafe" {
|
||||
continue // skip pseudo packages
|
||||
}
|
||||
imports[path] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports
|
||||
}
|
||||
|
||||
// ---------- Internal helpers ----------
|
||||
|
||||
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
|
||||
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
|
||||
p := int(pos)
|
||||
base := f.Base()
|
||||
return base <= p && p < base+f.Size()
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
-*- text -*-
|
||||
|
||||
Pointer analysis to-do list
|
||||
===========================
|
||||
|
||||
CONSTRAINT GENERATION:
|
||||
- support reflection:
|
||||
- a couple of operators are missing
|
||||
- reflect.Values may contain lvalues (CanAddr)
|
||||
- implement native intrinsics. These vary by platform.
|
||||
- add to pts(a.panic) a label representing all runtime panics, e.g.
|
||||
runtime.{TypeAssertionError,errorString,errorCString}.
|
||||
|
||||
OPTIMISATIONS
|
||||
- pre-solver:
|
||||
pointer equivalence: extend HVN to HRU
|
||||
location equivalence
|
||||
- solver: HCD, LCD.
|
||||
- experiment with map+slice worklist in lieu of bitset.
|
||||
It may have faster insert.
|
||||
|
||||
MISC:
|
||||
- Test on all platforms.
|
||||
Currently we assume these go/build tags: linux, amd64, !cgo.
|
||||
|
||||
MAINTAINABILITY
|
||||
- Think about ways to make debugging this code easier. PTA logs
|
||||
routinely exceed a million lines and require training to read.
|
||||
|
||||
BUGS:
|
||||
- There's a crash bug in stdlib_test + reflection, rVCallConstraint.
|
||||
|
||||
|
|
@ -0,0 +1,447 @@
|
|||
// 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 pointer
|
||||
|
||||
// This file defines the main datatypes and Analyze function of the pointer analysis.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// optimization options; enable all when committing
|
||||
optRenumber = true // enable renumbering optimization (makes logs hard to read)
|
||||
optHVN = true // enable pointer equivalence via Hash-Value Numbering
|
||||
|
||||
// debugging options; disable all when committing
|
||||
debugHVN = false // enable assertions in HVN
|
||||
debugHVNVerbose = false // enable extra HVN logging
|
||||
debugHVNCrossCheck = false // run solver with/without HVN and compare (caveats below)
|
||||
debugTimers = false // show running time of each phase
|
||||
)
|
||||
|
||||
// object.flags bitmask values.
|
||||
const (
|
||||
otTagged = 1 << iota // type-tagged object
|
||||
otIndirect // type-tagged object with indirect payload
|
||||
otFunction // function object
|
||||
)
|
||||
|
||||
// An object represents a contiguous block of memory to which some
|
||||
// (generalized) pointer may point.
|
||||
//
|
||||
// (Note: most variables called 'obj' are not *objects but nodeids
|
||||
// such that a.nodes[obj].obj != nil.)
|
||||
//
|
||||
type object struct {
|
||||
// flags is a bitset of the node type (ot*) flags defined above.
|
||||
flags uint32
|
||||
|
||||
// Number of following nodes belonging to the same "object"
|
||||
// allocation. Zero for all other nodes.
|
||||
size uint32
|
||||
|
||||
// data describes this object; it has one of these types:
|
||||
//
|
||||
// ssa.Value for an object allocated by an SSA operation.
|
||||
// types.Type for an rtype instance object or *rtype-tagged object.
|
||||
// string for an instrinsic object, e.g. the array behind os.Args.
|
||||
// nil for an object allocated by an instrinsic.
|
||||
// (cgn provides the identity of the intrinsic.)
|
||||
data interface{}
|
||||
|
||||
// The call-graph node (=context) in which this object was allocated.
|
||||
// May be nil for global objects: Global, Const, some Functions.
|
||||
cgn *cgnode
|
||||
}
|
||||
|
||||
// nodeid denotes a node.
|
||||
// It is an index within analysis.nodes.
|
||||
// We use small integers, not *node pointers, for many reasons:
|
||||
// - they are smaller on 64-bit systems.
|
||||
// - sets of them can be represented compactly in bitvectors or BDDs.
|
||||
// - order matters; a field offset can be computed by simple addition.
|
||||
type nodeid uint32
|
||||
|
||||
// A node is an equivalence class of memory locations.
|
||||
// Nodes may be pointers, pointed-to locations, neither, or both.
|
||||
//
|
||||
// Nodes that are pointed-to locations ("labels") have an enclosing
|
||||
// object (see analysis.enclosingObject).
|
||||
//
|
||||
type node struct {
|
||||
// If non-nil, this node is the start of an object
|
||||
// (addressable memory location).
|
||||
// The following obj.size nodes implicitly belong to the object;
|
||||
// they locate their object by scanning back.
|
||||
obj *object
|
||||
|
||||
// The type of the field denoted by this node. Non-aggregate,
|
||||
// unless this is an tagged.T node (i.e. the thing
|
||||
// pointed to by an interface) in which case typ is that type.
|
||||
typ types.Type
|
||||
|
||||
// subelement indicates which directly embedded subelement of
|
||||
// an object of aggregate type (struct, tuple, array) this is.
|
||||
subelement *fieldInfo // e.g. ".a.b[*].c"
|
||||
|
||||
// Solver state for the canonical node of this pointer-
|
||||
// equivalence class. Each node is created with its own state
|
||||
// but they become shared after HVN.
|
||||
solve *solverState
|
||||
}
|
||||
|
||||
// An analysis instance holds the state of a single pointer analysis problem.
|
||||
type analysis struct {
|
||||
config *Config // the client's control/observer interface
|
||||
prog *ssa.Program // the program being analyzed
|
||||
log io.Writer // log stream; nil to disable
|
||||
panicNode nodeid // sink for panic, source for recover
|
||||
nodes []*node // indexed by nodeid
|
||||
flattenMemo map[types.Type][]*fieldInfo // memoization of flatten()
|
||||
trackTypes map[types.Type]bool // memoization of shouldTrack()
|
||||
constraints []constraint // set of constraints
|
||||
cgnodes []*cgnode // all cgnodes
|
||||
genq []*cgnode // queue of functions to generate constraints for
|
||||
intrinsics map[*ssa.Function]intrinsic // non-nil values are summaries for intrinsic fns
|
||||
globalval map[ssa.Value]nodeid // node for each global ssa.Value
|
||||
globalobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
|
||||
localval map[ssa.Value]nodeid // node for each local ssa.Value
|
||||
localobj map[ssa.Value]nodeid // maps v to sole member of pts(v), if singleton
|
||||
atFuncs map[*ssa.Function]bool // address-taken functions (for presolver)
|
||||
mapValues []nodeid // values of makemap objects (indirect in HVN)
|
||||
work nodeset // solver's worklist
|
||||
result *Result // results of the analysis
|
||||
track track // pointerlike types whose aliasing we track
|
||||
deltaSpace []int // working space for iterating over PTS deltas
|
||||
|
||||
// Reflection & intrinsics:
|
||||
hasher typeutil.Hasher // cache of type hashes
|
||||
reflectValueObj types.Object // type symbol for reflect.Value (if present)
|
||||
reflectValueCall *ssa.Function // (reflect.Value).Call
|
||||
reflectRtypeObj types.Object // *types.TypeName for reflect.rtype (if present)
|
||||
reflectRtypePtr *types.Pointer // *reflect.rtype
|
||||
reflectType *types.Named // reflect.Type
|
||||
rtypes typeutil.Map // nodeid of canonical *rtype-tagged object for type T
|
||||
reflectZeros typeutil.Map // nodeid of canonical T-tagged object for zero value
|
||||
runtimeSetFinalizer *ssa.Function // runtime.SetFinalizer
|
||||
}
|
||||
|
||||
// enclosingObj returns the first node of the addressable memory
|
||||
// object that encloses node id. Panic ensues if that node does not
|
||||
// belong to any object.
|
||||
func (a *analysis) enclosingObj(id nodeid) nodeid {
|
||||
// Find previous node with obj != nil.
|
||||
for i := id; i >= 0; i-- {
|
||||
n := a.nodes[i]
|
||||
if obj := n.obj; obj != nil {
|
||||
if i+nodeid(obj.size) <= id {
|
||||
break // out of bounds
|
||||
}
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic("node has no enclosing object")
|
||||
}
|
||||
|
||||
// labelFor returns the Label for node id.
|
||||
// Panic ensues if that node is not addressable.
|
||||
func (a *analysis) labelFor(id nodeid) *Label {
|
||||
return &Label{
|
||||
obj: a.nodes[a.enclosingObj(id)].obj,
|
||||
subelement: a.nodes[id].subelement,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "%s: warning: %s\n", a.prog.Fset.Position(pos), msg)
|
||||
}
|
||||
a.result.Warnings = append(a.result.Warnings, Warning{pos, msg})
|
||||
}
|
||||
|
||||
// computeTrackBits sets a.track to the necessary 'track' bits for the pointer queries.
|
||||
func (a *analysis) computeTrackBits() {
|
||||
var queryTypes []types.Type
|
||||
for v := range a.config.Queries {
|
||||
queryTypes = append(queryTypes, v.Type())
|
||||
}
|
||||
for v := range a.config.IndirectQueries {
|
||||
queryTypes = append(queryTypes, mustDeref(v.Type()))
|
||||
}
|
||||
for _, t := range queryTypes {
|
||||
switch t.Underlying().(type) {
|
||||
case *types.Chan:
|
||||
a.track |= trackChan
|
||||
case *types.Map:
|
||||
a.track |= trackMap
|
||||
case *types.Pointer:
|
||||
a.track |= trackPtr
|
||||
case *types.Slice:
|
||||
a.track |= trackSlice
|
||||
case *types.Interface:
|
||||
a.track = trackAll
|
||||
return
|
||||
}
|
||||
if rVObj := a.reflectValueObj; rVObj != nil && types.Identical(t, rVObj.Type()) {
|
||||
a.track = trackAll
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze runs the pointer analysis with the scope and options
|
||||
// specified by config, and returns the (synthetic) root of the callgraph.
|
||||
//
|
||||
// Pointer analysis of a transitively closed well-typed program should
|
||||
// always succeed. An error can occur only due to an internal bug.
|
||||
//
|
||||
func Analyze(config *Config) (result *Result, err error) {
|
||||
if config.Mains == nil {
|
||||
return nil, fmt.Errorf("no main/test packages to analyze (check $GOROOT/$GOPATH)")
|
||||
}
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
err = fmt.Errorf("internal error in pointer analysis: %v (please report this bug)", p)
|
||||
fmt.Fprintln(os.Stderr, "Internal panic in pointer analysis:")
|
||||
debug.PrintStack()
|
||||
}
|
||||
}()
|
||||
|
||||
a := &analysis{
|
||||
config: config,
|
||||
log: config.Log,
|
||||
prog: config.prog(),
|
||||
globalval: make(map[ssa.Value]nodeid),
|
||||
globalobj: make(map[ssa.Value]nodeid),
|
||||
flattenMemo: make(map[types.Type][]*fieldInfo),
|
||||
trackTypes: make(map[types.Type]bool),
|
||||
atFuncs: make(map[*ssa.Function]bool),
|
||||
hasher: typeutil.MakeHasher(),
|
||||
intrinsics: make(map[*ssa.Function]intrinsic),
|
||||
result: &Result{
|
||||
Queries: make(map[ssa.Value]Pointer),
|
||||
IndirectQueries: make(map[ssa.Value]Pointer),
|
||||
},
|
||||
deltaSpace: make([]int, 0, 100),
|
||||
}
|
||||
|
||||
if false {
|
||||
a.log = os.Stderr // for debugging crashes; extremely verbose
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintln(a.log, "==== Starting analysis")
|
||||
}
|
||||
|
||||
// Pointer analysis requires a complete program for soundness.
|
||||
// Check to prevent accidental misconfiguration.
|
||||
for _, pkg := range a.prog.AllPackages() {
|
||||
// (This only checks that the package scope is complete,
|
||||
// not that func bodies exist, but it's a good signal.)
|
||||
if !pkg.Object.Complete() {
|
||||
return nil, fmt.Errorf(`pointer analysis requires a complete program yet package %q was incomplete`, pkg.Object.Path())
|
||||
}
|
||||
}
|
||||
|
||||
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
|
||||
rV := reflect.Object.Scope().Lookup("Value")
|
||||
a.reflectValueObj = rV
|
||||
a.reflectValueCall = a.prog.LookupMethod(rV.Type(), nil, "Call")
|
||||
a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named)
|
||||
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
|
||||
a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type())
|
||||
|
||||
// Override flattening of reflect.Value, treating it like a basic type.
|
||||
tReflectValue := a.reflectValueObj.Type()
|
||||
a.flattenMemo[tReflectValue] = []*fieldInfo{{typ: tReflectValue}}
|
||||
|
||||
// Override shouldTrack of reflect.Value and *reflect.rtype.
|
||||
// Always track pointers of these types.
|
||||
a.trackTypes[tReflectValue] = true
|
||||
a.trackTypes[a.reflectRtypePtr] = true
|
||||
|
||||
a.rtypes.SetHasher(a.hasher)
|
||||
a.reflectZeros.SetHasher(a.hasher)
|
||||
}
|
||||
if runtime := a.prog.ImportedPackage("runtime"); runtime != nil {
|
||||
a.runtimeSetFinalizer = runtime.Func("SetFinalizer")
|
||||
}
|
||||
a.computeTrackBits()
|
||||
|
||||
a.generate()
|
||||
a.showCounts()
|
||||
|
||||
if optRenumber {
|
||||
a.renumber()
|
||||
}
|
||||
|
||||
N := len(a.nodes) // excludes solver-created nodes
|
||||
|
||||
if optHVN {
|
||||
if debugHVNCrossCheck {
|
||||
// Cross-check: run the solver once without
|
||||
// optimization, once with, and compare the
|
||||
// solutions.
|
||||
savedConstraints := a.constraints
|
||||
|
||||
a.solve()
|
||||
a.dumpSolution("A.pts", N)
|
||||
|
||||
// Restore.
|
||||
a.constraints = savedConstraints
|
||||
for _, n := range a.nodes {
|
||||
n.solve = new(solverState)
|
||||
}
|
||||
a.nodes = a.nodes[:N]
|
||||
|
||||
// rtypes is effectively part of the solver state.
|
||||
a.rtypes = typeutil.Map{}
|
||||
a.rtypes.SetHasher(a.hasher)
|
||||
}
|
||||
|
||||
a.hvn()
|
||||
}
|
||||
|
||||
if debugHVNCrossCheck {
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
a.solve()
|
||||
|
||||
// Compare solutions.
|
||||
if optHVN && debugHVNCrossCheck {
|
||||
a.dumpSolution("B.pts", N)
|
||||
|
||||
if !diff("A.pts", "B.pts") {
|
||||
return nil, fmt.Errorf("internal error: optimization changed solution")
|
||||
}
|
||||
}
|
||||
|
||||
// Create callgraph.Nodes in deterministic order.
|
||||
if cg := a.result.CallGraph; cg != nil {
|
||||
for _, caller := range a.cgnodes {
|
||||
cg.CreateNode(caller.fn)
|
||||
}
|
||||
}
|
||||
|
||||
// Add dynamic edges to call graph.
|
||||
var space [100]int
|
||||
for _, caller := range a.cgnodes {
|
||||
for _, site := range caller.sites {
|
||||
for _, callee := range a.nodes[site.targets].solve.pts.AppendTo(space[:0]) {
|
||||
a.callEdge(caller, site, nodeid(callee))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return a.result, nil
|
||||
}
|
||||
|
||||
// callEdge is called for each edge in the callgraph.
|
||||
// calleeid is the callee's object node (has otFunction flag).
|
||||
//
|
||||
func (a *analysis) callEdge(caller *cgnode, site *callsite, calleeid nodeid) {
|
||||
obj := a.nodes[calleeid].obj
|
||||
if obj.flags&otFunction == 0 {
|
||||
panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid))
|
||||
}
|
||||
callee := obj.cgn
|
||||
|
||||
if cg := a.result.CallGraph; cg != nil {
|
||||
// TODO(adonovan): opt: I would expect duplicate edges
|
||||
// (to wrappers) to arise due to the elimination of
|
||||
// context information, but I haven't observed any.
|
||||
// Understand this better.
|
||||
callgraph.AddEdge(cg.CreateNode(caller.fn), site.instr, cg.CreateNode(callee.fn))
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\tcall edge %s -> %s\n", site, callee)
|
||||
}
|
||||
|
||||
// Warn about calls to non-intrinsic external functions.
|
||||
// TODO(adonovan): de-dup these messages.
|
||||
if fn := callee.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil {
|
||||
a.warnf(site.pos(), "unsound call to unknown intrinsic: %s", fn)
|
||||
a.warnf(fn.Pos(), " (declared here)")
|
||||
}
|
||||
}
|
||||
|
||||
// dumpSolution writes the PTS solution to the specified file.
|
||||
//
|
||||
// It only dumps the nodes that existed before solving. The order in
|
||||
// which solver-created nodes are created depends on pre-solver
|
||||
// optimization, so we can't include them in the cross-check.
|
||||
//
|
||||
func (a *analysis) dumpSolution(filename string, N int) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for id, n := range a.nodes[:N] {
|
||||
if _, err := fmt.Fprintf(f, "pts(n%d) = {", id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var sep string
|
||||
for _, l := range n.solve.pts.AppendTo(a.deltaSpace) {
|
||||
if l >= N {
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(f, "%s%d", sep, l)
|
||||
sep = " "
|
||||
}
|
||||
fmt.Fprintf(f, "} : %s\n", n.typ)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// showCounts logs the size of the constraint system. A typical
|
||||
// optimized distribution is 65% copy, 13% load, 11% addr, 5%
|
||||
// offsetAddr, 4% store, 2% others.
|
||||
//
|
||||
func (a *analysis) showCounts() {
|
||||
if a.log != nil {
|
||||
counts := make(map[reflect.Type]int)
|
||||
for _, c := range a.constraints {
|
||||
counts[reflect.TypeOf(c)]++
|
||||
}
|
||||
fmt.Fprintf(a.log, "# constraints:\t%d\n", len(a.constraints))
|
||||
var lines []string
|
||||
for t, n := range counts {
|
||||
line := fmt.Sprintf("%7d (%2d%%)\t%s", n, 100*n/len(a.constraints), t)
|
||||
lines = append(lines, line)
|
||||
}
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(lines)))
|
||||
for _, line := range lines {
|
||||
fmt.Fprintf(a.log, "\t%s\n", line)
|
||||
}
|
||||
|
||||
fmt.Fprintf(a.log, "# nodes:\t%d\n", len(a.nodes))
|
||||
|
||||
// Show number of pointer equivalence classes.
|
||||
m := make(map[*solverState]bool)
|
||||
for _, n := range a.nodes {
|
||||
m[n.solve] = true
|
||||
}
|
||||
fmt.Fprintf(a.log, "# ptsets:\t%d\n", len(m))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
// 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 pointer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
|
||||
"golang.org/x/tools/container/intsets"
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// A Config formulates a pointer analysis problem for Analyze().
|
||||
type Config struct {
|
||||
// Mains contains the set of 'main' packages to analyze
|
||||
// Clients must provide the analysis with at least one
|
||||
// package defining a main() function.
|
||||
//
|
||||
// Non-main packages in the ssa.Program that are not
|
||||
// dependencies of any main package may still affect the
|
||||
// analysis result, because they contribute runtime types and
|
||||
// thus methods.
|
||||
// TODO(adonovan): investigate whether this is desirable.
|
||||
Mains []*ssa.Package
|
||||
|
||||
// Reflection determines whether to handle reflection
|
||||
// operators soundly, which is currently rather slow since it
|
||||
// causes constraint to be generated during solving
|
||||
// proportional to the number of constraint variables, which
|
||||
// has not yet been reduced by presolver optimisation.
|
||||
Reflection bool
|
||||
|
||||
// BuildCallGraph determines whether to construct a callgraph.
|
||||
// If enabled, the graph will be available in Result.CallGraph.
|
||||
BuildCallGraph bool
|
||||
|
||||
// The client populates Queries[v] or IndirectQueries[v]
|
||||
// for each ssa.Value v of interest, to request that the
|
||||
// points-to sets pts(v) or pts(*v) be computed. If the
|
||||
// client needs both points-to sets, v may appear in both
|
||||
// maps.
|
||||
//
|
||||
// (IndirectQueries is typically used for Values corresponding
|
||||
// to source-level lvalues, e.g. an *ssa.Global.)
|
||||
//
|
||||
// The analysis populates the corresponding
|
||||
// Result.{Indirect,}Queries map when it creates the pointer
|
||||
// variable for v or *v. Upon completion the client can
|
||||
// inspect that map for the results.
|
||||
//
|
||||
// TODO(adonovan): this API doesn't scale well for batch tools
|
||||
// that want to dump the entire solution. Perhaps optionally
|
||||
// populate a map[*ssa.DebugRef]Pointer in the Result, one
|
||||
// entry per source expression.
|
||||
//
|
||||
Queries map[ssa.Value]struct{}
|
||||
IndirectQueries map[ssa.Value]struct{}
|
||||
|
||||
// If Log is non-nil, log messages are written to it.
|
||||
// Logging is extremely verbose.
|
||||
Log io.Writer
|
||||
}
|
||||
|
||||
type track uint32
|
||||
|
||||
const (
|
||||
trackChan track = 1 << iota // track 'chan' references
|
||||
trackMap // track 'map' references
|
||||
trackPtr // track regular pointers
|
||||
trackSlice // track slice references
|
||||
|
||||
trackAll = ^track(0)
|
||||
)
|
||||
|
||||
// AddQuery adds v to Config.Queries.
|
||||
// Precondition: CanPoint(v.Type()).
|
||||
// TODO(adonovan): consider returning a new Pointer for this query,
|
||||
// which will be initialized during analysis. That avoids the needs
|
||||
// for the corresponding ssa.Value-keyed maps in Config and Result.
|
||||
func (c *Config) AddQuery(v ssa.Value) {
|
||||
if !CanPoint(v.Type()) {
|
||||
panic(fmt.Sprintf("%s is not a pointer-like value: %s", v, v.Type()))
|
||||
}
|
||||
if c.Queries == nil {
|
||||
c.Queries = make(map[ssa.Value]struct{})
|
||||
}
|
||||
c.Queries[v] = struct{}{}
|
||||
}
|
||||
|
||||
// AddQuery adds v to Config.IndirectQueries.
|
||||
// Precondition: CanPoint(v.Type().Underlying().(*types.Pointer).Elem()).
|
||||
func (c *Config) AddIndirectQuery(v ssa.Value) {
|
||||
if c.IndirectQueries == nil {
|
||||
c.IndirectQueries = make(map[ssa.Value]struct{})
|
||||
}
|
||||
if !CanPoint(mustDeref(v.Type())) {
|
||||
panic(fmt.Sprintf("%s is not the address of a pointer-like value: %s", v, v.Type()))
|
||||
}
|
||||
c.IndirectQueries[v] = struct{}{}
|
||||
}
|
||||
|
||||
func (c *Config) prog() *ssa.Program {
|
||||
for _, main := range c.Mains {
|
||||
return main.Prog
|
||||
}
|
||||
panic("empty scope")
|
||||
}
|
||||
|
||||
type Warning struct {
|
||||
Pos token.Pos
|
||||
Message string
|
||||
}
|
||||
|
||||
// A Result contains the results of a pointer analysis.
|
||||
//
|
||||
// See Config for how to request the various Result components.
|
||||
//
|
||||
type Result struct {
|
||||
CallGraph *callgraph.Graph // discovered call graph
|
||||
Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries.
|
||||
IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
||||
Warnings []Warning // warnings of unsoundness
|
||||
}
|
||||
|
||||
// A Pointer is an equivalence class of pointer-like values.
|
||||
//
|
||||
// A Pointer doesn't have a unique type because pointers of distinct
|
||||
// types may alias the same object.
|
||||
//
|
||||
type Pointer struct {
|
||||
a *analysis
|
||||
n nodeid
|
||||
}
|
||||
|
||||
// A PointsToSet is a set of labels (locations or allocations).
|
||||
type PointsToSet struct {
|
||||
a *analysis // may be nil if pts is nil
|
||||
pts *nodeset
|
||||
}
|
||||
|
||||
func (s PointsToSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('[')
|
||||
if s.pts != nil {
|
||||
var space [50]int
|
||||
for i, l := range s.pts.AppendTo(space[:0]) {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(s.a.labelFor(nodeid(l)).String())
|
||||
}
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PointsTo returns the set of labels that this points-to set
|
||||
// contains.
|
||||
func (s PointsToSet) Labels() []*Label {
|
||||
var labels []*Label
|
||||
if s.pts != nil {
|
||||
var space [50]int
|
||||
for _, l := range s.pts.AppendTo(space[:0]) {
|
||||
labels = append(labels, s.a.labelFor(nodeid(l)))
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
// If this PointsToSet came from a Pointer of interface kind
|
||||
// or a reflect.Value, DynamicTypes returns the set of dynamic
|
||||
// types that it may contain. (For an interface, they will
|
||||
// always be concrete types.)
|
||||
//
|
||||
// The result is a mapping whose keys are the dynamic types to which
|
||||
// it may point. For each pointer-like key type, the corresponding
|
||||
// map value is the PointsToSet for pointers of that type.
|
||||
//
|
||||
// The result is empty unless CanHaveDynamicTypes(T).
|
||||
//
|
||||
func (s PointsToSet) DynamicTypes() *typeutil.Map {
|
||||
var tmap typeutil.Map
|
||||
tmap.SetHasher(s.a.hasher)
|
||||
if s.pts != nil {
|
||||
var space [50]int
|
||||
for _, x := range s.pts.AppendTo(space[:0]) {
|
||||
ifaceObjId := nodeid(x)
|
||||
if !s.a.isTaggedObject(ifaceObjId) {
|
||||
continue // !CanHaveDynamicTypes(tDyn)
|
||||
}
|
||||
tDyn, v, indirect := s.a.taggedValue(ifaceObjId)
|
||||
if indirect {
|
||||
panic("indirect tagged object") // implement later
|
||||
}
|
||||
pts, ok := tmap.At(tDyn).(PointsToSet)
|
||||
if !ok {
|
||||
pts = PointsToSet{s.a, new(nodeset)}
|
||||
tmap.Set(tDyn, pts)
|
||||
}
|
||||
pts.pts.addAll(&s.a.nodes[v].solve.pts)
|
||||
}
|
||||
}
|
||||
return &tmap
|
||||
}
|
||||
|
||||
// Intersects reports whether this points-to set and the
|
||||
// argument points-to set contain common members.
|
||||
func (x PointsToSet) Intersects(y PointsToSet) bool {
|
||||
if x.pts == nil || y.pts == nil {
|
||||
return false
|
||||
}
|
||||
// This takes Θ(|x|+|y|) time.
|
||||
var z intsets.Sparse
|
||||
z.Intersection(&x.pts.Sparse, &y.pts.Sparse)
|
||||
return !z.IsEmpty()
|
||||
}
|
||||
|
||||
func (p Pointer) String() string {
|
||||
return fmt.Sprintf("n%d", p.n)
|
||||
}
|
||||
|
||||
// PointsTo returns the points-to set of this pointer.
|
||||
func (p Pointer) PointsTo() PointsToSet {
|
||||
if p.n == 0 {
|
||||
return PointsToSet{}
|
||||
}
|
||||
return PointsToSet{p.a, &p.a.nodes[p.n].solve.pts}
|
||||
}
|
||||
|
||||
// MayAlias reports whether the receiver pointer may alias
|
||||
// the argument pointer.
|
||||
func (p Pointer) MayAlias(q Pointer) bool {
|
||||
return p.PointsTo().Intersects(q.PointsTo())
|
||||
}
|
||||
|
||||
// DynamicTypes returns p.PointsTo().DynamicTypes().
|
||||
func (p Pointer) DynamicTypes() *typeutil.Map {
|
||||
return p.PointsTo().DynamicTypes()
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// 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 pointer
|
||||
|
||||
// This file defines the internal (context-sensitive) call graph.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
type cgnode struct {
|
||||
fn *ssa.Function
|
||||
obj nodeid // start of this contour's object block
|
||||
sites []*callsite // ordered list of callsites within this function
|
||||
callersite *callsite // where called from, if known; nil for shared contours
|
||||
}
|
||||
|
||||
// contour returns a description of this node's contour.
|
||||
func (n *cgnode) contour() string {
|
||||
if n.callersite == nil {
|
||||
return "shared contour"
|
||||
}
|
||||
if n.callersite.instr != nil {
|
||||
return fmt.Sprintf("as called from %s", n.callersite.instr.Parent())
|
||||
}
|
||||
return fmt.Sprintf("as called from intrinsic (targets=n%d)", n.callersite.targets)
|
||||
}
|
||||
|
||||
func (n *cgnode) String() string {
|
||||
return fmt.Sprintf("cg%d:%s", n.obj, n.fn)
|
||||
}
|
||||
|
||||
// A callsite represents a single call site within a cgnode;
|
||||
// it is implicitly context-sensitive.
|
||||
// callsites never represent calls to built-ins;
|
||||
// they are handled as intrinsics.
|
||||
//
|
||||
type callsite struct {
|
||||
targets nodeid // pts(·) contains objects for dynamically called functions
|
||||
instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic
|
||||
}
|
||||
|
||||
func (c *callsite) String() string {
|
||||
if c.instr != nil {
|
||||
return c.instr.Common().Description()
|
||||
}
|
||||
return "synthetic function call"
|
||||
}
|
||||
|
||||
// pos returns the source position of this callsite, or token.NoPos if implicit.
|
||||
func (c *callsite) pos() token.Pos {
|
||||
if c.instr != nil {
|
||||
return c.instr.Pos()
|
||||
}
|
||||
return token.NoPos
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
// 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 pointer
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type constraint interface {
|
||||
// For a complex constraint, returns the nodeid of the pointer
|
||||
// to which it is attached. For addr and copy, returns dst.
|
||||
ptr() nodeid
|
||||
|
||||
// renumber replaces each nodeid n in the constraint by mapping[n].
|
||||
renumber(mapping []nodeid)
|
||||
|
||||
// presolve is a hook for constraint-specific behaviour during
|
||||
// pre-solver optimization. Typical implementations mark as
|
||||
// indirect the set of nodes to which the solver will add copy
|
||||
// edges or PTS labels.
|
||||
presolve(h *hvn)
|
||||
|
||||
// solve is called for complex constraints when the pts for
|
||||
// the node to which they are attached has changed.
|
||||
solve(a *analysis, delta *nodeset)
|
||||
|
||||
String() string
|
||||
}
|
||||
|
||||
// dst = &src
|
||||
// pts(dst) ⊇ {src}
|
||||
// A base constraint used to initialize the solver's pt sets
|
||||
type addrConstraint struct {
|
||||
dst nodeid // (ptr)
|
||||
src nodeid
|
||||
}
|
||||
|
||||
func (c *addrConstraint) ptr() nodeid { return c.dst }
|
||||
func (c *addrConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src
|
||||
// A simple constraint represented directly as a copyTo graph edge.
|
||||
type copyConstraint struct {
|
||||
dst nodeid // (ptr)
|
||||
src nodeid
|
||||
}
|
||||
|
||||
func (c *copyConstraint) ptr() nodeid { return c.dst }
|
||||
func (c *copyConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src[offset]
|
||||
// A complex constraint attached to src (the pointer)
|
||||
type loadConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *loadConstraint) ptr() nodeid { return c.src }
|
||||
func (c *loadConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst[offset] = src
|
||||
// A complex constraint attached to dst (the pointer)
|
||||
type storeConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid // (ptr)
|
||||
src nodeid
|
||||
}
|
||||
|
||||
func (c *storeConstraint) ptr() nodeid { return c.dst }
|
||||
func (c *storeConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = &src.f or dst = &src[0]
|
||||
// A complex constraint attached to dst (the pointer)
|
||||
type offsetAddrConstraint struct {
|
||||
offset uint32
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *offsetAddrConstraint) ptr() nodeid { return c.src }
|
||||
func (c *offsetAddrConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is an interface
|
||||
// A complex constraint attached to src (the interface).
|
||||
// No representation change: pts(dst) and pts(src) contains tagged objects.
|
||||
type typeFilterConstraint struct {
|
||||
typ types.Type // an interface type
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *typeFilterConstraint) ptr() nodeid { return c.src }
|
||||
func (c *typeFilterConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is a concrete type
|
||||
// A complex constraint attached to src (the interface).
|
||||
//
|
||||
// If exact, only tagged objects identical to typ are untagged.
|
||||
// If !exact, tagged objects assignable to typ are untagged too.
|
||||
// The latter is needed for various reflect operators, e.g. Send.
|
||||
//
|
||||
// This entails a representation change:
|
||||
// pts(src) contains tagged objects,
|
||||
// pts(dst) contains their payloads.
|
||||
type untagConstraint struct {
|
||||
typ types.Type // a concrete type
|
||||
dst nodeid
|
||||
src nodeid // (ptr)
|
||||
exact bool
|
||||
}
|
||||
|
||||
func (c *untagConstraint) ptr() nodeid { return c.src }
|
||||
func (c *untagConstraint) renumber(mapping []nodeid) {
|
||||
c.dst = mapping[c.dst]
|
||||
c.src = mapping[c.src]
|
||||
}
|
||||
|
||||
// src.method(params...)
|
||||
// A complex constraint attached to iface.
|
||||
type invokeConstraint struct {
|
||||
method *types.Func // the abstract method
|
||||
iface nodeid // (ptr) the interface
|
||||
params nodeid // the start of the identity/params/results block
|
||||
}
|
||||
|
||||
func (c *invokeConstraint) ptr() nodeid { return c.iface }
|
||||
func (c *invokeConstraint) renumber(mapping []nodeid) {
|
||||
c.iface = mapping[c.iface]
|
||||
c.params = mapping[c.params]
|
||||
}
|
|
@ -0,0 +1,610 @@
|
|||
// 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 pointer implements Andersen's analysis, an inclusion-based
|
||||
pointer analysis algorithm first described in (Andersen, 1994).
|
||||
|
||||
A pointer analysis relates every pointer expression in a whole program
|
||||
to the set of memory locations to which it might point. This
|
||||
information can be used to construct a call graph of the program that
|
||||
precisely represents the destinations of dynamic function and method
|
||||
calls. It can also be used to determine, for example, which pairs of
|
||||
channel operations operate on the same channel.
|
||||
|
||||
The package allows the client to request a set of expressions of
|
||||
interest for which the points-to information will be returned once the
|
||||
analysis is complete. In addition, the client may request that a
|
||||
callgraph is constructed. The example program in example_test.go
|
||||
demonstrates both of these features. Clients should not request more
|
||||
information than they need since it may increase the cost of the
|
||||
analysis significantly.
|
||||
|
||||
|
||||
CLASSIFICATION
|
||||
|
||||
Our algorithm is INCLUSION-BASED: the points-to sets for x and y will
|
||||
be related by pts(y) ⊇ pts(x) if the program contains the statement
|
||||
y = x.
|
||||
|
||||
It is FLOW-INSENSITIVE: it ignores all control flow constructs and the
|
||||
order of statements in a program. It is therefore a "MAY ALIAS"
|
||||
analysis: its facts are of the form "P may/may not point to L",
|
||||
not "P must point to L".
|
||||
|
||||
It is FIELD-SENSITIVE: it builds separate points-to sets for distinct
|
||||
fields, such as x and y in struct { x, y *int }.
|
||||
|
||||
It is mostly CONTEXT-INSENSITIVE: most functions are analyzed once,
|
||||
so values can flow in at one call to the function and return out at
|
||||
another. Only some smaller functions are analyzed with consideration
|
||||
of their calling context.
|
||||
|
||||
It has a CONTEXT-SENSITIVE HEAP: objects are named by both allocation
|
||||
site and context, so the objects returned by two distinct calls to f:
|
||||
func f() *T { return new(T) }
|
||||
are distinguished up to the limits of the calling context.
|
||||
|
||||
It is a WHOLE PROGRAM analysis: it requires SSA-form IR for the
|
||||
complete Go program and summaries for native code.
|
||||
|
||||
See the (Hind, PASTE'01) survey paper for an explanation of these terms.
|
||||
|
||||
|
||||
SOUNDNESS
|
||||
|
||||
The analysis is fully sound when invoked on pure Go programs that do not
|
||||
use reflection or unsafe.Pointer conversions. In other words, if there
|
||||
is any possible execution of the program in which pointer P may point to
|
||||
object O, the analysis will report that fact.
|
||||
|
||||
|
||||
REFLECTION
|
||||
|
||||
By default, the "reflect" library is ignored by the analysis, as if all
|
||||
its functions were no-ops, but if the client enables the Reflection flag,
|
||||
the analysis will make a reasonable attempt to model the effects of
|
||||
calls into this library. However, this comes at a significant
|
||||
performance cost, and not all features of that library are yet
|
||||
implemented. In addition, some simplifying approximations must be made
|
||||
to ensure that the analysis terminates; for example, reflection can be
|
||||
used to construct an infinite set of types and values of those types,
|
||||
but the analysis arbitrarily bounds the depth of such types.
|
||||
|
||||
Most but not all reflection operations are supported.
|
||||
In particular, addressable reflect.Values are not yet implemented, so
|
||||
operations such as (reflect.Value).Set have no analytic effect.
|
||||
|
||||
|
||||
UNSAFE POINTER CONVERSIONS
|
||||
|
||||
The pointer analysis makes no attempt to understand aliasing between the
|
||||
operand x and result y of an unsafe.Pointer conversion:
|
||||
y = (*T)(unsafe.Pointer(x))
|
||||
It is as if the conversion allocated an entirely new object:
|
||||
y = new(T)
|
||||
|
||||
|
||||
NATIVE CODE
|
||||
|
||||
The analysis cannot model the aliasing effects of functions written in
|
||||
languages other than Go, such as runtime intrinsics in C or assembly, or
|
||||
code accessed via cgo. The result is as if such functions are no-ops.
|
||||
However, various important intrinsics are understood by the analysis,
|
||||
along with built-ins such as append.
|
||||
|
||||
The analysis currently provides no way for users to specify the aliasing
|
||||
effects of native code.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
IMPLEMENTATION
|
||||
|
||||
The remaining documentation is intended for package maintainers and
|
||||
pointer analysis specialists. Maintainers should have a solid
|
||||
understanding of the referenced papers (especially those by H&L and PKH)
|
||||
before making making significant changes.
|
||||
|
||||
The implementation is similar to that described in (Pearce et al,
|
||||
PASTE'04). Unlike many algorithms which interleave constraint
|
||||
generation and solving, constructing the callgraph as they go, this
|
||||
implementation for the most part observes a phase ordering (generation
|
||||
before solving), with only simple (copy) constraints being generated
|
||||
during solving. (The exception is reflection, which creates various
|
||||
constraints during solving as new types flow to reflect.Value
|
||||
operations.) This improves the traction of presolver optimisations,
|
||||
but imposes certain restrictions, e.g. potential context sensitivity
|
||||
is limited since all variants must be created a priori.
|
||||
|
||||
|
||||
TERMINOLOGY
|
||||
|
||||
A type is said to be "pointer-like" if it is a reference to an object.
|
||||
Pointer-like types include pointers and also interfaces, maps, channels,
|
||||
functions and slices.
|
||||
|
||||
We occasionally use C's x->f notation to distinguish the case where x
|
||||
is a struct pointer from x.f where is a struct value.
|
||||
|
||||
Pointer analysis literature (and our comments) often uses the notation
|
||||
dst=*src+offset to mean something different than what it means in Go.
|
||||
It means: for each node index p in pts(src), the node index p+offset is
|
||||
in pts(dst). Similarly *dst+offset=src is used for store constraints
|
||||
and dst=src+offset for offset-address constraints.
|
||||
|
||||
|
||||
NODES
|
||||
|
||||
Nodes are the key datastructure of the analysis, and have a dual role:
|
||||
they represent both constraint variables (equivalence classes of
|
||||
pointers) and members of points-to sets (things that can be pointed
|
||||
at, i.e. "labels").
|
||||
|
||||
Nodes are naturally numbered. The numbering enables compact
|
||||
representations of sets of nodes such as bitvectors (or BDDs); and the
|
||||
ordering enables a very cheap way to group related nodes together. For
|
||||
example, passing n parameters consists of generating n parallel
|
||||
constraints from caller+i to callee+i for 0<=i<n.
|
||||
|
||||
The zero nodeid means "not a pointer". For simplicity, we generate flow
|
||||
constraints even for non-pointer types such as int. The pointer
|
||||
equivalence (PE) presolver optimization detects which variables cannot
|
||||
point to anything; this includes not only all variables of non-pointer
|
||||
types (such as int) but also variables of pointer-like types if they are
|
||||
always nil, or are parameters to a function that is never called.
|
||||
|
||||
Each node represents a scalar part of a value or object.
|
||||
Aggregate types (structs, tuples, arrays) are recursively flattened
|
||||
out into a sequential list of scalar component types, and all the
|
||||
elements of an array are represented by a single node. (The
|
||||
flattening of a basic type is a list containing a single node.)
|
||||
|
||||
Nodes are connected into a graph with various kinds of labelled edges:
|
||||
simple edges (or copy constraints) represent value flow. Complex
|
||||
edges (load, store, etc) trigger the creation of new simple edges
|
||||
during the solving phase.
|
||||
|
||||
|
||||
OBJECTS
|
||||
|
||||
Conceptually, an "object" is a contiguous sequence of nodes denoting
|
||||
an addressable location: something that a pointer can point to. The
|
||||
first node of an object has a non-nil obj field containing information
|
||||
about the allocation: its size, context, and ssa.Value.
|
||||
|
||||
Objects include:
|
||||
- functions and globals;
|
||||
- variable allocations in the stack frame or heap;
|
||||
- maps, channels and slices created by calls to make();
|
||||
- allocations to construct an interface;
|
||||
- allocations caused by conversions, e.g. []byte(str).
|
||||
- arrays allocated by calls to append();
|
||||
|
||||
Many objects have no Go types. For example, the func, map and chan type
|
||||
kinds in Go are all varieties of pointers, but their respective objects
|
||||
are actual functions (executable code), maps (hash tables), and channels
|
||||
(synchronized queues). Given the way we model interfaces, they too are
|
||||
pointers to "tagged" objects with no Go type. And an *ssa.Global denotes
|
||||
the address of a global variable, but the object for a Global is the
|
||||
actual data. So, the types of an ssa.Value that creates an object is
|
||||
"off by one indirection": a pointer to the object.
|
||||
|
||||
The individual nodes of an object are sometimes referred to as "labels".
|
||||
|
||||
For uniformity, all objects have a non-zero number of fields, even those
|
||||
of the empty type struct{}. (All arrays are treated as if of length 1,
|
||||
so there are no empty arrays. The empty tuple is never address-taken,
|
||||
so is never an object.)
|
||||
|
||||
|
||||
TAGGED OBJECTS
|
||||
|
||||
An tagged object has the following layout:
|
||||
|
||||
T -- obj.flags ⊇ {otTagged}
|
||||
v
|
||||
...
|
||||
|
||||
The T node's typ field is the dynamic type of the "payload": the value
|
||||
v which follows, flattened out. The T node's obj has the otTagged
|
||||
flag.
|
||||
|
||||
Tagged objects are needed when generalizing across types: interfaces,
|
||||
reflect.Values, reflect.Types. Each of these three types is modelled
|
||||
as a pointer that exclusively points to tagged objects.
|
||||
|
||||
Tagged objects may be indirect (obj.flags ⊇ {otIndirect}) meaning that
|
||||
the value v is not of type T but *T; this is used only for
|
||||
reflect.Values that represent lvalues. (These are not implemented yet.)
|
||||
|
||||
|
||||
ANALYSIS ABSTRACTION OF EACH TYPE
|
||||
|
||||
Variables of the following "scalar" types may be represented by a
|
||||
single node: basic types, pointers, channels, maps, slices, 'func'
|
||||
pointers, interfaces.
|
||||
|
||||
Pointers
|
||||
Nothing to say here, oddly.
|
||||
|
||||
Basic types (bool, string, numbers, unsafe.Pointer)
|
||||
Currently all fields in the flattening of a type, including
|
||||
non-pointer basic types such as int, are represented in objects and
|
||||
values. Though non-pointer nodes within values are uninteresting,
|
||||
non-pointer nodes in objects may be useful (if address-taken)
|
||||
because they permit the analysis to deduce, in this example,
|
||||
|
||||
var s struct{ ...; x int; ... }
|
||||
p := &s.x
|
||||
|
||||
that p points to s.x. If we ignored such object fields, we could only
|
||||
say that p points somewhere within s.
|
||||
|
||||
All other basic types are ignored. Expressions of these types have
|
||||
zero nodeid, and fields of these types within aggregate other types
|
||||
are omitted.
|
||||
|
||||
unsafe.Pointers are not modelled as pointers, so a conversion of an
|
||||
unsafe.Pointer to *T is (unsoundly) treated equivalent to new(T).
|
||||
|
||||
Channels
|
||||
An expression of type 'chan T' is a kind of pointer that points
|
||||
exclusively to channel objects, i.e. objects created by MakeChan (or
|
||||
reflection).
|
||||
|
||||
'chan T' is treated like *T.
|
||||
*ssa.MakeChan is treated as equivalent to new(T).
|
||||
*ssa.Send and receive (*ssa.UnOp(ARROW)) and are equivalent to store
|
||||
and load.
|
||||
|
||||
Maps
|
||||
An expression of type 'map[K]V' is a kind of pointer that points
|
||||
exclusively to map objects, i.e. objects created by MakeMap (or
|
||||
reflection).
|
||||
|
||||
map K[V] is treated like *M where M = struct{k K; v V}.
|
||||
*ssa.MakeMap is equivalent to new(M).
|
||||
*ssa.MapUpdate is equivalent to *y=x where *y and x have type M.
|
||||
*ssa.Lookup is equivalent to y=x.v where x has type *M.
|
||||
|
||||
Slices
|
||||
A slice []T, which dynamically resembles a struct{array *T, len, cap int},
|
||||
is treated as if it were just a *T pointer; the len and cap fields are
|
||||
ignored.
|
||||
|
||||
*ssa.MakeSlice is treated like new([1]T): an allocation of a
|
||||
singleton array.
|
||||
*ssa.Index on a slice is equivalent to a load.
|
||||
*ssa.IndexAddr on a slice returns the address of the sole element of the
|
||||
slice, i.e. the same address.
|
||||
*ssa.Slice is treated as a simple copy.
|
||||
|
||||
Functions
|
||||
An expression of type 'func...' is a kind of pointer that points
|
||||
exclusively to function objects.
|
||||
|
||||
A function object has the following layout:
|
||||
|
||||
identity -- typ:*types.Signature; obj.flags ⊇ {otFunction}
|
||||
params_0 -- (the receiver, if a method)
|
||||
...
|
||||
params_n-1
|
||||
results_0
|
||||
...
|
||||
results_m-1
|
||||
|
||||
There may be multiple function objects for the same *ssa.Function
|
||||
due to context-sensitive treatment of some functions.
|
||||
|
||||
The first node is the function's identity node.
|
||||
Associated with every callsite is a special "targets" variable,
|
||||
whose pts() contains the identity node of each function to which
|
||||
the call may dispatch. Identity words are not otherwise used during
|
||||
the analysis, but we construct the call graph from the pts()
|
||||
solution for such nodes.
|
||||
|
||||
The following block of contiguous nodes represents the flattened-out
|
||||
types of the parameters ("P-block") and results ("R-block") of the
|
||||
function object.
|
||||
|
||||
The treatment of free variables of closures (*ssa.FreeVar) is like
|
||||
that of global variables; it is not context-sensitive.
|
||||
*ssa.MakeClosure instructions create copy edges to Captures.
|
||||
|
||||
A Go value of type 'func' (i.e. a pointer to one or more functions)
|
||||
is a pointer whose pts() contains function objects. The valueNode()
|
||||
for an *ssa.Function returns a singleton for that function.
|
||||
|
||||
Interfaces
|
||||
An expression of type 'interface{...}' is a kind of pointer that
|
||||
points exclusively to tagged objects. All tagged objects pointed to
|
||||
by an interface are direct (the otIndirect flag is clear) and
|
||||
concrete (the tag type T is not itself an interface type). The
|
||||
associated ssa.Value for an interface's tagged objects may be an
|
||||
*ssa.MakeInterface instruction, or nil if the tagged object was
|
||||
created by an instrinsic (e.g. reflection).
|
||||
|
||||
Constructing an interface value causes generation of constraints for
|
||||
all of the concrete type's methods; we can't tell a priori which
|
||||
ones may be called.
|
||||
|
||||
TypeAssert y = x.(T) is implemented by a dynamic constraint
|
||||
triggered by each tagged object O added to pts(x): a typeFilter
|
||||
constraint if T is an interface type, or an untag constraint if T is
|
||||
a concrete type. A typeFilter tests whether O.typ implements T; if
|
||||
so, O is added to pts(y). An untagFilter tests whether O.typ is
|
||||
assignable to T,and if so, a copy edge O.v -> y is added.
|
||||
|
||||
ChangeInterface is a simple copy because the representation of
|
||||
tagged objects is independent of the interface type (in contrast
|
||||
to the "method tables" approach used by the gc runtime).
|
||||
|
||||
y := Invoke x.m(...) is implemented by allocating contiguous P/R
|
||||
blocks for the callsite and adding a dynamic rule triggered by each
|
||||
tagged object added to pts(x). The rule adds param/results copy
|
||||
edges to/from each discovered concrete method.
|
||||
|
||||
(Q. Why do we model an interface as a pointer to a pair of type and
|
||||
value, rather than as a pair of a pointer to type and a pointer to
|
||||
value?
|
||||
A. Control-flow joins would merge interfaces ({T1}, {V1}) and ({T2},
|
||||
{V2}) to make ({T1,T2}, {V1,V2}), leading to the infeasible and
|
||||
type-unsafe combination (T1,V2). Treating the value and its concrete
|
||||
type as inseparable makes the analysis type-safe.)
|
||||
|
||||
reflect.Value
|
||||
A reflect.Value is modelled very similar to an interface{}, i.e. as
|
||||
a pointer exclusively to tagged objects, but with two generalizations.
|
||||
|
||||
1) a reflect.Value that represents an lvalue points to an indirect
|
||||
(obj.flags ⊇ {otIndirect}) tagged object, which has a similar
|
||||
layout to an tagged object except that the value is a pointer to
|
||||
the dynamic type. Indirect tagged objects preserve the correct
|
||||
aliasing so that mutations made by (reflect.Value).Set can be
|
||||
observed.
|
||||
|
||||
Indirect objects only arise when an lvalue is derived from an
|
||||
rvalue by indirection, e.g. the following code:
|
||||
|
||||
type S struct { X T }
|
||||
var s S
|
||||
var i interface{} = &s // i points to a *S-tagged object (from MakeInterface)
|
||||
v1 := reflect.ValueOf(i) // v1 points to same *S-tagged object as i
|
||||
v2 := v1.Elem() // v2 points to an indirect S-tagged object, pointing to s
|
||||
v3 := v2.FieldByName("X") // v3 points to an indirect int-tagged object, pointing to s.X
|
||||
v3.Set(y) // pts(s.X) ⊇ pts(y)
|
||||
|
||||
Whether indirect or not, the concrete type of the tagged object
|
||||
corresponds to the user-visible dynamic type, and the existence
|
||||
of a pointer is an implementation detail.
|
||||
|
||||
(NB: indirect tagged objects are not yet implemented)
|
||||
|
||||
2) The dynamic type tag of a tagged object pointed to by a
|
||||
reflect.Value may be an interface type; it need not be concrete.
|
||||
|
||||
This arises in code such as this:
|
||||
tEface := reflect.TypeOf(new(interface{}).Elem() // interface{}
|
||||
eface := reflect.Zero(tEface)
|
||||
pts(eface) is a singleton containing an interface{}-tagged
|
||||
object. That tagged object's payload is an interface{} value,
|
||||
i.e. the pts of the payload contains only concrete-tagged
|
||||
objects, although in this example it's the zero interface{} value,
|
||||
so its pts is empty.
|
||||
|
||||
reflect.Type
|
||||
Just as in the real "reflect" library, we represent a reflect.Type
|
||||
as an interface whose sole implementation is the concrete type,
|
||||
*reflect.rtype. (This choice is forced on us by go/types: clients
|
||||
cannot fabricate types with arbitrary method sets.)
|
||||
|
||||
rtype instances are canonical: there is at most one per dynamic
|
||||
type. (rtypes are in fact large structs but since identity is all
|
||||
that matters, we represent them by a single node.)
|
||||
|
||||
The payload of each *rtype-tagged object is an *rtype pointer that
|
||||
points to exactly one such canonical rtype object. We exploit this
|
||||
by setting the node.typ of the payload to the dynamic type, not
|
||||
'*rtype'. This saves us an indirection in each resolution rule. As
|
||||
an optimisation, *rtype-tagged objects are canonicalized too.
|
||||
|
||||
|
||||
Aggregate types:
|
||||
|
||||
Aggregate types are treated as if all directly contained
|
||||
aggregates are recursively flattened out.
|
||||
|
||||
Structs
|
||||
*ssa.Field y = x.f creates a simple edge to y from x's node at f's offset.
|
||||
|
||||
*ssa.FieldAddr y = &x->f requires a dynamic closure rule to create
|
||||
simple edges for each struct discovered in pts(x).
|
||||
|
||||
The nodes of a struct consist of a special 'identity' node (whose
|
||||
type is that of the struct itself), followed by the nodes for all
|
||||
the struct's fields, recursively flattened out. A pointer to the
|
||||
struct is a pointer to its identity node. That node allows us to
|
||||
distinguish a pointer to a struct from a pointer to its first field.
|
||||
|
||||
Field offsets are logical field offsets (plus one for the identity
|
||||
node), so the sizes of the fields can be ignored by the analysis.
|
||||
|
||||
(The identity node is non-traditional but enables the distiction
|
||||
described above, which is valuable for code comprehension tools.
|
||||
Typical pointer analyses for C, whose purpose is compiler
|
||||
optimization, must soundly model unsafe.Pointer (void*) conversions,
|
||||
and this requires fidelity to the actual memory layout using physical
|
||||
field offsets.)
|
||||
|
||||
*ssa.Field y = x.f creates a simple edge to y from x's node at f's offset.
|
||||
|
||||
*ssa.FieldAddr y = &x->f requires a dynamic closure rule to create
|
||||
simple edges for each struct discovered in pts(x).
|
||||
|
||||
Arrays
|
||||
We model an array by an identity node (whose type is that of the
|
||||
array itself) followed by a node representing all the elements of
|
||||
the array; the analysis does not distinguish elements with different
|
||||
indices. Effectively, an array is treated like struct{elem T}, a
|
||||
load y=x[i] like y=x.elem, and a store x[i]=y like x.elem=y; the
|
||||
index i is ignored.
|
||||
|
||||
A pointer to an array is pointer to its identity node. (A slice is
|
||||
also a pointer to an array's identity node.) The identity node
|
||||
allows us to distinguish a pointer to an array from a pointer to one
|
||||
of its elements, but it is rather costly because it introduces more
|
||||
offset constraints into the system. Furthermore, sound treatment of
|
||||
unsafe.Pointer would require us to dispense with this node.
|
||||
|
||||
Arrays may be allocated by Alloc, by make([]T), by calls to append,
|
||||
and via reflection.
|
||||
|
||||
Tuples (T, ...)
|
||||
Tuples are treated like structs with naturally numbered fields.
|
||||
*ssa.Extract is analogous to *ssa.Field.
|
||||
|
||||
However, tuples have no identity field since by construction, they
|
||||
cannot be address-taken.
|
||||
|
||||
|
||||
FUNCTION CALLS
|
||||
|
||||
There are three kinds of function call:
|
||||
(1) static "call"-mode calls of functions.
|
||||
(2) dynamic "call"-mode calls of functions.
|
||||
(3) dynamic "invoke"-mode calls of interface methods.
|
||||
Cases 1 and 2 apply equally to methods and standalone functions.
|
||||
|
||||
Static calls.
|
||||
A static call consists three steps:
|
||||
- finding the function object of the callee;
|
||||
- creating copy edges from the actual parameter value nodes to the
|
||||
P-block in the function object (this includes the receiver if
|
||||
the callee is a method);
|
||||
- creating copy edges from the R-block in the function object to
|
||||
the value nodes for the result of the call.
|
||||
|
||||
A static function call is little more than two struct value copies
|
||||
between the P/R blocks of caller and callee:
|
||||
|
||||
callee.P = caller.P
|
||||
caller.R = callee.R
|
||||
|
||||
Context sensitivity
|
||||
|
||||
Static calls (alone) may be treated context sensitively,
|
||||
i.e. each callsite may cause a distinct re-analysis of the
|
||||
callee, improving precision. Our current context-sensitivity
|
||||
policy treats all intrinsics and getter/setter methods in this
|
||||
manner since such functions are small and seem like an obvious
|
||||
source of spurious confluences, though this has not yet been
|
||||
evaluated.
|
||||
|
||||
Dynamic function calls
|
||||
|
||||
Dynamic calls work in a similar manner except that the creation of
|
||||
copy edges occurs dynamically, in a similar fashion to a pair of
|
||||
struct copies in which the callee is indirect:
|
||||
|
||||
callee->P = caller.P
|
||||
caller.R = callee->R
|
||||
|
||||
(Recall that the function object's P- and R-blocks are contiguous.)
|
||||
|
||||
Interface method invocation
|
||||
|
||||
For invoke-mode calls, we create a params/results block for the
|
||||
callsite and attach a dynamic closure rule to the interface. For
|
||||
each new tagged object that flows to the interface, we look up
|
||||
the concrete method, find its function object, and connect its P/R
|
||||
blocks to the callsite's P/R blocks, adding copy edges to the graph
|
||||
during solving.
|
||||
|
||||
Recording call targets
|
||||
|
||||
The analysis notifies its clients of each callsite it encounters,
|
||||
passing a CallSite interface. Among other things, the CallSite
|
||||
contains a synthetic constraint variable ("targets") whose
|
||||
points-to solution includes the set of all function objects to
|
||||
which the call may dispatch.
|
||||
|
||||
It is via this mechanism that the callgraph is made available.
|
||||
Clients may also elect to be notified of callgraph edges directly;
|
||||
internally this just iterates all "targets" variables' pts(·)s.
|
||||
|
||||
|
||||
PRESOLVER
|
||||
|
||||
We implement Hash-Value Numbering (HVN), a pre-solver constraint
|
||||
optimization described in Hardekopf & Lin, SAS'07. This is documented
|
||||
in more detail in hvn.go. We intend to add its cousins HR and HU in
|
||||
future.
|
||||
|
||||
|
||||
SOLVER
|
||||
|
||||
The solver is currently a naive Andersen-style implementation; it does
|
||||
not perform online cycle detection, though we plan to add solver
|
||||
optimisations such as Hybrid- and Lazy- Cycle Detection from (Hardekopf
|
||||
& Lin, PLDI'07).
|
||||
|
||||
It uses difference propagation (Pearce et al, SQC'04) to avoid
|
||||
redundant re-triggering of closure rules for values already seen.
|
||||
|
||||
Points-to sets are represented using sparse bit vectors (similar to
|
||||
those used in LLVM and gcc), which are more space- and time-efficient
|
||||
than sets based on Go's built-in map type or dense bit vectors.
|
||||
|
||||
Nodes are permuted prior to solving so that object nodes (which may
|
||||
appear in points-to sets) are lower numbered than non-object (var)
|
||||
nodes. This improves the density of the set over which the PTSs
|
||||
range, and thus the efficiency of the representation.
|
||||
|
||||
Partly thanks to avoiding map iteration, the execution of the solver is
|
||||
100% deterministic, a great help during debugging.
|
||||
|
||||
|
||||
FURTHER READING
|
||||
|
||||
Andersen, L. O. 1994. Program analysis and specialization for the C
|
||||
programming language. Ph.D. dissertation. DIKU, University of
|
||||
Copenhagen.
|
||||
|
||||
David J. Pearce, Paul H. J. Kelly, and Chris Hankin. 2004. Efficient
|
||||
field-sensitive pointer analysis for C. In Proceedings of the 5th ACM
|
||||
SIGPLAN-SIGSOFT workshop on Program analysis for software tools and
|
||||
engineering (PASTE '04). ACM, New York, NY, USA, 37-42.
|
||||
http://doi.acm.org/10.1145/996821.996835
|
||||
|
||||
David J. Pearce, Paul H. J. Kelly, and Chris Hankin. 2004. Online
|
||||
Cycle Detection and Difference Propagation: Applications to Pointer
|
||||
Analysis. Software Quality Control 12, 4 (December 2004), 311-337.
|
||||
http://dx.doi.org/10.1023/B:SQJO.0000039791.93071.a2
|
||||
|
||||
David Grove and Craig Chambers. 2001. A framework for call graph
|
||||
construction algorithms. ACM Trans. Program. Lang. Syst. 23, 6
|
||||
(November 2001), 685-746.
|
||||
http://doi.acm.org/10.1145/506315.506316
|
||||
|
||||
Ben Hardekopf and Calvin Lin. 2007. The ant and the grasshopper: fast
|
||||
and accurate pointer analysis for millions of lines of code. In
|
||||
Proceedings of the 2007 ACM SIGPLAN conference on Programming language
|
||||
design and implementation (PLDI '07). ACM, New York, NY, USA, 290-299.
|
||||
http://doi.acm.org/10.1145/1250734.1250767
|
||||
|
||||
Ben Hardekopf and Calvin Lin. 2007. Exploiting pointer and location
|
||||
equivalence to optimize pointer analysis. In Proceedings of the 14th
|
||||
international conference on Static Analysis (SAS'07), Hanne Riis
|
||||
Nielson and Gilberto Filé (Eds.). Springer-Verlag, Berlin, Heidelberg,
|
||||
265-280.
|
||||
|
||||
Atanas Rountev and Satish Chandra. 2000. Off-line variable substitution
|
||||
for scaling points-to analysis. In Proceedings of the ACM SIGPLAN 2000
|
||||
conference on Programming language design and implementation (PLDI '00).
|
||||
ACM, New York, NY, USA, 47-56. DOI=10.1145/349299.349310
|
||||
http://doi.acm.org/10.1145/349299.349310
|
||||
|
||||
*/
|
||||
package pointer // import "golang.org/x/tools/go/pointer"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,969 @@
|
|||
package pointer
|
||||
|
||||
// This file implements Hash-Value Numbering (HVN), a pre-solver
|
||||
// constraint optimization described in Hardekopf & Lin, SAS'07 (see
|
||||
// doc.go) that analyses the graph topology to determine which sets of
|
||||
// variables are "pointer equivalent" (PE), i.e. must have identical
|
||||
// points-to sets in the solution.
|
||||
//
|
||||
// A separate ("offline") graph is constructed. Its nodes are those of
|
||||
// the main-graph, plus an additional node *X for each pointer node X.
|
||||
// With this graph we can reason about the unknown points-to set of
|
||||
// dereferenced pointers. (We do not generalize this to represent
|
||||
// unknown fields x->f, perhaps because such fields would be numerous,
|
||||
// though it might be worth an experiment.)
|
||||
//
|
||||
// Nodes whose points-to relations are not entirely captured by the
|
||||
// graph are marked as "indirect": the *X nodes, the parameters of
|
||||
// address-taken functions (which includes all functions in method
|
||||
// sets), or nodes updated by the solver rules for reflection, etc.
|
||||
//
|
||||
// All addr (y=&x) nodes are initially assigned a pointer-equivalence
|
||||
// (PE) label equal to x's nodeid in the main graph. (These are the
|
||||
// only PE labels that are less than len(a.nodes).)
|
||||
//
|
||||
// All offsetAddr (y=&x.f) constraints are initially assigned a PE
|
||||
// label; such labels are memoized, keyed by (x, f), so that equivalent
|
||||
// nodes y as assigned the same label.
|
||||
//
|
||||
// Then we process each strongly connected component (SCC) of the graph
|
||||
// in topological order, assigning it a PE label based on the set P of
|
||||
// PE labels that flow to it from its immediate dependencies.
|
||||
//
|
||||
// If any node in P is "indirect", the entire SCC is assigned a fresh PE
|
||||
// label. Otherwise:
|
||||
//
|
||||
// |P|=0 if P is empty, all nodes in the SCC are non-pointers (e.g.
|
||||
// uninitialized variables, or formal params of dead functions)
|
||||
// and the SCC is assigned the PE label of zero.
|
||||
//
|
||||
// |P|=1 if P is a singleton, the SCC is assigned the same label as the
|
||||
// sole element of P.
|
||||
//
|
||||
// |P|>1 if P contains multiple labels, a unique label representing P is
|
||||
// invented and recorded in an hash table, so that other
|
||||
// equivalent SCCs may also be assigned this label, akin to
|
||||
// conventional hash-value numbering in a compiler.
|
||||
//
|
||||
// Finally, a renumbering is computed such that each node is replaced by
|
||||
// the lowest-numbered node with the same PE label. All constraints are
|
||||
// renumbered, and any resulting duplicates are eliminated.
|
||||
//
|
||||
// The only nodes that are not renumbered are the objects x in addr
|
||||
// (y=&x) constraints, since the ids of these nodes (and fields derived
|
||||
// from them via offsetAddr rules) are the elements of all points-to
|
||||
// sets, so they must remain as they are if we want the same solution.
|
||||
//
|
||||
// The solverStates (node.solve) for nodes in the same equivalence class
|
||||
// are linked together so that all nodes in the class have the same
|
||||
// solution. This avoids the need to renumber nodeids buried in
|
||||
// Queries, cgnodes, etc (like (*analysis).renumber() does) since only
|
||||
// the solution is needed.
|
||||
//
|
||||
// The result of HVN is that the number of distinct nodes and
|
||||
// constraints is reduced, but the solution is identical (almost---see
|
||||
// CROSS-CHECK below). In particular, both linear and cyclic chains of
|
||||
// copies are each replaced by a single node.
|
||||
//
|
||||
// Nodes and constraints created "online" (e.g. while solving reflection
|
||||
// constraints) are not subject to this optimization.
|
||||
//
|
||||
// PERFORMANCE
|
||||
//
|
||||
// In two benchmarks (oracle and godoc), HVN eliminates about two thirds
|
||||
// of nodes, the majority accounted for by non-pointers: nodes of
|
||||
// non-pointer type, pointers that remain nil, formal parameters of dead
|
||||
// functions, nodes of untracked types, etc. It also reduces the number
|
||||
// of constraints, also by about two thirds, and the solving time by
|
||||
// 30--42%, although we must pay about 15% for the running time of HVN
|
||||
// itself. The benefit is greater for larger applications.
|
||||
//
|
||||
// There are many possible optimizations to improve the performance:
|
||||
// * Use fewer than 1:1 onodes to main graph nodes: many of the onodes
|
||||
// we create are not needed.
|
||||
// * HU (HVN with Union---see paper): coalesce "union" peLabels when
|
||||
// their expanded-out sets are equal.
|
||||
// * HR (HVN with deReference---see paper): this will require that we
|
||||
// apply HVN until fixed point, which may need more bookkeeping of the
|
||||
// correspondance of main nodes to onodes.
|
||||
// * Location Equivalence (see paper): have points-to sets contain not
|
||||
// locations but location-equivalence class labels, each representing
|
||||
// a set of locations.
|
||||
// * HVN with field-sensitive ref: model each of the fields of a
|
||||
// pointer-to-struct.
|
||||
//
|
||||
// CROSS-CHECK
|
||||
//
|
||||
// To verify the soundness of the optimization, when the
|
||||
// debugHVNCrossCheck option is enabled, we run the solver twice, once
|
||||
// before and once after running HVN, dumping the solution to disk, and
|
||||
// then we compare the results. If they are not identical, the analysis
|
||||
// panics.
|
||||
//
|
||||
// The solution dumped to disk includes only the N*N submatrix of the
|
||||
// complete solution where N is the number of nodes after generation.
|
||||
// In other words, we ignore pointer variables and objects created by
|
||||
// the solver itself, since their numbering depends on the solver order,
|
||||
// which is affected by the optimization. In any case, that's the only
|
||||
// part the client cares about.
|
||||
//
|
||||
// The cross-check is too strict and may fail spuriously. Although the
|
||||
// H&L paper describing HVN states that the solutions obtained should be
|
||||
// identical, this is not the case in practice because HVN can collapse
|
||||
// cycles involving *p even when pts(p)={}. Consider this example
|
||||
// distilled from testdata/hello.go:
|
||||
//
|
||||
// var x T
|
||||
// func f(p **T) {
|
||||
// t0 = *p
|
||||
// ...
|
||||
// t1 = φ(t0, &x)
|
||||
// *p = t1
|
||||
// }
|
||||
//
|
||||
// If f is dead code, we get:
|
||||
// unoptimized: pts(p)={} pts(t0)={} pts(t1)={&x}
|
||||
// optimized: pts(p)={} pts(t0)=pts(t1)=pts(*p)={&x}
|
||||
//
|
||||
// It's hard to argue that this is a bug: the result is sound and the
|
||||
// loss of precision is inconsequential---f is dead code, after all.
|
||||
// But unfortunately it limits the usefulness of the cross-check since
|
||||
// failures must be carefully analyzed. Ben Hardekopf suggests (in
|
||||
// personal correspondence) some approaches to mitigating it:
|
||||
//
|
||||
// If there is a node with an HVN points-to set that is a superset
|
||||
// of the NORM points-to set, then either it's a bug or it's a
|
||||
// result of this issue. If it's a result of this issue, then in
|
||||
// the offline constraint graph there should be a REF node inside
|
||||
// some cycle that reaches this node, and in the NORM solution the
|
||||
// pointer being dereferenced by that REF node should be the empty
|
||||
// set. If that isn't true then this is a bug. If it is true, then
|
||||
// you can further check that in the NORM solution the "extra"
|
||||
// points-to info in the HVN solution does in fact come from that
|
||||
// purported cycle (if it doesn't, then this is still a bug). If
|
||||
// you're doing the further check then you'll need to do it for
|
||||
// each "extra" points-to element in the HVN points-to set.
|
||||
//
|
||||
// There are probably ways to optimize these checks by taking
|
||||
// advantage of graph properties. For example, extraneous points-to
|
||||
// info will flow through the graph and end up in many
|
||||
// nodes. Rather than checking every node with extra info, you
|
||||
// could probably work out the "origin point" of the extra info and
|
||||
// just check there. Note that the check in the first bullet is
|
||||
// looking for soundness bugs, while the check in the second bullet
|
||||
// is looking for precision bugs; depending on your needs, you may
|
||||
// care more about one than the other.
|
||||
//
|
||||
// which we should evaluate. The cross-check is nonetheless invaluable
|
||||
// for all but one of the programs in the pointer_test suite.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/container/intsets"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// A peLabel is a pointer-equivalence label: two nodes with the same
|
||||
// peLabel have identical points-to solutions.
|
||||
//
|
||||
// The numbers are allocated consecutively like so:
|
||||
// 0 not a pointer
|
||||
// 1..N-1 addrConstraints (equals the constraint's .src field, hence sparse)
|
||||
// ... offsetAddr constraints
|
||||
// ... SCCs (with indirect nodes or multiple inputs)
|
||||
//
|
||||
// Each PE label denotes a set of pointers containing a single addr, a
|
||||
// single offsetAddr, or some set of other PE labels.
|
||||
//
|
||||
type peLabel int
|
||||
|
||||
type hvn struct {
|
||||
a *analysis
|
||||
N int // len(a.nodes) immediately after constraint generation
|
||||
log io.Writer // (optional) log of HVN lemmas
|
||||
onodes []*onode // nodes of the offline graph
|
||||
label peLabel // the next available PE label
|
||||
hvnLabel map[string]peLabel // hash-value numbering (PE label) for each set of onodeids
|
||||
stack []onodeid // DFS stack
|
||||
index int32 // next onode.index, from Tarjan's SCC algorithm
|
||||
|
||||
// For each distinct offsetAddrConstraint (src, offset) pair,
|
||||
// offsetAddrLabels records a unique PE label >= N.
|
||||
offsetAddrLabels map[offsetAddr]peLabel
|
||||
}
|
||||
|
||||
// The index of an node in the offline graph.
|
||||
// (Currently the first N align with the main nodes,
|
||||
// but this may change with HRU.)
|
||||
type onodeid uint32
|
||||
|
||||
// An onode is a node in the offline constraint graph.
|
||||
// (Where ambiguous, members of analysis.nodes are referred to as
|
||||
// "main graph" nodes.)
|
||||
//
|
||||
// Edges in the offline constraint graph (edges and implicit) point to
|
||||
// the source, i.e. against the flow of values: they are dependencies.
|
||||
// Implicit edges are used for SCC computation, but not for gathering
|
||||
// incoming labels.
|
||||
//
|
||||
type onode struct {
|
||||
rep onodeid // index of representative of SCC in offline constraint graph
|
||||
|
||||
edges intsets.Sparse // constraint edges X-->Y (this onode is X)
|
||||
implicit intsets.Sparse // implicit edges *X-->*Y (this onode is X)
|
||||
peLabels intsets.Sparse // set of peLabels are pointer-equivalent to this one
|
||||
indirect bool // node has points-to relations not represented in graph
|
||||
|
||||
// Tarjan's SCC algorithm
|
||||
index, lowlink int32 // Tarjan numbering
|
||||
scc int32 // -ve => on stack; 0 => unvisited; +ve => node is root of a found SCC
|
||||
}
|
||||
|
||||
type offsetAddr struct {
|
||||
ptr nodeid
|
||||
offset uint32
|
||||
}
|
||||
|
||||
// nextLabel issues the next unused pointer-equivalence label.
|
||||
func (h *hvn) nextLabel() peLabel {
|
||||
h.label++
|
||||
return h.label
|
||||
}
|
||||
|
||||
// ref(X) returns the index of the onode for *X.
|
||||
func (h *hvn) ref(id onodeid) onodeid {
|
||||
return id + onodeid(len(h.a.nodes))
|
||||
}
|
||||
|
||||
// hvn computes pointer-equivalence labels (peLabels) using the Hash-based
|
||||
// Value Numbering (HVN) algorithm described in Hardekopf & Lin, SAS'07.
|
||||
//
|
||||
func (a *analysis) hvn() {
|
||||
start("HVN")
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\n\n==== Pointer equivalence optimization\n\n")
|
||||
}
|
||||
|
||||
h := hvn{
|
||||
a: a,
|
||||
N: len(a.nodes),
|
||||
log: a.log,
|
||||
hvnLabel: make(map[string]peLabel),
|
||||
offsetAddrLabels: make(map[offsetAddr]peLabel),
|
||||
}
|
||||
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nCreating offline graph nodes...\n")
|
||||
}
|
||||
|
||||
// Create offline nodes. The first N nodes correspond to main
|
||||
// graph nodes; the next N are their corresponding ref() nodes.
|
||||
h.onodes = make([]*onode, 2*h.N)
|
||||
for id := range a.nodes {
|
||||
id := onodeid(id)
|
||||
h.onodes[id] = &onode{}
|
||||
h.onodes[h.ref(id)] = &onode{indirect: true}
|
||||
}
|
||||
|
||||
// Each node initially represents just itself.
|
||||
for id, o := range h.onodes {
|
||||
o.rep = onodeid(id)
|
||||
}
|
||||
|
||||
h.markIndirectNodes()
|
||||
|
||||
// Reserve the first N PE labels for addrConstraints.
|
||||
h.label = peLabel(h.N)
|
||||
|
||||
// Add offline constraint edges.
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nAdding offline graph edges...\n")
|
||||
}
|
||||
for _, c := range a.constraints {
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "; %s\n", c)
|
||||
}
|
||||
c.presolve(&h)
|
||||
}
|
||||
|
||||
// Find and collapse SCCs.
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nFinding SCCs...\n")
|
||||
}
|
||||
h.index = 1
|
||||
for id, o := range h.onodes {
|
||||
if id > 0 && o.index == 0 {
|
||||
// Start depth-first search at each unvisited node.
|
||||
h.visit(onodeid(id))
|
||||
}
|
||||
}
|
||||
|
||||
// Dump the solution
|
||||
// (NB: somewhat redundant with logging from simplify().)
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\nPointer equivalences:\n")
|
||||
for id, o := range h.onodes {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if id == int(h.N) {
|
||||
fmt.Fprintf(h.log, "---\n")
|
||||
}
|
||||
fmt.Fprintf(h.log, "o%d\t", id)
|
||||
if o.rep != onodeid(id) {
|
||||
fmt.Fprintf(h.log, "rep=o%d", o.rep)
|
||||
} else {
|
||||
fmt.Fprintf(h.log, "p%d", o.peLabels.Min())
|
||||
if o.indirect {
|
||||
fmt.Fprint(h.log, " indirect")
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(h.log)
|
||||
}
|
||||
}
|
||||
|
||||
// Simplify the main constraint graph
|
||||
h.simplify()
|
||||
|
||||
a.showCounts()
|
||||
|
||||
stop("HVN")
|
||||
}
|
||||
|
||||
// ---- constraint-specific rules ----
|
||||
|
||||
// dst := &src
|
||||
func (c *addrConstraint) presolve(h *hvn) {
|
||||
// Each object (src) is an initial PE label.
|
||||
label := peLabel(c.src) // label < N
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
// duplicate log messages are possible
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: {&n%d}\n", label, c.src)
|
||||
}
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
|
||||
// Assign dst this label.
|
||||
h.onodes[odst].peLabels.Insert(int(label))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", odst, label)
|
||||
}
|
||||
|
||||
h.addImplicitEdge(h.ref(odst), osrc) // *dst ~~> src.
|
||||
}
|
||||
|
||||
// dst = src
|
||||
func (c *copyConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
h.addEdge(odst, osrc) // dst --> src
|
||||
h.addImplicitEdge(h.ref(odst), h.ref(osrc)) // *dst ~~> *src
|
||||
}
|
||||
|
||||
// dst = *src + offset
|
||||
func (c *loadConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
if c.offset == 0 {
|
||||
h.addEdge(odst, h.ref(osrc)) // dst --> *src
|
||||
} else {
|
||||
// We don't interpret load-with-offset, e.g. results
|
||||
// of map value lookup, R-block of dynamic call, slice
|
||||
// copy/append, reflection.
|
||||
h.markIndirect(odst, "load with offset")
|
||||
}
|
||||
}
|
||||
|
||||
// *dst + offset = src
|
||||
func (c *storeConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
osrc := onodeid(c.src)
|
||||
if c.offset == 0 {
|
||||
h.onodes[h.ref(odst)].edges.Insert(int(osrc)) // *dst --> src
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d --> o%d\n", h.ref(odst), osrc)
|
||||
}
|
||||
} else {
|
||||
// We don't interpret store-with-offset.
|
||||
// See discussion of soundness at markIndirectNodes.
|
||||
}
|
||||
}
|
||||
|
||||
// dst = &src.offset
|
||||
func (c *offsetAddrConstraint) presolve(h *hvn) {
|
||||
// Give each distinct (addr, offset) pair a fresh PE label.
|
||||
// The cache performs CSE, effectively.
|
||||
key := offsetAddr{c.src, c.offset}
|
||||
label, ok := h.offsetAddrLabels[key]
|
||||
if !ok {
|
||||
label = h.nextLabel()
|
||||
h.offsetAddrLabels[key] = label
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: {&n%d.#%d}\n",
|
||||
label, c.src, c.offset)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign dst this label.
|
||||
h.onodes[c.dst].peLabels.Insert(int(label))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", c.dst, label)
|
||||
}
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is an interface
|
||||
func (c *typeFilterConstraint) presolve(h *hvn) {
|
||||
h.markIndirect(onodeid(c.dst), "typeFilter result")
|
||||
}
|
||||
|
||||
// dst = src.(typ) where typ is concrete
|
||||
func (c *untagConstraint) presolve(h *hvn) {
|
||||
odst := onodeid(c.dst)
|
||||
for end := odst + onodeid(h.a.sizeof(c.typ)); odst < end; odst++ {
|
||||
h.markIndirect(odst, "untag result")
|
||||
}
|
||||
}
|
||||
|
||||
// dst = src.method(c.params...)
|
||||
func (c *invokeConstraint) presolve(h *hvn) {
|
||||
// All methods are address-taken functions, so
|
||||
// their formal P-blocks were already marked indirect.
|
||||
|
||||
// Mark the caller's targets node as indirect.
|
||||
sig := c.method.Type().(*types.Signature)
|
||||
id := c.params
|
||||
h.markIndirect(onodeid(c.params), "invoke targets node")
|
||||
id++
|
||||
|
||||
id += nodeid(h.a.sizeof(sig.Params()))
|
||||
|
||||
// Mark the caller's R-block as indirect.
|
||||
end := id + nodeid(h.a.sizeof(sig.Results()))
|
||||
for id < end {
|
||||
h.markIndirect(onodeid(id), "invoke R-block")
|
||||
id++
|
||||
}
|
||||
}
|
||||
|
||||
// markIndirectNodes marks as indirect nodes whose points-to relations
|
||||
// are not entirely captured by the offline graph, including:
|
||||
//
|
||||
// (a) All address-taken nodes (including the following nodes within
|
||||
// the same object). This is described in the paper.
|
||||
//
|
||||
// The most subtle cause of indirect nodes is the generation of
|
||||
// store-with-offset constraints since the offline graph doesn't
|
||||
// represent them. A global audit of constraint generation reveals the
|
||||
// following uses of store-with-offset:
|
||||
//
|
||||
// (b) genDynamicCall, for P-blocks of dynamically called functions,
|
||||
// to which dynamic copy edges will be added to them during
|
||||
// solving: from storeConstraint for standalone functions,
|
||||
// and from invokeConstraint for methods.
|
||||
// All such P-blocks must be marked indirect.
|
||||
// (c) MakeUpdate, to update the value part of a map object.
|
||||
// All MakeMap objects's value parts must be marked indirect.
|
||||
// (d) copyElems, to update the destination array.
|
||||
// All array elements must be marked indirect.
|
||||
//
|
||||
// Not all indirect marking happens here. ref() nodes are marked
|
||||
// indirect at construction, and each constraint's presolve() method may
|
||||
// mark additional nodes.
|
||||
//
|
||||
func (h *hvn) markIndirectNodes() {
|
||||
// (a) all address-taken nodes, plus all nodes following them
|
||||
// within the same object, since these may be indirectly
|
||||
// stored or address-taken.
|
||||
for _, c := range h.a.constraints {
|
||||
if c, ok := c.(*addrConstraint); ok {
|
||||
start := h.a.enclosingObj(c.src)
|
||||
end := start + nodeid(h.a.nodes[start].obj.size)
|
||||
for id := c.src; id < end; id++ {
|
||||
h.markIndirect(onodeid(id), "A-T object")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (b) P-blocks of all address-taken functions.
|
||||
for id := 0; id < h.N; id++ {
|
||||
obj := h.a.nodes[id].obj
|
||||
|
||||
// TODO(adonovan): opt: if obj.cgn.fn is a method and
|
||||
// obj.cgn is not its shared contour, this is an
|
||||
// "inlined" static method call. We needn't consider it
|
||||
// address-taken since no invokeConstraint will affect it.
|
||||
|
||||
if obj != nil && obj.flags&otFunction != 0 && h.a.atFuncs[obj.cgn.fn] {
|
||||
// address-taken function
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "n%d is address-taken: %s\n", id, obj.cgn.fn)
|
||||
}
|
||||
h.markIndirect(onodeid(id), "A-T func identity")
|
||||
id++
|
||||
sig := obj.cgn.fn.Signature
|
||||
psize := h.a.sizeof(sig.Params())
|
||||
if sig.Recv() != nil {
|
||||
psize += h.a.sizeof(sig.Recv().Type())
|
||||
}
|
||||
for end := id + int(psize); id < end; id++ {
|
||||
h.markIndirect(onodeid(id), "A-T func P-block")
|
||||
}
|
||||
id--
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// (c) all map objects' value fields.
|
||||
for _, id := range h.a.mapValues {
|
||||
h.markIndirect(onodeid(id), "makemap.value")
|
||||
}
|
||||
|
||||
// (d) all array element objects.
|
||||
// TODO(adonovan): opt: can we do better?
|
||||
for id := 0; id < h.N; id++ {
|
||||
// Identity node for an object of array type?
|
||||
if tArray, ok := h.a.nodes[id].typ.(*types.Array); ok {
|
||||
// Mark the array element nodes indirect.
|
||||
// (Skip past the identity field.)
|
||||
for _ = range h.a.flatten(tArray.Elem()) {
|
||||
id++
|
||||
h.markIndirect(onodeid(id), "array elem")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hvn) markIndirect(oid onodeid, comment string) {
|
||||
h.onodes[oid].indirect = true
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d is indirect: %s\n", oid, comment)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds an edge dst-->src.
|
||||
// Note the unusual convention: edges are dependency (contraflow) edges.
|
||||
func (h *hvn) addEdge(odst, osrc onodeid) {
|
||||
h.onodes[odst].edges.Insert(int(osrc))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d --> o%d\n", odst, osrc)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hvn) addImplicitEdge(odst, osrc onodeid) {
|
||||
h.onodes[odst].implicit.Insert(int(osrc))
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d ~~> o%d\n", odst, osrc)
|
||||
}
|
||||
}
|
||||
|
||||
// visit implements the depth-first search of Tarjan's SCC algorithm.
|
||||
// Precondition: x is canonical.
|
||||
func (h *hvn) visit(x onodeid) {
|
||||
h.checkCanonical(x)
|
||||
xo := h.onodes[x]
|
||||
xo.index = h.index
|
||||
xo.lowlink = h.index
|
||||
h.index++
|
||||
|
||||
h.stack = append(h.stack, x) // push
|
||||
assert(xo.scc == 0, "node revisited")
|
||||
xo.scc = -1
|
||||
|
||||
var deps []int
|
||||
deps = xo.edges.AppendTo(deps)
|
||||
deps = xo.implicit.AppendTo(deps)
|
||||
|
||||
for _, y := range deps {
|
||||
// Loop invariant: x is canonical.
|
||||
|
||||
y := h.find(onodeid(y))
|
||||
|
||||
if x == y {
|
||||
continue // nodes already coalesced
|
||||
}
|
||||
|
||||
xo := h.onodes[x]
|
||||
yo := h.onodes[y]
|
||||
|
||||
switch {
|
||||
case yo.scc > 0:
|
||||
// y is already a collapsed SCC
|
||||
|
||||
case yo.scc < 0:
|
||||
// y is on the stack, and thus in the current SCC.
|
||||
if yo.index < xo.lowlink {
|
||||
xo.lowlink = yo.index
|
||||
}
|
||||
|
||||
default:
|
||||
// y is unvisited; visit it now.
|
||||
h.visit(y)
|
||||
// Note: x and y are now non-canonical.
|
||||
|
||||
x = h.find(onodeid(x))
|
||||
|
||||
if yo.lowlink < xo.lowlink {
|
||||
xo.lowlink = yo.lowlink
|
||||
}
|
||||
}
|
||||
}
|
||||
h.checkCanonical(x)
|
||||
|
||||
// Is x the root of an SCC?
|
||||
if xo.lowlink == xo.index {
|
||||
// Coalesce all nodes in the SCC.
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "scc o%d\n", x)
|
||||
}
|
||||
for {
|
||||
// Pop y from stack.
|
||||
i := len(h.stack) - 1
|
||||
y := h.stack[i]
|
||||
h.stack = h.stack[:i]
|
||||
|
||||
h.checkCanonical(x)
|
||||
xo := h.onodes[x]
|
||||
h.checkCanonical(y)
|
||||
yo := h.onodes[y]
|
||||
|
||||
if xo == yo {
|
||||
// SCC is complete.
|
||||
xo.scc = 1
|
||||
h.labelSCC(x)
|
||||
break
|
||||
}
|
||||
h.coalesce(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Precondition: x is canonical.
|
||||
func (h *hvn) labelSCC(x onodeid) {
|
||||
h.checkCanonical(x)
|
||||
xo := h.onodes[x]
|
||||
xpe := &xo.peLabels
|
||||
|
||||
// All indirect nodes get new labels.
|
||||
if xo.indirect {
|
||||
label := h.nextLabel()
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: indirect SCC\n", label)
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", x, label)
|
||||
}
|
||||
|
||||
// Remove pre-labeling, in case a direct pre-labeled node was
|
||||
// merged with an indirect one.
|
||||
xpe.Clear()
|
||||
xpe.Insert(int(label))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Invariant: all peLabels sets are non-empty.
|
||||
// Those that are logically empty contain zero as their sole element.
|
||||
// No other sets contains zero.
|
||||
|
||||
// Find all labels coming in to the coalesced SCC node.
|
||||
for _, y := range xo.edges.AppendTo(nil) {
|
||||
y := h.find(onodeid(y))
|
||||
if y == x {
|
||||
continue // already coalesced
|
||||
}
|
||||
ype := &h.onodes[y].peLabels
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tedge from o%d = %s\n", y, ype)
|
||||
}
|
||||
|
||||
if ype.IsEmpty() {
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tnode has no PE label\n")
|
||||
}
|
||||
}
|
||||
assert(!ype.IsEmpty(), "incoming node has no PE label")
|
||||
|
||||
if ype.Has(0) {
|
||||
// {0} represents a non-pointer.
|
||||
assert(ype.Len() == 1, "PE set contains {0, ...}")
|
||||
} else {
|
||||
xpe.UnionWith(ype)
|
||||
}
|
||||
}
|
||||
|
||||
switch xpe.Len() {
|
||||
case 0:
|
||||
// SCC has no incoming non-zero PE labels: it is a non-pointer.
|
||||
xpe.Insert(0)
|
||||
|
||||
case 1:
|
||||
// already a singleton
|
||||
|
||||
default:
|
||||
// SCC has multiple incoming non-zero PE labels.
|
||||
// Find the canonical label representing this set.
|
||||
// We use String() as a fingerprint consistent with Equals().
|
||||
key := xpe.String()
|
||||
label, ok := h.hvnLabel[key]
|
||||
if !ok {
|
||||
label = h.nextLabel()
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcreate p%d: union %s\n", label, xpe.String())
|
||||
}
|
||||
h.hvnLabel[key] = label
|
||||
}
|
||||
xpe.Clear()
|
||||
xpe.Insert(int(label))
|
||||
}
|
||||
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\to%d has p%d\n", x, xpe.Min())
|
||||
}
|
||||
}
|
||||
|
||||
// coalesce combines two nodes in the offline constraint graph.
|
||||
// Precondition: x and y are canonical.
|
||||
func (h *hvn) coalesce(x, y onodeid) {
|
||||
xo := h.onodes[x]
|
||||
yo := h.onodes[y]
|
||||
|
||||
// x becomes y's canonical representative.
|
||||
yo.rep = x
|
||||
|
||||
if debugHVNVerbose && h.log != nil {
|
||||
fmt.Fprintf(h.log, "\tcoalesce o%d into o%d\n", y, x)
|
||||
}
|
||||
|
||||
// x accumulates y's edges.
|
||||
xo.edges.UnionWith(&yo.edges)
|
||||
yo.edges.Clear()
|
||||
|
||||
// x accumulates y's implicit edges.
|
||||
xo.implicit.UnionWith(&yo.implicit)
|
||||
yo.implicit.Clear()
|
||||
|
||||
// x accumulates y's pointer-equivalence labels.
|
||||
xo.peLabels.UnionWith(&yo.peLabels)
|
||||
yo.peLabels.Clear()
|
||||
|
||||
// x accumulates y's indirect flag.
|
||||
if yo.indirect {
|
||||
xo.indirect = true
|
||||
}
|
||||
}
|
||||
|
||||
// simplify computes a degenerate renumbering of nodeids from the PE
|
||||
// labels assigned by the hvn, and uses it to simplify the main
|
||||
// constraint graph, eliminating non-pointer nodes and duplicate
|
||||
// constraints.
|
||||
//
|
||||
func (h *hvn) simplify() {
|
||||
// canon maps each peLabel to its canonical main node.
|
||||
canon := make([]nodeid, h.label)
|
||||
for i := range canon {
|
||||
canon[i] = nodeid(h.N) // indicates "unset"
|
||||
}
|
||||
|
||||
// mapping maps each main node index to the index of the canonical node.
|
||||
mapping := make([]nodeid, len(h.a.nodes))
|
||||
|
||||
for id := range h.a.nodes {
|
||||
id := nodeid(id)
|
||||
if id == 0 {
|
||||
canon[0] = 0
|
||||
mapping[0] = 0
|
||||
continue
|
||||
}
|
||||
oid := h.find(onodeid(id))
|
||||
peLabels := &h.onodes[oid].peLabels
|
||||
assert(peLabels.Len() == 1, "PE class is not a singleton")
|
||||
label := peLabel(peLabels.Min())
|
||||
|
||||
canonId := canon[label]
|
||||
if canonId == nodeid(h.N) {
|
||||
// id becomes the representative of the PE label.
|
||||
canonId = id
|
||||
canon[label] = canonId
|
||||
|
||||
if h.a.log != nil {
|
||||
fmt.Fprintf(h.a.log, "\tpts(n%d) is canonical : \t(%s)\n",
|
||||
id, h.a.nodes[id].typ)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Link the solver states for the two nodes.
|
||||
assert(h.a.nodes[canonId].solve != nil, "missing solver state")
|
||||
h.a.nodes[id].solve = h.a.nodes[canonId].solve
|
||||
|
||||
if h.a.log != nil {
|
||||
// TODO(adonovan): debug: reorganize the log so it prints
|
||||
// one line:
|
||||
// pe y = x1, ..., xn
|
||||
// for each canonical y. Requires allocation.
|
||||
fmt.Fprintf(h.a.log, "\tpts(n%d) = pts(n%d) : %s\n",
|
||||
id, canonId, h.a.nodes[id].typ)
|
||||
}
|
||||
}
|
||||
|
||||
mapping[id] = canonId
|
||||
}
|
||||
|
||||
// Renumber the constraints, eliminate duplicates, and eliminate
|
||||
// any containing non-pointers (n0).
|
||||
addrs := make(map[addrConstraint]bool)
|
||||
copys := make(map[copyConstraint]bool)
|
||||
loads := make(map[loadConstraint]bool)
|
||||
stores := make(map[storeConstraint]bool)
|
||||
offsetAddrs := make(map[offsetAddrConstraint]bool)
|
||||
untags := make(map[untagConstraint]bool)
|
||||
typeFilters := make(map[typeFilterConstraint]bool)
|
||||
invokes := make(map[invokeConstraint]bool)
|
||||
|
||||
nbefore := len(h.a.constraints)
|
||||
cc := h.a.constraints[:0] // in-situ compaction
|
||||
for _, c := range h.a.constraints {
|
||||
// Renumber.
|
||||
switch c := c.(type) {
|
||||
case *addrConstraint:
|
||||
// Don't renumber c.src since it is the label of
|
||||
// an addressable object and will appear in PT sets.
|
||||
c.dst = mapping[c.dst]
|
||||
default:
|
||||
c.renumber(mapping)
|
||||
}
|
||||
|
||||
if c.ptr() == 0 {
|
||||
continue // skip: constraint attached to non-pointer
|
||||
}
|
||||
|
||||
var dup bool
|
||||
switch c := c.(type) {
|
||||
case *addrConstraint:
|
||||
_, dup = addrs[*c]
|
||||
addrs[*c] = true
|
||||
|
||||
case *copyConstraint:
|
||||
if c.src == c.dst {
|
||||
continue // skip degenerate copies
|
||||
}
|
||||
if c.src == 0 {
|
||||
continue // skip copy from non-pointer
|
||||
}
|
||||
_, dup = copys[*c]
|
||||
copys[*c] = true
|
||||
|
||||
case *loadConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip load from non-pointer
|
||||
}
|
||||
_, dup = loads[*c]
|
||||
loads[*c] = true
|
||||
|
||||
case *storeConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip store from non-pointer
|
||||
}
|
||||
_, dup = stores[*c]
|
||||
stores[*c] = true
|
||||
|
||||
case *offsetAddrConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip offset from non-pointer
|
||||
}
|
||||
_, dup = offsetAddrs[*c]
|
||||
offsetAddrs[*c] = true
|
||||
|
||||
case *untagConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip untag of non-pointer
|
||||
}
|
||||
_, dup = untags[*c]
|
||||
untags[*c] = true
|
||||
|
||||
case *typeFilterConstraint:
|
||||
if c.src == 0 {
|
||||
continue // skip filter of non-pointer
|
||||
}
|
||||
_, dup = typeFilters[*c]
|
||||
typeFilters[*c] = true
|
||||
|
||||
case *invokeConstraint:
|
||||
if c.params == 0 {
|
||||
panic("non-pointer invoke.params")
|
||||
}
|
||||
if c.iface == 0 {
|
||||
continue // skip invoke on non-pointer
|
||||
}
|
||||
_, dup = invokes[*c]
|
||||
invokes[*c] = true
|
||||
|
||||
default:
|
||||
// We don't bother de-duping advanced constraints
|
||||
// (e.g. reflection) since they are uncommon.
|
||||
|
||||
// Eliminate constraints containing non-pointer nodeids.
|
||||
//
|
||||
// We use reflection to find the fields to avoid
|
||||
// adding yet another method to constraint.
|
||||
//
|
||||
// TODO(adonovan): experiment with a constraint
|
||||
// method that returns a slice of pointers to
|
||||
// nodeids fields to enable uniform iteration;
|
||||
// the renumber() method could be removed and
|
||||
// implemented using the new one.
|
||||
//
|
||||
// TODO(adonovan): opt: this is unsound since
|
||||
// some constraints still have an effect if one
|
||||
// of the operands is zero: rVCall, rVMapIndex,
|
||||
// rvSetMapIndex. Handle them specially.
|
||||
rtNodeid := reflect.TypeOf(nodeid(0))
|
||||
x := reflect.ValueOf(c).Elem()
|
||||
for i, nf := 0, x.NumField(); i < nf; i++ {
|
||||
f := x.Field(i)
|
||||
if f.Type() == rtNodeid {
|
||||
if f.Uint() == 0 {
|
||||
dup = true // skip it
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if dup {
|
||||
continue // skip duplicates
|
||||
}
|
||||
|
||||
cc = append(cc, c)
|
||||
}
|
||||
h.a.constraints = cc
|
||||
|
||||
if h.log != nil {
|
||||
fmt.Fprintf(h.log, "#constraints: was %d, now %d\n", nbefore, len(h.a.constraints))
|
||||
}
|
||||
}
|
||||
|
||||
// find returns the canonical onodeid for x.
|
||||
// (The onodes form a disjoint set forest.)
|
||||
func (h *hvn) find(x onodeid) onodeid {
|
||||
// TODO(adonovan): opt: this is a CPU hotspot. Try "union by rank".
|
||||
xo := h.onodes[x]
|
||||
rep := xo.rep
|
||||
if rep != x {
|
||||
rep = h.find(rep) // simple path compression
|
||||
xo.rep = rep
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
func (h *hvn) checkCanonical(x onodeid) {
|
||||
if debugHVN {
|
||||
assert(x == h.find(x), "not canonical")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(p bool, msg string) {
|
||||
if debugHVN && !p {
|
||||
panic("assertion failed: " + msg)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,380 @@
|
|||
// 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 pointer
|
||||
|
||||
// This package defines the treatment of intrinsics, i.e. library
|
||||
// functions requiring special analytical treatment.
|
||||
//
|
||||
// Most of these are C or assembly functions, but even some Go
|
||||
// functions require may special treatment if the analysis completely
|
||||
// replaces the implementation of an API such as reflection.
|
||||
|
||||
// TODO(adonovan): support a means of writing analytic summaries in
|
||||
// the target code, so that users can summarise the effects of their
|
||||
// own C functions using a snippet of Go.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// Instances of 'intrinsic' generate analysis constraints for calls to
|
||||
// intrinsic functions.
|
||||
// Implementations may exploit information from the calling site
|
||||
// via cgn.callersite; for shared contours this is nil.
|
||||
type intrinsic func(a *analysis, cgn *cgnode)
|
||||
|
||||
// Initialized in explicit init() to defeat (spurious) initialization
|
||||
// cycle error.
|
||||
var intrinsicsByName = make(map[string]intrinsic)
|
||||
|
||||
func init() {
|
||||
// Key strings are from Function.String().
|
||||
// That little dot ۰ is an Arabic zero numeral (U+06F0),
|
||||
// categories [Nd].
|
||||
for name, fn := range map[string]intrinsic{
|
||||
// Other packages.
|
||||
"bytes.Equal": ext۰NoEffect,
|
||||
"bytes.IndexByte": ext۰NoEffect,
|
||||
"crypto/aes.decryptBlockAsm": ext۰NoEffect,
|
||||
"crypto/aes.encryptBlockAsm": ext۰NoEffect,
|
||||
"crypto/aes.expandKeyAsm": ext۰NoEffect,
|
||||
"crypto/aes.hasAsm": ext۰NoEffect,
|
||||
"crypto/md5.block": ext۰NoEffect,
|
||||
"crypto/rc4.xorKeyStream": ext۰NoEffect,
|
||||
"crypto/sha1.block": ext۰NoEffect,
|
||||
"crypto/sha256.block": ext۰NoEffect,
|
||||
"hash/crc32.castagnoliSSE42": ext۰NoEffect,
|
||||
"hash/crc32.haveSSE42": ext۰NoEffect,
|
||||
"math.Abs": ext۰NoEffect,
|
||||
"math.Acos": ext۰NoEffect,
|
||||
"math.Asin": ext۰NoEffect,
|
||||
"math.Atan": ext۰NoEffect,
|
||||
"math.Atan2": ext۰NoEffect,
|
||||
"math.Ceil": ext۰NoEffect,
|
||||
"math.Cos": ext۰NoEffect,
|
||||
"math.Dim": ext۰NoEffect,
|
||||
"math.Exp": ext۰NoEffect,
|
||||
"math.Exp2": ext۰NoEffect,
|
||||
"math.Expm1": ext۰NoEffect,
|
||||
"math.Float32bits": ext۰NoEffect,
|
||||
"math.Float32frombits": ext۰NoEffect,
|
||||
"math.Float64bits": ext۰NoEffect,
|
||||
"math.Float64frombits": ext۰NoEffect,
|
||||
"math.Floor": ext۰NoEffect,
|
||||
"math.Frexp": ext۰NoEffect,
|
||||
"math.Hypot": ext۰NoEffect,
|
||||
"math.Ldexp": ext۰NoEffect,
|
||||
"math.Log": ext۰NoEffect,
|
||||
"math.Log10": ext۰NoEffect,
|
||||
"math.Log1p": ext۰NoEffect,
|
||||
"math.Log2": ext۰NoEffect,
|
||||
"math.Max": ext۰NoEffect,
|
||||
"math.Min": ext۰NoEffect,
|
||||
"math.Mod": ext۰NoEffect,
|
||||
"math.Modf": ext۰NoEffect,
|
||||
"math.Remainder": ext۰NoEffect,
|
||||
"math.Sin": ext۰NoEffect,
|
||||
"math.Sincos": ext۰NoEffect,
|
||||
"math.Sqrt": ext۰NoEffect,
|
||||
"math.Tan": ext۰NoEffect,
|
||||
"math.Trunc": ext۰NoEffect,
|
||||
"math/big.addMulVVW": ext۰NoEffect,
|
||||
"math/big.addVV": ext۰NoEffect,
|
||||
"math/big.addVW": ext۰NoEffect,
|
||||
"math/big.bitLen": ext۰NoEffect,
|
||||
"math/big.divWVW": ext۰NoEffect,
|
||||
"math/big.divWW": ext۰NoEffect,
|
||||
"math/big.mulAddVWW": ext۰NoEffect,
|
||||
"math/big.mulWW": ext۰NoEffect,
|
||||
"math/big.shlVU": ext۰NoEffect,
|
||||
"math/big.shrVU": ext۰NoEffect,
|
||||
"math/big.subVV": ext۰NoEffect,
|
||||
"math/big.subVW": ext۰NoEffect,
|
||||
"net.runtime_Semacquire": ext۰NoEffect,
|
||||
"net.runtime_Semrelease": ext۰NoEffect,
|
||||
"net.runtime_pollClose": ext۰NoEffect,
|
||||
"net.runtime_pollOpen": ext۰NoEffect,
|
||||
"net.runtime_pollReset": ext۰NoEffect,
|
||||
"net.runtime_pollServerInit": ext۰NoEffect,
|
||||
"net.runtime_pollSetDeadline": ext۰NoEffect,
|
||||
"net.runtime_pollUnblock": ext۰NoEffect,
|
||||
"net.runtime_pollWait": ext۰NoEffect,
|
||||
"net.runtime_pollWaitCanceled": ext۰NoEffect,
|
||||
"os.epipecheck": ext۰NoEffect,
|
||||
"runtime.BlockProfile": ext۰NoEffect,
|
||||
"runtime.Breakpoint": ext۰NoEffect,
|
||||
"runtime.CPUProfile": ext۰NoEffect, // good enough
|
||||
"runtime.Caller": ext۰NoEffect,
|
||||
"runtime.Callers": ext۰NoEffect, // good enough
|
||||
"runtime.FuncForPC": ext۰NoEffect,
|
||||
"runtime.GC": ext۰NoEffect,
|
||||
"runtime.GOMAXPROCS": ext۰NoEffect,
|
||||
"runtime.Goexit": ext۰NoEffect,
|
||||
"runtime.GoroutineProfile": ext۰NoEffect,
|
||||
"runtime.Gosched": ext۰NoEffect,
|
||||
"runtime.MemProfile": ext۰NoEffect,
|
||||
"runtime.NumCPU": ext۰NoEffect,
|
||||
"runtime.NumGoroutine": ext۰NoEffect,
|
||||
"runtime.ReadMemStats": ext۰NoEffect,
|
||||
"runtime.SetBlockProfileRate": ext۰NoEffect,
|
||||
"runtime.SetCPUProfileRate": ext۰NoEffect,
|
||||
"runtime.SetFinalizer": ext۰runtime۰SetFinalizer,
|
||||
"runtime.Stack": ext۰NoEffect,
|
||||
"runtime.ThreadCreateProfile": ext۰NoEffect,
|
||||
"runtime.cstringToGo": ext۰NoEffect,
|
||||
"runtime.funcentry_go": ext۰NoEffect,
|
||||
"runtime.funcline_go": ext۰NoEffect,
|
||||
"runtime.funcname_go": ext۰NoEffect,
|
||||
"runtime.getgoroot": ext۰NoEffect,
|
||||
"runtime/pprof.runtime_cyclesPerSecond": ext۰NoEffect,
|
||||
"strings.IndexByte": ext۰NoEffect,
|
||||
"sync.runtime_Semacquire": ext۰NoEffect,
|
||||
"sync.runtime_Semrelease": ext۰NoEffect,
|
||||
"sync.runtime_Syncsemacquire": ext۰NoEffect,
|
||||
"sync.runtime_Syncsemcheck": ext۰NoEffect,
|
||||
"sync.runtime_Syncsemrelease": ext۰NoEffect,
|
||||
"sync.runtime_procPin": ext۰NoEffect,
|
||||
"sync.runtime_procUnpin": ext۰NoEffect,
|
||||
"sync.runtime_registerPool": ext۰NoEffect,
|
||||
"sync/atomic.AddInt32": ext۰NoEffect,
|
||||
"sync/atomic.AddInt64": ext۰NoEffect,
|
||||
"sync/atomic.AddUint32": ext۰NoEffect,
|
||||
"sync/atomic.AddUint64": ext۰NoEffect,
|
||||
"sync/atomic.AddUintptr": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapInt32": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapUint32": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapUint64": ext۰NoEffect,
|
||||
"sync/atomic.CompareAndSwapUintptr": ext۰NoEffect,
|
||||
"sync/atomic.LoadInt32": ext۰NoEffect,
|
||||
"sync/atomic.LoadInt64": ext۰NoEffect,
|
||||
"sync/atomic.LoadPointer": ext۰NoEffect, // ignore unsafe.Pointers
|
||||
"sync/atomic.LoadUint32": ext۰NoEffect,
|
||||
"sync/atomic.LoadUint64": ext۰NoEffect,
|
||||
"sync/atomic.LoadUintptr": ext۰NoEffect,
|
||||
"sync/atomic.StoreInt32": ext۰NoEffect,
|
||||
"sync/atomic.StorePointer": ext۰NoEffect, // ignore unsafe.Pointers
|
||||
"sync/atomic.StoreUint32": ext۰NoEffect,
|
||||
"sync/atomic.StoreUintptr": ext۰NoEffect,
|
||||
"syscall.Close": ext۰NoEffect,
|
||||
"syscall.Exit": ext۰NoEffect,
|
||||
"syscall.Getpid": ext۰NoEffect,
|
||||
"syscall.Getwd": ext۰NoEffect,
|
||||
"syscall.Kill": ext۰NoEffect,
|
||||
"syscall.RawSyscall": ext۰NoEffect,
|
||||
"syscall.RawSyscall6": ext۰NoEffect,
|
||||
"syscall.Syscall": ext۰NoEffect,
|
||||
"syscall.Syscall6": ext۰NoEffect,
|
||||
"syscall.runtime_AfterFork": ext۰NoEffect,
|
||||
"syscall.runtime_BeforeFork": ext۰NoEffect,
|
||||
"syscall.setenv_c": ext۰NoEffect,
|
||||
"time.Sleep": ext۰NoEffect,
|
||||
"time.now": ext۰NoEffect,
|
||||
"time.startTimer": ext۰time۰startTimer,
|
||||
"time.stopTimer": ext۰NoEffect,
|
||||
} {
|
||||
intrinsicsByName[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// findIntrinsic returns the constraint generation function for an
|
||||
// intrinsic function fn, or nil if the function should be handled normally.
|
||||
//
|
||||
func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic {
|
||||
// Consult the *Function-keyed cache.
|
||||
// A cached nil indicates a normal non-intrinsic function.
|
||||
impl, ok := a.intrinsics[fn]
|
||||
if !ok {
|
||||
impl = intrinsicsByName[fn.String()] // may be nil
|
||||
|
||||
if a.isReflect(fn) {
|
||||
if !a.config.Reflection {
|
||||
impl = ext۰NoEffect // reflection disabled
|
||||
} else if impl == nil {
|
||||
// Ensure all "reflect" code is treated intrinsically.
|
||||
impl = ext۰NotYetImplemented
|
||||
}
|
||||
}
|
||||
|
||||
a.intrinsics[fn] = impl
|
||||
}
|
||||
return impl
|
||||
}
|
||||
|
||||
// isReflect reports whether fn belongs to the "reflect" package.
|
||||
func (a *analysis) isReflect(fn *ssa.Function) bool {
|
||||
if a.reflectValueObj == nil {
|
||||
return false // "reflect" package not loaded
|
||||
}
|
||||
reflectPackage := a.reflectValueObj.Pkg()
|
||||
if fn.Pkg != nil && fn.Pkg.Object == reflectPackage {
|
||||
return true
|
||||
}
|
||||
// Synthetic wrappers have a nil Pkg, so they slip through the
|
||||
// previous check. Check the receiver package.
|
||||
// TODO(adonovan): should synthetic wrappers have a non-nil Pkg?
|
||||
if recv := fn.Signature.Recv(); recv != nil {
|
||||
if named, ok := deref(recv.Type()).(*types.Named); ok {
|
||||
if named.Obj().Pkg() == reflectPackage {
|
||||
return true // e.g. wrapper of (reflect.Value).f
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A trivial intrinsic suitable for any function that does not:
|
||||
// 1) induce aliases between its arguments or any global variables;
|
||||
// 2) call any functions; or
|
||||
// 3) create any labels.
|
||||
//
|
||||
// Many intrinsics (such as CompareAndSwapInt32) have a fourth kind of
|
||||
// effect: loading or storing through a pointer. Though these could
|
||||
// be significant, we deliberately ignore them because they are
|
||||
// generally not worth the effort.
|
||||
//
|
||||
// We sometimes violate condition #3 if the function creates only
|
||||
// non-function labels, as the control-flow graph is still sound.
|
||||
//
|
||||
func ext۰NoEffect(a *analysis, cgn *cgnode) {}
|
||||
|
||||
func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
|
||||
fn := cgn.fn
|
||||
a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
||||
}
|
||||
|
||||
// ---------- func runtime.SetFinalizer(x, f interface{}) ----------
|
||||
|
||||
// runtime.SetFinalizer(x, f)
|
||||
type runtimeSetFinalizerConstraint struct {
|
||||
targets nodeid // (indirect)
|
||||
f nodeid // (ptr)
|
||||
x nodeid
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) ptr() nodeid { return c.f }
|
||||
func (c *runtimeSetFinalizerConstraint) presolve(h *hvn) {
|
||||
h.markIndirect(onodeid(c.targets), "SetFinalizer.targets")
|
||||
}
|
||||
func (c *runtimeSetFinalizerConstraint) renumber(mapping []nodeid) {
|
||||
c.targets = mapping[c.targets]
|
||||
c.f = mapping[c.f]
|
||||
c.x = mapping[c.x]
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) String() string {
|
||||
return fmt.Sprintf("runtime.SetFinalizer(n%d, n%d)", c.x, c.f)
|
||||
}
|
||||
|
||||
func (c *runtimeSetFinalizerConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, fObj := range delta.AppendTo(a.deltaSpace) {
|
||||
tDyn, f, indirect := a.taggedValue(nodeid(fObj))
|
||||
if indirect {
|
||||
// TODO(adonovan): we'll need to implement this
|
||||
// when we start creating indirect tagged objects.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
tSig, ok := tDyn.Underlying().(*types.Signature)
|
||||
if !ok {
|
||||
continue // not a function
|
||||
}
|
||||
if tSig.Recv() != nil {
|
||||
panic(tSig)
|
||||
}
|
||||
if tSig.Params().Len() != 1 {
|
||||
continue // not a unary function
|
||||
}
|
||||
|
||||
// Extract x to tmp.
|
||||
tx := tSig.Params().At(0).Type()
|
||||
tmp := a.addNodes(tx, "SetFinalizer.tmp")
|
||||
a.typeAssert(tx, tmp, c.x, false)
|
||||
|
||||
// Call f(tmp).
|
||||
a.store(f, tmp, 1, a.sizeof(tx))
|
||||
|
||||
// Add dynamic call target.
|
||||
if a.onlineCopy(c.targets, f) {
|
||||
a.addWork(c.targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰runtime۰SetFinalizer(a *analysis, cgn *cgnode) {
|
||||
// This is the shared contour, used for dynamic calls.
|
||||
targets := a.addOneNode(tInvalid, "SetFinalizer.targets", nil)
|
||||
cgn.sites = append(cgn.sites, &callsite{targets: targets})
|
||||
params := a.funcParams(cgn.obj)
|
||||
a.addConstraint(&runtimeSetFinalizerConstraint{
|
||||
targets: targets,
|
||||
x: params,
|
||||
f: params + 1,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- func time.startTimer(t *runtimeTimer) ----------
|
||||
|
||||
// time.StartTimer(t)
|
||||
type timeStartTimerConstraint struct {
|
||||
targets nodeid // (indirect)
|
||||
t nodeid // (ptr)
|
||||
}
|
||||
|
||||
func (c *timeStartTimerConstraint) ptr() nodeid { return c.t }
|
||||
func (c *timeStartTimerConstraint) presolve(h *hvn) {
|
||||
h.markIndirect(onodeid(c.targets), "StartTimer.targets")
|
||||
}
|
||||
func (c *timeStartTimerConstraint) renumber(mapping []nodeid) {
|
||||
c.targets = mapping[c.targets]
|
||||
c.t = mapping[c.t]
|
||||
}
|
||||
|
||||
func (c *timeStartTimerConstraint) String() string {
|
||||
return fmt.Sprintf("time.startTimer(n%d)", c.t)
|
||||
}
|
||||
|
||||
func (c *timeStartTimerConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, tObj := range delta.AppendTo(a.deltaSpace) {
|
||||
t := nodeid(tObj)
|
||||
|
||||
// We model startTimer as if it was defined thus:
|
||||
// func startTimer(t *runtimeTimer) { t.f(t.arg) }
|
||||
|
||||
// We hard-code the field offsets of time.runtimeTimer:
|
||||
// type runtimeTimer struct {
|
||||
// 0 __identity__
|
||||
// 1 i int32
|
||||
// 2 when int64
|
||||
// 3 period int64
|
||||
// 4 f func(int64, interface{})
|
||||
// 5 arg interface{}
|
||||
// }
|
||||
f := t + 4
|
||||
arg := t + 5
|
||||
|
||||
// store t.arg to t.f.params[0]
|
||||
// (offset 1 => skip identity)
|
||||
a.store(f, arg, 1, 1)
|
||||
|
||||
// Add dynamic call target.
|
||||
if a.onlineCopy(c.targets, f) {
|
||||
a.addWork(c.targets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ext۰time۰startTimer(a *analysis, cgn *cgnode) {
|
||||
// This is the shared contour, used for dynamic calls.
|
||||
targets := a.addOneNode(tInvalid, "startTimer.targets", nil)
|
||||
cgn.sites = append(cgn.sites, &callsite{targets: targets})
|
||||
params := a.funcParams(cgn.obj)
|
||||
a.addConstraint(&timeStartTimerConstraint{
|
||||
targets: targets,
|
||||
t: params,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
// 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 pointer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// A Label is an entity that may be pointed to by a pointer, map,
|
||||
// channel, 'func', slice or interface.
|
||||
//
|
||||
// Labels include:
|
||||
// - functions
|
||||
// - globals
|
||||
// - tagged objects, representing interfaces and reflect.Values
|
||||
// - arrays created by conversions (e.g. []byte("foo"), []byte(s))
|
||||
// - stack- and heap-allocated variables (including composite literals)
|
||||
// - channels, maps and arrays created by make()
|
||||
// - intrinsic or reflective operations that allocate (e.g. append, reflect.New)
|
||||
// - intrinsic objects, e.g. the initial array behind os.Args.
|
||||
// - and their subelements, e.g. "alloc.y[*].z"
|
||||
//
|
||||
// Labels are so varied that they defy good generalizations;
|
||||
// some have no value, no callgraph node, or no position.
|
||||
// Many objects have types that are inexpressible in Go:
|
||||
// maps, channels, functions, tagged objects.
|
||||
//
|
||||
// At most one of Value() or ReflectType() may return non-nil.
|
||||
//
|
||||
type Label struct {
|
||||
obj *object // the addressable memory location containing this label
|
||||
subelement *fieldInfo // subelement path within obj, e.g. ".a.b[*].c"
|
||||
}
|
||||
|
||||
// Value returns the ssa.Value that allocated this label's object, if any.
|
||||
func (l Label) Value() ssa.Value {
|
||||
val, _ := l.obj.data.(ssa.Value)
|
||||
return val
|
||||
}
|
||||
|
||||
// ReflectType returns the type represented by this label if it is an
|
||||
// reflect.rtype instance object or *reflect.rtype-tagged object.
|
||||
//
|
||||
func (l Label) ReflectType() types.Type {
|
||||
rtype, _ := l.obj.data.(types.Type)
|
||||
return rtype
|
||||
}
|
||||
|
||||
// Path returns the path to the subelement of the object containing
|
||||
// this label. For example, ".x[*].y".
|
||||
//
|
||||
func (l Label) Path() string {
|
||||
return l.subelement.path()
|
||||
}
|
||||
|
||||
// Pos returns the position of this label, if known, zero otherwise.
|
||||
func (l Label) Pos() token.Pos {
|
||||
switch data := l.obj.data.(type) {
|
||||
case ssa.Value:
|
||||
return data.Pos()
|
||||
case types.Type:
|
||||
if nt, ok := deref(data).(*types.Named); ok {
|
||||
return nt.Obj().Pos()
|
||||
}
|
||||
}
|
||||
if cgn := l.obj.cgn; cgn != nil {
|
||||
return cgn.fn.Pos()
|
||||
}
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
// String returns the printed form of this label.
|
||||
//
|
||||
// Examples: Object type:
|
||||
// x (a variable)
|
||||
// (sync.Mutex).Lock (a function)
|
||||
// convert (array created by conversion)
|
||||
// makemap (map allocated via make)
|
||||
// makechan (channel allocated via make)
|
||||
// makeinterface (tagged object allocated by makeinterface)
|
||||
// <alloc in reflect.Zero> (allocation in instrinsic)
|
||||
// sync.Mutex (a reflect.rtype instance)
|
||||
// <command-line arguments> (an intrinsic object)
|
||||
//
|
||||
// Labels within compound objects have subelement paths:
|
||||
// x.y[*].z (a struct variable, x)
|
||||
// append.y[*].z (array allocated by append)
|
||||
// makeslice.y[*].z (array allocated via make)
|
||||
//
|
||||
// TODO(adonovan): expose func LabelString(*types.Package, Label).
|
||||
//
|
||||
func (l Label) String() string {
|
||||
var s string
|
||||
switch v := l.obj.data.(type) {
|
||||
case types.Type:
|
||||
return v.String()
|
||||
|
||||
case string:
|
||||
s = v // an intrinsic object (e.g. os.Args[*])
|
||||
|
||||
case nil:
|
||||
if l.obj.cgn != nil {
|
||||
// allocation by intrinsic or reflective operation
|
||||
s = fmt.Sprintf("<alloc in %s>", l.obj.cgn.fn)
|
||||
} else {
|
||||
s = "<unknown>" // should be unreachable
|
||||
}
|
||||
|
||||
case *ssa.Function:
|
||||
s = v.String()
|
||||
|
||||
case *ssa.Global:
|
||||
s = v.String()
|
||||
|
||||
case *ssa.Const:
|
||||
s = v.Name()
|
||||
|
||||
case *ssa.Alloc:
|
||||
s = v.Comment
|
||||
if s == "" {
|
||||
s = "alloc"
|
||||
}
|
||||
|
||||
case *ssa.Call:
|
||||
// Currently only calls to append can allocate objects.
|
||||
if v.Call.Value.(*ssa.Builtin).Object().Name() != "append" {
|
||||
panic("unhandled *ssa.Call label: " + v.Name())
|
||||
}
|
||||
s = "append"
|
||||
|
||||
case *ssa.MakeMap, *ssa.MakeChan, *ssa.MakeSlice, *ssa.Convert:
|
||||
s = strings.ToLower(strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa."))
|
||||
|
||||
case *ssa.MakeInterface:
|
||||
// MakeInterface is usually implicit in Go source (so
|
||||
// Pos()==0), and tagged objects may be allocated
|
||||
// synthetically (so no *MakeInterface data).
|
||||
s = "makeinterface:" + v.X.Type().String()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled object data type: %T", v))
|
||||
}
|
||||
|
||||
return s + l.subelement.path()
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
// 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 pointer
|
||||
|
||||
// This file implements renumbering, a pre-solver optimization to
|
||||
// improve the efficiency of the solver's points-to set representation.
|
||||
//
|
||||
// TODO(adonovan): rename file "renumber.go"
|
||||
|
||||
import "fmt"
|
||||
|
||||
// renumber permutes a.nodes so that all nodes within an addressable
|
||||
// object appear before all non-addressable nodes, maintaining the
|
||||
// order of nodes within the same object (as required by offsetAddr).
|
||||
//
|
||||
// renumber must update every nodeid in the analysis (constraints,
|
||||
// Pointers, callgraph, etc) to reflect the new ordering.
|
||||
//
|
||||
// This is an optimisation to increase the locality and efficiency of
|
||||
// sparse representations of points-to sets. (Typically only about
|
||||
// 20% of nodes are within an object.)
|
||||
//
|
||||
// NB: nodes added during solving (e.g. for reflection, SetFinalizer)
|
||||
// will be appended to the end.
|
||||
//
|
||||
// Renumbering makes the PTA log inscrutable. To aid debugging, later
|
||||
// phases (e.g. HVN) must not rely on it having occurred.
|
||||
//
|
||||
func (a *analysis) renumber() {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\n\n==== Renumbering\n\n")
|
||||
}
|
||||
|
||||
N := nodeid(len(a.nodes))
|
||||
newNodes := make([]*node, N, N)
|
||||
renumbering := make([]nodeid, N, N) // maps old to new
|
||||
|
||||
var i, j nodeid
|
||||
|
||||
// The zero node is special.
|
||||
newNodes[j] = a.nodes[i]
|
||||
renumbering[i] = j
|
||||
i++
|
||||
j++
|
||||
|
||||
// Pass 1: object nodes.
|
||||
for i < N {
|
||||
obj := a.nodes[i].obj
|
||||
if obj == nil {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
end := i + nodeid(obj.size)
|
||||
for i < end {
|
||||
newNodes[j] = a.nodes[i]
|
||||
renumbering[i] = j
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
nobj := j
|
||||
|
||||
// Pass 2: non-object nodes.
|
||||
for i = 1; i < N; {
|
||||
obj := a.nodes[i].obj
|
||||
if obj != nil {
|
||||
i += nodeid(obj.size)
|
||||
continue
|
||||
}
|
||||
|
||||
newNodes[j] = a.nodes[i]
|
||||
renumbering[i] = j
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
if j != N {
|
||||
panic(fmt.Sprintf("internal error: j=%d, N=%d", j, N))
|
||||
}
|
||||
|
||||
// Log the remapping table.
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "Renumbering nodes to improve density:\n")
|
||||
fmt.Fprintf(a.log, "(%d object nodes of %d total)\n", nobj, N)
|
||||
for old, new := range renumbering {
|
||||
fmt.Fprintf(a.log, "\tn%d -> n%d\n", old, new)
|
||||
}
|
||||
}
|
||||
|
||||
// Now renumber all existing nodeids to use the new node permutation.
|
||||
// It is critical that all reachable nodeids are accounted for!
|
||||
|
||||
// Renumber nodeids in queried Pointers.
|
||||
for v, ptr := range a.result.Queries {
|
||||
ptr.n = renumbering[ptr.n]
|
||||
a.result.Queries[v] = ptr
|
||||
}
|
||||
for v, ptr := range a.result.IndirectQueries {
|
||||
ptr.n = renumbering[ptr.n]
|
||||
a.result.IndirectQueries[v] = ptr
|
||||
}
|
||||
|
||||
// Renumber nodeids in global objects.
|
||||
for v, id := range a.globalobj {
|
||||
a.globalobj[v] = renumbering[id]
|
||||
}
|
||||
|
||||
// Renumber nodeids in constraints.
|
||||
for _, c := range a.constraints {
|
||||
c.renumber(renumbering)
|
||||
}
|
||||
|
||||
// Renumber nodeids in the call graph.
|
||||
for _, cgn := range a.cgnodes {
|
||||
cgn.obj = renumbering[cgn.obj]
|
||||
for _, site := range cgn.sites {
|
||||
site.targets = renumbering[site.targets]
|
||||
}
|
||||
}
|
||||
|
||||
a.nodes = newNodes
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// 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 pointer
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *addrConstraint) String() string {
|
||||
return fmt.Sprintf("addr n%d <- {&n%d}", c.dst, c.src)
|
||||
}
|
||||
|
||||
func (c *copyConstraint) String() string {
|
||||
return fmt.Sprintf("copy n%d <- n%d", c.dst, c.src)
|
||||
}
|
||||
|
||||
func (c *loadConstraint) String() string {
|
||||
return fmt.Sprintf("load n%d <- n%d[%d]", c.dst, c.src, c.offset)
|
||||
}
|
||||
|
||||
func (c *storeConstraint) String() string {
|
||||
return fmt.Sprintf("store n%d[%d] <- n%d", c.dst, c.offset, c.src)
|
||||
}
|
||||
|
||||
func (c *offsetAddrConstraint) String() string {
|
||||
return fmt.Sprintf("offsetAddr n%d <- n%d.#%d", c.dst, c.src, c.offset)
|
||||
}
|
||||
|
||||
func (c *typeFilterConstraint) String() string {
|
||||
return fmt.Sprintf("typeFilter n%d <- n%d.(%s)", c.dst, c.src, c.typ)
|
||||
}
|
||||
|
||||
func (c *untagConstraint) String() string {
|
||||
return fmt.Sprintf("untag n%d <- n%d.(%s)", c.dst, c.src, c.typ)
|
||||
}
|
||||
|
||||
func (c *invokeConstraint) String() string {
|
||||
return fmt.Sprintf("invoke n%d.%s(n%d ...)", c.iface, c.method.Name(), c.params)
|
||||
}
|
||||
|
||||
func (n nodeid) String() string {
|
||||
return fmt.Sprintf("n%d", n)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,371 @@
|
|||
// 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 pointer
|
||||
|
||||
// This file defines a naive Andersen-style solver for the inclusion
|
||||
// constraint system.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type solverState struct {
|
||||
complex []constraint // complex constraints attached to this node
|
||||
copyTo nodeset // simple copy constraint edges
|
||||
pts nodeset // points-to set of this node
|
||||
prevPTS nodeset // pts(n) in previous iteration (for difference propagation)
|
||||
}
|
||||
|
||||
func (a *analysis) solve() {
|
||||
start("Solving")
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\n\n==== Solving constraints\n\n")
|
||||
}
|
||||
|
||||
// Solver main loop.
|
||||
var delta nodeset
|
||||
for {
|
||||
// Add new constraints to the graph:
|
||||
// static constraints from SSA on round 1,
|
||||
// dynamic constraints from reflection thereafter.
|
||||
a.processNewConstraints()
|
||||
|
||||
var x int
|
||||
if !a.work.TakeMin(&x) {
|
||||
break // empty
|
||||
}
|
||||
id := nodeid(x)
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\tnode n%d\n", id)
|
||||
}
|
||||
|
||||
n := a.nodes[id]
|
||||
|
||||
// Difference propagation.
|
||||
delta.Difference(&n.solve.pts.Sparse, &n.solve.prevPTS.Sparse)
|
||||
if delta.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tpts(n%d : %s) = %s + %s\n",
|
||||
id, n.typ, &delta, &n.solve.prevPTS)
|
||||
}
|
||||
n.solve.prevPTS.Copy(&n.solve.pts.Sparse)
|
||||
|
||||
// Apply all resolution rules attached to n.
|
||||
a.solveConstraints(n, &delta)
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, &n.solve.pts)
|
||||
}
|
||||
}
|
||||
|
||||
if !a.nodes[0].solve.pts.IsEmpty() {
|
||||
panic(fmt.Sprintf("pts(0) is nonempty: %s", &a.nodes[0].solve.pts))
|
||||
}
|
||||
|
||||
// Release working state (but keep final PTS).
|
||||
for _, n := range a.nodes {
|
||||
n.solve.complex = nil
|
||||
n.solve.copyTo.Clear()
|
||||
n.solve.prevPTS.Clear()
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "Solver done\n")
|
||||
|
||||
// Dump solution.
|
||||
for i, n := range a.nodes {
|
||||
if !n.solve.pts.IsEmpty() {
|
||||
fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, &n.solve.pts, n.typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
stop("Solving")
|
||||
}
|
||||
|
||||
// processNewConstraints takes the new constraints from a.constraints
|
||||
// and adds them to the graph, ensuring
|
||||
// that new constraints are applied to pre-existing labels and
|
||||
// that pre-existing constraints are applied to new labels.
|
||||
//
|
||||
func (a *analysis) processNewConstraints() {
|
||||
// Take the slice of new constraints.
|
||||
// (May grow during call to solveConstraints.)
|
||||
constraints := a.constraints
|
||||
a.constraints = nil
|
||||
|
||||
// Initialize points-to sets from addr-of (base) constraints.
|
||||
for _, c := range constraints {
|
||||
if c, ok := c.(*addrConstraint); ok {
|
||||
dst := a.nodes[c.dst]
|
||||
dst.solve.pts.add(c.src)
|
||||
|
||||
// Populate the worklist with nodes that point to
|
||||
// something initially (due to addrConstraints) and
|
||||
// have other constraints attached.
|
||||
// (A no-op in round 1.)
|
||||
if !dst.solve.copyTo.IsEmpty() || len(dst.solve.complex) > 0 {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach simple (copy) and complex constraints to nodes.
|
||||
var stale nodeset
|
||||
for _, c := range constraints {
|
||||
var id nodeid
|
||||
switch c := c.(type) {
|
||||
case *addrConstraint:
|
||||
// base constraints handled in previous loop
|
||||
continue
|
||||
case *copyConstraint:
|
||||
// simple (copy) constraint
|
||||
id = c.src
|
||||
a.nodes[id].solve.copyTo.add(c.dst)
|
||||
default:
|
||||
// complex constraint
|
||||
id = c.ptr()
|
||||
solve := a.nodes[id].solve
|
||||
solve.complex = append(solve.complex, c)
|
||||
}
|
||||
|
||||
if n := a.nodes[id]; !n.solve.pts.IsEmpty() {
|
||||
if !n.solve.prevPTS.IsEmpty() {
|
||||
stale.add(id)
|
||||
}
|
||||
a.addWork(id)
|
||||
}
|
||||
}
|
||||
// Apply new constraints to pre-existing PTS labels.
|
||||
var space [50]int
|
||||
for _, id := range stale.AppendTo(space[:0]) {
|
||||
n := a.nodes[nodeid(id)]
|
||||
a.solveConstraints(n, &n.solve.prevPTS)
|
||||
}
|
||||
}
|
||||
|
||||
// solveConstraints applies each resolution rule attached to node n to
|
||||
// the set of labels delta. It may generate new constraints in
|
||||
// a.constraints.
|
||||
//
|
||||
func (a *analysis) solveConstraints(n *node, delta *nodeset) {
|
||||
if delta.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
// Process complex constraints dependent on n.
|
||||
for _, c := range n.solve.complex {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tconstraint %s\n", c)
|
||||
}
|
||||
c.solve(a, delta)
|
||||
}
|
||||
|
||||
// Process copy constraints.
|
||||
var copySeen nodeset
|
||||
for _, x := range n.solve.copyTo.AppendTo(a.deltaSpace) {
|
||||
mid := nodeid(x)
|
||||
if copySeen.add(mid) {
|
||||
if a.nodes[mid].solve.pts.addAll(delta) {
|
||||
a.addWork(mid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addLabel adds label to the points-to set of ptr and reports whether the set grew.
|
||||
func (a *analysis) addLabel(ptr, label nodeid) bool {
|
||||
b := a.nodes[ptr].solve.pts.add(label)
|
||||
if b && a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\tpts(n%d) += n%d\n", ptr, label)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (a *analysis) addWork(id nodeid) {
|
||||
a.work.Insert(int(id))
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\twork: n%d\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
// onlineCopy adds a copy edge. It is called online, i.e. during
|
||||
// solving, so it adds edges and pts members directly rather than by
|
||||
// instantiating a 'constraint'.
|
||||
//
|
||||
// The size of the copy is implicitly 1.
|
||||
// It returns true if pts(dst) changed.
|
||||
//
|
||||
func (a *analysis) onlineCopy(dst, src nodeid) bool {
|
||||
if dst != src {
|
||||
if nsrc := a.nodes[src]; nsrc.solve.copyTo.add(dst) {
|
||||
if a.log != nil {
|
||||
fmt.Fprintf(a.log, "\t\t\tdynamic copy n%d <- n%d\n", dst, src)
|
||||
}
|
||||
// TODO(adonovan): most calls to onlineCopy
|
||||
// are followed by addWork, possibly batched
|
||||
// via a 'changed' flag; see if there's a
|
||||
// noticeable penalty to calling addWork here.
|
||||
return a.nodes[dst].solve.pts.addAll(&nsrc.solve.pts)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns sizeof.
|
||||
// Implicitly adds nodes to worklist.
|
||||
//
|
||||
// TODO(adonovan): now that we support a.copy() during solving, we
|
||||
// could eliminate onlineCopyN, but it's much slower. Investigate.
|
||||
//
|
||||
func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 {
|
||||
for i := uint32(0); i < sizeof; i++ {
|
||||
if a.onlineCopy(dst, src) {
|
||||
a.addWork(dst)
|
||||
}
|
||||
src++
|
||||
dst++
|
||||
}
|
||||
return sizeof
|
||||
}
|
||||
|
||||
func (c *loadConstraint) solve(a *analysis, delta *nodeset) {
|
||||
var changed bool
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
k := nodeid(x)
|
||||
koff := k + nodeid(c.offset)
|
||||
if a.onlineCopy(c.dst, koff) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *storeConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
k := nodeid(x)
|
||||
koff := k + nodeid(c.offset)
|
||||
if a.onlineCopy(koff, c.src) {
|
||||
a.addWork(koff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *offsetAddrConstraint) solve(a *analysis, delta *nodeset) {
|
||||
dst := a.nodes[c.dst]
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
k := nodeid(x)
|
||||
if dst.solve.pts.add(k + nodeid(c.offset)) {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *typeFilterConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
ifaceObj := nodeid(x)
|
||||
tDyn, _, indirect := a.taggedValue(ifaceObj)
|
||||
if indirect {
|
||||
// TODO(adonovan): we'll need to implement this
|
||||
// when we start creating indirect tagged objects.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
if types.AssignableTo(tDyn, c.typ) {
|
||||
if a.addLabel(c.dst, ifaceObj) {
|
||||
a.addWork(c.dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *untagConstraint) solve(a *analysis, delta *nodeset) {
|
||||
predicate := types.AssignableTo
|
||||
if c.exact {
|
||||
predicate = types.Identical
|
||||
}
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
ifaceObj := nodeid(x)
|
||||
tDyn, v, indirect := a.taggedValue(ifaceObj)
|
||||
if indirect {
|
||||
// TODO(adonovan): we'll need to implement this
|
||||
// when we start creating indirect tagged objects.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
if predicate(tDyn, c.typ) {
|
||||
// Copy payload sans tag to dst.
|
||||
//
|
||||
// TODO(adonovan): opt: if tDyn is
|
||||
// nonpointerlike we can skip this entire
|
||||
// constraint, perhaps. We only care about
|
||||
// pointers among the fields.
|
||||
a.onlineCopyN(c.dst, v, a.sizeof(tDyn))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *invokeConstraint) solve(a *analysis, delta *nodeset) {
|
||||
for _, x := range delta.AppendTo(a.deltaSpace) {
|
||||
ifaceObj := nodeid(x)
|
||||
tDyn, v, indirect := a.taggedValue(ifaceObj)
|
||||
if indirect {
|
||||
// TODO(adonovan): we may need to implement this if
|
||||
// we ever apply invokeConstraints to reflect.Value PTSs,
|
||||
// e.g. for (reflect.Value).Call.
|
||||
panic("indirect tagged object")
|
||||
}
|
||||
|
||||
// Look up the concrete method.
|
||||
fn := a.prog.LookupMethod(tDyn, c.method.Pkg(), c.method.Name())
|
||||
if fn == nil {
|
||||
panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, c.method))
|
||||
}
|
||||
sig := fn.Signature
|
||||
|
||||
fnObj := a.globalobj[fn] // dynamic calls use shared contour
|
||||
if fnObj == 0 {
|
||||
// a.objectNode(fn) was not called during gen phase.
|
||||
panic(fmt.Sprintf("a.globalobj[%s]==nil", fn))
|
||||
}
|
||||
|
||||
// Make callsite's fn variable point to identity of
|
||||
// concrete method. (There's no need to add it to
|
||||
// worklist since it never has attached constraints.)
|
||||
a.addLabel(c.params, fnObj)
|
||||
|
||||
// Extract value and connect to method's receiver.
|
||||
// Copy payload to method's receiver param (arg0).
|
||||
arg0 := a.funcParams(fnObj)
|
||||
recvSize := a.sizeof(sig.Recv().Type())
|
||||
a.onlineCopyN(arg0, v, recvSize)
|
||||
|
||||
src := c.params + 1 // skip past identity
|
||||
dst := arg0 + nodeid(recvSize)
|
||||
|
||||
// Copy caller's argument block to method formal parameters.
|
||||
paramsSize := a.sizeof(sig.Params())
|
||||
a.onlineCopyN(dst, src, paramsSize)
|
||||
src += nodeid(paramsSize)
|
||||
dst += nodeid(paramsSize)
|
||||
|
||||
// Copy method results to caller's result block.
|
||||
resultsSize := a.sizeof(sig.Results())
|
||||
a.onlineCopyN(src, dst, resultsSize)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *addrConstraint) solve(a *analysis, delta *nodeset) {
|
||||
panic("addr is not a complex constraint")
|
||||
}
|
||||
|
||||
func (c *copyConstraint) solve(a *analysis, delta *nodeset) {
|
||||
panic("copy is not a complex constraint")
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
// 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 pointer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/container/intsets"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// CanPoint reports whether the type T is pointerlike,
|
||||
// for the purposes of this analysis.
|
||||
func CanPoint(T types.Type) bool {
|
||||
switch T := T.(type) {
|
||||
case *types.Named:
|
||||
if obj := T.Obj(); obj.Name() == "Value" && obj.Pkg().Path() == "reflect" {
|
||||
return true // treat reflect.Value like interface{}
|
||||
}
|
||||
return CanPoint(T.Underlying())
|
||||
|
||||
case *types.Pointer, *types.Interface, *types.Map, *types.Chan, *types.Signature, *types.Slice:
|
||||
return true
|
||||
}
|
||||
|
||||
return false // array struct tuple builtin basic
|
||||
}
|
||||
|
||||
// CanHaveDynamicTypes reports whether the type T can "hold" dynamic types,
|
||||
// i.e. is an interface (incl. reflect.Type) or a reflect.Value.
|
||||
//
|
||||
func CanHaveDynamicTypes(T types.Type) bool {
|
||||
switch T := T.(type) {
|
||||
case *types.Named:
|
||||
if obj := T.Obj(); obj.Name() == "Value" && obj.Pkg().Path() == "reflect" {
|
||||
return true // reflect.Value
|
||||
}
|
||||
return CanHaveDynamicTypes(T.Underlying())
|
||||
case *types.Interface:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||
|
||||
// mustDeref returns the element type of its argument, which must be a
|
||||
// pointer; panic ensues otherwise.
|
||||
func mustDeref(typ types.Type) types.Type {
|
||||
return typ.Underlying().(*types.Pointer).Elem()
|
||||
}
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// A fieldInfo describes one subelement (node) of the flattening-out
|
||||
// of a type T: the subelement's type and its path from the root of T.
|
||||
//
|
||||
// For example, for this type:
|
||||
// type line struct{ points []struct{x, y int} }
|
||||
// flatten() of the inner struct yields the following []fieldInfo:
|
||||
// struct{ x, y int } ""
|
||||
// int ".x"
|
||||
// int ".y"
|
||||
// and flatten(line) yields:
|
||||
// struct{ points []struct{x, y int} } ""
|
||||
// struct{ x, y int } ".points[*]"
|
||||
// int ".points[*].x
|
||||
// int ".points[*].y"
|
||||
//
|
||||
type fieldInfo struct {
|
||||
typ types.Type
|
||||
|
||||
// op and tail describe the path to the element (e.g. ".a#2.b[*].c").
|
||||
op interface{} // *Array: true; *Tuple: int; *Struct: *types.Var; *Named: nil
|
||||
tail *fieldInfo
|
||||
}
|
||||
|
||||
// path returns a user-friendly string describing the subelement path.
|
||||
//
|
||||
func (fi *fieldInfo) path() string {
|
||||
var buf bytes.Buffer
|
||||
for p := fi; p != nil; p = p.tail {
|
||||
switch op := p.op.(type) {
|
||||
case bool:
|
||||
fmt.Fprintf(&buf, "[*]")
|
||||
case int:
|
||||
fmt.Fprintf(&buf, "#%d", op)
|
||||
case *types.Var:
|
||||
fmt.Fprintf(&buf, ".%s", op.Name())
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// flatten returns a list of directly contained fields in the preorder
|
||||
// traversal of the type tree of t. The resulting elements are all
|
||||
// scalars (basic types or pointerlike types), except for struct/array
|
||||
// "identity" nodes, whose type is that of the aggregate.
|
||||
//
|
||||
// reflect.Value is considered pointerlike, similar to interface{}.
|
||||
//
|
||||
// Callers must not mutate the result.
|
||||
//
|
||||
func (a *analysis) flatten(t types.Type) []*fieldInfo {
|
||||
fl, ok := a.flattenMemo[t]
|
||||
if !ok {
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
u := t.Underlying()
|
||||
if isInterface(u) {
|
||||
// Debuggability hack: don't remove
|
||||
// the named type from interfaces as
|
||||
// they're very verbose.
|
||||
fl = append(fl, &fieldInfo{typ: t})
|
||||
} else {
|
||||
fl = a.flatten(u)
|
||||
}
|
||||
|
||||
case *types.Basic,
|
||||
*types.Signature,
|
||||
*types.Chan,
|
||||
*types.Map,
|
||||
*types.Interface,
|
||||
*types.Slice,
|
||||
*types.Pointer:
|
||||
fl = append(fl, &fieldInfo{typ: t})
|
||||
|
||||
case *types.Array:
|
||||
fl = append(fl, &fieldInfo{typ: t}) // identity node
|
||||
for _, fi := range a.flatten(t.Elem()) {
|
||||
fl = append(fl, &fieldInfo{typ: fi.typ, op: true, tail: fi})
|
||||
}
|
||||
|
||||
case *types.Struct:
|
||||
fl = append(fl, &fieldInfo{typ: t}) // identity node
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
f := t.Field(i)
|
||||
for _, fi := range a.flatten(f.Type()) {
|
||||
fl = append(fl, &fieldInfo{typ: fi.typ, op: f, tail: fi})
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
// No identity node: tuples are never address-taken.
|
||||
n := t.Len()
|
||||
if n == 1 {
|
||||
// Don't add a fieldInfo link for singletons,
|
||||
// e.g. in params/results.
|
||||
fl = append(fl, a.flatten(t.At(0).Type())...)
|
||||
} else {
|
||||
for i := 0; i < n; i++ {
|
||||
f := t.At(i)
|
||||
for _, fi := range a.flatten(f.Type()) {
|
||||
fl = append(fl, &fieldInfo{typ: fi.typ, op: i, tail: fi})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic(t)
|
||||
}
|
||||
|
||||
a.flattenMemo[t] = fl
|
||||
}
|
||||
|
||||
return fl
|
||||
}
|
||||
|
||||
// sizeof returns the number of pointerlike abstractions (nodes) in the type t.
|
||||
func (a *analysis) sizeof(t types.Type) uint32 {
|
||||
return uint32(len(a.flatten(t)))
|
||||
}
|
||||
|
||||
// shouldTrack reports whether object type T contains (recursively)
|
||||
// any fields whose addresses should be tracked.
|
||||
func (a *analysis) shouldTrack(T types.Type) bool {
|
||||
if a.track == trackAll {
|
||||
return true // fast path
|
||||
}
|
||||
track, ok := a.trackTypes[T]
|
||||
if !ok {
|
||||
a.trackTypes[T] = true // break cycles conservatively
|
||||
// NB: reflect.Value, reflect.Type are pre-populated to true.
|
||||
for _, fi := range a.flatten(T) {
|
||||
switch ft := fi.typ.Underlying().(type) {
|
||||
case *types.Interface, *types.Signature:
|
||||
track = true // needed for callgraph
|
||||
case *types.Basic:
|
||||
// no-op
|
||||
case *types.Chan:
|
||||
track = a.track&trackChan != 0 || a.shouldTrack(ft.Elem())
|
||||
case *types.Map:
|
||||
track = a.track&trackMap != 0 || a.shouldTrack(ft.Key()) || a.shouldTrack(ft.Elem())
|
||||
case *types.Slice:
|
||||
track = a.track&trackSlice != 0 || a.shouldTrack(ft.Elem())
|
||||
case *types.Pointer:
|
||||
track = a.track&trackPtr != 0 || a.shouldTrack(ft.Elem())
|
||||
case *types.Array, *types.Struct:
|
||||
// No need to look at field types since they will follow (flattened).
|
||||
default:
|
||||
// Includes *types.Tuple, which are never address-taken.
|
||||
panic(ft)
|
||||
}
|
||||
if track {
|
||||
break
|
||||
}
|
||||
}
|
||||
a.trackTypes[T] = track
|
||||
if !track && a.log != nil {
|
||||
fmt.Fprintf(a.log, "\ttype not tracked: %s\n", T)
|
||||
}
|
||||
}
|
||||
return track
|
||||
}
|
||||
|
||||
// offsetOf returns the (abstract) offset of field index within struct
|
||||
// or tuple typ.
|
||||
func (a *analysis) offsetOf(typ types.Type, index int) uint32 {
|
||||
var offset uint32
|
||||
switch t := typ.Underlying().(type) {
|
||||
case *types.Tuple:
|
||||
for i := 0; i < index; i++ {
|
||||
offset += a.sizeof(t.At(i).Type())
|
||||
}
|
||||
case *types.Struct:
|
||||
offset++ // the node for the struct itself
|
||||
for i := 0; i < index; i++ {
|
||||
offset += a.sizeof(t.Field(i).Type())
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("offsetOf(%s : %T)", typ, typ))
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
// sliceToArray returns the type representing the arrays to which
|
||||
// slice type slice points.
|
||||
func sliceToArray(slice types.Type) *types.Array {
|
||||
return types.NewArray(slice.Underlying().(*types.Slice).Elem(), 1)
|
||||
}
|
||||
|
||||
// Node set -------------------------------------------------------------------
|
||||
|
||||
type nodeset struct {
|
||||
intsets.Sparse
|
||||
}
|
||||
|
||||
func (ns *nodeset) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune('{')
|
||||
var space [50]int
|
||||
for i, n := range ns.AppendTo(space[:0]) {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteRune('n')
|
||||
fmt.Fprintf(&buf, "%d", n)
|
||||
}
|
||||
buf.WriteRune('}')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (ns *nodeset) add(n nodeid) bool {
|
||||
return ns.Sparse.Insert(int(n))
|
||||
}
|
||||
|
||||
func (x *nodeset) addAll(y *nodeset) bool {
|
||||
return x.UnionWith(&y.Sparse)
|
||||
}
|
||||
|
||||
// Profiling & debugging -------------------------------------------------------
|
||||
|
||||
var timers = make(map[string]time.Time)
|
||||
|
||||
func start(name string) {
|
||||
if debugTimers {
|
||||
timers[name] = time.Now()
|
||||
log.Printf("%s...\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
func stop(name string) {
|
||||
if debugTimers {
|
||||
log.Printf("%s took %s\n", name, time.Since(timers[name]))
|
||||
}
|
||||
}
|
||||
|
||||
// diff runs the command "diff a b" and reports its success.
|
||||
func diff(a, b string) bool {
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "plan9":
|
||||
cmd = exec.Command("/bin/diff", "-c", a, b)
|
||||
default:
|
||||
cmd = exec.Command("/usr/bin/diff", "-u", a, b)
|
||||
}
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run() == nil
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
// 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 ssa
|
||||
|
||||
// Simple block optimizations to simplify the control flow graph.
|
||||
|
||||
// TODO(adonovan): opt: instead of creating several "unreachable" blocks
|
||||
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
|
||||
// to reduce garbage.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// If true, perform sanity checking and show progress at each
|
||||
// successive iteration of optimizeBlocks. Very verbose.
|
||||
const debugBlockOpt = false
|
||||
|
||||
// markReachable sets Index=-1 for all blocks reachable from b.
|
||||
func markReachable(b *BasicBlock) {
|
||||
b.Index = -1
|
||||
for _, succ := range b.Succs {
|
||||
if succ.Index == 0 {
|
||||
markReachable(succ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteUnreachableBlocks marks all reachable blocks of f and
|
||||
// eliminates (nils) all others, including possibly cyclic subgraphs.
|
||||
//
|
||||
func deleteUnreachableBlocks(f *Function) {
|
||||
const white, black = 0, -1
|
||||
// We borrow b.Index temporarily as the mark bit.
|
||||
for _, b := range f.Blocks {
|
||||
b.Index = white
|
||||
}
|
||||
markReachable(f.Blocks[0])
|
||||
if f.Recover != nil {
|
||||
markReachable(f.Recover)
|
||||
}
|
||||
for i, b := range f.Blocks {
|
||||
if b.Index == white {
|
||||
for _, c := range b.Succs {
|
||||
if c.Index == black {
|
||||
c.removePred(b) // delete white->black edge
|
||||
}
|
||||
}
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "unreachable", b)
|
||||
}
|
||||
f.Blocks[i] = nil // delete b
|
||||
}
|
||||
}
|
||||
f.removeNilBlocks()
|
||||
}
|
||||
|
||||
// jumpThreading attempts to apply simple jump-threading to block b,
|
||||
// in which a->b->c become a->c if b is just a Jump.
|
||||
// The result is true if the optimization was applied.
|
||||
//
|
||||
func jumpThreading(f *Function, b *BasicBlock) bool {
|
||||
if b.Index == 0 {
|
||||
return false // don't apply to entry block
|
||||
}
|
||||
if b.Instrs == nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := b.Instrs[0].(*Jump); !ok {
|
||||
return false // not just a jump
|
||||
}
|
||||
c := b.Succs[0]
|
||||
if c == b {
|
||||
return false // don't apply to degenerate jump-to-self.
|
||||
}
|
||||
if c.hasPhi() {
|
||||
return false // not sound without more effort
|
||||
}
|
||||
for j, a := range b.Preds {
|
||||
a.replaceSucc(b, c)
|
||||
|
||||
// If a now has two edges to c, replace its degenerate If by Jump.
|
||||
if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
|
||||
jump := new(Jump)
|
||||
jump.setBlock(a)
|
||||
a.Instrs[len(a.Instrs)-1] = jump
|
||||
a.Succs = a.Succs[:1]
|
||||
c.removePred(b)
|
||||
} else {
|
||||
if j == 0 {
|
||||
c.replacePred(b, a)
|
||||
} else {
|
||||
c.Preds = append(c.Preds, a)
|
||||
}
|
||||
}
|
||||
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
|
||||
}
|
||||
}
|
||||
f.Blocks[b.Index] = nil // delete b
|
||||
return true
|
||||
}
|
||||
|
||||
// fuseBlocks attempts to apply the block fusion optimization to block
|
||||
// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
|
||||
// The result is true if the optimization was applied.
|
||||
//
|
||||
func fuseBlocks(f *Function, a *BasicBlock) bool {
|
||||
if len(a.Succs) != 1 {
|
||||
return false
|
||||
}
|
||||
b := a.Succs[0]
|
||||
if len(b.Preds) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Degenerate &&/|| ops may result in a straight-line CFG
|
||||
// containing φ-nodes. (Ideally we'd replace such them with
|
||||
// their sole operand but that requires Referrers, built later.)
|
||||
if b.hasPhi() {
|
||||
return false // not sound without further effort
|
||||
}
|
||||
|
||||
// Eliminate jump at end of A, then copy all of B across.
|
||||
a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
|
||||
for _, instr := range b.Instrs {
|
||||
instr.setBlock(a)
|
||||
}
|
||||
|
||||
// A inherits B's successors
|
||||
a.Succs = append(a.succs2[:0], b.Succs...)
|
||||
|
||||
// Fix up Preds links of all successors of B.
|
||||
for _, c := range b.Succs {
|
||||
c.replacePred(b, a)
|
||||
}
|
||||
|
||||
if debugBlockOpt {
|
||||
fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
|
||||
}
|
||||
|
||||
f.Blocks[b.Index] = nil // delete b
|
||||
return true
|
||||
}
|
||||
|
||||
// optimizeBlocks() performs some simple block optimizations on a
|
||||
// completed function: dead block elimination, block fusion, jump
|
||||
// threading.
|
||||
//
|
||||
func optimizeBlocks(f *Function) {
|
||||
deleteUnreachableBlocks(f)
|
||||
|
||||
// Loop until no further progress.
|
||||
changed := true
|
||||
for changed {
|
||||
changed = false
|
||||
|
||||
if debugBlockOpt {
|
||||
f.WriteTo(os.Stderr)
|
||||
mustSanityCheck(f, nil)
|
||||
}
|
||||
|
||||
for _, b := range f.Blocks {
|
||||
// f.Blocks will temporarily contain nils to indicate
|
||||
// deleted blocks; we remove them at the end.
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Fuse blocks. b->c becomes bc.
|
||||
if fuseBlocks(f, b) {
|
||||
changed = true
|
||||
}
|
||||
|
||||
// a->b->c becomes a->c if b contains only a Jump.
|
||||
if jumpThreading(f, b) {
|
||||
changed = true
|
||||
continue // (b was disconnected)
|
||||
}
|
||||
}
|
||||
}
|
||||
f.removeNilBlocks()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,168 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file defines the Const SSA value type.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// NewConst returns a new constant of the specified value and type.
|
||||
// val must be valid according to the specification of Const.Value.
|
||||
//
|
||||
func NewConst(val exact.Value, typ types.Type) *Const {
|
||||
return &Const{typ, val}
|
||||
}
|
||||
|
||||
// intConst returns an 'int' constant that evaluates to i.
|
||||
// (i is an int64 in case the host is narrower than the target.)
|
||||
func intConst(i int64) *Const {
|
||||
return NewConst(exact.MakeInt64(i), tInt)
|
||||
}
|
||||
|
||||
// nilConst returns a nil constant of the specified type, which may
|
||||
// be any reference type, including interfaces.
|
||||
//
|
||||
func nilConst(typ types.Type) *Const {
|
||||
return NewConst(nil, typ)
|
||||
}
|
||||
|
||||
// stringConst returns a 'string' constant that evaluates to s.
|
||||
func stringConst(s string) *Const {
|
||||
return NewConst(exact.MakeString(s), tString)
|
||||
}
|
||||
|
||||
// zeroConst returns a new "zero" constant of the specified type,
|
||||
// which must not be an array or struct type: the zero values of
|
||||
// aggregates are well-defined but cannot be represented by Const.
|
||||
//
|
||||
func zeroConst(t types.Type) *Const {
|
||||
switch t := t.(type) {
|
||||
case *types.Basic:
|
||||
switch {
|
||||
case t.Info()&types.IsBoolean != 0:
|
||||
return NewConst(exact.MakeBool(false), t)
|
||||
case t.Info()&types.IsNumeric != 0:
|
||||
return NewConst(exact.MakeInt64(0), t)
|
||||
case t.Info()&types.IsString != 0:
|
||||
return NewConst(exact.MakeString(""), t)
|
||||
case t.Kind() == types.UnsafePointer:
|
||||
fallthrough
|
||||
case t.Kind() == types.UntypedNil:
|
||||
return nilConst(t)
|
||||
default:
|
||||
panic(fmt.Sprint("zeroConst for unexpected type:", t))
|
||||
}
|
||||
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
|
||||
return nilConst(t)
|
||||
case *types.Named:
|
||||
return NewConst(zeroConst(t.Underlying()).Value, t)
|
||||
case *types.Array, *types.Struct, *types.Tuple:
|
||||
panic(fmt.Sprint("zeroConst applied to aggregate:", t))
|
||||
}
|
||||
panic(fmt.Sprint("zeroConst: unexpected ", t))
|
||||
}
|
||||
|
||||
func (c *Const) RelString(from *types.Package) string {
|
||||
var s string
|
||||
if c.Value == nil {
|
||||
s = "nil"
|
||||
} else if c.Value.Kind() == exact.String {
|
||||
s = exact.StringVal(c.Value)
|
||||
const max = 20
|
||||
// TODO(adonovan): don't cut a rune in half.
|
||||
if len(s) > max {
|
||||
s = s[:max-3] + "..." // abbreviate
|
||||
}
|
||||
s = strconv.Quote(s)
|
||||
} else {
|
||||
s = c.Value.String()
|
||||
}
|
||||
return s + ":" + relType(c.Type(), from)
|
||||
}
|
||||
|
||||
func (c *Const) Name() string {
|
||||
return c.RelString(nil)
|
||||
}
|
||||
|
||||
func (c *Const) String() string {
|
||||
return c.Name()
|
||||
}
|
||||
|
||||
func (c *Const) Type() types.Type {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c *Const) Referrers() *[]Instruction {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Const) Parent() *Function { return nil }
|
||||
|
||||
func (c *Const) Pos() token.Pos {
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
// IsNil returns true if this constant represents a typed or untyped nil value.
|
||||
func (c *Const) IsNil() bool {
|
||||
return c.Value == nil
|
||||
}
|
||||
|
||||
// Int64 returns the numeric value of this constant truncated to fit
|
||||
// a signed 64-bit integer.
|
||||
//
|
||||
func (c *Const) Int64() int64 {
|
||||
switch x := c.Value; x.Kind() {
|
||||
case exact.Int:
|
||||
if i, ok := exact.Int64Val(x); ok {
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
case exact.Float:
|
||||
f, _ := exact.Float64Val(x)
|
||||
return int64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Uint64 returns the numeric value of this constant truncated to fit
|
||||
// an unsigned 64-bit integer.
|
||||
//
|
||||
func (c *Const) Uint64() uint64 {
|
||||
switch x := c.Value; x.Kind() {
|
||||
case exact.Int:
|
||||
if u, ok := exact.Uint64Val(x); ok {
|
||||
return u
|
||||
}
|
||||
return 0
|
||||
case exact.Float:
|
||||
f, _ := exact.Float64Val(x)
|
||||
return uint64(f)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected constant value: %T", c.Value))
|
||||
}
|
||||
|
||||
// Float64 returns the numeric value of this constant truncated to fit
|
||||
// a float64.
|
||||
//
|
||||
func (c *Const) Float64() float64 {
|
||||
f, _ := exact.Float64Val(c.Value)
|
||||
return f
|
||||
}
|
||||
|
||||
// Complex128 returns the complex value of this constant truncated to
|
||||
// fit a complex128.
|
||||
//
|
||||
func (c *Const) Complex128() complex128 {
|
||||
re, _ := exact.Float64Val(exact.Real(c.Value))
|
||||
im, _ := exact.Float64Val(exact.Imag(c.Value))
|
||||
return complex(re, im)
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file implements the CREATE phase of SSA construction.
|
||||
// See builder.go for explanation.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// NewProgram returns a new SSA Program.
|
||||
//
|
||||
// mode controls diagnostics and checking during SSA construction.
|
||||
//
|
||||
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||
prog := &Program{
|
||||
Fset: fset,
|
||||
imported: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
thunks: make(map[selectionKey]*Function),
|
||||
bounds: make(map[*types.Func]*Function),
|
||||
mode: mode,
|
||||
}
|
||||
|
||||
h := typeutil.MakeHasher() // protected by methodsMu, in effect
|
||||
prog.methodSets.SetHasher(h)
|
||||
prog.canon.SetHasher(h)
|
||||
|
||||
return prog
|
||||
}
|
||||
|
||||
// memberFromObject populates package pkg with a member for the
|
||||
// typechecker object obj.
|
||||
//
|
||||
// For objects from Go source code, syntax is the associated syntax
|
||||
// tree (for funcs and vars only); it will be used during the build
|
||||
// phase.
|
||||
//
|
||||
func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
|
||||
name := obj.Name()
|
||||
switch obj := obj.(type) {
|
||||
case *types.TypeName:
|
||||
pkg.Members[name] = &Type{
|
||||
object: obj,
|
||||
pkg: pkg,
|
||||
}
|
||||
|
||||
case *types.Const:
|
||||
c := &NamedConst{
|
||||
object: obj,
|
||||
Value: NewConst(obj.Val(), obj.Type()),
|
||||
pkg: pkg,
|
||||
}
|
||||
pkg.values[obj] = c.Value
|
||||
pkg.Members[name] = c
|
||||
|
||||
case *types.Var:
|
||||
g := &Global{
|
||||
Pkg: pkg,
|
||||
name: name,
|
||||
object: obj,
|
||||
typ: types.NewPointer(obj.Type()), // address
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
pkg.values[obj] = g
|
||||
pkg.Members[name] = g
|
||||
|
||||
case *types.Func:
|
||||
sig := obj.Type().(*types.Signature)
|
||||
if sig.Recv() == nil && name == "init" {
|
||||
pkg.ninit++
|
||||
name = fmt.Sprintf("init#%d", pkg.ninit)
|
||||
}
|
||||
fn := &Function{
|
||||
name: name,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
syntax: syntax,
|
||||
pos: obj.Pos(),
|
||||
Pkg: pkg,
|
||||
Prog: pkg.Prog,
|
||||
}
|
||||
if syntax == nil {
|
||||
fn.Synthetic = "loaded from gc object file"
|
||||
}
|
||||
|
||||
pkg.values[obj] = fn
|
||||
if sig.Recv() == nil {
|
||||
pkg.Members[name] = fn // package-level function
|
||||
}
|
||||
|
||||
default: // (incl. *types.Package)
|
||||
panic("unexpected Object type: " + obj.String())
|
||||
}
|
||||
}
|
||||
|
||||
// membersFromDecl populates package pkg with members for each
|
||||
// typechecker object (var, func, const or type) associated with the
|
||||
// specified decl.
|
||||
//
|
||||
func membersFromDecl(pkg *Package, decl ast.Decl) {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl: // import, const, type or var
|
||||
switch decl.Tok {
|
||||
case token.CONST:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case token.VAR:
|
||||
for _, spec := range decl.Specs {
|
||||
for _, id := range spec.(*ast.ValueSpec).Names {
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case token.TYPE:
|
||||
for _, spec := range decl.Specs {
|
||||
id := spec.(*ast.TypeSpec).Name
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
id := decl.Name
|
||||
if !isBlankIdent(id) {
|
||||
memberFromObject(pkg, pkg.info.Defs[id], decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePackage constructs and returns an SSA Package from the
|
||||
// specified type-checked, error-free file ASTs, and populates its
|
||||
// Members mapping.
|
||||
//
|
||||
// importable determines whether this package should be returned by a
|
||||
// subsequent call to ImportedPackage(pkg.Path()).
|
||||
//
|
||||
// The real work of building SSA form for each function is not done
|
||||
// until a subsequent call to Package.Build().
|
||||
//
|
||||
func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
|
||||
p := &Package{
|
||||
Prog: prog,
|
||||
Members: make(map[string]Member),
|
||||
values: make(map[types.Object]Value),
|
||||
Object: pkg,
|
||||
info: info, // transient (CREATE and BUILD phases)
|
||||
files: files, // transient (CREATE and BUILD phases)
|
||||
}
|
||||
|
||||
// Add init() function.
|
||||
p.init = &Function{
|
||||
name: "init",
|
||||
Signature: new(types.Signature),
|
||||
Synthetic: "package initializer",
|
||||
Pkg: p,
|
||||
Prog: prog,
|
||||
}
|
||||
p.Members[p.init.name] = p.init
|
||||
|
||||
// CREATE phase.
|
||||
// Allocate all package members: vars, funcs, consts and types.
|
||||
if len(files) > 0 {
|
||||
// Go source package.
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
membersFromDecl(p, decl)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// GC-compiled binary package.
|
||||
// No code.
|
||||
// No position information.
|
||||
scope := p.Object.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
obj := scope.Lookup(name)
|
||||
memberFromObject(p, obj, nil)
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
named := obj.Type().(*types.Named)
|
||||
for i, n := 0, named.NumMethods(); i < n; i++ {
|
||||
memberFromObject(p, named.Method(i), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prog.mode&BareInits == 0 {
|
||||
// Add initializer guard variable.
|
||||
initguard := &Global{
|
||||
Pkg: p,
|
||||
name: "init$guard",
|
||||
typ: types.NewPointer(tBool),
|
||||
}
|
||||
p.Members[initguard.Name()] = initguard
|
||||
}
|
||||
|
||||
if prog.mode&GlobalDebug != 0 {
|
||||
p.SetDebugMode(true)
|
||||
}
|
||||
|
||||
if prog.mode&PrintPackages != 0 {
|
||||
printMu.Lock()
|
||||
p.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if importable {
|
||||
prog.imported[p.Object.Path()] = p
|
||||
}
|
||||
prog.packages[p.Object] = p
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// printMu serializes printing of Packages/Functions to stdout.
|
||||
var printMu sync.Mutex
|
||||
|
||||
// AllPackages returns a new slice containing all packages in the
|
||||
// program prog in unspecified order.
|
||||
//
|
||||
func (prog *Program) AllPackages() []*Package {
|
||||
pkgs := make([]*Package, 0, len(prog.packages))
|
||||
for _, pkg := range prog.packages {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// ImportedPackage returns the importable SSA Package whose import
|
||||
// path is path, or nil if no such SSA package has been created.
|
||||
//
|
||||
// Not all packages are importable. For example, no import
|
||||
// declaration can resolve to the x_test package created by 'go test'
|
||||
// or the ad-hoc main package created 'go build foo.go'.
|
||||
//
|
||||
func (prog *Program) ImportedPackage(path string) *Package {
|
||||
return prog.imported[path]
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// 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 ssa defines a representation of the elements of Go programs
|
||||
// (packages, types, functions, variables and constants) using a
|
||||
// static single-assignment (SSA) form intermediate representation
|
||||
// (IR) for the bodies of functions.
|
||||
//
|
||||
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
|
||||
//
|
||||
// For an introduction to SSA form, see
|
||||
// http://en.wikipedia.org/wiki/Static_single_assignment_form.
|
||||
// This page provides a broader reading list:
|
||||
// http://www.dcs.gla.ac.uk/~jsinger/ssa.html.
|
||||
//
|
||||
// The level of abstraction of the SSA form is intentionally close to
|
||||
// the source language to facilitate construction of source analysis
|
||||
// tools. It is not intended for machine code generation.
|
||||
//
|
||||
// All looping, branching and switching constructs are replaced with
|
||||
// unstructured control flow. Higher-level control flow constructs
|
||||
// such as multi-way branch can be reconstructed as needed; see
|
||||
// ssautil.Switches() for an example.
|
||||
//
|
||||
// To construct an SSA-form program, call ssautil.CreateProgram on a
|
||||
// loader.Program, a set of type-checked packages created from
|
||||
// parsed Go source files. The resulting ssa.Program contains all the
|
||||
// packages and their members, but SSA code is not created for
|
||||
// function bodies until a subsequent call to (*Package).Build.
|
||||
//
|
||||
// The builder initially builds a naive SSA form in which all local
|
||||
// variables are addresses of stack locations with explicit loads and
|
||||
// stores. Registerisation of eligible locals and φ-node insertion
|
||||
// using dominance and dataflow are then performed as a second pass
|
||||
// called "lifting" to improve the accuracy and performance of
|
||||
// subsequent analyses; this pass can be skipped by setting the
|
||||
// NaiveForm builder flag.
|
||||
//
|
||||
// The primary interfaces of this package are:
|
||||
//
|
||||
// - Member: a named member of a Go package.
|
||||
// - Value: an expression that yields a value.
|
||||
// - Instruction: a statement that consumes values and performs computation.
|
||||
// - Node: a Value or Instruction (emphasizing its membership in the SSA value graph)
|
||||
//
|
||||
// A computation that yields a result implements both the Value and
|
||||
// Instruction interfaces. The following table shows for each
|
||||
// concrete type which of these interfaces it implements.
|
||||
//
|
||||
// Value? Instruction? Member?
|
||||
// *Alloc ✔ ✔
|
||||
// *BinOp ✔ ✔
|
||||
// *Builtin ✔
|
||||
// *Call ✔ ✔
|
||||
// *ChangeInterface ✔ ✔
|
||||
// *ChangeType ✔ ✔
|
||||
// *Const ✔
|
||||
// *Convert ✔ ✔
|
||||
// *DebugRef ✔
|
||||
// *Defer ✔
|
||||
// *Extract ✔ ✔
|
||||
// *Field ✔ ✔
|
||||
// *FieldAddr ✔ ✔
|
||||
// *FreeVar ✔
|
||||
// *Function ✔ ✔ (func)
|
||||
// *Global ✔ ✔ (var)
|
||||
// *Go ✔
|
||||
// *If ✔
|
||||
// *Index ✔ ✔
|
||||
// *IndexAddr ✔ ✔
|
||||
// *Jump ✔
|
||||
// *Lookup ✔ ✔
|
||||
// *MakeChan ✔ ✔
|
||||
// *MakeClosure ✔ ✔
|
||||
// *MakeInterface ✔ ✔
|
||||
// *MakeMap ✔ ✔
|
||||
// *MakeSlice ✔ ✔
|
||||
// *MapUpdate ✔
|
||||
// *NamedConst ✔ (const)
|
||||
// *Next ✔ ✔
|
||||
// *Panic ✔
|
||||
// *Parameter ✔
|
||||
// *Phi ✔ ✔
|
||||
// *Range ✔ ✔
|
||||
// *Return ✔
|
||||
// *RunDefers ✔
|
||||
// *Select ✔ ✔
|
||||
// *Send ✔
|
||||
// *Slice ✔ ✔
|
||||
// *Store ✔
|
||||
// *Type ✔ (type)
|
||||
// *TypeAssert ✔ ✔
|
||||
// *UnOp ✔ ✔
|
||||
//
|
||||
// Other key types in this package include: Program, Package, Function
|
||||
// and BasicBlock.
|
||||
//
|
||||
// The program representation constructed by this package is fully
|
||||
// resolved internally, i.e. it does not rely on the names of Values,
|
||||
// Packages, Functions, Types or BasicBlocks for the correct
|
||||
// interpretation of the program. Only the identities of objects and
|
||||
// the topology of the SSA and type graphs are semantically
|
||||
// significant. (There is one exception: Ids, used to identify field
|
||||
// and method names, contain strings.) Avoidance of name-based
|
||||
// operations simplifies the implementation of subsequent passes and
|
||||
// can make them very efficient. Many objects are nonetheless named
|
||||
// to aid in debugging, but it is not essential that the names be
|
||||
// either accurate or unambiguous. The public API exposes a number of
|
||||
// name-based maps for client convenience.
|
||||
//
|
||||
// The ssa/ssautil package provides various utilities that depend only
|
||||
// on the public API of this package.
|
||||
//
|
||||
// TODO(adonovan): Consider the exceptional control-flow implications
|
||||
// of defer and recover().
|
||||
//
|
||||
// TODO(adonovan): write a how-to document for all the various cases
|
||||
// of trying to determine corresponding elements across the four
|
||||
// domains of source locations, ast.Nodes, types.Objects,
|
||||
// ssa.Values/Instructions.
|
||||
//
|
||||
package ssa // import "golang.org/x/tools/go/ssa"
|
|
@ -0,0 +1,341 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file defines algorithms related to dominance.
|
||||
|
||||
// Dominator tree construction ----------------------------------------
|
||||
//
|
||||
// We use the algorithm described in Lengauer & Tarjan. 1979. A fast
|
||||
// algorithm for finding dominators in a flowgraph.
|
||||
// http://doi.acm.org/10.1145/357062.357071
|
||||
//
|
||||
// We also apply the optimizations to SLT described in Georgiadis et
|
||||
// al, Finding Dominators in Practice, JGAA 2006,
|
||||
// http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf
|
||||
// to avoid the need for buckets of size > 1.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Idom returns the block that immediately dominates b:
|
||||
// its parent in the dominator tree, if any.
|
||||
// Neither the entry node (b.Index==0) nor recover node
|
||||
// (b==b.Parent().Recover()) have a parent.
|
||||
//
|
||||
func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom }
|
||||
|
||||
// Dominees returns the list of blocks that b immediately dominates:
|
||||
// its children in the dominator tree.
|
||||
//
|
||||
func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children }
|
||||
|
||||
// Dominates reports whether b dominates c.
|
||||
func (b *BasicBlock) Dominates(c *BasicBlock) bool {
|
||||
return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post
|
||||
}
|
||||
|
||||
type byDomPreorder []*BasicBlock
|
||||
|
||||
func (a byDomPreorder) Len() int { return len(a) }
|
||||
func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre }
|
||||
|
||||
// DomPreorder returns a new slice containing the blocks of f in
|
||||
// dominator tree preorder.
|
||||
//
|
||||
func (f *Function) DomPreorder() []*BasicBlock {
|
||||
n := len(f.Blocks)
|
||||
order := make(byDomPreorder, n, n)
|
||||
copy(order, f.Blocks)
|
||||
sort.Sort(order)
|
||||
return order
|
||||
}
|
||||
|
||||
// domInfo contains a BasicBlock's dominance information.
|
||||
type domInfo struct {
|
||||
idom *BasicBlock // immediate dominator (parent in domtree)
|
||||
children []*BasicBlock // nodes immediately dominated by this one
|
||||
pre, post int32 // pre- and post-order numbering within domtree
|
||||
}
|
||||
|
||||
// ltState holds the working state for Lengauer-Tarjan algorithm
|
||||
// (during which domInfo.pre is repurposed for CFG DFS preorder number).
|
||||
type ltState struct {
|
||||
// Each slice is indexed by b.Index.
|
||||
sdom []*BasicBlock // b's semidominator
|
||||
parent []*BasicBlock // b's parent in DFS traversal of CFG
|
||||
ancestor []*BasicBlock // b's ancestor with least sdom
|
||||
}
|
||||
|
||||
// dfs implements the depth-first search part of the LT algorithm.
|
||||
func (lt *ltState) dfs(v *BasicBlock, i int32, preorder []*BasicBlock) int32 {
|
||||
preorder[i] = v
|
||||
v.dom.pre = i // For now: DFS preorder of spanning tree of CFG
|
||||
i++
|
||||
lt.sdom[v.Index] = v
|
||||
lt.link(nil, v)
|
||||
for _, w := range v.Succs {
|
||||
if lt.sdom[w.Index] == nil {
|
||||
lt.parent[w.Index] = v
|
||||
i = lt.dfs(w, i, preorder)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// eval implements the EVAL part of the LT algorithm.
|
||||
func (lt *ltState) eval(v *BasicBlock) *BasicBlock {
|
||||
// TODO(adonovan): opt: do path compression per simple LT.
|
||||
u := v
|
||||
for ; lt.ancestor[v.Index] != nil; v = lt.ancestor[v.Index] {
|
||||
if lt.sdom[v.Index].dom.pre < lt.sdom[u.Index].dom.pre {
|
||||
u = v
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// link implements the LINK part of the LT algorithm.
|
||||
func (lt *ltState) link(v, w *BasicBlock) {
|
||||
lt.ancestor[w.Index] = v
|
||||
}
|
||||
|
||||
// buildDomTree computes the dominator tree of f using the LT algorithm.
|
||||
// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run).
|
||||
//
|
||||
func buildDomTree(f *Function) {
|
||||
// The step numbers refer to the original LT paper; the
|
||||
// reordering is due to Georgiadis.
|
||||
|
||||
// Clear any previous domInfo.
|
||||
for _, b := range f.Blocks {
|
||||
b.dom = domInfo{}
|
||||
}
|
||||
|
||||
n := len(f.Blocks)
|
||||
// Allocate space for 5 contiguous [n]*BasicBlock arrays:
|
||||
// sdom, parent, ancestor, preorder, buckets.
|
||||
space := make([]*BasicBlock, 5*n, 5*n)
|
||||
lt := ltState{
|
||||
sdom: space[0:n],
|
||||
parent: space[n : 2*n],
|
||||
ancestor: space[2*n : 3*n],
|
||||
}
|
||||
|
||||
// Step 1. Number vertices by depth-first preorder.
|
||||
preorder := space[3*n : 4*n]
|
||||
root := f.Blocks[0]
|
||||
prenum := lt.dfs(root, 0, preorder)
|
||||
recover := f.Recover
|
||||
if recover != nil {
|
||||
lt.dfs(recover, prenum, preorder)
|
||||
}
|
||||
|
||||
buckets := space[4*n : 5*n]
|
||||
copy(buckets, preorder)
|
||||
|
||||
// In reverse preorder...
|
||||
for i := int32(n) - 1; i > 0; i-- {
|
||||
w := preorder[i]
|
||||
|
||||
// Step 3. Implicitly define the immediate dominator of each node.
|
||||
for v := buckets[i]; v != w; v = buckets[v.dom.pre] {
|
||||
u := lt.eval(v)
|
||||
if lt.sdom[u.Index].dom.pre < i {
|
||||
v.dom.idom = u
|
||||
} else {
|
||||
v.dom.idom = w
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2. Compute the semidominators of all nodes.
|
||||
lt.sdom[w.Index] = lt.parent[w.Index]
|
||||
for _, v := range w.Preds {
|
||||
u := lt.eval(v)
|
||||
if lt.sdom[u.Index].dom.pre < lt.sdom[w.Index].dom.pre {
|
||||
lt.sdom[w.Index] = lt.sdom[u.Index]
|
||||
}
|
||||
}
|
||||
|
||||
lt.link(lt.parent[w.Index], w)
|
||||
|
||||
if lt.parent[w.Index] == lt.sdom[w.Index] {
|
||||
w.dom.idom = lt.parent[w.Index]
|
||||
} else {
|
||||
buckets[i] = buckets[lt.sdom[w.Index].dom.pre]
|
||||
buckets[lt.sdom[w.Index].dom.pre] = w
|
||||
}
|
||||
}
|
||||
|
||||
// The final 'Step 3' is now outside the loop.
|
||||
for v := buckets[0]; v != root; v = buckets[v.dom.pre] {
|
||||
v.dom.idom = root
|
||||
}
|
||||
|
||||
// Step 4. Explicitly define the immediate dominator of each
|
||||
// node, in preorder.
|
||||
for _, w := range preorder[1:] {
|
||||
if w == root || w == recover {
|
||||
w.dom.idom = nil
|
||||
} else {
|
||||
if w.dom.idom != lt.sdom[w.Index] {
|
||||
w.dom.idom = w.dom.idom.dom.idom
|
||||
}
|
||||
// Calculate Children relation as inverse of Idom.
|
||||
w.dom.idom.dom.children = append(w.dom.idom.dom.children, w)
|
||||
}
|
||||
}
|
||||
|
||||
pre, post := numberDomTree(root, 0, 0)
|
||||
if recover != nil {
|
||||
numberDomTree(recover, pre, post)
|
||||
}
|
||||
|
||||
// printDomTreeDot(os.Stderr, f) // debugging
|
||||
// printDomTreeText(os.Stderr, root, 0) // debugging
|
||||
|
||||
if f.Prog.mode&SanityCheckFunctions != 0 {
|
||||
sanityCheckDomTree(f)
|
||||
}
|
||||
}
|
||||
|
||||
// numberDomTree sets the pre- and post-order numbers of a depth-first
|
||||
// traversal of the dominator tree rooted at v. These are used to
|
||||
// answer dominance queries in constant time.
|
||||
//
|
||||
func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) {
|
||||
v.dom.pre = pre
|
||||
pre++
|
||||
for _, child := range v.dom.children {
|
||||
pre, post = numberDomTree(child, pre, post)
|
||||
}
|
||||
v.dom.post = post
|
||||
post++
|
||||
return pre, post
|
||||
}
|
||||
|
||||
// Testing utilities ----------------------------------------
|
||||
|
||||
// sanityCheckDomTree checks the correctness of the dominator tree
|
||||
// computed by the LT algorithm by comparing against the dominance
|
||||
// relation computed by a naive Kildall-style forward dataflow
|
||||
// analysis (Algorithm 10.16 from the "Dragon" book).
|
||||
//
|
||||
func sanityCheckDomTree(f *Function) {
|
||||
n := len(f.Blocks)
|
||||
|
||||
// D[i] is the set of blocks that dominate f.Blocks[i],
|
||||
// represented as a bit-set of block indices.
|
||||
D := make([]big.Int, n)
|
||||
|
||||
one := big.NewInt(1)
|
||||
|
||||
// all is the set of all blocks; constant.
|
||||
var all big.Int
|
||||
all.Set(one).Lsh(&all, uint(n)).Sub(&all, one)
|
||||
|
||||
// Initialization.
|
||||
for i, b := range f.Blocks {
|
||||
if i == 0 || b == f.Recover {
|
||||
// A root is dominated only by itself.
|
||||
D[i].SetBit(&D[0], 0, 1)
|
||||
} else {
|
||||
// All other blocks are (initially) dominated
|
||||
// by every block.
|
||||
D[i].Set(&all)
|
||||
}
|
||||
}
|
||||
|
||||
// Iteration until fixed point.
|
||||
for changed := true; changed; {
|
||||
changed = false
|
||||
for i, b := range f.Blocks {
|
||||
if i == 0 || b == f.Recover {
|
||||
continue
|
||||
}
|
||||
// Compute intersection across predecessors.
|
||||
var x big.Int
|
||||
x.Set(&all)
|
||||
for _, pred := range b.Preds {
|
||||
x.And(&x, &D[pred.Index])
|
||||
}
|
||||
x.SetBit(&x, i, 1) // a block always dominates itself.
|
||||
if D[i].Cmp(&x) != 0 {
|
||||
D[i].Set(&x)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the entire relation. O(n^2).
|
||||
// The Recover block (if any) must be treated specially so we skip it.
|
||||
ok := true
|
||||
for i := 0; i < n; i++ {
|
||||
for j := 0; j < n; j++ {
|
||||
b, c := f.Blocks[i], f.Blocks[j]
|
||||
if c == f.Recover {
|
||||
continue
|
||||
}
|
||||
actual := b.Dominates(c)
|
||||
expected := D[j].Bit(i) == 1
|
||||
if actual != expected {
|
||||
fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preorder := f.DomPreorder()
|
||||
for _, b := range f.Blocks {
|
||||
if got := preorder[b.dom.pre]; got != b {
|
||||
fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
panic("sanityCheckDomTree failed for " + f.String())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Printing functions ----------------------------------------
|
||||
|
||||
// printDomTree prints the dominator tree as text, using indentation.
|
||||
func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) {
|
||||
fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v)
|
||||
for _, child := range v.dom.children {
|
||||
printDomTreeText(buf, child, indent+1)
|
||||
}
|
||||
}
|
||||
|
||||
// printDomTreeDot prints the dominator tree of f in AT&T GraphViz
|
||||
// (.dot) format.
|
||||
func printDomTreeDot(buf *bytes.Buffer, f *Function) {
|
||||
fmt.Fprintln(buf, "//", f)
|
||||
fmt.Fprintln(buf, "digraph domtree {")
|
||||
for i, b := range f.Blocks {
|
||||
v := b.dom
|
||||
fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post)
|
||||
// TODO(adonovan): improve appearance of edges
|
||||
// belonging to both dominator tree and CFG.
|
||||
|
||||
// Dominator tree edge.
|
||||
if i != 0 {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre)
|
||||
}
|
||||
// CFG edges.
|
||||
for _, pred := range b.Preds {
|
||||
fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(buf, "}")
|
||||
}
|
|
@ -0,0 +1,469 @@
|
|||
// 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 ssa
|
||||
|
||||
// Helpers for emitting SSA instructions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// emitNew emits to f a new (heap Alloc) instruction allocating an
|
||||
// object of type typ. pos is the optional source location.
|
||||
//
|
||||
func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc {
|
||||
v := &Alloc{Heap: true}
|
||||
v.setType(types.NewPointer(typ))
|
||||
v.setPos(pos)
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emitLoad emits to f an instruction to load the address addr into a
|
||||
// new temporary, and returns the value so defined.
|
||||
//
|
||||
func emitLoad(f *Function, addr Value) *UnOp {
|
||||
v := &UnOp{Op: token.MUL, X: addr}
|
||||
v.setType(deref(addr.Type()))
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
|
||||
// expression e with value v.
|
||||
//
|
||||
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
|
||||
if !f.debugInfo() {
|
||||
return // debugging not enabled
|
||||
}
|
||||
if v == nil || e == nil {
|
||||
panic("nil")
|
||||
}
|
||||
var obj types.Object
|
||||
e = unparen(e)
|
||||
if id, ok := e.(*ast.Ident); ok {
|
||||
if isBlankIdent(id) {
|
||||
return
|
||||
}
|
||||
obj = f.Pkg.objectOf(id)
|
||||
switch obj.(type) {
|
||||
case *types.Nil, *types.Const, *types.Builtin:
|
||||
return
|
||||
}
|
||||
}
|
||||
f.emit(&DebugRef{
|
||||
X: v,
|
||||
Expr: e,
|
||||
IsAddr: isAddr,
|
||||
object: obj,
|
||||
})
|
||||
}
|
||||
|
||||
// emitArith emits to f code to compute the binary operation op(x, y)
|
||||
// where op is an eager shift, logical or arithmetic operation.
|
||||
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
|
||||
// non-eager operations.)
|
||||
//
|
||||
func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.Pos) Value {
|
||||
switch op {
|
||||
case token.SHL, token.SHR:
|
||||
x = emitConv(f, x, t)
|
||||
// y may be signed or an 'untyped' constant.
|
||||
// TODO(adonovan): whence signed values?
|
||||
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 {
|
||||
y = emitConv(f, y, types.Typ[types.Uint64])
|
||||
}
|
||||
|
||||
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
|
||||
x = emitConv(f, x, t)
|
||||
y = emitConv(f, y, t)
|
||||
|
||||
default:
|
||||
panic("illegal op in emitArith: " + op.String())
|
||||
|
||||
}
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setPos(pos)
|
||||
v.setType(t)
|
||||
return f.emit(v)
|
||||
}
|
||||
|
||||
// emitCompare emits to f code compute the boolean result of
|
||||
// comparison comparison 'x op y'.
|
||||
//
|
||||
func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
|
||||
xt := x.Type().Underlying()
|
||||
yt := y.Type().Underlying()
|
||||
|
||||
// Special case to optimise a tagless SwitchStmt so that
|
||||
// these are equivalent
|
||||
// switch { case e: ...}
|
||||
// switch true { case e: ... }
|
||||
// if e==true { ... }
|
||||
// even in the case when e's type is an interface.
|
||||
// TODO(adonovan): opt: generalise to x==true, false!=y, etc.
|
||||
if x == vTrue && op == token.EQL {
|
||||
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
|
||||
return y
|
||||
}
|
||||
}
|
||||
|
||||
if types.Identical(xt, yt) {
|
||||
// no conversion necessary
|
||||
} else if _, ok := xt.(*types.Interface); ok {
|
||||
y = emitConv(f, y, x.Type())
|
||||
} else if _, ok := yt.(*types.Interface); ok {
|
||||
x = emitConv(f, x, y.Type())
|
||||
} else if _, ok := x.(*Const); ok {
|
||||
x = emitConv(f, x, y.Type())
|
||||
} else if _, ok := y.(*Const); ok {
|
||||
y = emitConv(f, y, x.Type())
|
||||
} else {
|
||||
// other cases, e.g. channels. No-op.
|
||||
}
|
||||
|
||||
v := &BinOp{
|
||||
Op: op,
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
v.setPos(pos)
|
||||
v.setType(tBool)
|
||||
return f.emit(v)
|
||||
}
|
||||
|
||||
// isValuePreserving returns true if a conversion from ut_src to
|
||||
// ut_dst is value-preserving, i.e. just a change of type.
|
||||
// Precondition: neither argument is a named type.
|
||||
//
|
||||
func isValuePreserving(ut_src, ut_dst types.Type) bool {
|
||||
// Identical underlying types?
|
||||
if types.Identical(ut_dst, ut_src) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch ut_dst.(type) {
|
||||
case *types.Chan:
|
||||
// Conversion between channel types?
|
||||
_, ok := ut_src.(*types.Chan)
|
||||
return ok
|
||||
|
||||
case *types.Pointer:
|
||||
// Conversion between pointers with identical base types?
|
||||
_, ok := ut_src.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// emitConv emits to f code to convert Value val to exactly type typ,
|
||||
// and returns the converted value. Implicit conversions are required
|
||||
// by language assignability rules in assignments, parameter passing,
|
||||
// etc. Conversions cannot fail dynamically.
|
||||
//
|
||||
func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||
t_src := val.Type()
|
||||
|
||||
// Identical types? Conversion is a no-op.
|
||||
if types.Identical(t_src, typ) {
|
||||
return val
|
||||
}
|
||||
|
||||
ut_dst := typ.Underlying()
|
||||
ut_src := t_src.Underlying()
|
||||
|
||||
// Just a change of type, but not value or representation?
|
||||
if isValuePreserving(ut_src, ut_dst) {
|
||||
c := &ChangeType{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
// Conversion to, or construction of a value of, an interface type?
|
||||
if _, ok := ut_dst.(*types.Interface); ok {
|
||||
// Assignment from one interface type to another?
|
||||
if _, ok := ut_src.(*types.Interface); ok {
|
||||
c := &ChangeInterface{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
// Untyped nil constant? Return interface-typed nil constant.
|
||||
if ut_src == tUntypedNil {
|
||||
return nilConst(typ)
|
||||
}
|
||||
|
||||
// Convert (non-nil) "untyped" literals to their default type.
|
||||
if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 {
|
||||
val = emitConv(f, val, DefaultType(ut_src))
|
||||
}
|
||||
|
||||
f.Pkg.Prog.needMethodsOf(val.Type())
|
||||
mi := &MakeInterface{X: val}
|
||||
mi.setType(typ)
|
||||
return f.emit(mi)
|
||||
}
|
||||
|
||||
// Conversion of a compile-time constant value?
|
||||
if c, ok := val.(*Const); ok {
|
||||
if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() {
|
||||
// Conversion of a compile-time constant to
|
||||
// another constant type results in a new
|
||||
// constant of the destination type and
|
||||
// (initially) the same abstract value.
|
||||
// We don't truncate the value yet.
|
||||
return NewConst(c.Value, typ)
|
||||
}
|
||||
|
||||
// We're converting from constant to non-constant type,
|
||||
// e.g. string -> []byte/[]rune.
|
||||
}
|
||||
|
||||
// A representation-changing conversion?
|
||||
// At least one of {ut_src,ut_dst} must be *Basic.
|
||||
// (The other may be []byte or []rune.)
|
||||
_, ok1 := ut_src.(*types.Basic)
|
||||
_, ok2 := ut_dst.(*types.Basic)
|
||||
if ok1 || ok2 {
|
||||
c := &Convert{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
|
||||
}
|
||||
|
||||
// emitStore emits to f an instruction to store value val at location
|
||||
// addr, applying implicit conversions as required by assignability rules.
|
||||
//
|
||||
func emitStore(f *Function, addr, val Value, pos token.Pos) *Store {
|
||||
s := &Store{
|
||||
Addr: addr,
|
||||
Val: emitConv(f, val, deref(addr.Type())),
|
||||
pos: pos,
|
||||
}
|
||||
f.emit(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// emitJump emits to f a jump to target, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
//
|
||||
func emitJump(f *Function, target *BasicBlock) {
|
||||
b := f.currentBlock
|
||||
b.emit(new(Jump))
|
||||
addEdge(b, target)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitIf emits to f a conditional jump to tblock or fblock based on
|
||||
// cond, and updates the control-flow graph.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
//
|
||||
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) {
|
||||
b := f.currentBlock
|
||||
b.emit(&If{Cond: cond})
|
||||
addEdge(b, tblock)
|
||||
addEdge(b, fblock)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitExtract emits to f an instruction to extract the index'th
|
||||
// component of tuple. It returns the extracted value.
|
||||
//
|
||||
func emitExtract(f *Function, tuple Value, index int) Value {
|
||||
e := &Extract{Tuple: tuple, Index: index}
|
||||
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
|
||||
return f.emit(e)
|
||||
}
|
||||
|
||||
// emitTypeAssert emits to f a type assertion value := x.(t) and
|
||||
// returns the value. x.Type() must be an interface.
|
||||
//
|
||||
func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||
a := &TypeAssert{X: x, AssertedType: t}
|
||||
a.setPos(pos)
|
||||
a.setType(t)
|
||||
return f.emit(a)
|
||||
}
|
||||
|
||||
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
|
||||
// a (value, ok) tuple. x.Type() must be an interface.
|
||||
//
|
||||
func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||
a := &TypeAssert{
|
||||
X: x,
|
||||
AssertedType: t,
|
||||
CommaOk: true,
|
||||
}
|
||||
a.setPos(pos)
|
||||
a.setType(types.NewTuple(
|
||||
newVar("value", t),
|
||||
varOk,
|
||||
))
|
||||
return f.emit(a)
|
||||
}
|
||||
|
||||
// emitTailCall emits to f a function call in tail position. The
|
||||
// caller is responsible for all fields of 'call' except its type.
|
||||
// Intended for wrapper methods.
|
||||
// Precondition: f does/will not use deferred procedure calls.
|
||||
// Postcondition: f.currentBlock is nil.
|
||||
//
|
||||
func emitTailCall(f *Function, call *Call) {
|
||||
tresults := f.Signature.Results()
|
||||
nr := tresults.Len()
|
||||
if nr == 1 {
|
||||
call.typ = tresults.At(0).Type()
|
||||
} else {
|
||||
call.typ = tresults
|
||||
}
|
||||
tuple := f.emit(call)
|
||||
var ret Return
|
||||
switch nr {
|
||||
case 0:
|
||||
// no-op
|
||||
case 1:
|
||||
ret.Results = []Value{tuple}
|
||||
default:
|
||||
for i := 0; i < nr; i++ {
|
||||
v := emitExtract(f, tuple, i)
|
||||
// TODO(adonovan): in principle, this is required:
|
||||
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
|
||||
// but in practice emitTailCall is only used when
|
||||
// the types exactly match.
|
||||
ret.Results = append(ret.Results, v)
|
||||
}
|
||||
}
|
||||
f.emit(&ret)
|
||||
f.currentBlock = nil
|
||||
}
|
||||
|
||||
// emitImplicitSelections emits to f code to apply the sequence of
|
||||
// implicit field selections specified by indices to base value v, and
|
||||
// returns the selected value.
|
||||
//
|
||||
// If v is the address of a struct, the result will be the address of
|
||||
// a field; if it is the value of a struct, the result will be the
|
||||
// value of a field.
|
||||
//
|
||||
func emitImplicitSelections(f *Function, v Value, indices []int) Value {
|
||||
for _, index := range indices {
|
||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||
|
||||
if isPointer(v.Type()) {
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr)
|
||||
// Load the field's value iff indirectly embedded.
|
||||
if isPointer(fld.Type()) {
|
||||
v = emitLoad(f, v)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// emitFieldSelection emits to f code to select the index'th field of v.
|
||||
//
|
||||
// If wantAddr, the input must be a pointer-to-struct and the result
|
||||
// will be the field's address; otherwise the result will be the
|
||||
// field's value.
|
||||
// Ident id is used for position and debug info.
|
||||
//
|
||||
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
|
||||
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
||||
if isPointer(v.Type()) {
|
||||
instr := &FieldAddr{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(id.Pos())
|
||||
instr.setType(types.NewPointer(fld.Type()))
|
||||
v = f.emit(instr)
|
||||
// Load the field's value iff we don't want its address.
|
||||
if !wantAddr {
|
||||
v = emitLoad(f, v)
|
||||
}
|
||||
} else {
|
||||
instr := &Field{
|
||||
X: v,
|
||||
Field: index,
|
||||
}
|
||||
instr.setPos(id.Pos())
|
||||
instr.setType(fld.Type())
|
||||
v = f.emit(instr)
|
||||
}
|
||||
emitDebugRef(f, id, v, wantAddr)
|
||||
return v
|
||||
}
|
||||
|
||||
// zeroValue emits to f code to produce a zero value of type t,
|
||||
// and returns it.
|
||||
//
|
||||
func zeroValue(f *Function, t types.Type) Value {
|
||||
switch t.Underlying().(type) {
|
||||
case *types.Struct, *types.Array:
|
||||
return emitLoad(f, f.addLocal(t, token.NoPos))
|
||||
default:
|
||||
return zeroConst(t)
|
||||
}
|
||||
}
|
||||
|
||||
// createRecoverBlock emits to f a block of code to return after a
|
||||
// recovered panic, and sets f.Recover to it.
|
||||
//
|
||||
// If f's result parameters are named, the code loads and returns
|
||||
// their current values, otherwise it returns the zero values of their
|
||||
// type.
|
||||
//
|
||||
// Idempotent.
|
||||
//
|
||||
func createRecoverBlock(f *Function) {
|
||||
if f.Recover != nil {
|
||||
return // already created
|
||||
}
|
||||
saved := f.currentBlock
|
||||
|
||||
f.Recover = f.newBasicBlock("recover")
|
||||
f.currentBlock = f.Recover
|
||||
|
||||
var results []Value
|
||||
if f.namedResults != nil {
|
||||
// Reload NRPs to form value tuple.
|
||||
for _, r := range f.namedResults {
|
||||
results = append(results, emitLoad(f, r))
|
||||
}
|
||||
} else {
|
||||
R := f.Signature.Results()
|
||||
for i, n := 0, R.Len(); i < n; i++ {
|
||||
T := R.At(i).Type()
|
||||
|
||||
// Return zero value of each result type.
|
||||
results = append(results, zeroValue(f, T))
|
||||
}
|
||||
}
|
||||
f.emit(&Return{Results: results})
|
||||
|
||||
f.currentBlock = saved
|
||||
}
|
|
@ -0,0 +1,690 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file implements the Function and BasicBlock types.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// addEdge adds a control-flow graph edge from from to to.
|
||||
func addEdge(from, to *BasicBlock) {
|
||||
from.Succs = append(from.Succs, to)
|
||||
to.Preds = append(to.Preds, from)
|
||||
}
|
||||
|
||||
// Parent returns the function that contains block b.
|
||||
func (b *BasicBlock) Parent() *Function { return b.parent }
|
||||
|
||||
// String returns a human-readable label of this block.
|
||||
// It is not guaranteed unique within the function.
|
||||
//
|
||||
func (b *BasicBlock) String() string {
|
||||
return fmt.Sprintf("%d", b.Index)
|
||||
}
|
||||
|
||||
// emit appends an instruction to the current basic block.
|
||||
// If the instruction defines a Value, it is returned.
|
||||
//
|
||||
func (b *BasicBlock) emit(i Instruction) Value {
|
||||
i.setBlock(b)
|
||||
b.Instrs = append(b.Instrs, i)
|
||||
v, _ := i.(Value)
|
||||
return v
|
||||
}
|
||||
|
||||
// predIndex returns the i such that b.Preds[i] == c or panics if
|
||||
// there is none.
|
||||
func (b *BasicBlock) predIndex(c *BasicBlock) int {
|
||||
for i, pred := range b.Preds {
|
||||
if pred == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("no edge %s -> %s", c, b))
|
||||
}
|
||||
|
||||
// hasPhi returns true if b.Instrs contains φ-nodes.
|
||||
func (b *BasicBlock) hasPhi() bool {
|
||||
_, ok := b.Instrs[0].(*Phi)
|
||||
return ok
|
||||
}
|
||||
|
||||
// phis returns the prefix of b.Instrs containing all the block's φ-nodes.
|
||||
func (b *BasicBlock) phis() []Instruction {
|
||||
for i, instr := range b.Instrs {
|
||||
if _, ok := instr.(*Phi); !ok {
|
||||
return b.Instrs[:i]
|
||||
}
|
||||
}
|
||||
return nil // unreachable in well-formed blocks
|
||||
}
|
||||
|
||||
// replacePred replaces all occurrences of p in b's predecessor list with q.
|
||||
// Ordinarily there should be at most one.
|
||||
//
|
||||
func (b *BasicBlock) replacePred(p, q *BasicBlock) {
|
||||
for i, pred := range b.Preds {
|
||||
if pred == p {
|
||||
b.Preds[i] = q
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replaceSucc replaces all occurrences of p in b's successor list with q.
|
||||
// Ordinarily there should be at most one.
|
||||
//
|
||||
func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
|
||||
for i, succ := range b.Succs {
|
||||
if succ == p {
|
||||
b.Succs[i] = q
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removePred removes all occurrences of p in b's
|
||||
// predecessor list and φ-nodes.
|
||||
// Ordinarily there should be at most one.
|
||||
//
|
||||
func (b *BasicBlock) removePred(p *BasicBlock) {
|
||||
phis := b.phis()
|
||||
|
||||
// We must preserve edge order for φ-nodes.
|
||||
j := 0
|
||||
for i, pred := range b.Preds {
|
||||
if pred != p {
|
||||
b.Preds[j] = b.Preds[i]
|
||||
// Strike out φ-edge too.
|
||||
for _, instr := range phis {
|
||||
phi := instr.(*Phi)
|
||||
phi.Edges[j] = phi.Edges[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out b.Preds[j:] and φ-edges[j:] to aid GC.
|
||||
for i := j; i < len(b.Preds); i++ {
|
||||
b.Preds[i] = nil
|
||||
for _, instr := range phis {
|
||||
instr.(*Phi).Edges[i] = nil
|
||||
}
|
||||
}
|
||||
b.Preds = b.Preds[:j]
|
||||
for _, instr := range phis {
|
||||
phi := instr.(*Phi)
|
||||
phi.Edges = phi.Edges[:j]
|
||||
}
|
||||
}
|
||||
|
||||
// Destinations associated with unlabelled for/switch/select stmts.
|
||||
// We push/pop one of these as we enter/leave each construct and for
|
||||
// each BranchStmt we scan for the innermost target of the right type.
|
||||
//
|
||||
type targets struct {
|
||||
tail *targets // rest of stack
|
||||
_break *BasicBlock
|
||||
_continue *BasicBlock
|
||||
_fallthrough *BasicBlock
|
||||
}
|
||||
|
||||
// Destinations associated with a labelled block.
|
||||
// We populate these as labels are encountered in forward gotos or
|
||||
// labelled statements.
|
||||
//
|
||||
type lblock struct {
|
||||
_goto *BasicBlock
|
||||
_break *BasicBlock
|
||||
_continue *BasicBlock
|
||||
}
|
||||
|
||||
// labelledBlock returns the branch target associated with the
|
||||
// specified label, creating it if needed.
|
||||
//
|
||||
func (f *Function) labelledBlock(label *ast.Ident) *lblock {
|
||||
lb := f.lblocks[label.Obj]
|
||||
if lb == nil {
|
||||
lb = &lblock{_goto: f.newBasicBlock(label.Name)}
|
||||
if f.lblocks == nil {
|
||||
f.lblocks = make(map[*ast.Object]*lblock)
|
||||
}
|
||||
f.lblocks[label.Obj] = lb
|
||||
}
|
||||
return lb
|
||||
}
|
||||
|
||||
// addParam adds a (non-escaping) parameter to f.Params of the
|
||||
// specified name, type and source position.
|
||||
//
|
||||
func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter {
|
||||
v := &Parameter{
|
||||
name: name,
|
||||
typ: typ,
|
||||
pos: pos,
|
||||
parent: f,
|
||||
}
|
||||
f.Params = append(f.Params, v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (f *Function) addParamObj(obj types.Object) *Parameter {
|
||||
name := obj.Name()
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("arg%d", len(f.Params))
|
||||
}
|
||||
param := f.addParam(name, obj.Type(), obj.Pos())
|
||||
param.object = obj
|
||||
return param
|
||||
}
|
||||
|
||||
// addSpilledParam declares a parameter that is pre-spilled to the
|
||||
// stack; the function body will load/store the spilled location.
|
||||
// Subsequent lifting will eliminate spills where possible.
|
||||
//
|
||||
func (f *Function) addSpilledParam(obj types.Object) {
|
||||
param := f.addParamObj(obj)
|
||||
spill := &Alloc{Comment: obj.Name()}
|
||||
spill.setType(types.NewPointer(obj.Type()))
|
||||
spill.setPos(obj.Pos())
|
||||
f.objects[obj] = spill
|
||||
f.Locals = append(f.Locals, spill)
|
||||
f.emit(spill)
|
||||
f.emit(&Store{Addr: spill, Val: param})
|
||||
}
|
||||
|
||||
// startBody initializes the function prior to generating SSA code for its body.
|
||||
// Precondition: f.Type() already set.
|
||||
//
|
||||
func (f *Function) startBody() {
|
||||
f.currentBlock = f.newBasicBlock("entry")
|
||||
f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init
|
||||
}
|
||||
|
||||
// createSyntacticParams populates f.Params and generates code (spills
|
||||
// and named result locals) for all the parameters declared in the
|
||||
// syntax. In addition it populates the f.objects mapping.
|
||||
//
|
||||
// Preconditions:
|
||||
// f.startBody() was called.
|
||||
// Postcondition:
|
||||
// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
|
||||
//
|
||||
func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) {
|
||||
// Receiver (at most one inner iteration).
|
||||
if recv != nil {
|
||||
for _, field := range recv.List {
|
||||
for _, n := range field.Names {
|
||||
f.addSpilledParam(f.Pkg.info.Defs[n])
|
||||
}
|
||||
// Anonymous receiver? No need to spill.
|
||||
if field.Names == nil {
|
||||
f.addParamObj(f.Signature.Recv())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters.
|
||||
if functype.Params != nil {
|
||||
n := len(f.Params) // 1 if has recv, 0 otherwise
|
||||
for _, field := range functype.Params.List {
|
||||
for _, n := range field.Names {
|
||||
f.addSpilledParam(f.Pkg.info.Defs[n])
|
||||
}
|
||||
// Anonymous parameter? No need to spill.
|
||||
if field.Names == nil {
|
||||
f.addParamObj(f.Signature.Params().At(len(f.Params) - n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Named results.
|
||||
if functype.Results != nil {
|
||||
for _, field := range functype.Results.List {
|
||||
// Implicit "var" decl of locals for named results.
|
||||
for _, n := range field.Names {
|
||||
f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// numberRegisters assigns numbers to all SSA registers
|
||||
// (value-defining Instructions) in f, to aid debugging.
|
||||
// (Non-Instruction Values are named at construction.)
|
||||
//
|
||||
func numberRegisters(f *Function) {
|
||||
v := 0
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr.(type) {
|
||||
case Value:
|
||||
instr.(interface {
|
||||
setNum(int)
|
||||
}).setNum(v)
|
||||
v++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildReferrers populates the def/use information in all non-nil
|
||||
// Value.Referrers slice.
|
||||
// Precondition: all such slices are initially empty.
|
||||
func buildReferrers(f *Function) {
|
||||
var rands []*Value
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
rands = instr.Operands(rands[:0]) // recycle storage
|
||||
for _, rand := range rands {
|
||||
if r := *rand; r != nil {
|
||||
if ref := r.Referrers(); ref != nil {
|
||||
*ref = append(*ref, instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finishBody() finalizes the function after SSA code generation of its body.
|
||||
func (f *Function) finishBody() {
|
||||
f.objects = nil
|
||||
f.currentBlock = nil
|
||||
f.lblocks = nil
|
||||
|
||||
// Don't pin the AST in memory (except in debug mode).
|
||||
if n := f.syntax; n != nil && !f.debugInfo() {
|
||||
f.syntax = extentNode{n.Pos(), n.End()}
|
||||
}
|
||||
|
||||
// Remove from f.Locals any Allocs that escape to the heap.
|
||||
j := 0
|
||||
for _, l := range f.Locals {
|
||||
if !l.Heap {
|
||||
f.Locals[j] = l
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out f.Locals[j:] to aid GC.
|
||||
for i := j; i < len(f.Locals); i++ {
|
||||
f.Locals[i] = nil
|
||||
}
|
||||
f.Locals = f.Locals[:j]
|
||||
|
||||
optimizeBlocks(f)
|
||||
|
||||
buildReferrers(f)
|
||||
|
||||
buildDomTree(f)
|
||||
|
||||
if f.Prog.mode&NaiveForm == 0 {
|
||||
// For debugging pre-state of lifting pass:
|
||||
// numberRegisters(f)
|
||||
// f.WriteTo(os.Stderr)
|
||||
lift(f)
|
||||
}
|
||||
|
||||
f.namedResults = nil // (used by lifting)
|
||||
|
||||
numberRegisters(f)
|
||||
|
||||
if f.Prog.mode&PrintFunctions != 0 {
|
||||
printMu.Lock()
|
||||
f.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if f.Prog.mode&SanityCheckFunctions != 0 {
|
||||
mustSanityCheck(f, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// removeNilBlocks eliminates nils from f.Blocks and updates each
|
||||
// BasicBlock.Index. Use this after any pass that may delete blocks.
|
||||
//
|
||||
func (f *Function) removeNilBlocks() {
|
||||
j := 0
|
||||
for _, b := range f.Blocks {
|
||||
if b != nil {
|
||||
b.Index = j
|
||||
f.Blocks[j] = b
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out f.Blocks[j:] to aid GC.
|
||||
for i := j; i < len(f.Blocks); i++ {
|
||||
f.Blocks[i] = nil
|
||||
}
|
||||
f.Blocks = f.Blocks[:j]
|
||||
}
|
||||
|
||||
// SetDebugMode sets the debug mode for package pkg. If true, all its
|
||||
// functions will include full debug info. This greatly increases the
|
||||
// size of the instruction stream, and causes Functions to depend upon
|
||||
// the ASTs, potentially keeping them live in memory for longer.
|
||||
//
|
||||
func (pkg *Package) SetDebugMode(debug bool) {
|
||||
// TODO(adonovan): do we want ast.File granularity?
|
||||
pkg.debug = debug
|
||||
}
|
||||
|
||||
// debugInfo reports whether debug info is wanted for this function.
|
||||
func (f *Function) debugInfo() bool {
|
||||
return f.Pkg != nil && f.Pkg.debug
|
||||
}
|
||||
|
||||
// addNamedLocal creates a local variable, adds it to function f and
|
||||
// returns it. Its name and type are taken from obj. Subsequent
|
||||
// calls to f.lookup(obj) will return the same local.
|
||||
//
|
||||
func (f *Function) addNamedLocal(obj types.Object) *Alloc {
|
||||
l := f.addLocal(obj.Type(), obj.Pos())
|
||||
l.Comment = obj.Name()
|
||||
f.objects[obj] = l
|
||||
return l
|
||||
}
|
||||
|
||||
func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc {
|
||||
return f.addNamedLocal(f.Pkg.info.Defs[id])
|
||||
}
|
||||
|
||||
// addLocal creates an anonymous local variable of type typ, adds it
|
||||
// to function f and returns it. pos is the optional source location.
|
||||
//
|
||||
func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc {
|
||||
v := &Alloc{}
|
||||
v.setType(types.NewPointer(typ))
|
||||
v.setPos(pos)
|
||||
f.Locals = append(f.Locals, v)
|
||||
f.emit(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// lookup returns the address of the named variable identified by obj
|
||||
// that is local to function f or one of its enclosing functions.
|
||||
// If escaping, the reference comes from a potentially escaping pointer
|
||||
// expression and the referent must be heap-allocated.
|
||||
//
|
||||
func (f *Function) lookup(obj types.Object, escaping bool) Value {
|
||||
if v, ok := f.objects[obj]; ok {
|
||||
if alloc, ok := v.(*Alloc); ok && escaping {
|
||||
alloc.Heap = true
|
||||
}
|
||||
return v // function-local var (address)
|
||||
}
|
||||
|
||||
// Definition must be in an enclosing function;
|
||||
// plumb it through intervening closures.
|
||||
if f.parent == nil {
|
||||
panic("no ssa.Value for " + obj.String())
|
||||
}
|
||||
outer := f.parent.lookup(obj, true) // escaping
|
||||
v := &FreeVar{
|
||||
name: obj.Name(),
|
||||
typ: outer.Type(),
|
||||
pos: outer.Pos(),
|
||||
outer: outer,
|
||||
parent: f,
|
||||
}
|
||||
f.objects[obj] = v
|
||||
f.FreeVars = append(f.FreeVars, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// emit emits the specified instruction to function f.
|
||||
func (f *Function) emit(instr Instruction) Value {
|
||||
return f.currentBlock.emit(instr)
|
||||
}
|
||||
|
||||
// RelString returns the full name of this function, qualified by
|
||||
// package name, receiver type, etc.
|
||||
//
|
||||
// The specific formatting rules are not guaranteed and may change.
|
||||
//
|
||||
// Examples:
|
||||
// "math.IsNaN" // a package-level function
|
||||
// "(*bytes.Buffer).Bytes" // a declared method or a wrapper
|
||||
// "(*bytes.Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
|
||||
// "(*bytes.Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
|
||||
// "main.main$1" // an anonymous function in main
|
||||
// "main.init#1" // a declared init function
|
||||
// "main.init" // the synthesized package initializer
|
||||
//
|
||||
// When these functions are referred to from within the same package
|
||||
// (i.e. from == f.Pkg.Object), they are rendered without the package path.
|
||||
// For example: "IsNaN", "(*Buffer).Bytes", etc.
|
||||
//
|
||||
// All non-synthetic functions have distinct package-qualified names.
|
||||
// (But two methods may have the same name "(T).f" if one is a synthetic
|
||||
// wrapper promoting a non-exported method "f" from another package; in
|
||||
// that case, the strings are equal but the identifiers "f" are distinct.)
|
||||
//
|
||||
func (f *Function) RelString(from *types.Package) string {
|
||||
// Anonymous?
|
||||
if f.parent != nil {
|
||||
// An anonymous function's Name() looks like "parentName$1",
|
||||
// but its String() should include the type/package/etc.
|
||||
parent := f.parent.RelString(from)
|
||||
for i, anon := range f.parent.AnonFuncs {
|
||||
if anon == f {
|
||||
return fmt.Sprintf("%s$%d", parent, 1+i)
|
||||
}
|
||||
}
|
||||
|
||||
return f.name // should never happen
|
||||
}
|
||||
|
||||
// Method (declared or wrapper)?
|
||||
if recv := f.Signature.Recv(); recv != nil {
|
||||
return f.relMethod(from, recv.Type())
|
||||
}
|
||||
|
||||
// Thunk?
|
||||
if f.method != nil {
|
||||
return f.relMethod(from, f.method.Recv())
|
||||
}
|
||||
|
||||
// Bound?
|
||||
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
|
||||
return f.relMethod(from, f.FreeVars[0].Type())
|
||||
}
|
||||
|
||||
// Package-level function?
|
||||
// Prefix with package name for cross-package references only.
|
||||
if p := f.pkgobj(); p != nil && p != from {
|
||||
return fmt.Sprintf("%s.%s", p.Path(), f.name)
|
||||
}
|
||||
|
||||
// Unknown.
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
|
||||
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
|
||||
}
|
||||
|
||||
// writeSignature writes to buf the signature sig in declaration syntax.
|
||||
func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *types.Signature, params []*Parameter) {
|
||||
buf.WriteString("func ")
|
||||
if recv := sig.Recv(); recv != nil {
|
||||
buf.WriteString("(")
|
||||
if n := params[0].Name(); n != "" {
|
||||
buf.WriteString(n)
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
types.WriteType(buf, params[0].Type(), types.RelativeTo(from))
|
||||
buf.WriteString(") ")
|
||||
}
|
||||
buf.WriteString(name)
|
||||
types.WriteSignature(buf, sig, types.RelativeTo(from))
|
||||
}
|
||||
|
||||
func (f *Function) pkgobj() *types.Package {
|
||||
if f.Pkg != nil {
|
||||
return f.Pkg.Object
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer
|
||||
|
||||
func (f *Function) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WriteFunction(&buf, f)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WriteFunction writes to buf a human-readable "disassembly" of f.
|
||||
func WriteFunction(buf *bytes.Buffer, f *Function) {
|
||||
fmt.Fprintf(buf, "# Name: %s\n", f.String())
|
||||
if f.Pkg != nil {
|
||||
fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Object.Path())
|
||||
}
|
||||
if syn := f.Synthetic; syn != "" {
|
||||
fmt.Fprintln(buf, "# Synthetic:", syn)
|
||||
}
|
||||
if pos := f.Pos(); pos.IsValid() {
|
||||
fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos))
|
||||
}
|
||||
|
||||
if f.parent != nil {
|
||||
fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name())
|
||||
}
|
||||
|
||||
if f.Recover != nil {
|
||||
fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
|
||||
}
|
||||
|
||||
from := f.pkgobj()
|
||||
|
||||
if f.FreeVars != nil {
|
||||
buf.WriteString("# Free variables:\n")
|
||||
for i, fv := range f.FreeVars {
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, fv.Name(), relType(fv.Type(), from))
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Locals) > 0 {
|
||||
buf.WriteString("# Locals:\n")
|
||||
for i, l := range f.Locals {
|
||||
fmt.Fprintf(buf, "# % 3d:\t%s %s\n", i, l.Name(), relType(deref(l.Type()), from))
|
||||
}
|
||||
}
|
||||
writeSignature(buf, from, f.Name(), f.Signature, f.Params)
|
||||
buf.WriteString(":\n")
|
||||
|
||||
if f.Blocks == nil {
|
||||
buf.WriteString("\t(external)\n")
|
||||
}
|
||||
|
||||
// NB. column calculations are confused by non-ASCII
|
||||
// characters and assume 8-space tabs.
|
||||
const punchcard = 80 // for old time's sake.
|
||||
const tabwidth = 8
|
||||
for _, b := range f.Blocks {
|
||||
if b == nil {
|
||||
// Corrupt CFG.
|
||||
fmt.Fprintf(buf, ".nil:\n")
|
||||
continue
|
||||
}
|
||||
n, _ := fmt.Fprintf(buf, "%d:", b.Index)
|
||||
bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs))
|
||||
fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg)
|
||||
|
||||
if false { // CFG debugging
|
||||
fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs)
|
||||
}
|
||||
for _, instr := range b.Instrs {
|
||||
buf.WriteString("\t")
|
||||
switch v := instr.(type) {
|
||||
case Value:
|
||||
l := punchcard - tabwidth
|
||||
// Left-align the instruction.
|
||||
if name := v.Name(); name != "" {
|
||||
n, _ := fmt.Fprintf(buf, "%s = ", name)
|
||||
l -= n
|
||||
}
|
||||
n, _ := buf.WriteString(instr.String())
|
||||
l -= n
|
||||
// Right-align the type if there's space.
|
||||
if t := v.Type(); t != nil {
|
||||
buf.WriteByte(' ')
|
||||
ts := relType(t, from)
|
||||
l -= len(ts) + len(" ") // (spaces before and after type)
|
||||
if l > 0 {
|
||||
fmt.Fprintf(buf, "%*s", l, "")
|
||||
}
|
||||
buf.WriteString(ts)
|
||||
}
|
||||
case nil:
|
||||
// Be robust against bad transforms.
|
||||
buf.WriteString("<deleted>")
|
||||
default:
|
||||
buf.WriteString(instr.String())
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
// newBasicBlock adds to f a new basic block and returns it. It does
|
||||
// not automatically become the current block for subsequent calls to emit.
|
||||
// comment is an optional string for more readable debugging output.
|
||||
//
|
||||
func (f *Function) newBasicBlock(comment string) *BasicBlock {
|
||||
b := &BasicBlock{
|
||||
Index: len(f.Blocks),
|
||||
Comment: comment,
|
||||
parent: f,
|
||||
}
|
||||
b.Succs = b.succs2[:0]
|
||||
f.Blocks = append(f.Blocks, b)
|
||||
return b
|
||||
}
|
||||
|
||||
// NewFunction returns a new synthetic Function instance belonging to
|
||||
// prog, with its name and signature fields set as specified.
|
||||
//
|
||||
// The caller is responsible for initializing the remaining fields of
|
||||
// the function object, e.g. Pkg, Params, Blocks.
|
||||
//
|
||||
// It is practically impossible for clients to construct well-formed
|
||||
// SSA functions/packages/programs directly, so we assume this is the
|
||||
// job of the Builder alone. NewFunction exists to provide clients a
|
||||
// little flexibility. For example, analysis tools may wish to
|
||||
// construct fake Functions for the root of the callgraph, a fake
|
||||
// "reflect" package, etc.
|
||||
//
|
||||
// TODO(adonovan): think harder about the API here.
|
||||
//
|
||||
func (prog *Program) NewFunction(name string, sig *types.Signature, provenance string) *Function {
|
||||
return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance}
|
||||
}
|
||||
|
||||
type extentNode [2]token.Pos
|
||||
|
||||
func (n extentNode) Pos() token.Pos { return n[0] }
|
||||
func (n extentNode) End() token.Pos { return n[1] }
|
||||
|
||||
// Syntax returns an ast.Node whose Pos/End methods provide the
|
||||
// lexical extent of the function if it was defined by Go source code
|
||||
// (f.Synthetic==""), or nil otherwise.
|
||||
//
|
||||
// If f was built with debug information (see Package.SetDebugRef),
|
||||
// the result is the *ast.FuncDecl or *ast.FuncLit that declared the
|
||||
// function. Otherwise, it is an opaque Node providing only position
|
||||
// information; this avoids pinning the AST in memory.
|
||||
//
|
||||
func (f *Function) Syntax() ast.Node { return f.syntax }
|
|
@ -0,0 +1,599 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file defines the lifting pass which tries to "lift" Alloc
|
||||
// cells (new/local variables) into SSA registers, replacing loads
|
||||
// with the dominating stored value, eliminating loads and stores, and
|
||||
// inserting φ-nodes as needed.
|
||||
|
||||
// Cited papers and resources:
|
||||
//
|
||||
// Ron Cytron et al. 1991. Efficiently computing SSA form...
|
||||
// http://doi.acm.org/10.1145/115372.115320
|
||||
//
|
||||
// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm.
|
||||
// Software Practice and Experience 2001, 4:1-10.
|
||||
// http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
|
||||
//
|
||||
// Daniel Berlin, llvmdev mailing list, 2012.
|
||||
// http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
|
||||
// (Be sure to expand the whole thread.)
|
||||
|
||||
// TODO(adonovan): opt: there are many optimizations worth evaluating, and
|
||||
// the conventional wisdom for SSA construction is that a simple
|
||||
// algorithm well engineered often beats those of better asymptotic
|
||||
// complexity on all but the most egregious inputs.
|
||||
//
|
||||
// Danny Berlin suggests that the Cooper et al. algorithm for
|
||||
// computing the dominance frontier is superior to Cytron et al.
|
||||
// Furthermore he recommends that rather than computing the DF for the
|
||||
// whole function then renaming all alloc cells, it may be cheaper to
|
||||
// compute the DF for each alloc cell separately and throw it away.
|
||||
//
|
||||
// Consider exploiting liveness information to avoid creating dead
|
||||
// φ-nodes which we then immediately remove.
|
||||
//
|
||||
// Integrate lifting with scalar replacement of aggregates (SRA) since
|
||||
// the two are synergistic.
|
||||
//
|
||||
// Also see many other "TODO: opt" suggestions in the code.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// If true, perform sanity checking and show diagnostic information at
|
||||
// each step of lifting. Very verbose.
|
||||
const debugLifting = false
|
||||
|
||||
// domFrontier maps each block to the set of blocks in its dominance
|
||||
// frontier. The outer slice is conceptually a map keyed by
|
||||
// Block.Index. The inner slice is conceptually a set, possibly
|
||||
// containing duplicates.
|
||||
//
|
||||
// TODO(adonovan): opt: measure impact of dups; consider a packed bit
|
||||
// representation, e.g. big.Int, and bitwise parallel operations for
|
||||
// the union step in the Children loop.
|
||||
//
|
||||
// domFrontier's methods mutate the slice's elements but not its
|
||||
// length, so their receivers needn't be pointers.
|
||||
//
|
||||
type domFrontier [][]*BasicBlock
|
||||
|
||||
func (df domFrontier) add(u, v *BasicBlock) {
|
||||
p := &df[u.Index]
|
||||
*p = append(*p, v)
|
||||
}
|
||||
|
||||
// build builds the dominance frontier df for the dominator (sub)tree
|
||||
// rooted at u, using the Cytron et al. algorithm.
|
||||
//
|
||||
// TODO(adonovan): opt: consider Berlin approach, computing pruned SSA
|
||||
// by pruning the entire IDF computation, rather than merely pruning
|
||||
// the DF -> IDF step.
|
||||
func (df domFrontier) build(u *BasicBlock) {
|
||||
// Encounter each node u in postorder of dom tree.
|
||||
for _, child := range u.dom.children {
|
||||
df.build(child)
|
||||
}
|
||||
for _, vb := range u.Succs {
|
||||
if v := vb.dom; v.idom != u {
|
||||
df.add(u, vb)
|
||||
}
|
||||
}
|
||||
for _, w := range u.dom.children {
|
||||
for _, vb := range df[w.Index] {
|
||||
// TODO(adonovan): opt: use word-parallel bitwise union.
|
||||
if v := vb.dom; v.idom != u {
|
||||
df.add(u, vb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildDomFrontier(fn *Function) domFrontier {
|
||||
df := make(domFrontier, len(fn.Blocks))
|
||||
df.build(fn.Blocks[0])
|
||||
if fn.Recover != nil {
|
||||
df.build(fn.Recover)
|
||||
}
|
||||
return df
|
||||
}
|
||||
|
||||
func removeInstr(refs []Instruction, instr Instruction) []Instruction {
|
||||
i := 0
|
||||
for _, ref := range refs {
|
||||
if ref == instr {
|
||||
continue
|
||||
}
|
||||
refs[i] = ref
|
||||
i++
|
||||
}
|
||||
for j := i; j != len(refs); j++ {
|
||||
refs[j] = nil // aid GC
|
||||
}
|
||||
return refs[:i]
|
||||
}
|
||||
|
||||
// lift attempts to replace local and new Allocs accessed only with
|
||||
// load/store by SSA registers, inserting φ-nodes where necessary.
|
||||
// The result is a program in classical pruned SSA form.
|
||||
//
|
||||
// Preconditions:
|
||||
// - fn has no dead blocks (blockopt has run).
|
||||
// - Def/use info (Operands and Referrers) is up-to-date.
|
||||
// - The dominator tree is up-to-date.
|
||||
//
|
||||
func lift(fn *Function) {
|
||||
// TODO(adonovan): opt: lots of little optimizations may be
|
||||
// worthwhile here, especially if they cause us to avoid
|
||||
// buildDomFrontier. For example:
|
||||
//
|
||||
// - Alloc never loaded? Eliminate.
|
||||
// - Alloc never stored? Replace all loads with a zero constant.
|
||||
// - Alloc stored once? Replace loads with dominating store;
|
||||
// don't forget that an Alloc is itself an effective store
|
||||
// of zero.
|
||||
// - Alloc used only within a single block?
|
||||
// Use degenerate algorithm avoiding φ-nodes.
|
||||
// - Consider synergy with scalar replacement of aggregates (SRA).
|
||||
// e.g. *(&x.f) where x is an Alloc.
|
||||
// Perhaps we'd get better results if we generated this as x.f
|
||||
// i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)).
|
||||
// Unclear.
|
||||
//
|
||||
// But we will start with the simplest correct code.
|
||||
df := buildDomFrontier(fn)
|
||||
|
||||
if debugLifting {
|
||||
title := false
|
||||
for i, blocks := range df {
|
||||
if blocks != nil {
|
||||
if !title {
|
||||
fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn)
|
||||
title = true
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newPhis := make(newPhiMap)
|
||||
|
||||
// During this pass we will replace some BasicBlock.Instrs
|
||||
// (allocs, loads and stores) with nil, keeping a count in
|
||||
// BasicBlock.gaps. At the end we will reset Instrs to the
|
||||
// concatenation of all non-dead newPhis and non-nil Instrs
|
||||
// for the block, reusing the original array if space permits.
|
||||
|
||||
// While we're here, we also eliminate 'rundefers'
|
||||
// instructions in functions that contain no 'defer'
|
||||
// instructions.
|
||||
usesDefer := false
|
||||
|
||||
// Determine which allocs we can lift and number them densely.
|
||||
// The renaming phase uses this numbering for compact maps.
|
||||
numAllocs := 0
|
||||
for _, b := range fn.Blocks {
|
||||
b.gaps = 0
|
||||
b.rundefers = 0
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *Alloc:
|
||||
index := -1
|
||||
if liftAlloc(df, instr, newPhis) {
|
||||
index = numAllocs
|
||||
numAllocs++
|
||||
}
|
||||
instr.index = index
|
||||
case *Defer:
|
||||
usesDefer = true
|
||||
case *RunDefers:
|
||||
b.rundefers++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// renaming maps an alloc (keyed by index) to its replacement
|
||||
// value. Initially the renaming contains nil, signifying the
|
||||
// zero constant of the appropriate type; we construct the
|
||||
// Const lazily at most once on each path through the domtree.
|
||||
// TODO(adonovan): opt: cache per-function not per subtree.
|
||||
renaming := make([]Value, numAllocs)
|
||||
|
||||
// Renaming.
|
||||
rename(fn.Blocks[0], renaming, newPhis)
|
||||
|
||||
// Eliminate dead new phis, then prepend the live ones to each block.
|
||||
for _, b := range fn.Blocks {
|
||||
|
||||
// Compress the newPhis slice to eliminate unused phis.
|
||||
// TODO(adonovan): opt: compute liveness to avoid
|
||||
// placing phis in blocks for which the alloc cell is
|
||||
// not live.
|
||||
nps := newPhis[b]
|
||||
j := 0
|
||||
for _, np := range nps {
|
||||
if !phiIsLive(np.phi) {
|
||||
// discard it, first removing it from referrers
|
||||
for _, newval := range np.phi.Edges {
|
||||
if refs := newval.Referrers(); refs != nil {
|
||||
*refs = removeInstr(*refs, np.phi)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
nps[j] = np
|
||||
j++
|
||||
}
|
||||
nps = nps[:j]
|
||||
|
||||
rundefersToKill := b.rundefers
|
||||
if usesDefer {
|
||||
rundefersToKill = 0
|
||||
}
|
||||
|
||||
if j+b.gaps+rundefersToKill == 0 {
|
||||
continue // fast path: no new phis or gaps
|
||||
}
|
||||
|
||||
// Compact nps + non-nil Instrs into a new slice.
|
||||
// TODO(adonovan): opt: compact in situ if there is
|
||||
// sufficient space or slack in the slice.
|
||||
dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill)
|
||||
for i, np := range nps {
|
||||
dst[i] = np.phi
|
||||
}
|
||||
for _, instr := range b.Instrs {
|
||||
if instr == nil {
|
||||
continue
|
||||
}
|
||||
if !usesDefer {
|
||||
if _, ok := instr.(*RunDefers); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
dst[j] = instr
|
||||
j++
|
||||
}
|
||||
for i, np := range nps {
|
||||
dst[i] = np.phi
|
||||
}
|
||||
b.Instrs = dst
|
||||
}
|
||||
|
||||
// Remove any fn.Locals that were lifted.
|
||||
j := 0
|
||||
for _, l := range fn.Locals {
|
||||
if l.index < 0 {
|
||||
fn.Locals[j] = l
|
||||
j++
|
||||
}
|
||||
}
|
||||
// Nil out fn.Locals[j:] to aid GC.
|
||||
for i := j; i < len(fn.Locals); i++ {
|
||||
fn.Locals[i] = nil
|
||||
}
|
||||
fn.Locals = fn.Locals[:j]
|
||||
}
|
||||
|
||||
func phiIsLive(phi *Phi) bool {
|
||||
for _, instr := range *phi.Referrers() {
|
||||
if instr == phi {
|
||||
continue // self-refs don't count
|
||||
}
|
||||
if _, ok := instr.(*DebugRef); ok {
|
||||
continue // debug refs don't count
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type blockSet struct{ big.Int } // (inherit methods from Int)
|
||||
|
||||
// add adds b to the set and returns true if the set changed.
|
||||
func (s *blockSet) add(b *BasicBlock) bool {
|
||||
i := b.Index
|
||||
if s.Bit(i) != 0 {
|
||||
return false
|
||||
}
|
||||
s.SetBit(&s.Int, i, 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// take removes an arbitrary element from a set s and
|
||||
// returns its index, or returns -1 if empty.
|
||||
func (s *blockSet) take() int {
|
||||
l := s.BitLen()
|
||||
for i := 0; i < l; i++ {
|
||||
if s.Bit(i) == 1 {
|
||||
s.SetBit(&s.Int, i, 0)
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// newPhi is a pair of a newly introduced φ-node and the lifted Alloc
|
||||
// it replaces.
|
||||
type newPhi struct {
|
||||
phi *Phi
|
||||
alloc *Alloc
|
||||
}
|
||||
|
||||
// newPhiMap records for each basic block, the set of newPhis that
|
||||
// must be prepended to the block.
|
||||
type newPhiMap map[*BasicBlock][]newPhi
|
||||
|
||||
// liftAlloc determines whether alloc can be lifted into registers,
|
||||
// and if so, it populates newPhis with all the φ-nodes it may require
|
||||
// and returns true.
|
||||
//
|
||||
func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
|
||||
// Don't lift aggregates into registers, because we don't have
|
||||
// a way to express their zero-constants.
|
||||
switch deref(alloc.Type()).Underlying().(type) {
|
||||
case *types.Array, *types.Struct:
|
||||
return false
|
||||
}
|
||||
|
||||
// Don't lift named return values in functions that defer
|
||||
// calls that may recover from panic.
|
||||
if fn := alloc.Parent(); fn.Recover != nil {
|
||||
for _, nr := range fn.namedResults {
|
||||
if nr == alloc {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute defblocks, the set of blocks containing a
|
||||
// definition of the alloc cell.
|
||||
var defblocks blockSet
|
||||
for _, instr := range *alloc.Referrers() {
|
||||
// Bail out if we discover the alloc is not liftable;
|
||||
// the only operations permitted to use the alloc are
|
||||
// loads/stores into the cell, and DebugRef.
|
||||
switch instr := instr.(type) {
|
||||
case *Store:
|
||||
if instr.Val == alloc {
|
||||
return false // address used as value
|
||||
}
|
||||
if instr.Addr != alloc {
|
||||
panic("Alloc.Referrers is inconsistent")
|
||||
}
|
||||
defblocks.add(instr.Block())
|
||||
case *UnOp:
|
||||
if instr.Op != token.MUL {
|
||||
return false // not a load
|
||||
}
|
||||
if instr.X != alloc {
|
||||
panic("Alloc.Referrers is inconsistent")
|
||||
}
|
||||
case *DebugRef:
|
||||
// ok
|
||||
default:
|
||||
return false // some other instruction
|
||||
}
|
||||
}
|
||||
// The Alloc itself counts as a (zero) definition of the cell.
|
||||
defblocks.add(alloc.Block())
|
||||
|
||||
if debugLifting {
|
||||
fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name())
|
||||
}
|
||||
|
||||
fn := alloc.Parent()
|
||||
|
||||
// Φ-insertion.
|
||||
//
|
||||
// What follows is the body of the main loop of the insert-φ
|
||||
// function described by Cytron et al, but instead of using
|
||||
// counter tricks, we just reset the 'hasAlready' and 'work'
|
||||
// sets each iteration. These are bitmaps so it's pretty cheap.
|
||||
//
|
||||
// TODO(adonovan): opt: recycle slice storage for W,
|
||||
// hasAlready, defBlocks across liftAlloc calls.
|
||||
var hasAlready blockSet
|
||||
|
||||
// Initialize W and work to defblocks.
|
||||
var work blockSet = defblocks // blocks seen
|
||||
var W blockSet // blocks to do
|
||||
W.Set(&defblocks.Int)
|
||||
|
||||
// Traverse iterated dominance frontier, inserting φ-nodes.
|
||||
for i := W.take(); i != -1; i = W.take() {
|
||||
u := fn.Blocks[i]
|
||||
for _, v := range df[u.Index] {
|
||||
if hasAlready.add(v) {
|
||||
// Create φ-node.
|
||||
// It will be prepended to v.Instrs later, if needed.
|
||||
phi := &Phi{
|
||||
Edges: make([]Value, len(v.Preds)),
|
||||
Comment: alloc.Comment,
|
||||
}
|
||||
phi.pos = alloc.Pos()
|
||||
phi.setType(deref(alloc.Type()))
|
||||
phi.block = v
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v)
|
||||
}
|
||||
newPhis[v] = append(newPhis[v], newPhi{phi, alloc})
|
||||
|
||||
if work.add(v) {
|
||||
W.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// replaceAll replaces all intraprocedural uses of x with y,
|
||||
// updating x.Referrers and y.Referrers.
|
||||
// Precondition: x.Referrers() != nil, i.e. x must be local to some function.
|
||||
//
|
||||
func replaceAll(x, y Value) {
|
||||
var rands []*Value
|
||||
pxrefs := x.Referrers()
|
||||
pyrefs := y.Referrers()
|
||||
for _, instr := range *pxrefs {
|
||||
rands = instr.Operands(rands[:0]) // recycle storage
|
||||
for _, rand := range rands {
|
||||
if *rand != nil {
|
||||
if *rand == x {
|
||||
*rand = y
|
||||
}
|
||||
}
|
||||
}
|
||||
if pyrefs != nil {
|
||||
*pyrefs = append(*pyrefs, instr) // dups ok
|
||||
}
|
||||
}
|
||||
*pxrefs = nil // x is now unreferenced
|
||||
}
|
||||
|
||||
// renamed returns the value to which alloc is being renamed,
|
||||
// constructing it lazily if it's the implicit zero initialization.
|
||||
//
|
||||
func renamed(renaming []Value, alloc *Alloc) Value {
|
||||
v := renaming[alloc.index]
|
||||
if v == nil {
|
||||
v = zeroConst(deref(alloc.Type()))
|
||||
renaming[alloc.index] = v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// rename implements the (Cytron et al) SSA renaming algorithm, a
|
||||
// preorder traversal of the dominator tree replacing all loads of
|
||||
// Alloc cells with the value stored to that cell by the dominating
|
||||
// store instruction. For lifting, we need only consider loads,
|
||||
// stores and φ-nodes.
|
||||
//
|
||||
// renaming is a map from *Alloc (keyed by index number) to its
|
||||
// dominating stored value; newPhis[x] is the set of new φ-nodes to be
|
||||
// prepended to block x.
|
||||
//
|
||||
func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
|
||||
// Each φ-node becomes the new name for its associated Alloc.
|
||||
for _, np := range newPhis[u] {
|
||||
phi := np.phi
|
||||
alloc := np.alloc
|
||||
renaming[alloc.index] = phi
|
||||
}
|
||||
|
||||
// Rename loads and stores of allocs.
|
||||
for i, instr := range u.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *Alloc:
|
||||
if instr.index >= 0 { // store of zero to Alloc cell
|
||||
// Replace dominated loads by the zero value.
|
||||
renaming[instr.index] = nil
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr)
|
||||
}
|
||||
// Delete the Alloc.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
|
||||
case *Store:
|
||||
if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell
|
||||
// Replace dominated loads by the stored value.
|
||||
renaming[alloc.index] = instr.Val
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n",
|
||||
instr, instr.Val.Name())
|
||||
}
|
||||
// Remove the store from the referrer list of the stored value.
|
||||
if refs := instr.Val.Referrers(); refs != nil {
|
||||
*refs = removeInstr(*refs, instr)
|
||||
}
|
||||
// Delete the Store.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
|
||||
case *UnOp:
|
||||
if instr.Op == token.MUL {
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
|
||||
newval := renamed(renaming, alloc)
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n",
|
||||
instr.Name(), instr, newval.Name())
|
||||
}
|
||||
// Replace all references to
|
||||
// the loaded value by the
|
||||
// dominating stored value.
|
||||
replaceAll(instr, newval)
|
||||
// Delete the Load.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
|
||||
case *DebugRef:
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell
|
||||
if instr.IsAddr {
|
||||
instr.X = renamed(renaming, alloc)
|
||||
instr.IsAddr = false
|
||||
|
||||
// Add DebugRef to instr.X's referrers.
|
||||
if refs := instr.X.Referrers(); refs != nil {
|
||||
*refs = append(*refs, instr)
|
||||
}
|
||||
} else {
|
||||
// A source expression denotes the address
|
||||
// of an Alloc that was optimized away.
|
||||
instr.X = nil
|
||||
|
||||
// Delete the DebugRef.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each φ-node in a CFG successor, rename the edge.
|
||||
for _, v := range u.Succs {
|
||||
phis := newPhis[v]
|
||||
if len(phis) == 0 {
|
||||
continue
|
||||
}
|
||||
i := v.predIndex(u)
|
||||
for _, np := range phis {
|
||||
phi := np.phi
|
||||
alloc := np.alloc
|
||||
newval := renamed(renaming, alloc)
|
||||
if debugLifting {
|
||||
fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n",
|
||||
phi.Name(), u, v, i, alloc.Name(), newval.Name())
|
||||
}
|
||||
phi.Edges[i] = newval
|
||||
if prefs := newval.Referrers(); prefs != nil {
|
||||
*prefs = append(*prefs, phi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue depth-first recursion over domtree, pushing a
|
||||
// fresh copy of the renaming map for each subtree.
|
||||
for _, v := range u.dom.children {
|
||||
// TODO(adonovan): opt: avoid copy on final iteration; use destructive update.
|
||||
r := make([]Value, len(renaming))
|
||||
copy(r, renaming)
|
||||
rename(v, r, newPhis)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// 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 ssa
|
||||
|
||||
// lvalues are the union of addressable expressions and map-index
|
||||
// expressions.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// An lvalue represents an assignable location that may appear on the
|
||||
// left-hand side of an assignment. This is a generalization of a
|
||||
// pointer to permit updates to elements of maps.
|
||||
//
|
||||
type lvalue interface {
|
||||
store(fn *Function, v Value) // stores v into the location
|
||||
load(fn *Function) Value // loads the contents of the location
|
||||
address(fn *Function) Value // address of the location
|
||||
typ() types.Type // returns the type of the location
|
||||
}
|
||||
|
||||
// An address is an lvalue represented by a true pointer.
|
||||
type address struct {
|
||||
addr Value
|
||||
pos token.Pos // source position
|
||||
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||
}
|
||||
|
||||
func (a *address) load(fn *Function) Value {
|
||||
load := emitLoad(fn, a.addr)
|
||||
load.pos = a.pos
|
||||
return load
|
||||
}
|
||||
|
||||
func (a *address) store(fn *Function, v Value) {
|
||||
store := emitStore(fn, a.addr, v, a.pos)
|
||||
if a.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, a.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *address) address(fn *Function) Value {
|
||||
if a.expr != nil {
|
||||
emitDebugRef(fn, a.expr, a.addr, true)
|
||||
}
|
||||
return a.addr
|
||||
}
|
||||
|
||||
func (a *address) typ() types.Type {
|
||||
return deref(a.addr.Type())
|
||||
}
|
||||
|
||||
// An element is an lvalue represented by m[k], the location of an
|
||||
// element of a map or string. These locations are not addressable
|
||||
// since pointers cannot be formed from them, but they do support
|
||||
// load(), and in the case of maps, store().
|
||||
//
|
||||
type element struct {
|
||||
m, k Value // map or string
|
||||
t types.Type // map element type or string byte type
|
||||
pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v)
|
||||
}
|
||||
|
||||
func (e *element) load(fn *Function) Value {
|
||||
l := &Lookup{
|
||||
X: e.m,
|
||||
Index: e.k,
|
||||
}
|
||||
l.setPos(e.pos)
|
||||
l.setType(e.t)
|
||||
return fn.emit(l)
|
||||
}
|
||||
|
||||
func (e *element) store(fn *Function, v Value) {
|
||||
up := &MapUpdate{
|
||||
Map: e.m,
|
||||
Key: e.k,
|
||||
Value: emitConv(fn, v, e.t),
|
||||
}
|
||||
up.pos = e.pos
|
||||
fn.emit(up)
|
||||
}
|
||||
|
||||
func (e *element) address(fn *Function) Value {
|
||||
panic("map/string elements are not addressable")
|
||||
}
|
||||
|
||||
func (e *element) typ() types.Type {
|
||||
return e.t
|
||||
}
|
||||
|
||||
// A blank is a dummy variable whose name is "_".
|
||||
// It is not reified: loads are illegal and stores are ignored.
|
||||
//
|
||||
type blank struct{}
|
||||
|
||||
func (bl blank) load(fn *Function) Value {
|
||||
panic("blank.load is illegal")
|
||||
}
|
||||
|
||||
func (bl blank) store(fn *Function, v Value) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
func (bl blank) address(fn *Function) Value {
|
||||
panic("blank var is not addressable")
|
||||
}
|
||||
|
||||
func (bl blank) typ() types.Type {
|
||||
// This should be the type of the blank Ident; the typechecker
|
||||
// doesn't provide this yet, but fortunately, we don't need it
|
||||
// yet either.
|
||||
panic("blank.typ is unimplemented")
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file defines utilities for population of method sets.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// Method returns the Function implementing method sel, building
|
||||
// wrapper methods on demand. It returns nil if sel denotes an
|
||||
// abstract (interface) method.
|
||||
//
|
||||
// Precondition: sel.Kind() == MethodVal.
|
||||
//
|
||||
// TODO(adonovan): rename this to MethodValue because of the
|
||||
// precondition, and for consistency with functions in source.go.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) Method(sel *types.Selection) *Function {
|
||||
if sel.Kind() != types.MethodVal {
|
||||
panic(fmt.Sprintf("Method(%s) kind != MethodVal", sel))
|
||||
}
|
||||
T := sel.Recv()
|
||||
if isInterface(T) {
|
||||
return nil // abstract method
|
||||
}
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("Method %s %v", T, sel)()
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
return prog.addMethod(prog.createMethodSet(T), sel)
|
||||
}
|
||||
|
||||
// LookupMethod returns the implementation of the method of type T
|
||||
// identified by (pkg, name). It returns nil if the method exists but
|
||||
// is abstract, and panics if T has no such method.
|
||||
//
|
||||
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
|
||||
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
|
||||
if sel == nil {
|
||||
panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name)))
|
||||
}
|
||||
return prog.Method(sel)
|
||||
}
|
||||
|
||||
// methodSet contains the (concrete) methods of a non-interface type.
|
||||
type methodSet struct {
|
||||
mapping map[string]*Function // populated lazily
|
||||
complete bool // mapping contains all methods
|
||||
}
|
||||
|
||||
// Precondition: !isInterface(T).
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
func (prog *Program) createMethodSet(T types.Type) *methodSet {
|
||||
mset, ok := prog.methodSets.At(T).(*methodSet)
|
||||
if !ok {
|
||||
mset = &methodSet{mapping: make(map[string]*Function)}
|
||||
prog.methodSets.Set(T, mset)
|
||||
}
|
||||
return mset
|
||||
}
|
||||
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function {
|
||||
if sel.Kind() == types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
id := sel.Obj().Id()
|
||||
fn := mset.mapping[id]
|
||||
if fn == nil {
|
||||
obj := sel.Obj().(*types.Func)
|
||||
|
||||
needsPromotion := len(sel.Index()) > 1
|
||||
needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv())
|
||||
if needsPromotion || needsIndirection {
|
||||
fn = makeWrapper(prog, sel)
|
||||
} else {
|
||||
fn = prog.declaredFunc(obj)
|
||||
}
|
||||
if fn.Signature.Recv() == nil {
|
||||
panic(fn) // missing receiver
|
||||
}
|
||||
mset.mapping[id] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// RuntimeTypes returns a new unordered slice containing all
|
||||
// concrete types in the program for which a complete (non-empty)
|
||||
// method set is required at run-time.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) RuntimeTypes() []types.Type {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
var res []types.Type
|
||||
prog.methodSets.Iterate(func(T types.Type, v interface{}) {
|
||||
if v.(*methodSet).complete {
|
||||
res = append(res, T)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// declaredFunc returns the concrete function/method denoted by obj.
|
||||
// Panic ensues if there is none.
|
||||
//
|
||||
func (prog *Program) declaredFunc(obj *types.Func) *Function {
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Function)
|
||||
}
|
||||
panic("no concrete method: " + obj.String())
|
||||
}
|
||||
|
||||
// needMethodsOf ensures that runtime type information (including the
|
||||
// complete method set) is available for the specified type T and all
|
||||
// its subcomponents.
|
||||
//
|
||||
// needMethodsOf must be called for at least every type that is an
|
||||
// operand of some MakeInterface instruction, and for the type of
|
||||
// every exported package member.
|
||||
//
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
//
|
||||
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
|
||||
//
|
||||
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) needMethodsOf(T types.Type) {
|
||||
prog.methodsMu.Lock()
|
||||
prog.needMethods(T, false)
|
||||
prog.methodsMu.Unlock()
|
||||
}
|
||||
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
// Recursive case: skip => don't create methods for T.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) needMethods(T types.Type, skip bool) {
|
||||
// Each package maintains its own set of types it has visited.
|
||||
if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok {
|
||||
// needMethods(T) was previously called
|
||||
if !prevSkip || skip {
|
||||
return // already seen, with same or false 'skip' value
|
||||
}
|
||||
}
|
||||
prog.runtimeTypes.Set(T, skip)
|
||||
|
||||
tmset := prog.MethodSets.MethodSet(T)
|
||||
|
||||
if !skip && !isInterface(T) && tmset.Len() > 0 {
|
||||
// Create methods of T.
|
||||
mset := prog.createMethodSet(T)
|
||||
if !mset.complete {
|
||||
mset.complete = true
|
||||
n := tmset.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
prog.addMethod(mset, tmset.At(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursion over signatures of each method.
|
||||
for i := 0; i < tmset.Len(); i++ {
|
||||
sig := tmset.At(i).Type().(*types.Signature)
|
||||
prog.needMethods(sig.Params(), false)
|
||||
prog.needMethods(sig.Results(), false)
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface:
|
||||
// nop---handled by recursion over method set.
|
||||
|
||||
case *types.Pointer:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Slice:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Chan:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Map:
|
||||
prog.needMethods(t.Key(), false)
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Signature:
|
||||
if t.Recv() != nil {
|
||||
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||
}
|
||||
prog.needMethods(t.Params(), false)
|
||||
prog.needMethods(t.Results(), false)
|
||||
|
||||
case *types.Named:
|
||||
// A pointer-to-named type can be derived from a named
|
||||
// type via reflection. It may have methods too.
|
||||
prog.needMethods(types.NewPointer(T), false)
|
||||
|
||||
// Consider 'type T struct{S}' where S has methods.
|
||||
// Reflection provides no way to get from T to struct{S},
|
||||
// only to S, so the method set of struct{S} is unwanted,
|
||||
// so set 'skip' flag during recursion.
|
||||
prog.needMethods(t.Underlying(), true)
|
||||
|
||||
case *types.Array:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Struct:
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
prog.needMethods(t.Field(i).Type(), false)
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i, n := 0, t.Len(); i < n; i++ {
|
||||
prog.needMethods(t.At(i).Type(), false)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(T)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssa
|
||||
|
||||
// This file defines the BuilderMode type and its command-line flag.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// BuilderMode is a bitmask of options for diagnostics and checking.
|
||||
type BuilderMode uint
|
||||
|
||||
const (
|
||||
PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout
|
||||
PrintFunctions // Print function SSA code to stdout
|
||||
LogSource // Log source locations as SSA builder progresses
|
||||
SanityCheckFunctions // Perform sanity checking of function bodies
|
||||
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
|
||||
BuildSerially // Build packages serially, not in parallel.
|
||||
GlobalDebug // Enable debug info for all packages
|
||||
BareInits // Build init functions without guards or calls to dependent inits
|
||||
)
|
||||
|
||||
const modeFlagUsage = `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 print [P]ackage inventory.
|
||||
F print [F]unction SSA code.
|
||||
S log [S]ource locations as SSA builder progresses.
|
||||
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.
|
||||
I build bare [I]nit functions: no init guards or calls to dependent inits.
|
||||
`
|
||||
|
||||
// BuilderModeFlag creates a new command line flag of type BuilderMode,
|
||||
// adds it to the specified flag set, and returns it.
|
||||
//
|
||||
// Example:
|
||||
// var ssabuild = BuilderModeFlag(flag.CommandLine, "ssabuild", 0)
|
||||
//
|
||||
func BuilderModeFlag(set *flag.FlagSet, name string, value BuilderMode) *BuilderMode {
|
||||
set.Var((*builderModeValue)(&value), name, modeFlagUsage)
|
||||
return &value
|
||||
}
|
||||
|
||||
type builderModeValue BuilderMode // satisfies flag.Value and flag.Getter.
|
||||
|
||||
func (v *builderModeValue) Set(s string) error {
|
||||
var mode BuilderMode
|
||||
for _, c := range s {
|
||||
switch c {
|
||||
case 'D':
|
||||
mode |= GlobalDebug
|
||||
case 'P':
|
||||
mode |= PrintPackages
|
||||
case 'F':
|
||||
mode |= PrintFunctions
|
||||
case 'S':
|
||||
mode |= LogSource | BuildSerially
|
||||
case 'C':
|
||||
mode |= SanityCheckFunctions
|
||||
case 'N':
|
||||
mode |= NaiveForm
|
||||
case 'L':
|
||||
mode |= BuildSerially
|
||||
default:
|
||||
return fmt.Errorf("unknown BuilderMode option: %q", c)
|
||||
}
|
||||
}
|
||||
*v = builderModeValue(mode)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *builderModeValue) Get() interface{} { return BuilderMode(*v) }
|
||||
|
||||
func (v *builderModeValue) String() string {
|
||||
mode := BuilderMode(*v)
|
||||
var buf bytes.Buffer
|
||||
if mode&GlobalDebug != 0 {
|
||||
buf.WriteByte('D')
|
||||
}
|
||||
if mode&PrintPackages != 0 {
|
||||
buf.WriteByte('P')
|
||||
}
|
||||
if mode&PrintFunctions != 0 {
|
||||
buf.WriteByte('F')
|
||||
}
|
||||
if mode&LogSource != 0 {
|
||||
buf.WriteByte('S')
|
||||
}
|
||||
if mode&SanityCheckFunctions != 0 {
|
||||
buf.WriteByte('C')
|
||||
}
|
||||
if mode&NaiveForm != 0 {
|
||||
buf.WriteByte('N')
|
||||
}
|
||||
if mode&BuildSerially != 0 {
|
||||
buf.WriteByte('L')
|
||||
}
|
||||
return buf.String()
|
||||
}
|
|
@ -0,0 +1,427 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file implements the String() methods for all Value and
|
||||
// Instruction types.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// relName returns the name of v relative to i.
|
||||
// In most cases, this is identical to v.Name(), but references to
|
||||
// Functions (including methods) and Globals use RelString and
|
||||
// all types are displayed with relType, so that only cross-package
|
||||
// references are package-qualified.
|
||||
//
|
||||
func relName(v Value, i Instruction) string {
|
||||
var from *types.Package
|
||||
if i != nil {
|
||||
from = i.Parent().pkgobj()
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case Member: // *Function or *Global
|
||||
return v.RelString(from)
|
||||
case *Const:
|
||||
return v.RelString(from)
|
||||
}
|
||||
return v.Name()
|
||||
}
|
||||
|
||||
func relType(t types.Type, from *types.Package) string {
|
||||
return types.TypeString(t, types.RelativeTo(from))
|
||||
}
|
||||
|
||||
func relString(m Member, from *types.Package) string {
|
||||
// NB: not all globals have an Object (e.g. init$guard),
|
||||
// so use Package().Object not Object.Package().
|
||||
if obj := m.Package().Object; obj != nil && obj != from {
|
||||
return fmt.Sprintf("%s.%s", obj.Path(), m.Name())
|
||||
}
|
||||
return m.Name()
|
||||
}
|
||||
|
||||
// Value.String()
|
||||
//
|
||||
// This method is provided only for debugging.
|
||||
// It never appears in disassembly, which uses Value.Name().
|
||||
|
||||
func (v *Parameter) String() string {
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("parameter %s : %s", v.Name(), relType(v.Type(), from))
|
||||
}
|
||||
|
||||
func (v *FreeVar) String() string {
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from))
|
||||
}
|
||||
|
||||
func (v *Builtin) String() string {
|
||||
return fmt.Sprintf("builtin %s", v.Name())
|
||||
}
|
||||
|
||||
// Instruction.String()
|
||||
|
||||
func (v *Alloc) String() string {
|
||||
op := "local"
|
||||
if v.Heap {
|
||||
op = "new"
|
||||
}
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("%s %s (%s)", op, relType(deref(v.Type()), from), v.Comment)
|
||||
}
|
||||
|
||||
func (v *Phi) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("phi [")
|
||||
for i, edge := range v.Edges {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
// Be robust against malformed CFG.
|
||||
block := -1
|
||||
if v.block != nil && i < len(v.block.Preds) {
|
||||
block = v.block.Preds[i].Index
|
||||
}
|
||||
fmt.Fprintf(&b, "%d: ", block)
|
||||
edgeVal := "<nil>" // be robust
|
||||
if edge != nil {
|
||||
edgeVal = relName(edge, v)
|
||||
}
|
||||
b.WriteString(edgeVal)
|
||||
}
|
||||
b.WriteString("]")
|
||||
if v.Comment != "" {
|
||||
b.WriteString(" #")
|
||||
b.WriteString(v.Comment)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func printCall(v *CallCommon, prefix string, instr Instruction) string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString(prefix)
|
||||
if !v.IsInvoke() {
|
||||
b.WriteString(relName(v.Value, instr))
|
||||
} else {
|
||||
fmt.Fprintf(&b, "invoke %s.%s", relName(v.Value, instr), v.Method.Name())
|
||||
}
|
||||
b.WriteString("(")
|
||||
for i, arg := range v.Args {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(arg, instr))
|
||||
}
|
||||
if v.Signature().Variadic() {
|
||||
b.WriteString("...")
|
||||
}
|
||||
b.WriteString(")")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *CallCommon) String() string {
|
||||
return printCall(c, "", nil)
|
||||
}
|
||||
|
||||
func (v *Call) String() string {
|
||||
return printCall(&v.Call, "", v)
|
||||
}
|
||||
|
||||
func (v *BinOp) String() string {
|
||||
return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v))
|
||||
}
|
||||
|
||||
func (v *UnOp) String() string {
|
||||
return fmt.Sprintf("%s%s%s", v.Op, relName(v.X, v), commaOk(v.CommaOk))
|
||||
}
|
||||
|
||||
func printConv(prefix string, v, x Value) string {
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("%s %s <- %s (%s)",
|
||||
prefix,
|
||||
relType(v.Type(), from),
|
||||
relType(x.Type(), from),
|
||||
relName(x, v.(Instruction)))
|
||||
}
|
||||
|
||||
func (v *ChangeType) String() string { return printConv("changetype", v, v.X) }
|
||||
func (v *Convert) String() string { return printConv("convert", v, v.X) }
|
||||
func (v *ChangeInterface) String() string { return printConv("change interface", v, v.X) }
|
||||
func (v *MakeInterface) String() string { return printConv("make", v, v.X) }
|
||||
|
||||
func (v *MakeClosure) String() string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v))
|
||||
if v.Bindings != nil {
|
||||
b.WriteString(" [")
|
||||
for i, c := range v.Bindings {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(c, v))
|
||||
}
|
||||
b.WriteString("]")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeSlice) String() string {
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("make %s %s %s",
|
||||
relType(v.Type(), from),
|
||||
relName(v.Len, v),
|
||||
relName(v.Cap, v))
|
||||
}
|
||||
|
||||
func (v *Slice) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("slice ")
|
||||
b.WriteString(relName(v.X, v))
|
||||
b.WriteString("[")
|
||||
if v.Low != nil {
|
||||
b.WriteString(relName(v.Low, v))
|
||||
}
|
||||
b.WriteString(":")
|
||||
if v.High != nil {
|
||||
b.WriteString(relName(v.High, v))
|
||||
}
|
||||
if v.Max != nil {
|
||||
b.WriteString(":")
|
||||
b.WriteString(relName(v.Max, v))
|
||||
}
|
||||
b.WriteString("]")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v *MakeMap) String() string {
|
||||
res := ""
|
||||
if v.Reserve != nil {
|
||||
res = relName(v.Reserve, v)
|
||||
}
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("make %s %s", relType(v.Type(), from), res)
|
||||
}
|
||||
|
||||
func (v *MakeChan) String() string {
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v))
|
||||
}
|
||||
|
||||
func (v *FieldAddr) String() string {
|
||||
st := deref(v.X.Type()).Underlying().(*types.Struct)
|
||||
// Be robust against a bad index.
|
||||
name := "?"
|
||||
if 0 <= v.Field && v.Field < st.NumFields() {
|
||||
name = st.Field(v.Field).Name()
|
||||
}
|
||||
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
|
||||
}
|
||||
|
||||
func (v *Field) String() string {
|
||||
st := v.X.Type().Underlying().(*types.Struct)
|
||||
// Be robust against a bad index.
|
||||
name := "?"
|
||||
if 0 <= v.Field && v.Field < st.NumFields() {
|
||||
name = st.Field(v.Field).Name()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field)
|
||||
}
|
||||
|
||||
func (v *IndexAddr) String() string {
|
||||
return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Index) String() string {
|
||||
return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v))
|
||||
}
|
||||
|
||||
func (v *Lookup) String() string {
|
||||
return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk))
|
||||
}
|
||||
|
||||
func (v *Range) String() string {
|
||||
return "range " + relName(v.X, v)
|
||||
}
|
||||
|
||||
func (v *Next) String() string {
|
||||
return "next " + relName(v.Iter, v)
|
||||
}
|
||||
|
||||
func (v *TypeAssert) String() string {
|
||||
from := v.Parent().pkgobj()
|
||||
return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from))
|
||||
}
|
||||
|
||||
func (v *Extract) String() string {
|
||||
return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index)
|
||||
}
|
||||
|
||||
func (s *Jump) String() string {
|
||||
// Be robust against malformed CFG.
|
||||
block := -1
|
||||
if s.block != nil && len(s.block.Succs) == 1 {
|
||||
block = s.block.Succs[0].Index
|
||||
}
|
||||
return fmt.Sprintf("jump %d", block)
|
||||
}
|
||||
|
||||
func (s *If) String() string {
|
||||
// Be robust against malformed CFG.
|
||||
tblock, fblock := -1, -1
|
||||
if s.block != nil && len(s.block.Succs) == 2 {
|
||||
tblock = s.block.Succs[0].Index
|
||||
fblock = s.block.Succs[1].Index
|
||||
}
|
||||
return fmt.Sprintf("if %s goto %d else %d", relName(s.Cond, s), tblock, fblock)
|
||||
}
|
||||
|
||||
func (s *Go) String() string {
|
||||
return printCall(&s.Call, "go ", s)
|
||||
}
|
||||
|
||||
func (s *Panic) String() string {
|
||||
return "panic " + relName(s.X, s)
|
||||
}
|
||||
|
||||
func (s *Return) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("return")
|
||||
for i, r := range s.Results {
|
||||
if i == 0 {
|
||||
b.WriteString(" ")
|
||||
} else {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(relName(r, s))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (*RunDefers) String() string {
|
||||
return "rundefers"
|
||||
}
|
||||
|
||||
func (s *Send) String() string {
|
||||
return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s))
|
||||
}
|
||||
|
||||
func (s *Defer) String() string {
|
||||
return printCall(&s.Call, "defer ", s)
|
||||
}
|
||||
|
||||
func (s *Select) String() string {
|
||||
var b bytes.Buffer
|
||||
for i, st := range s.States {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
if st.Dir == types.RecvOnly {
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
} else {
|
||||
b.WriteString(relName(st.Chan, s))
|
||||
b.WriteString("<-")
|
||||
b.WriteString(relName(st.Send, s))
|
||||
}
|
||||
}
|
||||
non := ""
|
||||
if !s.Blocking {
|
||||
non = "non"
|
||||
}
|
||||
return fmt.Sprintf("select %sblocking [%s]", non, b.String())
|
||||
}
|
||||
|
||||
func (s *Store) String() string {
|
||||
return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s))
|
||||
}
|
||||
|
||||
func (s *MapUpdate) String() string {
|
||||
return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
|
||||
}
|
||||
|
||||
func (s *DebugRef) String() string {
|
||||
p := s.Parent().Prog.Fset.Position(s.Pos())
|
||||
var descr interface{}
|
||||
if s.object != nil {
|
||||
descr = s.object // e.g. "var x int"
|
||||
} else {
|
||||
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
|
||||
}
|
||||
var addr string
|
||||
if s.IsAddr {
|
||||
addr = "address of "
|
||||
}
|
||||
return fmt.Sprintf("; %s%s @ %d:%d is %s", addr, descr, p.Line, p.Column, s.X.Name())
|
||||
}
|
||||
|
||||
func (p *Package) String() string {
|
||||
return "package " + p.Object.Path()
|
||||
}
|
||||
|
||||
var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer
|
||||
|
||||
func (p *Package) WriteTo(w io.Writer) (int64, error) {
|
||||
var buf bytes.Buffer
|
||||
WritePackage(&buf, p)
|
||||
n, err := w.Write(buf.Bytes())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// WritePackage writes to buf a human-readable summary of p.
|
||||
func WritePackage(buf *bytes.Buffer, p *Package) {
|
||||
fmt.Fprintf(buf, "%s:\n", p)
|
||||
|
||||
var names []string
|
||||
maxname := 0
|
||||
for name := range p.Members {
|
||||
if l := len(name); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
from := p.Object
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
switch mem := p.Members[name].(type) {
|
||||
case *NamedConst:
|
||||
fmt.Fprintf(buf, " const %-*s %s = %s\n",
|
||||
maxname, name, mem.Name(), mem.Value.RelString(from))
|
||||
|
||||
case *Function:
|
||||
fmt.Fprintf(buf, " func %-*s %s\n",
|
||||
maxname, name, relType(mem.Type(), from))
|
||||
|
||||
case *Type:
|
||||
fmt.Fprintf(buf, " type %-*s %s\n",
|
||||
maxname, name, relType(mem.Type().Underlying(), from))
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) {
|
||||
fmt.Fprintf(buf, " %s\n", types.SelectionString(meth, types.RelativeTo(from)))
|
||||
}
|
||||
|
||||
case *Global:
|
||||
fmt.Fprintf(buf, " var %-*s %s\n",
|
||||
maxname, name, relType(mem.Type().(*types.Pointer).Elem(), from))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, "\n")
|
||||
}
|
||||
|
||||
func commaOk(x bool) string {
|
||||
if x {
|
||||
return ",ok"
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,520 @@
|
|||
// 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 ssa
|
||||
|
||||
// An optional pass for sanity-checking invariants of the SSA representation.
|
||||
// Currently it checks CFG invariants but little at the instruction level.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
type sanity struct {
|
||||
reporter io.Writer
|
||||
fn *Function
|
||||
block *BasicBlock
|
||||
instrs map[Instruction]struct{}
|
||||
insane bool
|
||||
}
|
||||
|
||||
// sanityCheck performs integrity checking of the SSA representation
|
||||
// of the function fn and returns true if it was valid. Diagnostics
|
||||
// are written to reporter if non-nil, os.Stderr otherwise. Some
|
||||
// diagnostics are only warnings and do not imply a negative result.
|
||||
//
|
||||
// Sanity-checking is intended to facilitate the debugging of code
|
||||
// transformation passes.
|
||||
//
|
||||
func sanityCheck(fn *Function, reporter io.Writer) bool {
|
||||
if reporter == nil {
|
||||
reporter = os.Stderr
|
||||
}
|
||||
return (&sanity{reporter: reporter}).checkFunction(fn)
|
||||
}
|
||||
|
||||
// mustSanityCheck is like sanityCheck but panics instead of returning
|
||||
// a negative result.
|
||||
//
|
||||
func mustSanityCheck(fn *Function, reporter io.Writer) {
|
||||
if !sanityCheck(fn, reporter) {
|
||||
fn.WriteTo(os.Stderr)
|
||||
panic("SanityCheck failed")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) diagnostic(prefix, format string, args ...interface{}) {
|
||||
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn)
|
||||
if s.block != nil {
|
||||
fmt.Fprintf(s.reporter, ", block %s", s.block)
|
||||
}
|
||||
io.WriteString(s.reporter, ": ")
|
||||
fmt.Fprintf(s.reporter, format, args...)
|
||||
io.WriteString(s.reporter, "\n")
|
||||
}
|
||||
|
||||
func (s *sanity) errorf(format string, args ...interface{}) {
|
||||
s.insane = true
|
||||
s.diagnostic("Error", format, args...)
|
||||
}
|
||||
|
||||
func (s *sanity) warnf(format string, args ...interface{}) {
|
||||
s.diagnostic("Warning", format, args...)
|
||||
}
|
||||
|
||||
// findDuplicate returns an arbitrary basic block that appeared more
|
||||
// than once in blocks, or nil if all were unique.
|
||||
func findDuplicate(blocks []*BasicBlock) *BasicBlock {
|
||||
if len(blocks) < 2 {
|
||||
return nil
|
||||
}
|
||||
if blocks[0] == blocks[1] {
|
||||
return blocks[0]
|
||||
}
|
||||
// Slow path:
|
||||
m := make(map[*BasicBlock]bool)
|
||||
for _, b := range blocks {
|
||||
if m[b] {
|
||||
return b
|
||||
}
|
||||
m[b] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sanity) checkInstr(idx int, instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If, *Jump, *Return, *Panic:
|
||||
s.errorf("control flow instruction not at end of block")
|
||||
case *Phi:
|
||||
if idx == 0 {
|
||||
// It suffices to apply this check to just the first phi node.
|
||||
if dup := findDuplicate(s.block.Preds); dup != nil {
|
||||
s.errorf("phi node in block with duplicate predecessor %s", dup)
|
||||
}
|
||||
} else {
|
||||
prev := s.block.Instrs[idx-1]
|
||||
if _, ok := prev.(*Phi); !ok {
|
||||
s.errorf("Phi instruction follows a non-Phi: %T", prev)
|
||||
}
|
||||
}
|
||||
if ne, np := len(instr.Edges), len(s.block.Preds); ne != np {
|
||||
s.errorf("phi node has %d edges but %d predecessors", ne, np)
|
||||
|
||||
} else {
|
||||
for i, e := range instr.Edges {
|
||||
if e == nil {
|
||||
s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment, i, s.block.Preds[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *Alloc:
|
||||
if !instr.Heap {
|
||||
found := false
|
||||
for _, l := range s.fn.Locals {
|
||||
if l == instr {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr)
|
||||
}
|
||||
}
|
||||
|
||||
case *BinOp:
|
||||
case *Call:
|
||||
case *ChangeInterface:
|
||||
case *ChangeType:
|
||||
case *Convert:
|
||||
if _, ok := instr.X.Type().Underlying().(*types.Basic); !ok {
|
||||
if _, ok := instr.Type().Underlying().(*types.Basic); !ok {
|
||||
s.errorf("convert %s -> %s: at least one type must be basic", instr.X.Type(), instr.Type())
|
||||
}
|
||||
}
|
||||
|
||||
case *Defer:
|
||||
case *Extract:
|
||||
case *Field:
|
||||
case *FieldAddr:
|
||||
case *Go:
|
||||
case *Index:
|
||||
case *IndexAddr:
|
||||
case *Lookup:
|
||||
case *MakeChan:
|
||||
case *MakeClosure:
|
||||
numFree := len(instr.Fn.(*Function).FreeVars)
|
||||
numBind := len(instr.Bindings)
|
||||
if numFree != numBind {
|
||||
s.errorf("MakeClosure has %d Bindings for function %s with %d free vars",
|
||||
numBind, instr.Fn, numFree)
|
||||
|
||||
}
|
||||
if recv := instr.Type().(*types.Signature).Recv(); recv != nil {
|
||||
s.errorf("MakeClosure's type includes receiver %s", recv.Type())
|
||||
}
|
||||
|
||||
case *MakeInterface:
|
||||
case *MakeMap:
|
||||
case *MakeSlice:
|
||||
case *MapUpdate:
|
||||
case *Next:
|
||||
case *Range:
|
||||
case *RunDefers:
|
||||
case *Select:
|
||||
case *Send:
|
||||
case *Slice:
|
||||
case *Store:
|
||||
case *TypeAssert:
|
||||
case *UnOp:
|
||||
case *DebugRef:
|
||||
// TODO(adonovan): implement checks.
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
|
||||
}
|
||||
|
||||
if call, ok := instr.(CallInstruction); ok {
|
||||
if call.Common().Signature() == nil {
|
||||
s.errorf("nil signature: %s", call)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that value-defining instructions have valid types
|
||||
// and a valid referrer list.
|
||||
if v, ok := instr.(Value); ok {
|
||||
t := v.Type()
|
||||
if t == nil {
|
||||
s.errorf("no type: %s = %s", v.Name(), v)
|
||||
} else if t == tRangeIter {
|
||||
// not a proper type; ignore.
|
||||
} else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
|
||||
s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t)
|
||||
}
|
||||
s.checkReferrerList(v)
|
||||
}
|
||||
|
||||
// Untyped constants are legal as instruction Operands(),
|
||||
// for example:
|
||||
// _ = "foo"[0]
|
||||
// or:
|
||||
// if wordsize==64 {...}
|
||||
|
||||
// All other non-Instruction Values can be found via their
|
||||
// enclosing Function or Package.
|
||||
}
|
||||
|
||||
func (s *sanity) checkFinalInstr(idx int, instr Instruction) {
|
||||
switch instr := instr.(type) {
|
||||
case *If:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 2 {
|
||||
s.errorf("If-terminated block has %d successors; expected 2", nsuccs)
|
||||
return
|
||||
}
|
||||
if s.block.Succs[0] == s.block.Succs[1] {
|
||||
s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0])
|
||||
return
|
||||
}
|
||||
|
||||
case *Jump:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 1 {
|
||||
s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
case *Return:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 0 {
|
||||
s.errorf("Return-terminated block has %d successors; expected none", nsuccs)
|
||||
return
|
||||
}
|
||||
if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na {
|
||||
s.errorf("%d-ary return in %d-ary function", na, nf)
|
||||
}
|
||||
|
||||
case *Panic:
|
||||
if nsuccs := len(s.block.Succs); nsuccs != 0 {
|
||||
s.errorf("Panic-terminated block has %d successors; expected none", nsuccs)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
s.errorf("non-control flow instruction at end of block")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkBlock(b *BasicBlock, index int) {
|
||||
s.block = b
|
||||
|
||||
if b.Index != index {
|
||||
s.errorf("block has incorrect Index %d", b.Index)
|
||||
}
|
||||
if b.parent != s.fn {
|
||||
s.errorf("block has incorrect parent %s", b.parent)
|
||||
}
|
||||
|
||||
// Check all blocks are reachable.
|
||||
// (The entry block is always implicitly reachable,
|
||||
// as is the Recover block, if any.)
|
||||
if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 {
|
||||
s.warnf("unreachable block")
|
||||
if b.Instrs == nil {
|
||||
// Since this block is about to be pruned,
|
||||
// tolerating transient problems in it
|
||||
// simplifies other optimizations.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check predecessor and successor relations are dual,
|
||||
// and that all blocks in CFG belong to same function.
|
||||
for _, a := range b.Preds {
|
||||
found := false
|
||||
for _, bb := range a.Succs {
|
||||
if bb == b {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
|
||||
}
|
||||
if a.parent != s.fn {
|
||||
s.errorf("predecessor %s belongs to different function %s", a, a.parent)
|
||||
}
|
||||
}
|
||||
for _, c := range b.Succs {
|
||||
found := false
|
||||
for _, bb := range c.Preds {
|
||||
if bb == b {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
|
||||
}
|
||||
if c.parent != s.fn {
|
||||
s.errorf("successor %s belongs to different function %s", c, c.parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Check each instruction is sane.
|
||||
n := len(b.Instrs)
|
||||
if n == 0 {
|
||||
s.errorf("basic block contains no instructions")
|
||||
}
|
||||
var rands [10]*Value // reuse storage
|
||||
for j, instr := range b.Instrs {
|
||||
if instr == nil {
|
||||
s.errorf("nil instruction at index %d", j)
|
||||
continue
|
||||
}
|
||||
if b2 := instr.Block(); b2 == nil {
|
||||
s.errorf("nil Block() for instruction at index %d", j)
|
||||
continue
|
||||
} else if b2 != b {
|
||||
s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j)
|
||||
continue
|
||||
}
|
||||
if j < n-1 {
|
||||
s.checkInstr(j, instr)
|
||||
} else {
|
||||
s.checkFinalInstr(j, instr)
|
||||
}
|
||||
|
||||
// Check Instruction.Operands.
|
||||
operands:
|
||||
for i, op := range instr.Operands(rands[:0]) {
|
||||
if op == nil {
|
||||
s.errorf("nil operand pointer %d of %s", i, instr)
|
||||
continue
|
||||
}
|
||||
val := *op
|
||||
if val == nil {
|
||||
continue // a nil operand is ok
|
||||
}
|
||||
|
||||
// Check that "untyped" types only appear on constant operands.
|
||||
if _, ok := (*op).(*Const); !ok {
|
||||
if basic, ok := (*op).Type().(*types.Basic); ok {
|
||||
if basic.Info()&types.IsUntyped != 0 {
|
||||
s.errorf("operand #%d of %s is untyped: %s", i, instr, basic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that Operands that are also Instructions belong to same function.
|
||||
// TODO(adonovan): also check their block dominates block b.
|
||||
if val, ok := val.(Instruction); ok {
|
||||
if val.Parent() != s.fn {
|
||||
s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent())
|
||||
}
|
||||
}
|
||||
|
||||
// Check that each function-local operand of
|
||||
// instr refers back to instr. (NB: quadratic)
|
||||
switch val := val.(type) {
|
||||
case *Const, *Global, *Builtin:
|
||||
continue // not local
|
||||
case *Function:
|
||||
if val.parent == nil {
|
||||
continue // only anon functions are local
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): check val.Parent() != nil <=> val.Referrers() is defined.
|
||||
|
||||
if refs := val.Referrers(); refs != nil {
|
||||
for _, ref := range *refs {
|
||||
if ref == instr {
|
||||
continue operands
|
||||
}
|
||||
}
|
||||
s.errorf("operand %d of %s (%s) does not refer to us", i, instr, val)
|
||||
} else {
|
||||
s.errorf("operand %d of %s (%s) has no referrers", i, instr, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkReferrerList(v Value) {
|
||||
refs := v.Referrers()
|
||||
if refs == nil {
|
||||
s.errorf("%s has missing referrer list", v.Name())
|
||||
return
|
||||
}
|
||||
for i, ref := range *refs {
|
||||
if _, ok := s.instrs[ref]; !ok {
|
||||
s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sanity) checkFunction(fn *Function) bool {
|
||||
// TODO(adonovan): check Function invariants:
|
||||
// - check params match signature
|
||||
// - check transient fields are nil
|
||||
// - warn if any fn.Locals do not appear among block instructions.
|
||||
s.fn = fn
|
||||
if fn.Prog == nil {
|
||||
s.errorf("nil Prog")
|
||||
}
|
||||
|
||||
fn.String() // must not crash
|
||||
fn.RelString(fn.pkgobj()) // must not crash
|
||||
|
||||
// All functions have a package, except delegates (which are
|
||||
// shared across packages, or duplicated as weak symbols in a
|
||||
// separate-compilation model), and error.Error.
|
||||
if fn.Pkg == nil {
|
||||
if strings.HasPrefix(fn.Synthetic, "wrapper ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "bound ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "thunk ") ||
|
||||
strings.HasSuffix(fn.name, "Error") {
|
||||
// ok
|
||||
} else {
|
||||
s.errorf("nil Pkg")
|
||||
}
|
||||
}
|
||||
if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
|
||||
s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
|
||||
}
|
||||
for i, l := range fn.Locals {
|
||||
if l.Parent() != fn {
|
||||
s.errorf("Local %s at index %d has wrong parent", l.Name(), i)
|
||||
}
|
||||
if l.Heap {
|
||||
s.errorf("Local %s at index %d has Heap flag set", l.Name(), i)
|
||||
}
|
||||
}
|
||||
// Build the set of valid referrers.
|
||||
s.instrs = make(map[Instruction]struct{})
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
s.instrs[instr] = struct{}{}
|
||||
}
|
||||
}
|
||||
for i, p := range fn.Params {
|
||||
if p.Parent() != fn {
|
||||
s.errorf("Param %s at index %d has wrong parent", p.Name(), i)
|
||||
}
|
||||
s.checkReferrerList(p)
|
||||
}
|
||||
for i, fv := range fn.FreeVars {
|
||||
if fv.Parent() != fn {
|
||||
s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i)
|
||||
}
|
||||
s.checkReferrerList(fv)
|
||||
}
|
||||
|
||||
if fn.Blocks != nil && len(fn.Blocks) == 0 {
|
||||
// Function _had_ blocks (so it's not external) but
|
||||
// they were "optimized" away, even the entry block.
|
||||
s.errorf("Blocks slice is non-nil but empty")
|
||||
}
|
||||
for i, b := range fn.Blocks {
|
||||
if b == nil {
|
||||
s.warnf("nil *BasicBlock at f.Blocks[%d]", i)
|
||||
continue
|
||||
}
|
||||
s.checkBlock(b, i)
|
||||
}
|
||||
if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover {
|
||||
s.errorf("Recover block is not in Blocks slice")
|
||||
}
|
||||
|
||||
s.block = nil
|
||||
for i, anon := range fn.AnonFuncs {
|
||||
if anon.Parent() != fn {
|
||||
s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent())
|
||||
}
|
||||
}
|
||||
s.fn = nil
|
||||
return !s.insane
|
||||
}
|
||||
|
||||
// sanityCheckPackage checks invariants of packages upon creation.
|
||||
// It does not require that the package is built.
|
||||
// Unlike sanityCheck (for functions), it just panics at the first error.
|
||||
func sanityCheckPackage(pkg *Package) {
|
||||
if pkg.Object == nil {
|
||||
panic(fmt.Sprintf("Package %s has no Object", pkg))
|
||||
}
|
||||
pkg.String() // must not crash
|
||||
|
||||
for name, mem := range pkg.Members {
|
||||
if name != mem.Name() {
|
||||
panic(fmt.Sprintf("%s: %T.Name() = %s, want %s",
|
||||
pkg.Object.Path(), mem, mem.Name(), name))
|
||||
}
|
||||
obj := mem.Object()
|
||||
if obj == nil {
|
||||
// This check is sound because fields
|
||||
// {Global,Function}.object have type
|
||||
// types.Object. (If they were declared as
|
||||
// *types.{Var,Func}, we'd have a non-empty
|
||||
// interface containing a nil pointer.)
|
||||
|
||||
continue // not all members have typechecker objects
|
||||
}
|
||||
if obj.Name() != name {
|
||||
if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") {
|
||||
// Ok. The name of a declared init function varies between
|
||||
// its types.Func ("init") and its ssa.Function ("init#%d").
|
||||
} else {
|
||||
panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s",
|
||||
pkg.Object.Path(), mem, obj.Name(), name))
|
||||
}
|
||||
}
|
||||
if obj.Pos() != mem.Pos() {
|
||||
panic(fmt.Sprintf("%s Pos=%d obj.Pos=%d", mem, mem.Pos(), obj.Pos()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file defines utilities for working with source positions
|
||||
// or source-level named entities ("objects").
|
||||
|
||||
// TODO(adonovan): test that {Value,Instruction}.Pos() positions match
|
||||
// the originating syntax, as specified.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// EnclosingFunction returns the function that contains the syntax
|
||||
// node denoted by path.
|
||||
//
|
||||
// Syntax associated with package-level variable specifications is
|
||||
// enclosed by the package's init() function.
|
||||
//
|
||||
// Returns nil if not found; reasons might include:
|
||||
// - the node is not enclosed by any function.
|
||||
// - the node is within an anonymous function (FuncLit) and
|
||||
// its SSA function has not been created yet
|
||||
// (pkg.Build() has not yet been called).
|
||||
//
|
||||
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
|
||||
// Start with package-level function...
|
||||
fn := findEnclosingPackageLevelFunction(pkg, path)
|
||||
if fn == nil {
|
||||
return nil // not in any function
|
||||
}
|
||||
|
||||
// ...then walk down the nested anonymous functions.
|
||||
n := len(path)
|
||||
outer:
|
||||
for i := range path {
|
||||
if lit, ok := path[n-1-i].(*ast.FuncLit); ok {
|
||||
for _, anon := range fn.AnonFuncs {
|
||||
if anon.Pos() == lit.Type.Func {
|
||||
fn = anon
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
// SSA function not found:
|
||||
// - package not yet built, or maybe
|
||||
// - builder skipped FuncLit in dead block
|
||||
// (in principle; but currently the Builder
|
||||
// generates even dead FuncLits).
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// HasEnclosingFunction returns true if the AST node denoted by path
|
||||
// is contained within the declaration of some function or
|
||||
// package-level variable.
|
||||
//
|
||||
// Unlike EnclosingFunction, the behaviour of this function does not
|
||||
// depend on whether SSA code for pkg has been built, so it can be
|
||||
// used to quickly reject check inputs that will cause
|
||||
// EnclosingFunction to fail, prior to SSA building.
|
||||
//
|
||||
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
|
||||
return findEnclosingPackageLevelFunction(pkg, path) != nil
|
||||
}
|
||||
|
||||
// findEnclosingPackageLevelFunction returns the Function
|
||||
// corresponding to the package-level function enclosing path.
|
||||
//
|
||||
func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function {
|
||||
if n := len(path); n >= 2 { // [... {Gen,Func}Decl File]
|
||||
switch decl := path[n-2].(type) {
|
||||
case *ast.GenDecl:
|
||||
if decl.Tok == token.VAR && n >= 3 {
|
||||
// Package-level 'var' initializer.
|
||||
return pkg.init
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
if decl.Recv == nil && decl.Name.Name == "init" {
|
||||
// Explicit init() function.
|
||||
for _, b := range pkg.init.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if instr, ok := instr.(*Call); ok {
|
||||
if callee, ok := instr.Call.Value.(*Function); ok && callee.Pkg == pkg && callee.Pos() == decl.Name.NamePos {
|
||||
return callee
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hack: return non-nil when SSA is not yet
|
||||
// built so that HasEnclosingFunction works.
|
||||
return pkg.init
|
||||
}
|
||||
// Declared function/method.
|
||||
return findNamedFunc(pkg, decl.Name.NamePos)
|
||||
}
|
||||
}
|
||||
return nil // not in any function
|
||||
}
|
||||
|
||||
// findNamedFunc returns the named function whose FuncDecl.Ident is at
|
||||
// position pos.
|
||||
//
|
||||
func findNamedFunc(pkg *Package, pos token.Pos) *Function {
|
||||
// Look at all package members and method sets of named types.
|
||||
// Not very efficient.
|
||||
for _, mem := range pkg.Members {
|
||||
switch mem := mem.(type) {
|
||||
case *Function:
|
||||
if mem.Pos() == pos {
|
||||
return mem
|
||||
}
|
||||
case *Type:
|
||||
mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
// Don't call Program.Method: avoid creating wrappers.
|
||||
obj := mset.At(i).Obj().(*types.Func)
|
||||
if obj.Pos() == pos {
|
||||
return pkg.values[obj].(*Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueForExpr returns the SSA Value that corresponds to non-constant
|
||||
// expression e.
|
||||
//
|
||||
// It returns nil if no value was found, e.g.
|
||||
// - the expression is not lexically contained within f;
|
||||
// - f was not built with debug information; or
|
||||
// - e is a constant expression. (For efficiency, no debug
|
||||
// information is stored for constants. Use
|
||||
// go/types.Info.Types[e].Value instead.)
|
||||
// - e is a reference to nil or a built-in function.
|
||||
// - the value was optimised away.
|
||||
//
|
||||
// If e is an addressable expression used in an lvalue context,
|
||||
// value is the address denoted by e, and isAddr is true.
|
||||
//
|
||||
// The types of e (or &e, if isAddr) and the result are equal
|
||||
// (modulo "untyped" bools resulting from comparisons).
|
||||
//
|
||||
// (Tip: to find the ssa.Value given a source position, use
|
||||
// importer.PathEnclosingInterval to locate the ast.Node, then
|
||||
// EnclosingFunction to locate the Function, then ValueForExpr to find
|
||||
// the ssa.Value.)
|
||||
//
|
||||
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
|
||||
if f.debugInfo() { // (opt)
|
||||
e = unparen(e)
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if ref, ok := instr.(*DebugRef); ok {
|
||||
if ref.Expr == e {
|
||||
return ref.X, ref.IsAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// --- Lookup functions for source-level named entities (types.Objects) ---
|
||||
|
||||
// Package returns the SSA Package corresponding to the specified
|
||||
// type-checker package object.
|
||||
// It returns nil if no such SSA package has been created.
|
||||
//
|
||||
func (prog *Program) Package(obj *types.Package) *Package {
|
||||
return prog.packages[obj]
|
||||
}
|
||||
|
||||
// packageLevelValue returns the package-level value corresponding to
|
||||
// the specified named object, which may be a package-level const
|
||||
// (*Const), var (*Global) or func (*Function) of some package in
|
||||
// prog. It returns nil if the object is not found.
|
||||
//
|
||||
func (prog *Program) packageLevelValue(obj types.Object) Value {
|
||||
if pkg, ok := prog.packages[obj.Pkg()]; ok {
|
||||
return pkg.values[obj]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FuncValue returns the concrete Function denoted by the source-level
|
||||
// named function obj, or nil if obj denotes an interface method.
|
||||
//
|
||||
// TODO(adonovan): check the invariant that obj.Type() matches the
|
||||
// result's Signature, both in the params/results and in the receiver.
|
||||
//
|
||||
func (prog *Program) FuncValue(obj *types.Func) *Function {
|
||||
fn, _ := prog.packageLevelValue(obj).(*Function)
|
||||
return fn
|
||||
}
|
||||
|
||||
// ConstValue returns the SSA Value denoted by the source-level named
|
||||
// constant obj.
|
||||
//
|
||||
func (prog *Program) ConstValue(obj *types.Const) *Const {
|
||||
// TODO(adonovan): opt: share (don't reallocate)
|
||||
// Consts for const objects and constant ast.Exprs.
|
||||
|
||||
// Universal constant? {true,false,nil}
|
||||
if obj.Parent() == types.Universe {
|
||||
return NewConst(obj.Val(), obj.Type())
|
||||
}
|
||||
// Package-level named constant?
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Const)
|
||||
}
|
||||
return NewConst(obj.Val(), obj.Type())
|
||||
}
|
||||
|
||||
// VarValue returns the SSA Value that corresponds to a specific
|
||||
// identifier denoting the source-level named variable obj.
|
||||
//
|
||||
// VarValue returns nil if a local variable was not found, perhaps
|
||||
// because its package was not built, the debug information was not
|
||||
// requested during SSA construction, or the value was optimized away.
|
||||
//
|
||||
// ref is the path to an ast.Ident (e.g. from PathEnclosingInterval),
|
||||
// and that ident must resolve to obj.
|
||||
//
|
||||
// pkg is the package enclosing the reference. (A reference to a var
|
||||
// always occurs within a function, so we need to know where to find it.)
|
||||
//
|
||||
// If the identifier is a field selector and its base expression is
|
||||
// non-addressable, then VarValue returns the value of that field.
|
||||
// For example:
|
||||
// func f() struct {x int}
|
||||
// f().x // VarValue(x) returns a *Field instruction of type int
|
||||
//
|
||||
// All other identifiers denote addressable locations (variables).
|
||||
// For them, VarValue may return either the variable's address or its
|
||||
// value, even when the expression is evaluated only for its value; the
|
||||
// situation is reported by isAddr, the second component of the result.
|
||||
//
|
||||
// If !isAddr, the returned value is the one associated with the
|
||||
// specific identifier. For example,
|
||||
// var x int // VarValue(x) returns Const 0 here
|
||||
// x = 1 // VarValue(x) returns Const 1 here
|
||||
//
|
||||
// It is not specified whether the value or the address is returned in
|
||||
// any particular case, as it may depend upon optimizations performed
|
||||
// during SSA code generation, such as registerization, constant
|
||||
// folding, avoidance of materialization of subexpressions, etc.
|
||||
//
|
||||
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
|
||||
// All references to a var are local to some function, possibly init.
|
||||
fn := EnclosingFunction(pkg, ref)
|
||||
if fn == nil {
|
||||
return // e.g. def of struct field; SSA not built?
|
||||
}
|
||||
|
||||
id := ref[0].(*ast.Ident)
|
||||
|
||||
// Defining ident of a parameter?
|
||||
if id.Pos() == obj.Pos() {
|
||||
for _, param := range fn.Params {
|
||||
if param.Object() == obj {
|
||||
return param, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Other ident?
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if dr, ok := instr.(*DebugRef); ok {
|
||||
if dr.Pos() == id.Pos() {
|
||||
return dr.X, dr.IsAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Defining ident of package-level var?
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Global), true
|
||||
}
|
||||
|
||||
return // e.g. debug info not requested, or var optimized away
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package ssautil
|
||||
|
||||
// This file defines utility functions for constructing programs in SSA form.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// CreateProgram returns a new program in SSA form, given a program
|
||||
// loaded from source. An SSA package is created for each transitively
|
||||
// error-free package of lprog.
|
||||
//
|
||||
// Code for bodies of functions is not built until BuildAll() is called
|
||||
// on the result.
|
||||
//
|
||||
// mode controls diagnostics and checking during SSA construction.
|
||||
//
|
||||
func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program {
|
||||
prog := ssa.NewProgram(lprog.Fset, mode)
|
||||
|
||||
for _, info := range lprog.AllPackages {
|
||||
if info.TransitivelyErrorFree {
|
||||
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
|
||||
}
|
||||
}
|
||||
|
||||
return prog
|
||||
}
|
||||
|
||||
// BuildPackage builds an SSA program with IR for a single package.
|
||||
//
|
||||
// It populates pkg by type-checking the specified file ASTs. All
|
||||
// dependencies are loaded using the importer specified by tc, which
|
||||
// typically loads compiler export data; SSA code cannot be built for
|
||||
// those packages. BuildPackage then constructs an ssa.Program with all
|
||||
// dependency packages created, and builds and returns the SSA package
|
||||
// corresponding to pkg.
|
||||
//
|
||||
// The caller must have set pkg.Path() to the import path.
|
||||
//
|
||||
// The operation fails if there were any type-checking or import errors.
|
||||
//
|
||||
// See ../ssa/example_test.go for an example.
|
||||
//
|
||||
func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.Package, *types.Info, error) {
|
||||
if fset == nil {
|
||||
panic("no token.FileSet")
|
||||
}
|
||||
if pkg.Path() == "" {
|
||||
panic("package has no import path")
|
||||
}
|
||||
|
||||
info := &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
}
|
||||
if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
prog := ssa.NewProgram(fset, mode)
|
||||
|
||||
// Create SSA packages for all imports.
|
||||
// Order is not significant.
|
||||
created := make(map[*types.Package]bool)
|
||||
var createAll func(pkgs []*types.Package)
|
||||
createAll = func(pkgs []*types.Package) {
|
||||
for _, p := range pkgs {
|
||||
if !created[p] {
|
||||
created[p] = true
|
||||
prog.CreatePackage(p, nil, nil, true)
|
||||
createAll(p.Imports())
|
||||
}
|
||||
}
|
||||
}
|
||||
createAll(pkg.Imports())
|
||||
|
||||
// Create and build the primary package.
|
||||
ssapkg := prog.CreatePackage(pkg, files, info, false)
|
||||
ssapkg.Build()
|
||||
return ssapkg, info, nil
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
// 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 ssautil
|
||||
|
||||
// This file implements discovery of switch and type-switch constructs
|
||||
// from low-level control flow.
|
||||
//
|
||||
// Many techniques exist for compiling a high-level switch with
|
||||
// constant cases to efficient machine code. The optimal choice will
|
||||
// depend on the data type, the specific case values, the code in the
|
||||
// body of each case, and the hardware.
|
||||
// Some examples:
|
||||
// - a lookup table (for a switch that maps constants to constants)
|
||||
// - a computed goto
|
||||
// - a binary tree
|
||||
// - a perfect hash
|
||||
// - a two-level switch (to partition constant strings by their first byte).
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// A ConstCase represents a single constant comparison.
|
||||
// It is part of a Switch.
|
||||
type ConstCase struct {
|
||||
Block *ssa.BasicBlock // block performing the comparison
|
||||
Body *ssa.BasicBlock // body of the case
|
||||
Value *ssa.Const // case comparand
|
||||
}
|
||||
|
||||
// A TypeCase represents a single type assertion.
|
||||
// It is part of a Switch.
|
||||
type TypeCase struct {
|
||||
Block *ssa.BasicBlock // block performing the type assert
|
||||
Body *ssa.BasicBlock // body of the case
|
||||
Type types.Type // case type
|
||||
Binding ssa.Value // value bound by this case
|
||||
}
|
||||
|
||||
// A Switch is a logical high-level control flow operation
|
||||
// (a multiway branch) discovered by analysis of a CFG containing
|
||||
// only if/else chains. It is not part of the ssa.Instruction set.
|
||||
//
|
||||
// One of ConstCases and TypeCases has length >= 2;
|
||||
// the other is nil.
|
||||
//
|
||||
// In a value switch, the list of cases may contain duplicate constants.
|
||||
// A type switch may contain duplicate types, or types assignable
|
||||
// to an interface type also in the list.
|
||||
// TODO(adonovan): eliminate such duplicates.
|
||||
//
|
||||
type Switch struct {
|
||||
Start *ssa.BasicBlock // block containing start of if/else chain
|
||||
X ssa.Value // the switch operand
|
||||
ConstCases []ConstCase // ordered list of constant comparisons
|
||||
TypeCases []TypeCase // ordered list of type assertions
|
||||
Default *ssa.BasicBlock // successor if all comparisons fail
|
||||
}
|
||||
|
||||
func (sw *Switch) String() string {
|
||||
// We represent each block by the String() of its
|
||||
// first Instruction, e.g. "print(42:int)".
|
||||
var buf bytes.Buffer
|
||||
if sw.ConstCases != nil {
|
||||
fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
|
||||
for _, c := range sw.ConstCases {
|
||||
fmt.Fprintf(&buf, "case %s: %s\n", c.Value, c.Body.Instrs[0])
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
|
||||
for _, c := range sw.TypeCases {
|
||||
fmt.Fprintf(&buf, "case %s %s: %s\n",
|
||||
c.Binding.Name(), c.Type, c.Body.Instrs[0])
|
||||
}
|
||||
}
|
||||
if sw.Default != nil {
|
||||
fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0])
|
||||
}
|
||||
fmt.Fprintf(&buf, "}")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Switches examines the control-flow graph of fn and returns the
|
||||
// set of inferred value and type switches. A value switch tests an
|
||||
// ssa.Value for equality against two or more compile-time constant
|
||||
// values. Switches involving link-time constants (addresses) are
|
||||
// ignored. A type switch type-asserts an ssa.Value against two or
|
||||
// more types.
|
||||
//
|
||||
// The switches are returned in dominance order.
|
||||
//
|
||||
// The resulting switches do not necessarily correspond to uses of the
|
||||
// 'switch' keyword in the source: for example, a single source-level
|
||||
// switch statement with non-constant cases may result in zero, one or
|
||||
// many Switches, one per plural sequence of constant cases.
|
||||
// Switches may even be inferred from if/else- or goto-based control flow.
|
||||
// (In general, the control flow constructs of the source program
|
||||
// cannot be faithfully reproduced from the SSA representation.)
|
||||
//
|
||||
func Switches(fn *ssa.Function) []Switch {
|
||||
// Traverse the CFG in dominance order, so we don't
|
||||
// enter an if/else-chain in the middle.
|
||||
var switches []Switch
|
||||
seen := make(map[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.blockSet
|
||||
for _, b := range fn.DomPreorder() {
|
||||
if x, k := isComparisonBlock(b); x != nil {
|
||||
// Block b starts a switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
valueSwitch(&sw, k, seen)
|
||||
if len(sw.ConstCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
|
||||
if y, x, T := isTypeAssertBlock(b); y != nil {
|
||||
// Block b starts a type switch.
|
||||
sw := Switch{Start: b, X: x}
|
||||
typeSwitch(&sw, y, T, seen)
|
||||
if len(sw.TypeCases) > 1 {
|
||||
switches = append(switches, sw)
|
||||
}
|
||||
}
|
||||
}
|
||||
return switches
|
||||
}
|
||||
|
||||
func valueSwitch(sw *Switch, k *ssa.Const, seen map[*ssa.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for x == sw.X {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.ConstCases = append(sw.ConstCases, ConstCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Value: k,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
if len(b.Instrs) > 2 {
|
||||
// Block b contains not just 'if x == k',
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
x, k = isComparisonBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
func typeSwitch(sw *Switch, y ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) {
|
||||
b := sw.Start
|
||||
x := sw.X
|
||||
for x == sw.X {
|
||||
if seen[b] {
|
||||
break
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
sw.TypeCases = append(sw.TypeCases, TypeCase{
|
||||
Block: b,
|
||||
Body: b.Succs[0],
|
||||
Type: T,
|
||||
Binding: y,
|
||||
})
|
||||
b = b.Succs[1]
|
||||
if len(b.Instrs) > 4 {
|
||||
// Block b contains not just
|
||||
// {TypeAssert; Extract #0; Extract #1; If}
|
||||
// so it may have side effects that
|
||||
// make it unsafe to elide.
|
||||
break
|
||||
}
|
||||
if len(b.Preds) != 1 {
|
||||
// Block b has multiple predecessors,
|
||||
// so it cannot be treated as a case.
|
||||
break
|
||||
}
|
||||
y, x, T = isTypeAssertBlock(b)
|
||||
}
|
||||
sw.Default = b
|
||||
}
|
||||
|
||||
// isComparisonBlock returns the operands (v, k) if a block ends with
|
||||
// a comparison v==k, where k is a compile-time constant.
|
||||
//
|
||||
func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) {
|
||||
if n := len(b.Instrs); n >= 2 {
|
||||
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||
if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
|
||||
if k, ok := binop.Y.(*ssa.Const); ok {
|
||||
return binop.X, k
|
||||
}
|
||||
if k, ok := binop.X.(*ssa.Const); ok {
|
||||
return binop.Y, k
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isTypeAssertBlock returns the operands (y, x, T) if a block ends with
|
||||
// a type assertion "if y, ok := x.(T); ok {".
|
||||
//
|
||||
func isTypeAssertBlock(b *ssa.BasicBlock) (y, x ssa.Value, T types.Type) {
|
||||
if n := len(b.Instrs); n >= 4 {
|
||||
if i, ok := b.Instrs[n-1].(*ssa.If); ok {
|
||||
if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
|
||||
if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b {
|
||||
// hack: relies upon instruction ordering.
|
||||
if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok {
|
||||
return ext0, ta.X, ta.AssertedType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// 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 ssautil // import "golang.org/x/tools/go/ssa/ssautil"
|
||||
|
||||
import "golang.org/x/tools/go/ssa"
|
||||
|
||||
// This file defines utilities for visiting the SSA representation of
|
||||
// a Program.
|
||||
//
|
||||
// TODO(adonovan): test coverage.
|
||||
|
||||
// AllFunctions finds and returns the set of functions potentially
|
||||
// needed by program prog, as determined by a simple linker-style
|
||||
// reachability algorithm starting from the members and method-sets of
|
||||
// each package. The result may include anonymous functions and
|
||||
// synthetic wrappers.
|
||||
//
|
||||
// Precondition: all packages are built.
|
||||
//
|
||||
func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool {
|
||||
visit := visitor{
|
||||
prog: prog,
|
||||
seen: make(map[*ssa.Function]bool),
|
||||
}
|
||||
visit.program()
|
||||
return visit.seen
|
||||
}
|
||||
|
||||
type visitor struct {
|
||||
prog *ssa.Program
|
||||
seen map[*ssa.Function]bool
|
||||
}
|
||||
|
||||
func (visit *visitor) program() {
|
||||
for _, pkg := range visit.prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
if fn, ok := mem.(*ssa.Function); ok {
|
||||
visit.function(fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, T := range visit.prog.RuntimeTypes() {
|
||||
mset := visit.prog.MethodSets.MethodSet(T)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
visit.function(visit.prog.Method(mset.At(i)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (visit *visitor) function(fn *ssa.Function) {
|
||||
if !visit.seen[fn] {
|
||||
visit.seen[fn] = true
|
||||
var buf [10]*ssa.Value // avoid alloc in common case
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
for _, op := range instr.Operands(buf[:0]) {
|
||||
if fn, ok := (*op).(*ssa.Function); ok {
|
||||
visit.function(fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,296 @@
|
|||
// 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 ssa
|
||||
|
||||
// CreateTestMainPackage synthesizes a main package that runs all the
|
||||
// tests of the supplied packages.
|
||||
// It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// FindTests returns the list of packages that define at least one Test,
|
||||
// Example or Benchmark function (as defined by "go test"), and the
|
||||
// lists of all such functions.
|
||||
//
|
||||
func FindTests(pkgs []*Package) (testpkgs []*Package, tests, benchmarks, examples []*Function) {
|
||||
if len(pkgs) == 0 {
|
||||
return
|
||||
}
|
||||
prog := pkgs[0].Prog
|
||||
|
||||
// The first two of these may be nil: if the program doesn't import "testing",
|
||||
// it can't contain any tests, but it may yet contain Examples.
|
||||
var testSig *types.Signature // func(*testing.T)
|
||||
var benchmarkSig *types.Signature // func(*testing.B)
|
||||
var exampleSig = types.NewSignature(nil, nil, nil, false) // func()
|
||||
|
||||
// Obtain the types from the parameters of testing.Main().
|
||||
if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
|
||||
params := testingPkg.Func("Main").Signature.Params()
|
||||
testSig = funcField(params.At(1).Type())
|
||||
benchmarkSig = funcField(params.At(2).Type())
|
||||
}
|
||||
|
||||
seen := make(map[*Package]bool)
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Prog != prog {
|
||||
panic("wrong Program")
|
||||
}
|
||||
|
||||
// TODO(adonovan): use a stable order, e.g. lexical.
|
||||
for _, mem := range pkg.Members {
|
||||
if f, ok := mem.(*Function); ok &&
|
||||
ast.IsExported(f.Name()) &&
|
||||
strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") {
|
||||
|
||||
switch {
|
||||
case testSig != nil && isTestSig(f, "Test", testSig):
|
||||
tests = append(tests, f)
|
||||
case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig):
|
||||
benchmarks = append(benchmarks, f)
|
||||
case isTestSig(f, "Example", exampleSig):
|
||||
examples = append(examples, f)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if !seen[pkg] {
|
||||
seen[pkg] = true
|
||||
testpkgs = append(testpkgs, pkg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Like isTest, but checks the signature too.
|
||||
func isTestSig(f *Function, prefix string, sig *types.Signature) bool {
|
||||
return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig)
|
||||
}
|
||||
|
||||
// If non-nil, testMainStartBodyHook is called immediately after
|
||||
// startBody for main.init and main.main, making it easy for users to
|
||||
// add custom imports and initialization steps for proprietary build
|
||||
// systems that don't exactly follow 'go test' conventions.
|
||||
var testMainStartBodyHook func(*Function)
|
||||
|
||||
// CreateTestMainPackage creates and returns a synthetic "main"
|
||||
// package that runs all the tests of the supplied packages, similar
|
||||
// to the one that would be created by the 'go test' tool.
|
||||
//
|
||||
// It returns nil if the program contains no tests.
|
||||
//
|
||||
func (prog *Program) CreateTestMainPackage(pkgs ...*Package) *Package {
|
||||
pkgs, tests, benchmarks, examples := FindTests(pkgs)
|
||||
if len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
testmain := &Package{
|
||||
Prog: prog,
|
||||
Members: make(map[string]Member),
|
||||
values: make(map[types.Object]Value),
|
||||
Object: types.NewPackage("test$main", "main"),
|
||||
}
|
||||
|
||||
// Build package's init function.
|
||||
init := &Function{
|
||||
name: "init",
|
||||
Signature: new(types.Signature),
|
||||
Synthetic: "package initializer",
|
||||
Pkg: testmain,
|
||||
Prog: prog,
|
||||
}
|
||||
init.startBody()
|
||||
|
||||
if testMainStartBodyHook != nil {
|
||||
testMainStartBodyHook(init)
|
||||
}
|
||||
|
||||
// Initialize packages to test.
|
||||
var pkgpaths []string
|
||||
for _, pkg := range pkgs {
|
||||
var v Call
|
||||
v.Call.Value = pkg.init
|
||||
v.setType(types.NewTuple())
|
||||
init.emit(&v)
|
||||
|
||||
pkgpaths = append(pkgpaths, pkg.Object.Path())
|
||||
}
|
||||
sort.Strings(pkgpaths)
|
||||
init.emit(new(Return))
|
||||
init.finishBody()
|
||||
testmain.init = init
|
||||
testmain.Object.MarkComplete()
|
||||
testmain.Members[init.name] = init
|
||||
|
||||
// For debugging convenience, define an unexported const
|
||||
// that enumerates the packages.
|
||||
packagesConst := types.NewConst(token.NoPos, testmain.Object, "packages", tString,
|
||||
exact.MakeString(strings.Join(pkgpaths, " ")))
|
||||
memberFromObject(testmain, packagesConst, nil)
|
||||
|
||||
// Create main *types.Func and *ssa.Function
|
||||
mainFunc := types.NewFunc(token.NoPos, testmain.Object, "main", new(types.Signature))
|
||||
memberFromObject(testmain, mainFunc, nil)
|
||||
main := testmain.Func("main")
|
||||
main.Synthetic = "test main function"
|
||||
|
||||
main.startBody()
|
||||
|
||||
if testMainStartBodyHook != nil {
|
||||
testMainStartBodyHook(main)
|
||||
}
|
||||
|
||||
if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil {
|
||||
testingMain := testingPkg.Func("Main")
|
||||
testingMainParams := testingMain.Signature.Params()
|
||||
|
||||
// The generated code is as if compiled from this:
|
||||
//
|
||||
// func main() {
|
||||
// match := func(_, _ string) (bool, error) { return true, nil }
|
||||
// tests := []testing.InternalTest{{"TestFoo", TestFoo}, ...}
|
||||
// benchmarks := []testing.InternalBenchmark{...}
|
||||
// examples := []testing.InternalExample{...}
|
||||
// testing.Main(match, tests, benchmarks, examples)
|
||||
// }
|
||||
|
||||
matcher := &Function{
|
||||
name: "matcher",
|
||||
Signature: testingMainParams.At(0).Type().(*types.Signature),
|
||||
Synthetic: "test matcher predicate",
|
||||
parent: main,
|
||||
Pkg: testmain,
|
||||
Prog: prog,
|
||||
}
|
||||
main.AnonFuncs = append(main.AnonFuncs, matcher)
|
||||
matcher.startBody()
|
||||
matcher.emit(&Return{Results: []Value{vTrue, nilConst(types.Universe.Lookup("error").Type())}})
|
||||
matcher.finishBody()
|
||||
|
||||
// Emit call: testing.Main(matcher, tests, benchmarks, examples).
|
||||
var c Call
|
||||
c.Call.Value = testingMain
|
||||
c.Call.Args = []Value{
|
||||
matcher,
|
||||
testMainSlice(main, tests, testingMainParams.At(1).Type()),
|
||||
testMainSlice(main, benchmarks, testingMainParams.At(2).Type()),
|
||||
testMainSlice(main, examples, testingMainParams.At(3).Type()),
|
||||
}
|
||||
emitTailCall(main, &c)
|
||||
} else {
|
||||
// The program does not import "testing", but FindTests
|
||||
// returned non-nil, which must mean there were Examples
|
||||
// but no Tests or Benchmarks.
|
||||
// We'll simply call them from testmain.main; this will
|
||||
// ensure they don't panic, but will not check any
|
||||
// "Output:" comments.
|
||||
for _, eg := range examples {
|
||||
var c Call
|
||||
c.Call.Value = eg
|
||||
c.setType(types.NewTuple())
|
||||
main.emit(&c)
|
||||
}
|
||||
main.emit(&Return{})
|
||||
main.currentBlock = nil
|
||||
}
|
||||
|
||||
main.finishBody()
|
||||
|
||||
testmain.Members["main"] = main
|
||||
|
||||
if prog.mode&PrintPackages != 0 {
|
||||
printMu.Lock()
|
||||
testmain.WriteTo(os.Stdout)
|
||||
printMu.Unlock()
|
||||
}
|
||||
|
||||
if prog.mode&SanityCheckFunctions != 0 {
|
||||
sanityCheckPackage(testmain)
|
||||
}
|
||||
|
||||
prog.packages[testmain.Object] = testmain
|
||||
|
||||
return testmain
|
||||
}
|
||||
|
||||
// testMainSlice emits to fn code to construct a slice of type slice
|
||||
// (one of []testing.Internal{Test,Benchmark,Example}) for all
|
||||
// functions in testfuncs. It returns the slice value.
|
||||
//
|
||||
func testMainSlice(fn *Function, testfuncs []*Function, slice types.Type) Value {
|
||||
if testfuncs == nil {
|
||||
return nilConst(slice)
|
||||
}
|
||||
|
||||
tElem := slice.(*types.Slice).Elem()
|
||||
tPtrString := types.NewPointer(tString)
|
||||
tPtrElem := types.NewPointer(tElem)
|
||||
tPtrFunc := types.NewPointer(funcField(slice))
|
||||
|
||||
// Emit: array = new [n]testing.InternalTest
|
||||
tArray := types.NewArray(tElem, int64(len(testfuncs)))
|
||||
array := emitNew(fn, tArray, token.NoPos)
|
||||
array.Comment = "test main"
|
||||
for i, testfunc := range testfuncs {
|
||||
// Emit: pitem = &array[i]
|
||||
ia := &IndexAddr{X: array, Index: intConst(int64(i))}
|
||||
ia.setType(tPtrElem)
|
||||
pitem := fn.emit(ia)
|
||||
|
||||
// Emit: pname = &pitem.Name
|
||||
fa := &FieldAddr{X: pitem, Field: 0} // .Name
|
||||
fa.setType(tPtrString)
|
||||
pname := fn.emit(fa)
|
||||
|
||||
// Emit: *pname = "testfunc"
|
||||
emitStore(fn, pname, stringConst(testfunc.Name()), token.NoPos)
|
||||
|
||||
// Emit: pfunc = &pitem.F
|
||||
fa = &FieldAddr{X: pitem, Field: 1} // .F
|
||||
fa.setType(tPtrFunc)
|
||||
pfunc := fn.emit(fa)
|
||||
|
||||
// Emit: *pfunc = testfunc
|
||||
emitStore(fn, pfunc, testfunc, token.NoPos)
|
||||
}
|
||||
|
||||
// Emit: slice array[:]
|
||||
sl := &Slice{X: array}
|
||||
sl.setType(slice)
|
||||
return fn.emit(sl)
|
||||
}
|
||||
|
||||
// Given the type of one of the three slice parameters of testing.Main,
|
||||
// returns the function type.
|
||||
func funcField(slice types.Type) *types.Signature {
|
||||
return slice.(*types.Slice).Elem().Underlying().(*types.Struct).Field(1).Type().(*types.Signature)
|
||||
}
|
||||
|
||||
// Plundered from $GOROOT/src/cmd/go/test.go
|
||||
|
||||
// isTest tells whether name looks like a test (or benchmark, according to prefix).
|
||||
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
|
||||
// We don't want TesticularCancer.
|
||||
func isTest(name, prefix string) bool {
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
return false
|
||||
}
|
||||
if len(name) == len(prefix) { // "Test" is ok
|
||||
return true
|
||||
}
|
||||
return ast.IsExported(name[len(prefix):])
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file defines a number of miscellaneous utility functions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
//// AST utilities
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
|
||||
|
||||
// isBlankIdent returns true iff e is an Ident with name "_".
|
||||
// They have no associated types.Object, and thus no type.
|
||||
//
|
||||
func isBlankIdent(e ast.Expr) bool {
|
||||
id, ok := e.(*ast.Ident)
|
||||
return ok && id.Name == "_"
|
||||
}
|
||||
|
||||
//// Type utilities. Some of these belong in go/types.
|
||||
|
||||
// isPointer returns true for types whose underlying type is a pointer.
|
||||
func isPointer(typ types.Type) bool {
|
||||
_, ok := typ.Underlying().(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isInterface(T types.Type) bool { return types.IsInterface(T) }
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// recvType returns the receiver type of method obj.
|
||||
func recvType(obj *types.Func) types.Type {
|
||||
return obj.Type().(*types.Signature).Recv().Type()
|
||||
}
|
||||
|
||||
// DefaultType returns the default "typed" type for an "untyped" type;
|
||||
// it returns the incoming type for all other types. The default type
|
||||
// for untyped nil is untyped nil.
|
||||
//
|
||||
// Exported to ssa/interp.
|
||||
//
|
||||
// TODO(gri): this is a copy of go/types.defaultType; export that function.
|
||||
//
|
||||
func DefaultType(typ types.Type) types.Type {
|
||||
if t, ok := typ.(*types.Basic); ok {
|
||||
k := t.Kind()
|
||||
switch k {
|
||||
case types.UntypedBool:
|
||||
k = types.Bool
|
||||
case types.UntypedInt:
|
||||
k = types.Int
|
||||
case types.UntypedRune:
|
||||
k = types.Rune
|
||||
case types.UntypedFloat:
|
||||
k = types.Float64
|
||||
case types.UntypedComplex:
|
||||
k = types.Complex128
|
||||
case types.UntypedString:
|
||||
k = types.String
|
||||
}
|
||||
typ = types.Typ[k]
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// logStack prints the formatted "start" message to stderr and
|
||||
// returns a closure that prints the corresponding "end" message.
|
||||
// Call using 'defer logStack(...)()' to show builder stack on panic.
|
||||
// Don't forget trailing parens!
|
||||
//
|
||||
func logStack(format string, args ...interface{}) func() {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, "\n")
|
||||
return func() {
|
||||
io.WriteString(os.Stderr, msg)
|
||||
io.WriteString(os.Stderr, " end\n")
|
||||
}
|
||||
}
|
||||
|
||||
// newVar creates a 'var' for use in a types.Tuple.
|
||||
func newVar(name string, typ types.Type) *types.Var {
|
||||
return types.NewParam(token.NoPos, nil, name, typ)
|
||||
}
|
||||
|
||||
// anonVar creates an anonymous 'var' for use in a types.Tuple.
|
||||
func anonVar(typ types.Type) *types.Var {
|
||||
return newVar("", typ)
|
||||
}
|
||||
|
||||
var lenResults = types.NewTuple(anonVar(tInt))
|
||||
|
||||
// makeLen returns the len builtin specialized to type func(T)int.
|
||||
func makeLen(T types.Type) *Builtin {
|
||||
lenParams := types.NewTuple(anonVar(T))
|
||||
return &Builtin{
|
||||
name: "len",
|
||||
sig: types.NewSignature(nil, lenParams, lenResults, false),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
// 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 ssa
|
||||
|
||||
// This file defines synthesis of Functions that delegate to declared
|
||||
// methods; they come in three kinds:
|
||||
//
|
||||
// (1) wrappers: methods that wrap declared methods, performing
|
||||
// implicit pointer indirections and embedded field selections.
|
||||
//
|
||||
// (2) thunks: funcs that wrap declared methods. Like wrappers,
|
||||
// thunks perform indirections and field selections. The thunk's
|
||||
// first parameter is used as the receiver for the method call.
|
||||
//
|
||||
// (3) bounds: funcs that wrap declared methods. The bound's sole
|
||||
// free variable, supplied by a closure, is used as the receiver
|
||||
// for the method call. No indirections or field selections are
|
||||
// performed since they can be done before the call.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
// -- wrappers -----------------------------------------------------------
|
||||
|
||||
// makeWrapper returns a synthetic method that delegates to the
|
||||
// declared method denoted by meth.Obj(), first performing any
|
||||
// necessary pointer indirections or field selections implied by meth.
|
||||
//
|
||||
// The resulting method's receiver type is meth.Recv().
|
||||
//
|
||||
// This function is versatile but quite subtle! Consider the
|
||||
// following axes of variation when making changes:
|
||||
// - optional receiver indirection
|
||||
// - optional implicit field selections
|
||||
// - meth.Obj() may denote a concrete or an interface method
|
||||
// - the result may be a thunk or a wrapper.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func makeWrapper(prog *Program, sel *types.Selection) *Function {
|
||||
obj := sel.Obj().(*types.Func) // the declared function
|
||||
sig := sel.Type().(*types.Signature) // type of this wrapper
|
||||
|
||||
var recv *types.Var // wrapper's receiver or thunk's params[0]
|
||||
name := obj.Name()
|
||||
var description string
|
||||
var start int // first regular param
|
||||
if sel.Kind() == types.MethodExpr {
|
||||
name += "$thunk"
|
||||
description = "thunk"
|
||||
recv = sig.Params().At(0)
|
||||
start = 1
|
||||
} else {
|
||||
description = "wrapper"
|
||||
recv = sig.Recv()
|
||||
}
|
||||
|
||||
description = fmt.Sprintf("%s for %s", description, sel.Obj())
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("make %s to (%s)", description, recv.Type())()
|
||||
}
|
||||
fn := &Function{
|
||||
name: name,
|
||||
method: sel,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
fn.startBody()
|
||||
fn.addSpilledParam(recv)
|
||||
createParams(fn, start)
|
||||
|
||||
indices := sel.Index()
|
||||
|
||||
var v Value = fn.Locals[0] // spilled receiver
|
||||
if isPointer(sel.Recv()) {
|
||||
v = emitLoad(fn, v)
|
||||
|
||||
// For simple indirection wrappers, perform an informative nil-check:
|
||||
// "value method (T).f called using nil *T pointer"
|
||||
if len(indices) == 1 && !isPointer(recvType(obj)) {
|
||||
var c Call
|
||||
c.Call.Value = &Builtin{
|
||||
name: "ssa:wrapnilchk",
|
||||
sig: types.NewSignature(nil,
|
||||
types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)),
|
||||
types.NewTuple(anonVar(sel.Recv())), false),
|
||||
}
|
||||
c.Call.Args = []Value{
|
||||
v,
|
||||
stringConst(deref(sel.Recv()).String()),
|
||||
stringConst(sel.Obj().Name()),
|
||||
}
|
||||
c.setType(v.Type())
|
||||
v = fn.emit(&c)
|
||||
}
|
||||
}
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of *A receiver param, or
|
||||
// address of A spilled receiver.
|
||||
|
||||
// We use pointer arithmetic (FieldAddr possibly followed by
|
||||
// Load) in preference to value extraction (Field possibly
|
||||
// preceded by Load).
|
||||
|
||||
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
|
||||
|
||||
// Invariant: v is a pointer, either
|
||||
// value of implicit *C field, or
|
||||
// address of implicit C field.
|
||||
|
||||
var c Call
|
||||
if r := recvType(obj); !isInterface(r) { // concrete method
|
||||
if !isPointer(r) {
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
c.Call.Args = append(c.Call.Args, v)
|
||||
} else {
|
||||
c.Call.Method = obj
|
||||
c.Call.Value = emitLoad(fn, v)
|
||||
}
|
||||
for _, arg := range fn.Params[1:] {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
return fn
|
||||
}
|
||||
|
||||
// createParams creates parameters for wrapper method fn based on its
|
||||
// Signature.Params, which do not include the receiver.
|
||||
// start is the index of the first regular parameter to use.
|
||||
//
|
||||
func createParams(fn *Function, start int) {
|
||||
var last *Parameter
|
||||
tparams := fn.Signature.Params()
|
||||
for i, n := start, tparams.Len(); i < n; i++ {
|
||||
last = fn.addParamObj(tparams.At(i))
|
||||
}
|
||||
if fn.Signature.Variadic() {
|
||||
last.typ = types.NewSlice(last.typ)
|
||||
}
|
||||
}
|
||||
|
||||
// -- bounds -----------------------------------------------------------
|
||||
|
||||
// makeBound returns a bound method wrapper (or "bound"), a synthetic
|
||||
// function that delegates to a concrete or interface method denoted
|
||||
// by obj. The resulting function has no receiver, but has one free
|
||||
// variable which will be used as the method's receiver in the
|
||||
// tail-call.
|
||||
//
|
||||
// Use MakeClosure with such a wrapper to construct a bound method
|
||||
// closure. e.g.:
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// var t T
|
||||
// f := t.meth
|
||||
// f() // calls t.meth()
|
||||
//
|
||||
// f is a closure of a synthetic wrapper defined as if by:
|
||||
//
|
||||
// f := func() { return t.meth() }
|
||||
//
|
||||
// Unlike makeWrapper, makeBound need perform no indirection or field
|
||||
// selections because that can be done before the closure is
|
||||
// constructed.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func makeBound(prog *Program, obj *types.Func) *Function {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
fn, ok := prog.bounds[obj]
|
||||
if !ok {
|
||||
description := fmt.Sprintf("bound method wrapper for %s", obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("%s", description)()
|
||||
}
|
||||
fn = &Function{
|
||||
name: obj.Name() + "$bound",
|
||||
object: obj,
|
||||
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
|
||||
fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
|
||||
fn.FreeVars = []*FreeVar{fv}
|
||||
fn.startBody()
|
||||
createParams(fn, 0)
|
||||
var c Call
|
||||
|
||||
if !isInterface(recvType(obj)) { // concrete
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
c.Call.Args = []Value{fv}
|
||||
} else {
|
||||
c.Call.Value = fv
|
||||
c.Call.Method = obj
|
||||
}
|
||||
for _, arg := range fn.Params {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
|
||||
prog.bounds[obj] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// -- thunks -----------------------------------------------------------
|
||||
|
||||
// makeThunk returns a thunk, a synthetic function that delegates to a
|
||||
// concrete or interface method denoted by sel.Obj(). The resulting
|
||||
// function has no receiver, but has an additional (first) regular
|
||||
// parameter.
|
||||
//
|
||||
// Precondition: sel.Kind() == types.MethodExpr.
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// f := T.meth
|
||||
// var t T
|
||||
// f(t) // calls t.meth()
|
||||
//
|
||||
// f is a synthetic wrapper defined as if by:
|
||||
//
|
||||
// f := func(t T) { return t.meth() }
|
||||
//
|
||||
// TODO(adonovan): opt: currently the stub is created even when used
|
||||
// directly in a function call: C.f(i, 0). This is less efficient
|
||||
// than inlining the stub.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func makeThunk(prog *Program, sel *types.Selection) *Function {
|
||||
if sel.Kind() != types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
|
||||
key := selectionKey{
|
||||
kind: sel.Kind(),
|
||||
recv: sel.Recv(),
|
||||
obj: sel.Obj(),
|
||||
index: fmt.Sprint(sel.Index()),
|
||||
indirect: sel.Indirect(),
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
// Canonicalize key.recv to avoid constructing duplicate thunks.
|
||||
canonRecv, ok := prog.canon.At(key.recv).(types.Type)
|
||||
if !ok {
|
||||
canonRecv = key.recv
|
||||
prog.canon.Set(key.recv, canonRecv)
|
||||
}
|
||||
key.recv = canonRecv
|
||||
|
||||
fn, ok := prog.thunks[key]
|
||||
if !ok {
|
||||
fn = makeWrapper(prog, sel)
|
||||
if fn.Signature.Recv() != nil {
|
||||
panic(fn) // unexpected receiver
|
||||
}
|
||||
prog.thunks[key] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
|
||||
return types.NewSignature(recv, s.Params(), s.Results(), s.Variadic())
|
||||
}
|
||||
|
||||
// selectionKey is like types.Selection but a usable map key.
|
||||
type selectionKey struct {
|
||||
kind types.SelectionKind
|
||||
recv types.Type // canonicalized via Program.canon
|
||||
obj types.Object
|
||||
index string
|
||||
indirect bool
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
// 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 types declares the data types and implements
|
||||
// the algorithms for type-checking of Go packages.
|
||||
// Use Check and Config.Check to invoke the type-checker.
|
||||
//
|
||||
// Type-checking consists of several interdependent phases:
|
||||
//
|
||||
// Name resolution maps each identifier (ast.Ident) in the program to the
|
||||
// language object (Object) it denotes.
|
||||
// Use Info.{Defs,Uses,Implicits} for the results of name resolution.
|
||||
//
|
||||
// Constant folding computes the exact constant value (exact.Value) for
|
||||
// every expression (ast.Expr) that is a compile-time constant.
|
||||
// Use Info.Types[expr].Value for the results of constant folding.
|
||||
//
|
||||
// Type inference computes the type (Type) of every expression (ast.Expr)
|
||||
// and checks for compliance with the language specification.
|
||||
// Use Info.Types[expr].Type for the results of type inference.
|
||||
//
|
||||
package types // import "golang.org/x/tools/go/types"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
)
|
||||
|
||||
// Check type-checks a package and returns the resulting complete package
|
||||
// object, or a nil package and the first error. The package is specified
|
||||
// by a list of *ast.Files and corresponding file set, and the import path
|
||||
// the package is identified with. The clean path must not be empty or dot (".").
|
||||
//
|
||||
// For more control over type-checking and results, use Config.Check.
|
||||
func Check(path string, fset *token.FileSet, files []*ast.File) (*Package, error) {
|
||||
var conf Config
|
||||
pkg, err := conf.Check(path, fset, files, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
// An Error describes a type-checking error; it implements the error interface.
|
||||
// A "soft" error is an error that still permits a valid interpretation of a
|
||||
// package (such as "unused variable"); "hard" errors may lead to unpredictable
|
||||
// behavior if ignored.
|
||||
type Error struct {
|
||||
Fset *token.FileSet // file set for interpretation of Pos
|
||||
Pos token.Pos // error position
|
||||
Msg string // error message
|
||||
Soft bool // if set, error is "soft"
|
||||
}
|
||||
|
||||
// Error returns an error string formatted as follows:
|
||||
// filename:line:column: message
|
||||
func (err Error) Error() string {
|
||||
return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg)
|
||||
}
|
||||
|
||||
// An importer resolves import paths to Packages.
|
||||
// The imports map records packages already known,
|
||||
// indexed by package path. The type-checker
|
||||
// will invoke Import with Config.Packages.
|
||||
// An importer must determine the canonical package path and
|
||||
// check imports to see if it is already present in the map.
|
||||
// If so, the Importer can return the map entry. Otherwise,
|
||||
// the importer must load the package data for the given path
|
||||
// into a new *Package, record it in imports map, and return
|
||||
// the package.
|
||||
// TODO(gri) Need to be clearer about requirements of completeness.
|
||||
type Importer func(map[string]*Package, string) (*Package, error)
|
||||
|
||||
// A Config specifies the configuration for type checking.
|
||||
// The zero value for Config is a ready-to-use default configuration.
|
||||
type Config struct {
|
||||
// If IgnoreFuncBodies is set, function bodies are not
|
||||
// type-checked.
|
||||
IgnoreFuncBodies bool
|
||||
|
||||
// If FakeImportC is set, `import "C"` (for packages requiring Cgo)
|
||||
// declares an empty "C" package and errors are omitted for qualified
|
||||
// identifiers referring to package C (which won't find an object).
|
||||
// This feature is intended for the standard library cmd/api tool.
|
||||
//
|
||||
// Caution: Effects may be unpredictable due to follow-up errors.
|
||||
// Do not use casually!
|
||||
FakeImportC bool
|
||||
|
||||
// Packages is used to look up (and thus canonicalize) packages by
|
||||
// package path. If Packages is nil, it is set to a new empty map.
|
||||
// During type-checking, imported packages are added to the map.
|
||||
Packages map[string]*Package
|
||||
|
||||
// If Error != nil, it is called with each error found
|
||||
// during type checking; err has dynamic type Error.
|
||||
// Secondary errors (for instance, to enumerate all types
|
||||
// involved in an invalid recursive type declaration) have
|
||||
// error strings that start with a '\t' character.
|
||||
// If Error == nil, type-checking stops with the first
|
||||
// error found.
|
||||
Error func(err error)
|
||||
|
||||
// If Import != nil, it is called for each imported package.
|
||||
// Otherwise, DefaultImport is called.
|
||||
Import Importer
|
||||
|
||||
// If Sizes != nil, it provides the sizing functions for package unsafe.
|
||||
// Otherwise &StdSizes{WordSize: 8, MaxAlign: 8} is used instead.
|
||||
Sizes Sizes
|
||||
|
||||
// If DisableUnusedImportCheck is set, packages are not checked
|
||||
// for unused imports.
|
||||
DisableUnusedImportCheck bool
|
||||
}
|
||||
|
||||
// DefaultImport is the default importer invoked if Config.Import == nil.
|
||||
// The declaration:
|
||||
//
|
||||
// import _ "golang.org/x/tools/go/gcimporter"
|
||||
//
|
||||
// in a client of go/types will initialize DefaultImport to gcimporter.Import.
|
||||
var DefaultImport Importer
|
||||
|
||||
// Info holds result type information for a type-checked package.
|
||||
// Only the information for which a map is provided is collected.
|
||||
// If the package has type errors, the collected information may
|
||||
// be incomplete.
|
||||
type Info struct {
|
||||
// Types maps expressions to their types, and for constant
|
||||
// expressions, their values. Invalid expressions are omitted.
|
||||
//
|
||||
// For (possibly parenthesized) identifiers denoting built-in
|
||||
// functions, the recorded signatures are call-site specific:
|
||||
// if the call result is not a constant, the recorded type is
|
||||
// an argument-specific signature. Otherwise, the recorded type
|
||||
// is invalid.
|
||||
//
|
||||
// Identifiers on the lhs of declarations (i.e., the identifiers
|
||||
// which are being declared) are collected in the Defs map.
|
||||
// Identifiers denoting packages are collected in the Uses maps.
|
||||
Types map[ast.Expr]TypeAndValue
|
||||
|
||||
// Defs maps identifiers to the objects they define (including
|
||||
// package names, dots "." of dot-imports, and blank "_" identifiers).
|
||||
// For identifiers that do not denote objects (e.g., the package name
|
||||
// in package clauses, or symbolic variables t in t := x.(type) of
|
||||
// type switch headers), the corresponding objects are nil.
|
||||
//
|
||||
// For an anonymous field, Defs returns the field *Var it defines.
|
||||
//
|
||||
// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
|
||||
Defs map[*ast.Ident]Object
|
||||
|
||||
// Uses maps identifiers to the objects they denote.
|
||||
//
|
||||
// For an anonymous field, Uses returns the *TypeName it denotes.
|
||||
//
|
||||
// Invariant: Uses[id].Pos() != id.Pos()
|
||||
Uses map[*ast.Ident]Object
|
||||
|
||||
// Implicits maps nodes to their implicitly declared objects, if any.
|
||||
// The following node and object types may appear:
|
||||
//
|
||||
// node declared object
|
||||
//
|
||||
// *ast.ImportSpec *PkgName for dot-imports and imports without renames
|
||||
// *ast.CaseClause type-specific *Var for each type switch case clause (incl. default)
|
||||
// *ast.Field anonymous struct field or parameter *Var
|
||||
//
|
||||
Implicits map[ast.Node]Object
|
||||
|
||||
// Selections maps selector expressions (excluding qualified identifiers)
|
||||
// to their corresponding selections.
|
||||
Selections map[*ast.SelectorExpr]*Selection
|
||||
|
||||
// Scopes maps ast.Nodes to the scopes they define. Package scopes are not
|
||||
// associated with a specific node but with all files belonging to a package.
|
||||
// Thus, the package scope can be found in the type-checked Package object.
|
||||
// Scopes nest, with the Universe scope being the outermost scope, enclosing
|
||||
// the package scope, which contains (one or more) files scopes, which enclose
|
||||
// function scopes which in turn enclose statement and function literal scopes.
|
||||
// Note that even though package-level functions are declared in the package
|
||||
// scope, the function scopes are embedded in the file scope of the file
|
||||
// containing the function declaration.
|
||||
//
|
||||
// The following node types may appear in Scopes:
|
||||
//
|
||||
// *ast.File
|
||||
// *ast.FuncType
|
||||
// *ast.BlockStmt
|
||||
// *ast.IfStmt
|
||||
// *ast.SwitchStmt
|
||||
// *ast.TypeSwitchStmt
|
||||
// *ast.CaseClause
|
||||
// *ast.CommClause
|
||||
// *ast.ForStmt
|
||||
// *ast.RangeStmt
|
||||
//
|
||||
Scopes map[ast.Node]*Scope
|
||||
|
||||
// InitOrder is the list of package-level initializers in the order in which
|
||||
// they must be executed. Initializers referring to variables related by an
|
||||
// initialization dependency appear in topological order, the others appear
|
||||
// in source order. Variables without an initialization expression do not
|
||||
// appear in this list.
|
||||
InitOrder []*Initializer
|
||||
}
|
||||
|
||||
// TypeOf returns the type of expression e, or nil if not found.
|
||||
// Precondition: the Types, Uses and Defs maps are populated.
|
||||
//
|
||||
func (info *Info) TypeOf(e ast.Expr) Type {
|
||||
if t, ok := info.Types[e]; ok {
|
||||
return t.Type
|
||||
}
|
||||
if id, _ := e.(*ast.Ident); id != nil {
|
||||
if obj := info.ObjectOf(id); obj != nil {
|
||||
return obj.Type()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectOf returns the object denoted by the specified id,
|
||||
// or nil if not found.
|
||||
//
|
||||
// If id is an anonymous struct field, ObjectOf returns the field (*Var)
|
||||
// it uses, not the type (*TypeName) it defines.
|
||||
//
|
||||
// Precondition: the Uses and Defs maps are populated.
|
||||
//
|
||||
func (info *Info) ObjectOf(id *ast.Ident) Object {
|
||||
if obj, _ := info.Defs[id]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
return info.Uses[id]
|
||||
}
|
||||
|
||||
// TypeAndValue reports the type and value (for constants)
|
||||
// of the corresponding expression.
|
||||
type TypeAndValue struct {
|
||||
mode operandMode
|
||||
Type Type
|
||||
Value exact.Value
|
||||
}
|
||||
|
||||
// TODO(gri) Consider eliminating the IsVoid predicate. Instead, report
|
||||
// "void" values as regular values but with the empty tuple type.
|
||||
|
||||
// IsVoid reports whether the corresponding expression
|
||||
// is a function call without results.
|
||||
func (tv TypeAndValue) IsVoid() bool {
|
||||
return tv.mode == novalue
|
||||
}
|
||||
|
||||
// IsType reports whether the corresponding expression specifies a type.
|
||||
func (tv TypeAndValue) IsType() bool {
|
||||
return tv.mode == typexpr
|
||||
}
|
||||
|
||||
// IsBuiltin reports whether the corresponding expression denotes
|
||||
// a (possibly parenthesized) built-in function.
|
||||
func (tv TypeAndValue) IsBuiltin() bool {
|
||||
return tv.mode == builtin
|
||||
}
|
||||
|
||||
// IsValue reports whether the corresponding expression is a value.
|
||||
// Builtins are not considered values. Constant values have a non-
|
||||
// nil Value.
|
||||
func (tv TypeAndValue) IsValue() bool {
|
||||
switch tv.mode {
|
||||
case constant, variable, mapindex, value, commaok:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsNil reports whether the corresponding expression denotes the
|
||||
// predeclared value nil.
|
||||
func (tv TypeAndValue) IsNil() bool {
|
||||
return tv.mode == value && tv.Type == Typ[UntypedNil]
|
||||
}
|
||||
|
||||
// Addressable reports whether the corresponding expression
|
||||
// is addressable (http://golang.org/ref/spec#Address_operators).
|
||||
func (tv TypeAndValue) Addressable() bool {
|
||||
return tv.mode == variable
|
||||
}
|
||||
|
||||
// Assignable reports whether the corresponding expression
|
||||
// is assignable to (provided a value of the right type).
|
||||
func (tv TypeAndValue) Assignable() bool {
|
||||
return tv.mode == variable || tv.mode == mapindex
|
||||
}
|
||||
|
||||
// HasOk reports whether the corresponding expression may be
|
||||
// used on the lhs of a comma-ok assignment.
|
||||
func (tv TypeAndValue) HasOk() bool {
|
||||
return tv.mode == commaok || tv.mode == mapindex
|
||||
}
|
||||
|
||||
// An Initializer describes a package-level variable, or a list of variables in case
|
||||
// of a multi-valued initialization expression, and the corresponding initialization
|
||||
// expression.
|
||||
type Initializer struct {
|
||||
Lhs []*Var // var Lhs = Rhs
|
||||
Rhs ast.Expr
|
||||
}
|
||||
|
||||
func (init *Initializer) String() string {
|
||||
var buf bytes.Buffer
|
||||
for i, lhs := range init.Lhs {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(lhs.Name())
|
||||
}
|
||||
buf.WriteString(" = ")
|
||||
WriteExpr(&buf, init.Rhs)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Check type-checks a package and returns the resulting package object,
|
||||
// the first error if any, and if info != nil, additional type information.
|
||||
// The package is marked as complete if no errors occurred, otherwise it is
|
||||
// incomplete. See Config.Error for controlling behavior in the presence of
|
||||
// errors.
|
||||
//
|
||||
// The package is specified by a list of *ast.Files and corresponding
|
||||
// file set, and the package path the package is identified with.
|
||||
// The clean path must not be empty or dot (".").
|
||||
func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) {
|
||||
pkg := NewPackage(path, "")
|
||||
return pkg, NewChecker(conf, fset, pkg, info).Files(files)
|
||||
}
|
||||
|
||||
// AssertableTo reports whether a value of type V can be asserted to have type T.
|
||||
func AssertableTo(V *Interface, T Type) bool {
|
||||
m, _ := assertableTo(V, T)
|
||||
return m == nil
|
||||
}
|
||||
|
||||
// AssignableTo reports whether a value of type V is assignable to a variable of type T.
|
||||
func AssignableTo(V, T Type) bool {
|
||||
x := operand{mode: value, typ: V}
|
||||
return x.assignableTo(nil, T) // config not needed for non-constant x
|
||||
}
|
||||
|
||||
// ConvertibleTo reports whether a value of type V is convertible to a value of type T.
|
||||
func ConvertibleTo(V, T Type) bool {
|
||||
x := operand{mode: value, typ: V}
|
||||
return x.convertibleTo(nil, T) // config not needed for non-constant x
|
||||
}
|
||||
|
||||
// Implements reports whether type V implements interface T.
|
||||
func Implements(V Type, T *Interface) bool {
|
||||
f, _ := MissingMethod(V, T, true)
|
||||
return f == nil
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
// 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 initialization and assignment checks.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// assignment reports whether x can be assigned to a variable of type T,
|
||||
// if necessary by attempting to convert untyped values to the appropriate
|
||||
// type. If x.mode == invalid upon return, then assignment has already
|
||||
// issued an error message and the caller doesn't have to report another.
|
||||
// Use T == nil to indicate assignment to an untyped blank identifier.
|
||||
//
|
||||
// TODO(gri) Should find a better way to handle in-band errors.
|
||||
//
|
||||
func (check *Checker) assignment(x *operand, T Type) bool {
|
||||
switch x.mode {
|
||||
case invalid:
|
||||
return true // error reported before
|
||||
case constant, variable, mapindex, value, commaok:
|
||||
// ok
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
// x must be a single value
|
||||
// (tuple types are never named - no need for underlying type)
|
||||
if t, _ := x.typ.(*Tuple); t != nil {
|
||||
assert(t.Len() > 1)
|
||||
check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x)
|
||||
x.mode = invalid
|
||||
return false
|
||||
}
|
||||
|
||||
if isUntyped(x.typ) {
|
||||
target := T
|
||||
// spec: "If an untyped constant is assigned to a variable of interface
|
||||
// type or the blank identifier, the constant is first converted to type
|
||||
// bool, rune, int, float64, complex128 or string respectively, depending
|
||||
// on whether the value is a boolean, rune, integer, floating-point, complex,
|
||||
// or string constant."
|
||||
if T == nil || IsInterface(T) {
|
||||
if T == nil && x.typ == Typ[UntypedNil] {
|
||||
check.errorf(x.pos(), "use of untyped nil")
|
||||
x.mode = invalid
|
||||
return false
|
||||
}
|
||||
target = defaultType(x.typ)
|
||||
}
|
||||
check.convertUntyped(x, target)
|
||||
if x.mode == invalid {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// spec: "If a left-hand side is the blank identifier, any typed or
|
||||
// non-constant value except for the predeclared identifier nil may
|
||||
// be assigned to it."
|
||||
return T == nil || x.assignableTo(check.conf, T)
|
||||
}
|
||||
|
||||
func (check *Checker) initConst(lhs *Const, x *operand) {
|
||||
if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
|
||||
if lhs.typ == nil {
|
||||
lhs.typ = Typ[Invalid]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// rhs must be a constant
|
||||
if x.mode != constant {
|
||||
check.errorf(x.pos(), "%s is not constant", x)
|
||||
if lhs.typ == nil {
|
||||
lhs.typ = Typ[Invalid]
|
||||
}
|
||||
return
|
||||
}
|
||||
assert(isConstType(x.typ))
|
||||
|
||||
// If the lhs doesn't have a type yet, use the type of x.
|
||||
if lhs.typ == nil {
|
||||
lhs.typ = x.typ
|
||||
}
|
||||
|
||||
if !check.assignment(x, lhs.typ) {
|
||||
if x.mode != invalid {
|
||||
check.errorf(x.pos(), "cannot define constant %s (type %s) as %s", lhs.Name(), lhs.typ, x)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
lhs.val = x.val
|
||||
}
|
||||
|
||||
// If result is set, lhs is a function result parameter and x is a return result.
|
||||
func (check *Checker) initVar(lhs *Var, x *operand, result bool) Type {
|
||||
if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
|
||||
if lhs.typ == nil {
|
||||
lhs.typ = Typ[Invalid]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the lhs doesn't have a type yet, use the type of x.
|
||||
if lhs.typ == nil {
|
||||
typ := x.typ
|
||||
if isUntyped(typ) {
|
||||
// convert untyped types to default types
|
||||
if typ == Typ[UntypedNil] {
|
||||
check.errorf(x.pos(), "use of untyped nil")
|
||||
lhs.typ = Typ[Invalid]
|
||||
return nil
|
||||
}
|
||||
typ = defaultType(typ)
|
||||
}
|
||||
lhs.typ = typ
|
||||
}
|
||||
|
||||
if !check.assignment(x, lhs.typ) {
|
||||
if x.mode != invalid {
|
||||
if result {
|
||||
// don't refer to lhs.name because it may be an anonymous result parameter
|
||||
check.errorf(x.pos(), "cannot return %s as value of type %s", x, lhs.typ)
|
||||
} else {
|
||||
check.errorf(x.pos(), "cannot initialize %s with %s", lhs, x)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return x.typ
|
||||
}
|
||||
|
||||
func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type {
|
||||
if x.mode == invalid || x.typ == Typ[Invalid] {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine if the lhs is a (possibly parenthesized) identifier.
|
||||
ident, _ := unparen(lhs).(*ast.Ident)
|
||||
|
||||
// Don't evaluate lhs if it is the blank identifier.
|
||||
if ident != nil && ident.Name == "_" {
|
||||
check.recordDef(ident, nil)
|
||||
if !check.assignment(x, nil) {
|
||||
assert(x.mode == invalid)
|
||||
x.typ = nil
|
||||
}
|
||||
return x.typ
|
||||
}
|
||||
|
||||
// If the lhs is an identifier denoting a variable v, this assignment
|
||||
// is not a 'use' of v. Remember current value of v.used and restore
|
||||
// after evaluating the lhs via check.expr.
|
||||
var v *Var
|
||||
var v_used bool
|
||||
if ident != nil {
|
||||
if _, obj := check.scope.LookupParent(ident.Name, token.NoPos); obj != nil {
|
||||
v, _ = obj.(*Var)
|
||||
if v != nil {
|
||||
v_used = v.used
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var z operand
|
||||
check.expr(&z, lhs)
|
||||
if v != nil {
|
||||
v.used = v_used // restore v.used
|
||||
}
|
||||
|
||||
if z.mode == invalid || z.typ == Typ[Invalid] {
|
||||
return nil
|
||||
}
|
||||
|
||||
// spec: "Each left-hand side operand must be addressable, a map index
|
||||
// expression, or the blank identifier. Operands may be parenthesized."
|
||||
switch z.mode {
|
||||
case invalid:
|
||||
return nil
|
||||
case variable, mapindex:
|
||||
// ok
|
||||
default:
|
||||
check.errorf(z.pos(), "cannot assign to %s", &z)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !check.assignment(x, z.typ) {
|
||||
if x.mode != invalid {
|
||||
check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return x.typ
|
||||
}
|
||||
|
||||
// If returnPos is valid, initVars is called to type-check the assignment of
|
||||
// return expressions, and returnPos is the position of the return statement.
|
||||
func (check *Checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) {
|
||||
l := len(lhs)
|
||||
get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid())
|
||||
if get == nil || l != r {
|
||||
// invalidate lhs and use rhs
|
||||
for _, obj := range lhs {
|
||||
if obj.typ == nil {
|
||||
obj.typ = Typ[Invalid]
|
||||
}
|
||||
}
|
||||
if get == nil {
|
||||
return // error reported by unpack
|
||||
}
|
||||
check.useGetter(get, r)
|
||||
if returnPos.IsValid() {
|
||||
check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r)
|
||||
return
|
||||
}
|
||||
check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r)
|
||||
return
|
||||
}
|
||||
|
||||
var x operand
|
||||
if commaOk {
|
||||
var a [2]Type
|
||||
for i := range a {
|
||||
get(&x, i)
|
||||
a[i] = check.initVar(lhs[i], &x, returnPos.IsValid())
|
||||
}
|
||||
check.recordCommaOkTypes(rhs[0], a)
|
||||
return
|
||||
}
|
||||
|
||||
for i, lhs := range lhs {
|
||||
get(&x, i)
|
||||
check.initVar(lhs, &x, returnPos.IsValid())
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) assignVars(lhs, rhs []ast.Expr) {
|
||||
l := len(lhs)
|
||||
get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2)
|
||||
if get == nil {
|
||||
return // error reported by unpack
|
||||
}
|
||||
if l != r {
|
||||
check.useGetter(get, r)
|
||||
check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r)
|
||||
return
|
||||
}
|
||||
|
||||
var x operand
|
||||
if commaOk {
|
||||
var a [2]Type
|
||||
for i := range a {
|
||||
get(&x, i)
|
||||
a[i] = check.assignVar(lhs[i], &x)
|
||||
}
|
||||
check.recordCommaOkTypes(rhs[0], a)
|
||||
return
|
||||
}
|
||||
|
||||
for i, lhs := range lhs {
|
||||
get(&x, i)
|
||||
check.assignVar(lhs, &x)
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) {
|
||||
scope := check.scope
|
||||
|
||||
// collect lhs variables
|
||||
var newVars []*Var
|
||||
var lhsVars = make([]*Var, len(lhs))
|
||||
for i, lhs := range lhs {
|
||||
var obj *Var
|
||||
if ident, _ := lhs.(*ast.Ident); ident != nil {
|
||||
// Use the correct obj if the ident is redeclared. The
|
||||
// variable's scope starts after the declaration; so we
|
||||
// must use Scope.Lookup here and call Scope.Insert
|
||||
// (via check.declare) later.
|
||||
name := ident.Name
|
||||
if alt := scope.Lookup(name); alt != nil {
|
||||
// redeclared object must be a variable
|
||||
if alt, _ := alt.(*Var); alt != nil {
|
||||
obj = alt
|
||||
} else {
|
||||
check.errorf(lhs.Pos(), "cannot assign to %s", lhs)
|
||||
}
|
||||
check.recordUse(ident, alt)
|
||||
} else {
|
||||
// declare new variable, possibly a blank (_) variable
|
||||
obj = NewVar(ident.Pos(), check.pkg, name, nil)
|
||||
if name != "_" {
|
||||
newVars = append(newVars, obj)
|
||||
}
|
||||
check.recordDef(ident, obj)
|
||||
}
|
||||
} else {
|
||||
check.errorf(lhs.Pos(), "cannot declare %s", lhs)
|
||||
}
|
||||
if obj == nil {
|
||||
obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable
|
||||
}
|
||||
lhsVars[i] = obj
|
||||
}
|
||||
|
||||
check.initVars(lhsVars, rhs, token.NoPos)
|
||||
|
||||
// declare new variables
|
||||
if len(newVars) > 0 {
|
||||
// spec: "The scope of a constant or variable identifier declared inside
|
||||
// a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
|
||||
// for short variable declarations) and ends at the end of the innermost
|
||||
// containing block."
|
||||
scopePos := rhs[len(rhs)-1].End()
|
||||
for _, obj := range newVars {
|
||||
check.declare(scope, nil, obj, scopePos) // recordObject already called
|
||||
}
|
||||
} else {
|
||||
check.softErrorf(pos, "no new variables on left side of :=")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,628 @@
|
|||
// 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 implements typechecking of builtin function calls.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
)
|
||||
|
||||
// builtin type-checks a call to the built-in specified by id and
|
||||
// returns true if the call is valid, with *x holding the result;
|
||||
// but x.expr is not set. If the call is invalid, the result is
|
||||
// false, and *x is undefined.
|
||||
//
|
||||
func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ bool) {
|
||||
// append is the only built-in that permits the use of ... for the last argument
|
||||
bin := predeclaredFuncs[id]
|
||||
if call.Ellipsis.IsValid() && id != _Append {
|
||||
check.invalidOp(call.Ellipsis, "invalid use of ... with built-in %s", bin.name)
|
||||
check.use(call.Args...)
|
||||
return
|
||||
}
|
||||
|
||||
// For len(x) and cap(x) we need to know if x contains any function calls or
|
||||
// receive operations. Save/restore current setting and set hasCallOrRecv to
|
||||
// false for the evaluation of x so that we can check it afterwards.
|
||||
// Note: We must do this _before_ calling unpack because unpack evaluates the
|
||||
// first argument before we even call arg(x, 0)!
|
||||
if id == _Len || id == _Cap {
|
||||
defer func(b bool) {
|
||||
check.hasCallOrRecv = b
|
||||
}(check.hasCallOrRecv)
|
||||
check.hasCallOrRecv = false
|
||||
}
|
||||
|
||||
// determine actual arguments
|
||||
var arg getter
|
||||
nargs := len(call.Args)
|
||||
switch id {
|
||||
default:
|
||||
// make argument getter
|
||||
arg, nargs, _ = unpack(func(x *operand, i int) { check.expr(x, call.Args[i]) }, nargs, false)
|
||||
if arg == nil {
|
||||
return
|
||||
}
|
||||
// evaluate first argument, if present
|
||||
if nargs > 0 {
|
||||
arg(x, 0)
|
||||
if x.mode == invalid {
|
||||
return
|
||||
}
|
||||
}
|
||||
case _Make, _New, _Offsetof, _Trace:
|
||||
// arguments require special handling
|
||||
}
|
||||
|
||||
// check argument count
|
||||
{
|
||||
msg := ""
|
||||
if nargs < bin.nargs {
|
||||
msg = "not enough"
|
||||
} else if !bin.variadic && nargs > bin.nargs {
|
||||
msg = "too many"
|
||||
}
|
||||
if msg != "" {
|
||||
check.invalidOp(call.Rparen, "%s arguments for %s (expected %d, found %d)", msg, call, bin.nargs, nargs)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch id {
|
||||
case _Append:
|
||||
// append(s S, x ...T) S, where T is the element type of S
|
||||
// spec: "The variadic function append appends zero or more values x to s of type
|
||||
// S, which must be a slice type, and returns the resulting slice, also of type S.
|
||||
// The values x are passed to a parameter of type ...T where T is the element type
|
||||
// of S and the respective parameter passing rules apply."
|
||||
S := x.typ
|
||||
var T Type
|
||||
if s, _ := S.Underlying().(*Slice); s != nil {
|
||||
T = s.elem
|
||||
} else {
|
||||
check.invalidArg(x.pos(), "%s is not a slice", x)
|
||||
return
|
||||
}
|
||||
|
||||
// remember arguments that have been evaluated already
|
||||
alist := []operand{*x}
|
||||
|
||||
// spec: "As a special case, append also accepts a first argument assignable
|
||||
// to type []byte with a second argument of string type followed by ... .
|
||||
// This form appends the bytes of the string.
|
||||
if nargs == 2 && call.Ellipsis.IsValid() && x.assignableTo(check.conf, NewSlice(universeByte)) {
|
||||
arg(x, 1)
|
||||
if x.mode == invalid {
|
||||
return
|
||||
}
|
||||
if isString(x.typ) {
|
||||
if check.Types != nil {
|
||||
sig := makeSig(S, S, x.typ)
|
||||
sig.variadic = true
|
||||
check.recordBuiltinType(call.Fun, sig)
|
||||
}
|
||||
x.mode = value
|
||||
x.typ = S
|
||||
break
|
||||
}
|
||||
alist = append(alist, *x)
|
||||
// fallthrough
|
||||
}
|
||||
|
||||
// check general case by creating custom signature
|
||||
sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature
|
||||
sig.variadic = true
|
||||
check.arguments(x, call, sig, func(x *operand, i int) {
|
||||
// only evaluate arguments that have not been evaluated before
|
||||
if i < len(alist) {
|
||||
*x = alist[i]
|
||||
return
|
||||
}
|
||||
arg(x, i)
|
||||
}, nargs)
|
||||
// ok to continue even if check.arguments reported errors
|
||||
|
||||
x.mode = value
|
||||
x.typ = S
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, sig)
|
||||
}
|
||||
|
||||
case _Cap, _Len:
|
||||
// cap(x)
|
||||
// len(x)
|
||||
mode := invalid
|
||||
var typ Type
|
||||
var val exact.Value
|
||||
switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) {
|
||||
case *Basic:
|
||||
if isString(t) && id == _Len {
|
||||
if x.mode == constant {
|
||||
mode = constant
|
||||
val = exact.MakeInt64(int64(len(exact.StringVal(x.val))))
|
||||
} else {
|
||||
mode = value
|
||||
}
|
||||
}
|
||||
|
||||
case *Array:
|
||||
mode = value
|
||||
// spec: "The expressions len(s) and cap(s) are constants
|
||||
// if the type of s is an array or pointer to an array and
|
||||
// the expression s does not contain channel receives or
|
||||
// function calls; in this case s is not evaluated."
|
||||
if !check.hasCallOrRecv {
|
||||
mode = constant
|
||||
val = exact.MakeInt64(t.len)
|
||||
}
|
||||
|
||||
case *Slice, *Chan:
|
||||
mode = value
|
||||
|
||||
case *Map:
|
||||
if id == _Len {
|
||||
mode = value
|
||||
}
|
||||
}
|
||||
|
||||
if mode == invalid {
|
||||
check.invalidArg(x.pos(), "%s for %s", x, bin.name)
|
||||
return
|
||||
}
|
||||
|
||||
x.mode = mode
|
||||
x.typ = Typ[Int]
|
||||
x.val = val
|
||||
if check.Types != nil && mode != constant {
|
||||
check.recordBuiltinType(call.Fun, makeSig(x.typ, typ))
|
||||
}
|
||||
|
||||
case _Close:
|
||||
// close(c)
|
||||
c, _ := x.typ.Underlying().(*Chan)
|
||||
if c == nil {
|
||||
check.invalidArg(x.pos(), "%s is not a channel", x)
|
||||
return
|
||||
}
|
||||
if c.dir == RecvOnly {
|
||||
check.invalidArg(x.pos(), "%s must not be a receive-only channel", x)
|
||||
return
|
||||
}
|
||||
|
||||
x.mode = novalue
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, makeSig(nil, c))
|
||||
}
|
||||
|
||||
case _Complex:
|
||||
// complex(x, y realT) complexT
|
||||
if !check.complexArg(x) {
|
||||
return
|
||||
}
|
||||
|
||||
var y operand
|
||||
arg(&y, 1)
|
||||
if y.mode == invalid {
|
||||
return
|
||||
}
|
||||
if !check.complexArg(&y) {
|
||||
return
|
||||
}
|
||||
|
||||
check.convertUntyped(x, y.typ)
|
||||
if x.mode == invalid {
|
||||
return
|
||||
}
|
||||
check.convertUntyped(&y, x.typ)
|
||||
if y.mode == invalid {
|
||||
return
|
||||
}
|
||||
|
||||
if !Identical(x.typ, y.typ) {
|
||||
check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ)
|
||||
return
|
||||
}
|
||||
|
||||
if x.mode == constant && y.mode == constant {
|
||||
x.val = exact.BinaryOp(x.val, token.ADD, exact.MakeImag(y.val))
|
||||
} else {
|
||||
x.mode = value
|
||||
}
|
||||
|
||||
realT := x.typ
|
||||
complexT := Typ[Invalid]
|
||||
switch realT.Underlying().(*Basic).kind {
|
||||
case Float32:
|
||||
complexT = Typ[Complex64]
|
||||
case Float64:
|
||||
complexT = Typ[Complex128]
|
||||
case UntypedInt, UntypedRune, UntypedFloat:
|
||||
if x.mode == constant {
|
||||
realT = defaultType(realT).(*Basic)
|
||||
complexT = Typ[UntypedComplex]
|
||||
} else {
|
||||
// untyped but not constant; probably because one
|
||||
// operand is a non-constant shift of untyped lhs
|
||||
realT = Typ[Float64]
|
||||
complexT = Typ[Complex128]
|
||||
}
|
||||
default:
|
||||
check.invalidArg(x.pos(), "float32 or float64 arguments expected")
|
||||
return
|
||||
}
|
||||
|
||||
x.typ = complexT
|
||||
if check.Types != nil && x.mode != constant {
|
||||
check.recordBuiltinType(call.Fun, makeSig(complexT, realT, realT))
|
||||
}
|
||||
|
||||
if x.mode != constant {
|
||||
// The arguments have now their final types, which at run-
|
||||
// time will be materialized. Update the expression trees.
|
||||
// If the current types are untyped, the materialized type
|
||||
// is the respective default type.
|
||||
// (If the result is constant, the arguments are never
|
||||
// materialized and there is nothing to do.)
|
||||
check.updateExprType(x.expr, realT, true)
|
||||
check.updateExprType(y.expr, realT, true)
|
||||
}
|
||||
|
||||
case _Copy:
|
||||
// copy(x, y []T) int
|
||||
var dst Type
|
||||
if t, _ := x.typ.Underlying().(*Slice); t != nil {
|
||||
dst = t.elem
|
||||
}
|
||||
|
||||
var y operand
|
||||
arg(&y, 1)
|
||||
if y.mode == invalid {
|
||||
return
|
||||
}
|
||||
var src Type
|
||||
switch t := y.typ.Underlying().(type) {
|
||||
case *Basic:
|
||||
if isString(y.typ) {
|
||||
src = universeByte
|
||||
}
|
||||
case *Slice:
|
||||
src = t.elem
|
||||
}
|
||||
|
||||
if dst == nil || src == nil {
|
||||
check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y)
|
||||
return
|
||||
}
|
||||
|
||||
if !Identical(dst, src) {
|
||||
check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src)
|
||||
return
|
||||
}
|
||||
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, makeSig(Typ[Int], x.typ, y.typ))
|
||||
}
|
||||
x.mode = value
|
||||
x.typ = Typ[Int]
|
||||
|
||||
case _Delete:
|
||||
// delete(m, k)
|
||||
m, _ := x.typ.Underlying().(*Map)
|
||||
if m == nil {
|
||||
check.invalidArg(x.pos(), "%s is not a map", x)
|
||||
return
|
||||
}
|
||||
arg(x, 1) // k
|
||||
if x.mode == invalid {
|
||||
return
|
||||
}
|
||||
|
||||
if !x.assignableTo(check.conf, m.key) {
|
||||
check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.key)
|
||||
return
|
||||
}
|
||||
|
||||
x.mode = novalue
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, makeSig(nil, m, m.key))
|
||||
}
|
||||
|
||||
case _Imag, _Real:
|
||||
// imag(complexT) realT
|
||||
// real(complexT) realT
|
||||
if !isComplex(x.typ) {
|
||||
check.invalidArg(x.pos(), "%s must be a complex number", x)
|
||||
return
|
||||
}
|
||||
if x.mode == constant {
|
||||
if id == _Real {
|
||||
x.val = exact.Real(x.val)
|
||||
} else {
|
||||
x.val = exact.Imag(x.val)
|
||||
}
|
||||
} else {
|
||||
x.mode = value
|
||||
}
|
||||
var k BasicKind
|
||||
switch x.typ.Underlying().(*Basic).kind {
|
||||
case Complex64:
|
||||
k = Float32
|
||||
case Complex128:
|
||||
k = Float64
|
||||
case UntypedComplex:
|
||||
k = UntypedFloat
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
if check.Types != nil && x.mode != constant {
|
||||
check.recordBuiltinType(call.Fun, makeSig(Typ[k], x.typ))
|
||||
}
|
||||
x.typ = Typ[k]
|
||||
|
||||
case _Make:
|
||||
// make(T, n)
|
||||
// make(T, n, m)
|
||||
// (no argument evaluated yet)
|
||||
arg0 := call.Args[0]
|
||||
T := check.typ(arg0)
|
||||
if T == Typ[Invalid] {
|
||||
return
|
||||
}
|
||||
|
||||
var min int // minimum number of arguments
|
||||
switch T.Underlying().(type) {
|
||||
case *Slice:
|
||||
min = 2
|
||||
case *Map, *Chan:
|
||||
min = 1
|
||||
default:
|
||||
check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0)
|
||||
return
|
||||
}
|
||||
if nargs < min || min+1 < nargs {
|
||||
check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, nargs)
|
||||
return
|
||||
}
|
||||
var sizes []int64 // constant integer arguments, if any
|
||||
for _, arg := range call.Args[1:] {
|
||||
if s, ok := check.index(arg, -1); ok && s >= 0 {
|
||||
sizes = append(sizes, s)
|
||||
}
|
||||
}
|
||||
if len(sizes) == 2 && sizes[0] > sizes[1] {
|
||||
check.invalidArg(call.Args[1].Pos(), "length and capacity swapped")
|
||||
// safe to continue
|
||||
}
|
||||
x.mode = value
|
||||
x.typ = T
|
||||
if check.Types != nil {
|
||||
params := [...]Type{T, Typ[Int], Typ[Int]}
|
||||
check.recordBuiltinType(call.Fun, makeSig(x.typ, params[:1+len(sizes)]...))
|
||||
}
|
||||
|
||||
case _New:
|
||||
// new(T)
|
||||
// (no argument evaluated yet)
|
||||
T := check.typ(call.Args[0])
|
||||
if T == Typ[Invalid] {
|
||||
return
|
||||
}
|
||||
|
||||
x.mode = value
|
||||
x.typ = &Pointer{base: T}
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, makeSig(x.typ, T))
|
||||
}
|
||||
|
||||
case _Panic:
|
||||
// panic(x)
|
||||
T := new(Interface)
|
||||
if !check.assignment(x, T) {
|
||||
assert(x.mode == invalid)
|
||||
return
|
||||
}
|
||||
|
||||
x.mode = novalue
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, makeSig(nil, T))
|
||||
}
|
||||
|
||||
case _Print, _Println:
|
||||
// print(x, y, ...)
|
||||
// println(x, y, ...)
|
||||
var params []Type
|
||||
if nargs > 0 {
|
||||
params = make([]Type, nargs)
|
||||
for i := 0; i < nargs; i++ {
|
||||
if i > 0 {
|
||||
arg(x, i) // first argument already evaluated
|
||||
}
|
||||
if !check.assignment(x, nil) {
|
||||
assert(x.mode == invalid)
|
||||
return
|
||||
}
|
||||
params[i] = x.typ
|
||||
}
|
||||
}
|
||||
|
||||
x.mode = novalue
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, makeSig(nil, params...))
|
||||
}
|
||||
|
||||
case _Recover:
|
||||
// recover() interface{}
|
||||
x.mode = value
|
||||
x.typ = new(Interface)
|
||||
if check.Types != nil {
|
||||
check.recordBuiltinType(call.Fun, makeSig(x.typ))
|
||||
}
|
||||
|
||||
case _Alignof:
|
||||
// unsafe.Alignof(x T) uintptr
|
||||
if !check.assignment(x, nil) {
|
||||
assert(x.mode == invalid)
|
||||
return
|
||||
}
|
||||
|
||||
x.mode = constant
|
||||
x.val = exact.MakeInt64(check.conf.alignof(x.typ))
|
||||
x.typ = Typ[Uintptr]
|
||||
// result is constant - no need to record signature
|
||||
|
||||
case _Offsetof:
|
||||
// unsafe.Offsetof(x T) uintptr, where x must be a selector
|
||||
// (no argument evaluated yet)
|
||||
arg0 := call.Args[0]
|
||||
selx, _ := unparen(arg0).(*ast.SelectorExpr)
|
||||
if selx == nil {
|
||||
check.invalidArg(arg0.Pos(), "%s is not a selector expression", arg0)
|
||||
check.use(arg0)
|
||||
return
|
||||
}
|
||||
|
||||
check.expr(x, selx.X)
|
||||
if x.mode == invalid {
|
||||
return
|
||||
}
|
||||
|
||||
base := derefStructPtr(x.typ)
|
||||
sel := selx.Sel.Name
|
||||
obj, index, indirect := LookupFieldOrMethod(base, false, check.pkg, sel)
|
||||
switch obj.(type) {
|
||||
case nil:
|
||||
check.invalidArg(x.pos(), "%s has no single field %s", base, sel)
|
||||
return
|
||||
case *Func:
|
||||
// TODO(gri) Using derefStructPtr may result in methods being found
|
||||
// that don't actually exist. An error either way, but the error
|
||||
// message is confusing. See: http://play.golang.org/p/al75v23kUy ,
|
||||
// but go/types reports: "invalid argument: x.m is a method value".
|
||||
check.invalidArg(arg0.Pos(), "%s is a method value", arg0)
|
||||
return
|
||||
}
|
||||
if indirect {
|
||||
check.invalidArg(x.pos(), "field %s is embedded via a pointer in %s", sel, base)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(gri) Should we pass x.typ instead of base (and indirect report if derefStructPtr indirected)?
|
||||
check.recordSelection(selx, FieldVal, base, obj, index, false)
|
||||
|
||||
offs := check.conf.offsetof(base, index)
|
||||
x.mode = constant
|
||||
x.val = exact.MakeInt64(offs)
|
||||
x.typ = Typ[Uintptr]
|
||||
// result is constant - no need to record signature
|
||||
|
||||
case _Sizeof:
|
||||
// unsafe.Sizeof(x T) uintptr
|
||||
if !check.assignment(x, nil) {
|
||||
assert(x.mode == invalid)
|
||||
return
|
||||
}
|
||||
|
||||
x.mode = constant
|
||||
x.val = exact.MakeInt64(check.conf.sizeof(x.typ))
|
||||
x.typ = Typ[Uintptr]
|
||||
// result is constant - no need to record signature
|
||||
|
||||
case _Assert:
|
||||
// assert(pred) causes a typechecker error if pred is false.
|
||||
// The result of assert is the value of pred if there is no error.
|
||||
// Note: assert is only available in self-test mode.
|
||||
if x.mode != constant || !isBoolean(x.typ) {
|
||||
check.invalidArg(x.pos(), "%s is not a boolean constant", x)
|
||||
return
|
||||
}
|
||||
if x.val.Kind() != exact.Bool {
|
||||
check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x)
|
||||
return
|
||||
}
|
||||
if !exact.BoolVal(x.val) {
|
||||
check.errorf(call.Pos(), "%s failed", call)
|
||||
// compile-time assertion failure - safe to continue
|
||||
}
|
||||
// result is constant - no need to record signature
|
||||
|
||||
case _Trace:
|
||||
// trace(x, y, z, ...) dumps the positions, expressions, and
|
||||
// values of its arguments. The result of trace is the value
|
||||
// of the first argument.
|
||||
// Note: trace is only available in self-test mode.
|
||||
// (no argument evaluated yet)
|
||||
if nargs == 0 {
|
||||
check.dump("%s: trace() without arguments", call.Pos())
|
||||
x.mode = novalue
|
||||
break
|
||||
}
|
||||
var t operand
|
||||
x1 := x
|
||||
for _, arg := range call.Args {
|
||||
check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T))
|
||||
check.dump("%s: %s", x1.pos(), x1)
|
||||
x1 = &t // use incoming x only for first argument
|
||||
}
|
||||
// trace is only available in test mode - no need to record signature
|
||||
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// makeSig makes a signature for the given argument and result types.
|
||||
// Default types are used for untyped arguments, and res may be nil.
|
||||
func makeSig(res Type, args ...Type) *Signature {
|
||||
list := make([]*Var, len(args))
|
||||
for i, param := range args {
|
||||
list[i] = NewVar(token.NoPos, nil, "", defaultType(param))
|
||||
}
|
||||
params := NewTuple(list...)
|
||||
var result *Tuple
|
||||
if res != nil {
|
||||
assert(!isUntyped(res))
|
||||
result = NewTuple(NewVar(token.NoPos, nil, "", res))
|
||||
}
|
||||
return &Signature{params: params, results: result}
|
||||
}
|
||||
|
||||
// implicitArrayDeref returns A if typ is of the form *A and A is an array;
|
||||
// otherwise it returns typ.
|
||||
//
|
||||
func implicitArrayDeref(typ Type) Type {
|
||||
if p, ok := typ.(*Pointer); ok {
|
||||
if a, ok := p.base.Underlying().(*Array); ok {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// unparen returns e with any enclosing parentheses stripped.
|
||||
func unparen(e ast.Expr) ast.Expr {
|
||||
for {
|
||||
p, ok := e.(*ast.ParenExpr)
|
||||
if !ok {
|
||||
return e
|
||||
}
|
||||
e = p.X
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) complexArg(x *operand) bool {
|
||||
t, _ := x.typ.Underlying().(*Basic)
|
||||
if t != nil && (t.info&IsFloat != 0 || t.kind == UntypedInt || t.kind == UntypedRune) {
|
||||
return true
|
||||
}
|
||||
check.invalidArg(x.pos(), "%s must be a float32, float64, or an untyped non-complex numeric constant", x)
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,441 @@
|
|||
// 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 typechecking of call and selector expressions.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind {
|
||||
check.exprOrType(x, e.Fun)
|
||||
|
||||
switch x.mode {
|
||||
case invalid:
|
||||
check.use(e.Args...)
|
||||
x.mode = invalid
|
||||
x.expr = e
|
||||
return statement
|
||||
|
||||
case typexpr:
|
||||
// conversion
|
||||
T := x.typ
|
||||
x.mode = invalid
|
||||
switch n := len(e.Args); n {
|
||||
case 0:
|
||||
check.errorf(e.Rparen, "missing argument in conversion to %s", T)
|
||||
case 1:
|
||||
check.expr(x, e.Args[0])
|
||||
if x.mode != invalid {
|
||||
check.conversion(x, T)
|
||||
}
|
||||
default:
|
||||
check.errorf(e.Args[n-1].Pos(), "too many arguments in conversion to %s", T)
|
||||
}
|
||||
x.expr = e
|
||||
return conversion
|
||||
|
||||
case builtin:
|
||||
id := x.id
|
||||
if !check.builtin(x, e, id) {
|
||||
x.mode = invalid
|
||||
}
|
||||
x.expr = e
|
||||
// a non-constant result implies a function call
|
||||
if x.mode != invalid && x.mode != constant {
|
||||
check.hasCallOrRecv = true
|
||||
}
|
||||
return predeclaredFuncs[id].kind
|
||||
|
||||
default:
|
||||
// function/method call
|
||||
sig, _ := x.typ.Underlying().(*Signature)
|
||||
if sig == nil {
|
||||
check.invalidOp(x.pos(), "cannot call non-function %s", x)
|
||||
x.mode = invalid
|
||||
x.expr = e
|
||||
return statement
|
||||
}
|
||||
|
||||
arg, n, _ := unpack(func(x *operand, i int) { check.expr(x, e.Args[i]) }, len(e.Args), false)
|
||||
if arg == nil {
|
||||
x.mode = invalid
|
||||
x.expr = e
|
||||
return statement
|
||||
}
|
||||
|
||||
check.arguments(x, e, sig, arg, n)
|
||||
|
||||
// determine result
|
||||
switch sig.results.Len() {
|
||||
case 0:
|
||||
x.mode = novalue
|
||||
case 1:
|
||||
x.mode = value
|
||||
x.typ = sig.results.vars[0].typ // unpack tuple
|
||||
default:
|
||||
x.mode = value
|
||||
x.typ = sig.results
|
||||
}
|
||||
x.expr = e
|
||||
check.hasCallOrRecv = true
|
||||
|
||||
return statement
|
||||
}
|
||||
}
|
||||
|
||||
// use type-checks each argument.
|
||||
// Useful to make sure expressions are evaluated
|
||||
// (and variables are "used") in the presence of other errors.
|
||||
func (check *Checker) use(arg ...ast.Expr) {
|
||||
var x operand
|
||||
for _, e := range arg {
|
||||
check.rawExpr(&x, e, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// useGetter is like use, but takes a getter instead of a list of expressions.
|
||||
// It should be called instead of use if a getter is present to avoid repeated
|
||||
// evaluation of the first argument (since the getter was likely obtained via
|
||||
// unpack, which may have evaluated the first argument already).
|
||||
func (check *Checker) useGetter(get getter, n int) {
|
||||
var x operand
|
||||
for i := 0; i < n; i++ {
|
||||
get(&x, i)
|
||||
}
|
||||
}
|
||||
|
||||
// A getter sets x as the i'th operand, where 0 <= i < n and n is the total
|
||||
// number of operands (context-specific, and maintained elsewhere). A getter
|
||||
// type-checks the i'th operand; the details of the actual check are getter-
|
||||
// specific.
|
||||
type getter func(x *operand, i int)
|
||||
|
||||
// unpack takes a getter get and a number of operands n. If n == 1, unpack
|
||||
// calls the incoming getter for the first operand. If that operand is
|
||||
// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a
|
||||
// function call, or a comma-ok expression and allowCommaOk is set, the result
|
||||
// is a new getter and operand count providing access to the function results,
|
||||
// or comma-ok values, respectively. The third result value reports if it
|
||||
// is indeed the comma-ok case. In all other cases, the incoming getter and
|
||||
// operand count are returned unchanged, and the third result value is false.
|
||||
//
|
||||
// In other words, if there's exactly one operand that - after type-checking
|
||||
// by calling get - stands for multiple operands, the resulting getter provides
|
||||
// access to those operands instead.
|
||||
//
|
||||
// If the returned getter is called at most once for a given operand index i
|
||||
// (including i == 0), that operand is guaranteed to cause only one call of
|
||||
// the incoming getter with that i.
|
||||
//
|
||||
func unpack(get getter, n int, allowCommaOk bool) (getter, int, bool) {
|
||||
if n == 1 {
|
||||
// possibly result of an n-valued function call or comma,ok value
|
||||
var x0 operand
|
||||
get(&x0, 0)
|
||||
if x0.mode == invalid {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
if t, ok := x0.typ.(*Tuple); ok {
|
||||
// result of an n-valued function call
|
||||
return func(x *operand, i int) {
|
||||
x.mode = value
|
||||
x.expr = x0.expr
|
||||
x.typ = t.At(i).typ
|
||||
}, t.Len(), false
|
||||
}
|
||||
|
||||
if x0.mode == mapindex || x0.mode == commaok {
|
||||
// comma-ok value
|
||||
if allowCommaOk {
|
||||
a := [2]Type{x0.typ, Typ[UntypedBool]}
|
||||
return func(x *operand, i int) {
|
||||
x.mode = value
|
||||
x.expr = x0.expr
|
||||
x.typ = a[i]
|
||||
}, 2, true
|
||||
}
|
||||
x0.mode = value
|
||||
}
|
||||
|
||||
// single value
|
||||
return func(x *operand, i int) {
|
||||
if i != 0 {
|
||||
unreachable()
|
||||
}
|
||||
*x = x0
|
||||
}, 1, false
|
||||
}
|
||||
|
||||
// zero or multiple values
|
||||
return get, n, false
|
||||
}
|
||||
|
||||
// arguments checks argument passing for the call with the given signature.
|
||||
// The arg function provides the operand for the i'th argument.
|
||||
func (check *Checker) arguments(x *operand, call *ast.CallExpr, sig *Signature, arg getter, n int) {
|
||||
if call.Ellipsis.IsValid() {
|
||||
// last argument is of the form x...
|
||||
if len(call.Args) == 1 && n > 1 {
|
||||
// f()... is not permitted if f() is multi-valued
|
||||
check.errorf(call.Ellipsis, "cannot use ... with %d-valued expression %s", n, call.Args[0])
|
||||
check.useGetter(arg, n)
|
||||
return
|
||||
}
|
||||
if !sig.variadic {
|
||||
check.errorf(call.Ellipsis, "cannot use ... in call to non-variadic %s", call.Fun)
|
||||
check.useGetter(arg, n)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// evaluate arguments
|
||||
for i := 0; i < n; i++ {
|
||||
arg(x, i)
|
||||
if x.mode != invalid {
|
||||
var ellipsis token.Pos
|
||||
if i == n-1 && call.Ellipsis.IsValid() {
|
||||
ellipsis = call.Ellipsis
|
||||
}
|
||||
check.argument(sig, i, x, ellipsis)
|
||||
}
|
||||
}
|
||||
|
||||
// check argument count
|
||||
if sig.variadic {
|
||||
// a variadic function accepts an "empty"
|
||||
// last argument: count one extra
|
||||
n++
|
||||
}
|
||||
if n < sig.params.Len() {
|
||||
check.errorf(call.Rparen, "too few arguments in call to %s", call.Fun)
|
||||
// ok to continue
|
||||
}
|
||||
}
|
||||
|
||||
// argument checks passing of argument x to the i'th parameter of the given signature.
|
||||
// If ellipsis is valid, the argument is followed by ... at that position in the call.
|
||||
func (check *Checker) argument(sig *Signature, i int, x *operand, ellipsis token.Pos) {
|
||||
n := sig.params.Len()
|
||||
|
||||
// determine parameter type
|
||||
var typ Type
|
||||
switch {
|
||||
case i < n:
|
||||
typ = sig.params.vars[i].typ
|
||||
case sig.variadic:
|
||||
typ = sig.params.vars[n-1].typ
|
||||
if debug {
|
||||
if _, ok := typ.(*Slice); !ok {
|
||||
check.dump("%s: expected unnamed slice type, got %s", sig.params.vars[n-1].Pos(), typ)
|
||||
}
|
||||
}
|
||||
default:
|
||||
check.errorf(x.pos(), "too many arguments")
|
||||
return
|
||||
}
|
||||
|
||||
if ellipsis.IsValid() {
|
||||
// argument is of the form x...
|
||||
if i != n-1 {
|
||||
check.errorf(ellipsis, "can only use ... with matching parameter")
|
||||
return
|
||||
}
|
||||
switch t := x.typ.Underlying().(type) {
|
||||
case *Slice:
|
||||
// ok
|
||||
case *Tuple:
|
||||
check.errorf(ellipsis, "cannot use ... with %d-valued expression %s", t.Len(), x)
|
||||
return
|
||||
default:
|
||||
check.errorf(x.pos(), "cannot use %s as parameter of type %s", x, typ)
|
||||
return
|
||||
}
|
||||
} else if sig.variadic && i >= n-1 {
|
||||
// use the variadic parameter slice's element type
|
||||
typ = typ.(*Slice).elem
|
||||
}
|
||||
|
||||
if !check.assignment(x, typ) && x.mode != invalid {
|
||||
check.errorf(x.pos(), "cannot pass argument %s to parameter of type %s", x, typ)
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) selector(x *operand, e *ast.SelectorExpr) {
|
||||
// these must be declared before the "goto Error" statements
|
||||
var (
|
||||
obj Object
|
||||
index []int
|
||||
indirect bool
|
||||
)
|
||||
|
||||
sel := e.Sel.Name
|
||||
// If the identifier refers to a package, handle everything here
|
||||
// so we don't need a "package" mode for operands: package names
|
||||
// can only appear in qualified identifiers which are mapped to
|
||||
// selector expressions.
|
||||
if ident, ok := e.X.(*ast.Ident); ok {
|
||||
_, obj := check.scope.LookupParent(ident.Name, check.pos)
|
||||
if pkg, _ := obj.(*PkgName); pkg != nil {
|
||||
assert(pkg.pkg == check.pkg)
|
||||
check.recordUse(ident, pkg)
|
||||
pkg.used = true
|
||||
exp := pkg.imported.scope.Lookup(sel)
|
||||
if exp == nil {
|
||||
if !pkg.imported.fake {
|
||||
check.errorf(e.Pos(), "%s not declared by package %s", sel, ident)
|
||||
}
|
||||
goto Error
|
||||
}
|
||||
if !exp.Exported() {
|
||||
check.errorf(e.Pos(), "%s not exported by package %s", sel, ident)
|
||||
// ok to continue
|
||||
}
|
||||
check.recordUse(e.Sel, exp)
|
||||
// Simplified version of the code for *ast.Idents:
|
||||
// - imported objects are always fully initialized
|
||||
switch exp := exp.(type) {
|
||||
case *Const:
|
||||
assert(exp.Val() != nil)
|
||||
x.mode = constant
|
||||
x.typ = exp.typ
|
||||
x.val = exp.val
|
||||
case *TypeName:
|
||||
x.mode = typexpr
|
||||
x.typ = exp.typ
|
||||
case *Var:
|
||||
x.mode = variable
|
||||
x.typ = exp.typ
|
||||
case *Func:
|
||||
x.mode = value
|
||||
x.typ = exp.typ
|
||||
case *Builtin:
|
||||
x.mode = builtin
|
||||
x.typ = exp.typ
|
||||
x.id = exp.id
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
x.expr = e
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
check.exprOrType(x, e.X)
|
||||
if x.mode == invalid {
|
||||
goto Error
|
||||
}
|
||||
|
||||
obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel)
|
||||
if obj == nil {
|
||||
switch {
|
||||
case index != nil:
|
||||
// TODO(gri) should provide actual type where the conflict happens
|
||||
check.invalidOp(e.Pos(), "ambiguous selector %s", sel)
|
||||
case indirect:
|
||||
check.invalidOp(e.Pos(), "%s is not in method set of %s", sel, x.typ)
|
||||
default:
|
||||
check.invalidOp(e.Pos(), "%s has no field or method %s", x, sel)
|
||||
}
|
||||
goto Error
|
||||
}
|
||||
|
||||
if x.mode == typexpr {
|
||||
// method expression
|
||||
m, _ := obj.(*Func)
|
||||
if m == nil {
|
||||
check.invalidOp(e.Pos(), "%s has no method %s", x, sel)
|
||||
goto Error
|
||||
}
|
||||
|
||||
check.recordSelection(e, MethodExpr, x.typ, m, index, indirect)
|
||||
|
||||
// the receiver type becomes the type of the first function
|
||||
// argument of the method expression's function type
|
||||
var params []*Var
|
||||
sig := m.typ.(*Signature)
|
||||
if sig.params != nil {
|
||||
params = sig.params.vars
|
||||
}
|
||||
x.mode = value
|
||||
x.typ = &Signature{
|
||||
params: NewTuple(append([]*Var{NewVar(token.NoPos, check.pkg, "", x.typ)}, params...)...),
|
||||
results: sig.results,
|
||||
variadic: sig.variadic,
|
||||
}
|
||||
|
||||
check.addDeclDep(m)
|
||||
|
||||
} else {
|
||||
// regular selector
|
||||
switch obj := obj.(type) {
|
||||
case *Var:
|
||||
check.recordSelection(e, FieldVal, x.typ, obj, index, indirect)
|
||||
if x.mode == variable || indirect {
|
||||
x.mode = variable
|
||||
} else {
|
||||
x.mode = value
|
||||
}
|
||||
x.typ = obj.typ
|
||||
|
||||
case *Func:
|
||||
// TODO(gri) If we needed to take into account the receiver's
|
||||
// addressability, should we report the type &(x.typ) instead?
|
||||
check.recordSelection(e, MethodVal, x.typ, obj, index, indirect)
|
||||
|
||||
if debug {
|
||||
// Verify that LookupFieldOrMethod and MethodSet.Lookup agree.
|
||||
typ := x.typ
|
||||
if x.mode == variable {
|
||||
// If typ is not an (unnamed) pointer or an interface,
|
||||
// use *typ instead, because the method set of *typ
|
||||
// includes the methods of typ.
|
||||
// Variables are addressable, so we can always take their
|
||||
// address.
|
||||
if _, ok := typ.(*Pointer); !ok && !IsInterface(typ) {
|
||||
typ = &Pointer{base: typ}
|
||||
}
|
||||
}
|
||||
// If we created a synthetic pointer type above, we will throw
|
||||
// away the method set computed here after use.
|
||||
// TODO(gri) Method set computation should probably always compute
|
||||
// both, the value and the pointer receiver method set and represent
|
||||
// them in a single structure.
|
||||
// TODO(gri) Consider also using a method set cache for the lifetime
|
||||
// of checker once we rely on MethodSet lookup instead of individual
|
||||
// lookup.
|
||||
mset := NewMethodSet(typ)
|
||||
if m := mset.Lookup(check.pkg, sel); m == nil || m.obj != obj {
|
||||
check.dump("%s: (%s).%v -> %s", e.Pos(), typ, obj.name, m)
|
||||
check.dump("%s\n", mset)
|
||||
panic("method sets and lookup don't agree")
|
||||
}
|
||||
}
|
||||
|
||||
x.mode = value
|
||||
|
||||
// remove receiver
|
||||
sig := *obj.typ.(*Signature)
|
||||
sig.recv = nil
|
||||
x.typ = &sig
|
||||
|
||||
check.addDeclDep(obj)
|
||||
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
// everything went well
|
||||
x.expr = e
|
||||
return
|
||||
|
||||
Error:
|
||||
x.mode = invalid
|
||||
x.expr = e
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
// 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 file implements the Check function, which drives type-checking.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
)
|
||||
|
||||
// debugging/development support
|
||||
const (
|
||||
debug = false // leave on during development
|
||||
trace = false // turn on for detailed type resolution traces
|
||||
)
|
||||
|
||||
// If Strict is set, the type-checker enforces additional
|
||||
// rules not specified by the Go 1 spec, but which will
|
||||
// catch guaranteed run-time errors if the respective
|
||||
// code is executed. In other words, programs passing in
|
||||
// Strict mode are Go 1 compliant, but not all Go 1 programs
|
||||
// will pass in Strict mode. The additional rules are:
|
||||
//
|
||||
// - A type assertion x.(T) where T is an interface type
|
||||
// is invalid if any (statically known) method that exists
|
||||
// for both x and T have different signatures.
|
||||
//
|
||||
const strict = false
|
||||
|
||||
// exprInfo stores information about an untyped expression.
|
||||
type exprInfo struct {
|
||||
isLhs bool // expression is lhs operand of a shift with delayed type-check
|
||||
mode operandMode
|
||||
typ *Basic
|
||||
val exact.Value // constant value; or nil (if not a constant)
|
||||
}
|
||||
|
||||
// funcInfo stores the information required for type-checking a function.
|
||||
type funcInfo struct {
|
||||
name string // for debugging/tracing only
|
||||
decl *declInfo // for cycle detection
|
||||
sig *Signature
|
||||
body *ast.BlockStmt
|
||||
}
|
||||
|
||||
// A context represents the context within which an object is type-checked.
|
||||
type context struct {
|
||||
decl *declInfo // package-level declaration whose init expression/function body is checked
|
||||
scope *Scope // top-most scope for lookups
|
||||
iota exact.Value // value of iota in a constant declaration; nil otherwise
|
||||
sig *Signature // function signature if inside a function; nil otherwise
|
||||
hasLabel bool // set if a function makes use of labels (only ~1% of functions); unused outside functions
|
||||
hasCallOrRecv bool // set if an expression contains a function call or channel receive operation
|
||||
}
|
||||
|
||||
// A Checker maintains the state of the type checker.
|
||||
// It must be created with NewChecker.
|
||||
type Checker struct {
|
||||
// package information
|
||||
// (initialized by NewChecker, valid for the life-time of checker)
|
||||
conf *Config
|
||||
fset *token.FileSet
|
||||
pkg *Package
|
||||
*Info
|
||||
objMap map[Object]*declInfo // maps package-level object to declaration info
|
||||
|
||||
// information collected during type-checking of a set of package files
|
||||
// (initialized by Files, valid only for the duration of check.Files;
|
||||
// maps and lists are allocated on demand)
|
||||
files []*ast.File // package files
|
||||
unusedDotImports map[*Scope]map[*Package]token.Pos // positions of unused dot-imported packages for each file scope
|
||||
|
||||
firstErr error // first error encountered
|
||||
methods map[string][]*Func // maps type names to associated methods
|
||||
untyped map[ast.Expr]exprInfo // map of expressions without final type
|
||||
funcs []funcInfo // list of functions to type-check
|
||||
delayed []func() // delayed checks requiring fully setup types
|
||||
|
||||
// context within which the current object is type-checked
|
||||
// (valid only for the duration of type-checking a specific object)
|
||||
context
|
||||
pos token.Pos // if valid, identifiers are looked up as if at position pos (used by Eval)
|
||||
|
||||
// debugging
|
||||
indent int // indentation for tracing
|
||||
}
|
||||
|
||||
// addUnusedImport adds the position of a dot-imported package
|
||||
// pkg to the map of dot imports for the given file scope.
|
||||
func (check *Checker) addUnusedDotImport(scope *Scope, pkg *Package, pos token.Pos) {
|
||||
mm := check.unusedDotImports
|
||||
if mm == nil {
|
||||
mm = make(map[*Scope]map[*Package]token.Pos)
|
||||
check.unusedDotImports = mm
|
||||
}
|
||||
m := mm[scope]
|
||||
if m == nil {
|
||||
m = make(map[*Package]token.Pos)
|
||||
mm[scope] = m
|
||||
}
|
||||
m[pkg] = pos
|
||||
}
|
||||
|
||||
// addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists
|
||||
func (check *Checker) addDeclDep(to Object) {
|
||||
from := check.decl
|
||||
if from == nil {
|
||||
return // not in a package-level init expression
|
||||
}
|
||||
if _, found := check.objMap[to]; !found {
|
||||
return // to is not a package-level object
|
||||
}
|
||||
from.addDep(to)
|
||||
}
|
||||
|
||||
func (check *Checker) assocMethod(tname string, meth *Func) {
|
||||
m := check.methods
|
||||
if m == nil {
|
||||
m = make(map[string][]*Func)
|
||||
check.methods = m
|
||||
}
|
||||
m[tname] = append(m[tname], meth)
|
||||
}
|
||||
|
||||
func (check *Checker) rememberUntyped(e ast.Expr, lhs bool, mode operandMode, typ *Basic, val exact.Value) {
|
||||
m := check.untyped
|
||||
if m == nil {
|
||||
m = make(map[ast.Expr]exprInfo)
|
||||
check.untyped = m
|
||||
}
|
||||
m[e] = exprInfo{lhs, mode, typ, val}
|
||||
}
|
||||
|
||||
func (check *Checker) later(name string, decl *declInfo, sig *Signature, body *ast.BlockStmt) {
|
||||
check.funcs = append(check.funcs, funcInfo{name, decl, sig, body})
|
||||
}
|
||||
|
||||
func (check *Checker) delay(f func()) {
|
||||
check.delayed = append(check.delayed, f)
|
||||
}
|
||||
|
||||
// NewChecker returns a new Checker instance for a given package.
|
||||
// Package files may be added incrementally via checker.Files.
|
||||
func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker {
|
||||
// make sure we have a configuration
|
||||
if conf == nil {
|
||||
conf = new(Config)
|
||||
}
|
||||
|
||||
// make sure we have a package canonicalization map
|
||||
if conf.Packages == nil {
|
||||
conf.Packages = make(map[string]*Package)
|
||||
}
|
||||
|
||||
// make sure we have an info struct
|
||||
if info == nil {
|
||||
info = new(Info)
|
||||
}
|
||||
|
||||
return &Checker{
|
||||
conf: conf,
|
||||
fset: fset,
|
||||
pkg: pkg,
|
||||
Info: info,
|
||||
objMap: make(map[Object]*declInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// initFiles initializes the files-specific portion of checker.
|
||||
// The provided files must all belong to the same package.
|
||||
func (check *Checker) initFiles(files []*ast.File) {
|
||||
// start with a clean slate (check.Files may be called multiple times)
|
||||
check.files = nil
|
||||
check.unusedDotImports = nil
|
||||
|
||||
check.firstErr = nil
|
||||
check.methods = nil
|
||||
check.untyped = nil
|
||||
check.funcs = nil
|
||||
check.delayed = nil
|
||||
|
||||
// determine package name and collect valid files
|
||||
pkg := check.pkg
|
||||
for _, file := range files {
|
||||
switch name := file.Name.Name; pkg.name {
|
||||
case "":
|
||||
if name != "_" {
|
||||
pkg.name = name
|
||||
} else {
|
||||
check.errorf(file.Name.Pos(), "invalid package name _")
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case name:
|
||||
check.files = append(check.files, file)
|
||||
|
||||
default:
|
||||
check.errorf(file.Package, "package %s; expected %s", name, pkg.name)
|
||||
// ignore this file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A bailout panic is used for early termination.
|
||||
type bailout struct{}
|
||||
|
||||
func (check *Checker) handleBailout(err *error) {
|
||||
switch p := recover().(type) {
|
||||
case nil, bailout:
|
||||
// normal return or early exit
|
||||
*err = check.firstErr
|
||||
default:
|
||||
// re-panic
|
||||
panic(p)
|
||||
}
|
||||
}
|
||||
|
||||
// Files checks the provided files as part of the checker's package.
|
||||
func (check *Checker) Files(files []*ast.File) (err error) {
|
||||
defer check.handleBailout(&err)
|
||||
|
||||
check.initFiles(files)
|
||||
|
||||
check.collectObjects()
|
||||
|
||||
check.packageObjects(check.resolveOrder())
|
||||
|
||||
check.functionBodies()
|
||||
|
||||
check.initOrder()
|
||||
|
||||
if !check.conf.DisableUnusedImportCheck {
|
||||
check.unusedImports()
|
||||
}
|
||||
|
||||
// perform delayed checks
|
||||
for _, f := range check.delayed {
|
||||
f()
|
||||
}
|
||||
|
||||
check.recordUntyped()
|
||||
|
||||
check.pkg.complete = true
|
||||
return
|
||||
}
|
||||
|
||||
func (check *Checker) recordUntyped() {
|
||||
if !debug && check.Types == nil {
|
||||
return // nothing to do
|
||||
}
|
||||
|
||||
for x, info := range check.untyped {
|
||||
if debug && isTyped(info.typ) {
|
||||
check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ)
|
||||
unreachable()
|
||||
}
|
||||
check.recordTypeAndValue(x, info.mode, info.typ, info.val)
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordTypeAndValue(x ast.Expr, mode operandMode, typ Type, val exact.Value) {
|
||||
assert(x != nil)
|
||||
assert(typ != nil)
|
||||
if mode == invalid {
|
||||
return // omit
|
||||
}
|
||||
assert(typ != nil)
|
||||
if mode == constant {
|
||||
assert(val != nil)
|
||||
assert(typ == Typ[Invalid] || isConstType(typ))
|
||||
}
|
||||
if m := check.Types; m != nil {
|
||||
m[x] = TypeAndValue{mode, typ, val}
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordBuiltinType(f ast.Expr, sig *Signature) {
|
||||
// f must be a (possibly parenthesized) identifier denoting a built-in
|
||||
// (built-ins in package unsafe always produce a constant result and
|
||||
// we don't record their signatures, so we don't see qualified idents
|
||||
// here): record the signature for f and possible children.
|
||||
for {
|
||||
check.recordTypeAndValue(f, builtin, sig, nil)
|
||||
switch p := f.(type) {
|
||||
case *ast.Ident:
|
||||
return // we're done
|
||||
case *ast.ParenExpr:
|
||||
f = p.X
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
|
||||
assert(x != nil)
|
||||
if a[0] == nil || a[1] == nil {
|
||||
return
|
||||
}
|
||||
assert(isTyped(a[0]) && isTyped(a[1]) && isBoolean(a[1]))
|
||||
if m := check.Types; m != nil {
|
||||
for {
|
||||
tv := m[x]
|
||||
assert(tv.Type != nil) // should have been recorded already
|
||||
pos := x.Pos()
|
||||
tv.Type = NewTuple(
|
||||
NewVar(pos, check.pkg, "", a[0]),
|
||||
NewVar(pos, check.pkg, "", a[1]),
|
||||
)
|
||||
m[x] = tv
|
||||
// if x is a parenthesized expression (p.X), update p.X
|
||||
p, _ := x.(*ast.ParenExpr)
|
||||
if p == nil {
|
||||
break
|
||||
}
|
||||
x = p.X
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordDef(id *ast.Ident, obj Object) {
|
||||
assert(id != nil)
|
||||
if m := check.Defs; m != nil {
|
||||
m[id] = obj
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordUse(id *ast.Ident, obj Object) {
|
||||
assert(id != nil)
|
||||
assert(obj != nil)
|
||||
if m := check.Uses; m != nil {
|
||||
m[id] = obj
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordImplicit(node ast.Node, obj Object) {
|
||||
assert(node != nil)
|
||||
assert(obj != nil)
|
||||
if m := check.Implicits; m != nil {
|
||||
m[node] = obj
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) {
|
||||
assert(obj != nil && (recv == nil || len(index) > 0))
|
||||
check.recordUse(x.Sel, obj)
|
||||
// TODO(gri) Should we also call recordTypeAndValue?
|
||||
if m := check.Selections; m != nil {
|
||||
m[x] = &Selection{kind, recv, obj, index, indirect}
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) recordScope(node ast.Node, scope *Scope) {
|
||||
assert(node != nil)
|
||||
assert(scope != nil)
|
||||
if m := check.Scopes; m != nil {
|
||||
m[node] = scope
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
// 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 implements typechecking of conversions.
|
||||
|
||||
package types
|
||||
|
||||
import "golang.org/x/tools/go/exact"
|
||||
|
||||
// Conversion type-checks the conversion T(x).
|
||||
// The result is in x.
|
||||
func (check *Checker) conversion(x *operand, T Type) {
|
||||
constArg := x.mode == constant
|
||||
|
||||
var ok bool
|
||||
switch {
|
||||
case constArg && isConstType(T):
|
||||
// constant conversion
|
||||
switch t := T.Underlying().(*Basic); {
|
||||
case representableConst(x.val, check.conf, t.kind, &x.val):
|
||||
ok = true
|
||||
case isInteger(x.typ) && isString(t):
|
||||
codepoint := int64(-1)
|
||||
if i, ok := exact.Int64Val(x.val); ok {
|
||||
codepoint = i
|
||||
}
|
||||
// If codepoint < 0 the absolute value is too large (or unknown) for
|
||||
// conversion. This is the same as converting any other out-of-range
|
||||
// value - let string(codepoint) do the work.
|
||||
x.val = exact.MakeString(string(codepoint))
|
||||
ok = true
|
||||
}
|
||||
case x.convertibleTo(check.conf, T):
|
||||
// non-constant conversion
|
||||
x.mode = value
|
||||
ok = true
|
||||
}
|
||||
|
||||
if !ok {
|
||||
check.errorf(x.pos(), "cannot convert %s to %s", x, T)
|
||||
x.mode = invalid
|
||||
return
|
||||
}
|
||||
|
||||
// The conversion argument types are final. For untyped values the
|
||||
// conversion provides the type, per the spec: "A constant may be
|
||||
// given a type explicitly by a constant declaration or conversion,...".
|
||||
final := x.typ
|
||||
if isUntyped(x.typ) {
|
||||
final = T
|
||||
// - For conversions to interfaces, use the argument's default type.
|
||||
// - For conversions of untyped constants to non-constant types, also
|
||||
// use the default type (e.g., []byte("foo") should report string
|
||||
// not []byte as type for the constant "foo").
|
||||
// - Keep untyped nil for untyped nil arguments.
|
||||
if IsInterface(T) || constArg && !isConstType(T) {
|
||||
final = defaultType(x.typ)
|
||||
}
|
||||
check.updateExprType(x.expr, final, true)
|
||||
}
|
||||
|
||||
x.typ = T
|
||||
}
|
||||
|
||||
func (x *operand) convertibleTo(conf *Config, T Type) bool {
|
||||
// "x is assignable to T"
|
||||
if x.assignableTo(conf, T) {
|
||||
return true
|
||||
}
|
||||
|
||||
// "x's type and T have identical underlying types"
|
||||
V := x.typ
|
||||
Vu := V.Underlying()
|
||||
Tu := T.Underlying()
|
||||
if Identical(Vu, Tu) {
|
||||
return true
|
||||
}
|
||||
|
||||
// "x's type and T are unnamed pointer types and their pointer base types have identical underlying types"
|
||||
if V, ok := V.(*Pointer); ok {
|
||||
if T, ok := T.(*Pointer); ok {
|
||||
if Identical(V.base.Underlying(), T.base.Underlying()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "x's type and T are both integer or floating point types"
|
||||
if (isInteger(V) || isFloat(V)) && (isInteger(T) || isFloat(T)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// "x's type and T are both complex types"
|
||||
if isComplex(V) && isComplex(T) {
|
||||
return true
|
||||
}
|
||||
|
||||
// "x is an integer or a slice of bytes or runes and T is a string type"
|
||||
if (isInteger(V) || isBytesOrRunes(Vu)) && isString(T) {
|
||||
return true
|
||||
}
|
||||
|
||||
// "x is a string and T is a slice of bytes or runes"
|
||||
if isString(V) && isBytesOrRunes(Tu) {
|
||||
return true
|
||||
}
|
||||
|
||||
// package unsafe:
|
||||
// "any pointer or value of underlying type uintptr can be converted into a unsafe.Pointer"
|
||||
if (isPointer(Vu) || isUintptr(Vu)) && isUnsafePointer(T) {
|
||||
return true
|
||||
}
|
||||
// "and vice versa"
|
||||
if isUnsafePointer(V) && (isPointer(Tu) || isUintptr(Tu)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isUintptr(typ Type) bool {
|
||||
t, ok := typ.Underlying().(*Basic)
|
||||
return ok && t.kind == Uintptr
|
||||
}
|
||||
|
||||
func isUnsafePointer(typ Type) bool {
|
||||
// TODO(gri): Is this (typ.Underlying() instead of just typ) correct?
|
||||
// The spec does not say so, but gc claims it is. See also
|
||||
// issue 6326.
|
||||
t, ok := typ.Underlying().(*Basic)
|
||||
return ok && t.kind == UnsafePointer
|
||||
}
|
||||
|
||||
func isPointer(typ Type) bool {
|
||||
_, ok := typ.Underlying().(*Pointer)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isBytesOrRunes(typ Type) bool {
|
||||
if s, ok := typ.(*Slice); ok {
|
||||
t, ok := s.elem.Underlying().(*Basic)
|
||||
return ok && (t.kind == Byte || t.kind == Rune)
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,431 @@
|
|||
// 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 types
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
)
|
||||
|
||||
func (check *Checker) reportAltDecl(obj Object) {
|
||||
if pos := obj.Pos(); pos.IsValid() {
|
||||
// We use "other" rather than "previous" here because
|
||||
// the first declaration seen may not be textually
|
||||
// earlier in the source.
|
||||
check.errorf(pos, "\tother declaration of %s", obj.Name()) // secondary error, \t indented
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) declare(scope *Scope, id *ast.Ident, obj Object, pos token.Pos) {
|
||||
// spec: "The blank identifier, represented by the underscore
|
||||
// character _, may be used in a declaration like any other
|
||||
// identifier but the declaration does not introduce a new
|
||||
// binding."
|
||||
if obj.Name() != "_" {
|
||||
if alt := scope.Insert(obj); alt != nil {
|
||||
check.errorf(obj.Pos(), "%s redeclared in this block", obj.Name())
|
||||
check.reportAltDecl(alt)
|
||||
return
|
||||
}
|
||||
obj.setScopePos(pos)
|
||||
}
|
||||
if id != nil {
|
||||
check.recordDef(id, obj)
|
||||
}
|
||||
}
|
||||
|
||||
// objDecl type-checks the declaration of obj in its respective (file) context.
|
||||
// See check.typ for the details on def and path.
|
||||
func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
|
||||
if obj.Type() != nil {
|
||||
return // already checked - nothing to do
|
||||
}
|
||||
|
||||
if trace {
|
||||
check.trace(obj.Pos(), "-- declaring %s", obj.Name())
|
||||
check.indent++
|
||||
defer func() {
|
||||
check.indent--
|
||||
check.trace(obj.Pos(), "=> %s", obj)
|
||||
}()
|
||||
}
|
||||
|
||||
d := check.objMap[obj]
|
||||
if d == nil {
|
||||
check.dump("%s: %s should have been declared", obj.Pos(), obj.Name())
|
||||
unreachable()
|
||||
}
|
||||
|
||||
// save/restore current context and setup object context
|
||||
defer func(ctxt context) {
|
||||
check.context = ctxt
|
||||
}(check.context)
|
||||
check.context = context{
|
||||
scope: d.file,
|
||||
}
|
||||
|
||||
// Const and var declarations must not have initialization
|
||||
// cycles. We track them by remembering the current declaration
|
||||
// in check.decl. Initialization expressions depending on other
|
||||
// consts, vars, or functions, add dependencies to the current
|
||||
// check.decl.
|
||||
switch obj := obj.(type) {
|
||||
case *Const:
|
||||
check.decl = d // new package-level const decl
|
||||
check.constDecl(obj, d.typ, d.init)
|
||||
case *Var:
|
||||
check.decl = d // new package-level var decl
|
||||
check.varDecl(obj, d.lhs, d.typ, d.init)
|
||||
case *TypeName:
|
||||
// invalid recursive types are detected via path
|
||||
check.typeDecl(obj, d.typ, def, path)
|
||||
case *Func:
|
||||
// functions may be recursive - no need to track dependencies
|
||||
check.funcDecl(obj, d)
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) {
|
||||
assert(obj.typ == nil)
|
||||
|
||||
if obj.visited {
|
||||
obj.typ = Typ[Invalid]
|
||||
return
|
||||
}
|
||||
obj.visited = true
|
||||
|
||||
// use the correct value of iota
|
||||
assert(check.iota == nil)
|
||||
check.iota = obj.val
|
||||
defer func() { check.iota = nil }()
|
||||
|
||||
// provide valid constant value under all circumstances
|
||||
obj.val = exact.MakeUnknown()
|
||||
|
||||
// determine type, if any
|
||||
if typ != nil {
|
||||
t := check.typ(typ)
|
||||
if !isConstType(t) {
|
||||
check.errorf(typ.Pos(), "invalid constant type %s", t)
|
||||
obj.typ = Typ[Invalid]
|
||||
return
|
||||
}
|
||||
obj.typ = t
|
||||
}
|
||||
|
||||
// check initialization
|
||||
var x operand
|
||||
if init != nil {
|
||||
check.expr(&x, init)
|
||||
}
|
||||
check.initConst(obj, &x)
|
||||
}
|
||||
|
||||
func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) {
|
||||
assert(obj.typ == nil)
|
||||
|
||||
if obj.visited {
|
||||
obj.typ = Typ[Invalid]
|
||||
return
|
||||
}
|
||||
obj.visited = true
|
||||
|
||||
// var declarations cannot use iota
|
||||
assert(check.iota == nil)
|
||||
|
||||
// determine type, if any
|
||||
if typ != nil {
|
||||
obj.typ = check.typ(typ)
|
||||
}
|
||||
|
||||
// check initialization
|
||||
if init == nil {
|
||||
if typ == nil {
|
||||
// error reported before by arityMatch
|
||||
obj.typ = Typ[Invalid]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if lhs == nil || len(lhs) == 1 {
|
||||
assert(lhs == nil || lhs[0] == obj)
|
||||
var x operand
|
||||
check.expr(&x, init)
|
||||
check.initVar(obj, &x, false)
|
||||
return
|
||||
}
|
||||
|
||||
if debug {
|
||||
// obj must be one of lhs
|
||||
found := false
|
||||
for _, lhs := range lhs {
|
||||
if obj == lhs {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
panic("inconsistent lhs")
|
||||
}
|
||||
}
|
||||
check.initVars(lhs, []ast.Expr{init}, token.NoPos)
|
||||
}
|
||||
|
||||
// underlying returns the underlying type of typ; possibly by following
|
||||
// forward chains of named types. Such chains only exist while named types
|
||||
// are incomplete.
|
||||
func underlying(typ Type) Type {
|
||||
for {
|
||||
n, _ := typ.(*Named)
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
typ = n.underlying
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func (n *Named) setUnderlying(typ Type) {
|
||||
if n != nil {
|
||||
n.underlying = typ
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, path []*TypeName) {
|
||||
assert(obj.typ == nil)
|
||||
|
||||
// type declarations cannot use iota
|
||||
assert(check.iota == nil)
|
||||
|
||||
named := &Named{obj: obj}
|
||||
def.setUnderlying(named)
|
||||
obj.typ = named // make sure recursive type declarations terminate
|
||||
|
||||
// determine underlying type of named
|
||||
check.typExpr(typ, named, append(path, obj))
|
||||
|
||||
// The underlying type of named may be itself a named type that is
|
||||
// incomplete:
|
||||
//
|
||||
// type (
|
||||
// A B
|
||||
// B *C
|
||||
// C A
|
||||
// )
|
||||
//
|
||||
// The type of C is the (named) type of A which is incomplete,
|
||||
// and which has as its underlying type the named type B.
|
||||
// Determine the (final, unnamed) underlying type by resolving
|
||||
// any forward chain (they always end in an unnamed type).
|
||||
named.underlying = underlying(named.underlying)
|
||||
|
||||
// check and add associated methods
|
||||
// TODO(gri) It's easy to create pathological cases where the
|
||||
// current approach is incorrect: In general we need to know
|
||||
// and add all methods _before_ type-checking the type.
|
||||
// See http://play.golang.org/p/WMpE0q2wK8
|
||||
check.addMethodDecls(obj)
|
||||
}
|
||||
|
||||
func (check *Checker) addMethodDecls(obj *TypeName) {
|
||||
// get associated methods
|
||||
methods := check.methods[obj.name]
|
||||
if len(methods) == 0 {
|
||||
return // no methods
|
||||
}
|
||||
delete(check.methods, obj.name)
|
||||
|
||||
// use an objset to check for name conflicts
|
||||
var mset objset
|
||||
|
||||
// spec: "If the base type is a struct type, the non-blank method
|
||||
// and field names must be distinct."
|
||||
base := obj.typ.(*Named)
|
||||
if t, _ := base.underlying.(*Struct); t != nil {
|
||||
for _, fld := range t.fields {
|
||||
if fld.name != "_" {
|
||||
assert(mset.insert(fld) == nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checker.Files may be called multiple times; additional package files
|
||||
// may add methods to already type-checked types. Add pre-existing methods
|
||||
// so that we can detect redeclarations.
|
||||
for _, m := range base.methods {
|
||||
assert(m.name != "_")
|
||||
assert(mset.insert(m) == nil)
|
||||
}
|
||||
|
||||
// type-check methods
|
||||
for _, m := range methods {
|
||||
// spec: "For a base type, the non-blank names of methods bound
|
||||
// to it must be unique."
|
||||
if m.name != "_" {
|
||||
if alt := mset.insert(m); alt != nil {
|
||||
switch alt.(type) {
|
||||
case *Var:
|
||||
check.errorf(m.pos, "field and method with the same name %s", m.name)
|
||||
case *Func:
|
||||
check.errorf(m.pos, "method %s already declared for %s", m.name, base)
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
check.reportAltDecl(alt)
|
||||
continue
|
||||
}
|
||||
}
|
||||
check.objDecl(m, nil, nil)
|
||||
// methods with blank _ names cannot be found - don't keep them
|
||||
if m.name != "_" {
|
||||
base.methods = append(base.methods, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
|
||||
assert(obj.typ == nil)
|
||||
|
||||
// func declarations cannot use iota
|
||||
assert(check.iota == nil)
|
||||
|
||||
sig := new(Signature)
|
||||
obj.typ = sig // guard against cycles
|
||||
fdecl := decl.fdecl
|
||||
check.funcType(sig, fdecl.Recv, fdecl.Type)
|
||||
if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
|
||||
check.errorf(fdecl.Pos(), "func init must have no arguments and no return values")
|
||||
// ok to continue
|
||||
}
|
||||
|
||||
// function body must be type-checked after global declarations
|
||||
// (functions implemented elsewhere have no body)
|
||||
if !check.conf.IgnoreFuncBodies && fdecl.Body != nil {
|
||||
check.later(obj.name, decl, sig, fdecl.Body)
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) declStmt(decl ast.Decl) {
|
||||
pkg := check.pkg
|
||||
|
||||
switch d := decl.(type) {
|
||||
case *ast.BadDecl:
|
||||
// ignore
|
||||
|
||||
case *ast.GenDecl:
|
||||
var last *ast.ValueSpec // last ValueSpec with type or init exprs seen
|
||||
for iota, spec := range d.Specs {
|
||||
switch s := spec.(type) {
|
||||
case *ast.ValueSpec:
|
||||
switch d.Tok {
|
||||
case token.CONST:
|
||||
// determine which init exprs to use
|
||||
switch {
|
||||
case s.Type != nil || len(s.Values) > 0:
|
||||
last = s
|
||||
case last == nil:
|
||||
last = new(ast.ValueSpec) // make sure last exists
|
||||
}
|
||||
|
||||
// declare all constants
|
||||
lhs := make([]*Const, len(s.Names))
|
||||
for i, name := range s.Names {
|
||||
obj := NewConst(name.Pos(), pkg, name.Name, nil, exact.MakeInt64(int64(iota)))
|
||||
lhs[i] = obj
|
||||
|
||||
var init ast.Expr
|
||||
if i < len(last.Values) {
|
||||
init = last.Values[i]
|
||||
}
|
||||
|
||||
check.constDecl(obj, last.Type, init)
|
||||
}
|
||||
|
||||
check.arityMatch(s, last)
|
||||
|
||||
// spec: "The scope of a constant or variable identifier declared
|
||||
// inside a function begins at the end of the ConstSpec or VarSpec
|
||||
// (ShortVarDecl for short variable declarations) and ends at the
|
||||
// end of the innermost containing block."
|
||||
scopePos := s.End()
|
||||
for i, name := range s.Names {
|
||||
check.declare(check.scope, name, lhs[i], scopePos)
|
||||
}
|
||||
|
||||
case token.VAR:
|
||||
lhs0 := make([]*Var, len(s.Names))
|
||||
for i, name := range s.Names {
|
||||
lhs0[i] = NewVar(name.Pos(), pkg, name.Name, nil)
|
||||
}
|
||||
|
||||
// initialize all variables
|
||||
for i, obj := range lhs0 {
|
||||
var lhs []*Var
|
||||
var init ast.Expr
|
||||
switch len(s.Values) {
|
||||
case len(s.Names):
|
||||
// lhs and rhs match
|
||||
init = s.Values[i]
|
||||
case 1:
|
||||
// rhs is expected to be a multi-valued expression
|
||||
lhs = lhs0
|
||||
init = s.Values[0]
|
||||
default:
|
||||
if i < len(s.Values) {
|
||||
init = s.Values[i]
|
||||
}
|
||||
}
|
||||
check.varDecl(obj, lhs, s.Type, init)
|
||||
if len(s.Values) == 1 {
|
||||
// If we have a single lhs variable we are done either way.
|
||||
// If we have a single rhs expression, it must be a multi-
|
||||
// valued expression, in which case handling the first lhs
|
||||
// variable will cause all lhs variables to have a type
|
||||
// assigned, and we are done as well.
|
||||
if debug {
|
||||
for _, obj := range lhs0 {
|
||||
assert(obj.typ != nil)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
check.arityMatch(s, nil)
|
||||
|
||||
// declare all variables
|
||||
// (only at this point are the variable scopes (parents) set)
|
||||
scopePos := s.End() // see constant declarations
|
||||
for i, name := range s.Names {
|
||||
// see constant declarations
|
||||
check.declare(check.scope, name, lhs0[i], scopePos)
|
||||
}
|
||||
|
||||
default:
|
||||
check.invalidAST(s.Pos(), "invalid token %s", d.Tok)
|
||||
}
|
||||
|
||||
case *ast.TypeSpec:
|
||||
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
|
||||
// spec: "The scope of a type identifier declared inside a function
|
||||
// begins at the identifier in the TypeSpec and ends at the end of
|
||||
// the innermost containing block."
|
||||
scopePos := s.Name.Pos()
|
||||
check.declare(check.scope, s.Name, obj, scopePos)
|
||||
check.typeDecl(obj, s.Type, nil, nil)
|
||||
|
||||
default:
|
||||
check.invalidAST(s.Pos(), "const, type, or var declaration expected")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// 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 implements various error reporters.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func assert(p bool) {
|
||||
if !p {
|
||||
panic("assertion failed")
|
||||
}
|
||||
}
|
||||
|
||||
func unreachable() {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (check *Checker) qualifier(pkg *Package) string {
|
||||
if pkg != check.pkg {
|
||||
return pkg.path
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (check *Checker) sprintf(format string, args ...interface{}) string {
|
||||
for i, arg := range args {
|
||||
switch a := arg.(type) {
|
||||
case nil:
|
||||
arg = "<nil>"
|
||||
case operand:
|
||||
panic("internal error: should always pass *operand")
|
||||
case *operand:
|
||||
arg = operandString(a, check.qualifier)
|
||||
case token.Pos:
|
||||
arg = check.fset.Position(a).String()
|
||||
case ast.Expr:
|
||||
arg = ExprString(a)
|
||||
case Object:
|
||||
arg = ObjectString(a, check.qualifier)
|
||||
case Type:
|
||||
arg = TypeString(a, check.qualifier)
|
||||
}
|
||||
args[i] = arg
|
||||
}
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func (check *Checker) trace(pos token.Pos, format string, args ...interface{}) {
|
||||
fmt.Printf("%s:\t%s%s\n",
|
||||
check.fset.Position(pos),
|
||||
strings.Repeat(". ", check.indent),
|
||||
check.sprintf(format, args...),
|
||||
)
|
||||
}
|
||||
|
||||
// dump is only needed for debugging
|
||||
func (check *Checker) dump(format string, args ...interface{}) {
|
||||
fmt.Println(check.sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (check *Checker) err(pos token.Pos, msg string, soft bool) {
|
||||
err := Error{check.fset, pos, msg, soft}
|
||||
if check.firstErr == nil {
|
||||
check.firstErr = err
|
||||
}
|
||||
f := check.conf.Error
|
||||
if f == nil {
|
||||
panic(bailout{}) // report only first error
|
||||
}
|
||||
f(err)
|
||||
}
|
||||
|
||||
func (check *Checker) error(pos token.Pos, msg string) {
|
||||
check.err(pos, msg, false)
|
||||
}
|
||||
|
||||
func (check *Checker) errorf(pos token.Pos, format string, args ...interface{}) {
|
||||
check.err(pos, check.sprintf(format, args...), false)
|
||||
}
|
||||
|
||||
func (check *Checker) softErrorf(pos token.Pos, format string, args ...interface{}) {
|
||||
check.err(pos, check.sprintf(format, args...), true)
|
||||
}
|
||||
|
||||
func (check *Checker) invalidAST(pos token.Pos, format string, args ...interface{}) {
|
||||
check.errorf(pos, "invalid AST: "+format, args...)
|
||||
}
|
||||
|
||||
func (check *Checker) invalidArg(pos token.Pos, format string, args ...interface{}) {
|
||||
check.errorf(pos, "invalid argument: "+format, args...)
|
||||
}
|
||||
|
||||
func (check *Checker) invalidOp(pos token.Pos, format string, args ...interface{}) {
|
||||
check.errorf(pos, "invalid operation: "+format, args...)
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// 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 types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// Eval returns the type and, if constant, the value for the
|
||||
// expression expr, evaluated at position pos of package pkg,
|
||||
// which must have been derived from type-checking an AST with
|
||||
// complete position information relative to the provided file
|
||||
// set.
|
||||
//
|
||||
// If the expression contains function literals, their bodies
|
||||
// are ignored (i.e., the bodies are not type-checked).
|
||||
//
|
||||
// If pkg == nil, the Universe scope is used and the provided
|
||||
// position pos is ignored. If pkg != nil, and pos is invalid,
|
||||
// the package scope is used. Otherwise, pos must belong to the
|
||||
// package.
|
||||
//
|
||||
// An error is returned if pos is not within the package or
|
||||
// if the node cannot be evaluated.
|
||||
//
|
||||
// Note: Eval should not be used instead of running Check to compute
|
||||
// types and values, but in addition to Check. Eval will re-evaluate
|
||||
// its argument each time, and it also does not know about the context
|
||||
// in which an expression is used (e.g., an assignment). Thus, top-
|
||||
// level untyped constants will return an untyped type rather then the
|
||||
// respective context-specific type.
|
||||
//
|
||||
func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (tv TypeAndValue, err error) {
|
||||
// determine scope
|
||||
var scope *Scope
|
||||
if pkg == nil {
|
||||
scope = Universe
|
||||
pos = token.NoPos
|
||||
} else if !pos.IsValid() {
|
||||
scope = pkg.scope
|
||||
} else {
|
||||
// The package scope extent (position information) may be
|
||||
// incorrect (files spread accross a wide range of fset
|
||||
// positions) - ignore it and just consider its children
|
||||
// (file scopes).
|
||||
for _, fscope := range pkg.scope.children {
|
||||
if scope = fscope.Innermost(pos); scope != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if scope == nil || debug {
|
||||
s := scope
|
||||
for s != nil && s != pkg.scope {
|
||||
s = s.parent
|
||||
}
|
||||
// s == nil || s == pkg.scope
|
||||
if s == nil {
|
||||
return TypeAndValue{}, fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse expressions
|
||||
// BUG(gri) In case of type-checking errors below, the type checker
|
||||
// doesn't have the correct file set for expr. The correct
|
||||
// solution requires a ParseExpr that uses the incoming
|
||||
// file set fset.
|
||||
node, err := parser.ParseExpr(expr)
|
||||
if err != nil {
|
||||
return TypeAndValue{}, err
|
||||
}
|
||||
|
||||
// initialize checker
|
||||
check := NewChecker(nil, fset, pkg, nil)
|
||||
check.scope = scope
|
||||
check.pos = pos
|
||||
defer check.handleBailout(&err)
|
||||
|
||||
// evaluate node
|
||||
var x operand
|
||||
check.rawExpr(&x, node, nil)
|
||||
return TypeAndValue{x.mode, x.typ, x.val}, err
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,220 @@
|
|||
// 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 printing of expressions.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
)
|
||||
|
||||
// ExprString returns the (possibly simplified) string representation for x.
|
||||
func ExprString(x ast.Expr) string {
|
||||
var buf bytes.Buffer
|
||||
WriteExpr(&buf, x)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// WriteExpr writes the (possibly simplified) string representation for x to buf.
|
||||
func WriteExpr(buf *bytes.Buffer, x ast.Expr) {
|
||||
// The AST preserves source-level parentheses so there is
|
||||
// no need to introduce them here to correct for different
|
||||
// operator precedences. (This assumes that the AST was
|
||||
// generated by a Go parser.)
|
||||
|
||||
switch x := x.(type) {
|
||||
default:
|
||||
buf.WriteString("(bad expr)") // nil, ast.BadExpr, ast.KeyValueExpr
|
||||
|
||||
case *ast.Ident:
|
||||
buf.WriteString(x.Name)
|
||||
|
||||
case *ast.Ellipsis:
|
||||
buf.WriteString("...")
|
||||
if x.Elt != nil {
|
||||
WriteExpr(buf, x.Elt)
|
||||
}
|
||||
|
||||
case *ast.BasicLit:
|
||||
buf.WriteString(x.Value)
|
||||
|
||||
case *ast.FuncLit:
|
||||
buf.WriteByte('(')
|
||||
WriteExpr(buf, x.Type)
|
||||
buf.WriteString(" literal)") // simplified
|
||||
|
||||
case *ast.CompositeLit:
|
||||
buf.WriteByte('(')
|
||||
WriteExpr(buf, x.Type)
|
||||
buf.WriteString(" literal)") // simplified
|
||||
|
||||
case *ast.ParenExpr:
|
||||
buf.WriteByte('(')
|
||||
WriteExpr(buf, x.X)
|
||||
buf.WriteByte(')')
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
WriteExpr(buf, x.X)
|
||||
buf.WriteByte('.')
|
||||
buf.WriteString(x.Sel.Name)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
WriteExpr(buf, x.X)
|
||||
buf.WriteByte('[')
|
||||
WriteExpr(buf, x.Index)
|
||||
buf.WriteByte(']')
|
||||
|
||||
case *ast.SliceExpr:
|
||||
WriteExpr(buf, x.X)
|
||||
buf.WriteByte('[')
|
||||
if x.Low != nil {
|
||||
WriteExpr(buf, x.Low)
|
||||
}
|
||||
buf.WriteByte(':')
|
||||
if x.High != nil {
|
||||
WriteExpr(buf, x.High)
|
||||
}
|
||||
if x.Slice3 {
|
||||
buf.WriteByte(':')
|
||||
if x.Max != nil {
|
||||
WriteExpr(buf, x.Max)
|
||||
}
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
WriteExpr(buf, x.X)
|
||||
buf.WriteString(".(")
|
||||
WriteExpr(buf, x.Type)
|
||||
buf.WriteByte(')')
|
||||
|
||||
case *ast.CallExpr:
|
||||
WriteExpr(buf, x.Fun)
|
||||
buf.WriteByte('(')
|
||||
for i, arg := range x.Args {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
WriteExpr(buf, arg)
|
||||
}
|
||||
if x.Ellipsis.IsValid() {
|
||||
buf.WriteString("...")
|
||||
}
|
||||
buf.WriteByte(')')
|
||||
|
||||
case *ast.StarExpr:
|
||||
buf.WriteByte('*')
|
||||
WriteExpr(buf, x.X)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
buf.WriteString(x.Op.String())
|
||||
WriteExpr(buf, x.X)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
WriteExpr(buf, x.X)
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(x.Op.String())
|
||||
buf.WriteByte(' ')
|
||||
WriteExpr(buf, x.Y)
|
||||
|
||||
case *ast.ArrayType:
|
||||
buf.WriteByte('[')
|
||||
if x.Len != nil {
|
||||
WriteExpr(buf, x.Len)
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
WriteExpr(buf, x.Elt)
|
||||
|
||||
case *ast.StructType:
|
||||
buf.WriteString("struct{")
|
||||
writeFieldList(buf, x.Fields, "; ", false)
|
||||
buf.WriteByte('}')
|
||||
|
||||
case *ast.FuncType:
|
||||
buf.WriteString("func")
|
||||
writeSigExpr(buf, x)
|
||||
|
||||
case *ast.InterfaceType:
|
||||
buf.WriteString("interface{")
|
||||
writeFieldList(buf, x.Methods, "; ", true)
|
||||
buf.WriteByte('}')
|
||||
|
||||
case *ast.MapType:
|
||||
buf.WriteString("map[")
|
||||
WriteExpr(buf, x.Key)
|
||||
buf.WriteByte(']')
|
||||
WriteExpr(buf, x.Value)
|
||||
|
||||
case *ast.ChanType:
|
||||
var s string
|
||||
switch x.Dir {
|
||||
case ast.SEND:
|
||||
s = "chan<- "
|
||||
case ast.RECV:
|
||||
s = "<-chan "
|
||||
default:
|
||||
s = "chan "
|
||||
}
|
||||
buf.WriteString(s)
|
||||
WriteExpr(buf, x.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) {
|
||||
buf.WriteByte('(')
|
||||
writeFieldList(buf, sig.Params, ", ", false)
|
||||
buf.WriteByte(')')
|
||||
|
||||
res := sig.Results
|
||||
n := res.NumFields()
|
||||
if n == 0 {
|
||||
// no result
|
||||
return
|
||||
}
|
||||
|
||||
buf.WriteByte(' ')
|
||||
if n == 1 && len(res.List[0].Names) == 0 {
|
||||
// single unnamed result
|
||||
WriteExpr(buf, res.List[0].Type)
|
||||
return
|
||||
}
|
||||
|
||||
// multiple or named result(s)
|
||||
buf.WriteByte('(')
|
||||
writeFieldList(buf, res, ", ", false)
|
||||
buf.WriteByte(')')
|
||||
}
|
||||
|
||||
func writeFieldList(buf *bytes.Buffer, fields *ast.FieldList, sep string, iface bool) {
|
||||
for i, f := range fields.List {
|
||||
if i > 0 {
|
||||
buf.WriteString(sep)
|
||||
}
|
||||
|
||||
// field list names
|
||||
for i, name := range f.Names {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(name.Name)
|
||||
}
|
||||
|
||||
// types of interface methods consist of signatures only
|
||||
if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface {
|
||||
writeSigExpr(buf, sig)
|
||||
continue
|
||||
}
|
||||
|
||||
// named fields are separated with a blank from the field type
|
||||
if len(f.Names) > 0 {
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
WriteExpr(buf, f.Type)
|
||||
|
||||
// ignore tag
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// 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 !go1.2
|
||||
|
||||
package types
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func slice3(x *ast.SliceExpr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func sliceMax(x *ast.SliceExpr) ast.Expr {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// 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 go1.2
|
||||
|
||||
package types
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func slice3(x *ast.SliceExpr) bool {
|
||||
return x.Slice3
|
||||
}
|
||||
|
||||
func sliceMax(x *ast.SliceExpr) ast.Expr {
|
||||
return x.Max
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue