go.tools/cmd/godex: a tool to dump export information

Initial implementation. Lots of missing pieces.

Example use:
        godex math
        godex math.Sin
        godex math.Sin math.Cos

LGTM=adonovan
R=adonovan
CC=golang-codereviews
https://golang.org/cl/76890044
This commit is contained in:
Robert Griesemer 2014-03-25 15:26:38 -07:00
parent a1c1cf19ba
commit 4a27ee3a1b
6 changed files with 458 additions and 0 deletions

38
cmd/godex/doc.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The godex command prints (dumps) exported information of packages
// or selected package objects.
//
// In contrast to godoc, godex extracts this information from compiled
// object files. Hence the exported data is truly what a compiler will
// see, at the cost of missing commentary.
//
// Usage: godex [flags] {path|qualifiedIdent}
//
// Each argument must be a package path, or a qualified identifier.
//
// The flags are:
//
// -s=src
// only consider packages from src, where src is one of the supported compilers
// -v
// verbose mode
//
// The following sources are supported:
//
// gc
// gc-generated object files
// gccgo
// gccgo-generated object files
// gccgo-new
// gccgo-generated object files using a condensed format (experimental)
// source
// (uncompiled) source code (not yet implemented)
//
// If no -s argument is provided, godex will try to find a matching source.
//
// TODO(gri) expand this documentation
//
package main

15
cmd/godex/gc.go Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements access to gc-generated export data.
package main
import (
"code.google.com/p/go.tools/go/gcimporter"
)
func init() {
register("gc", protect(gcimporter.Import))
}

88
cmd/godex/gccgo.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements access to gccgo-generated export data.
package main
import (
"debug/elf"
"fmt"
"io"
"os"
"code.google.com/p/go.tools/go/gccgoimporter"
"code.google.com/p/go.tools/go/importer"
"code.google.com/p/go.tools/go/types"
)
func init() {
// importer for default gccgo
var inst gccgoimporter.GccgoInstallation
inst.InitFromDriver("gccgo")
register("gccgo", protect(inst.GetImporter(nil)))
// importer for gccgo using condensed export format (experimental)
register("gccgo-new", protect(gccgoNewImporter))
}
func gccgoNewImporter(packages map[string]*types.Package, path string) (*types.Package, error) {
reader, closer, err := openGccgoExportFile(path)
if err != nil {
return nil, err
}
defer closer.Close()
// TODO(gri) importer.ImportData takes a []byte instead of an io.Reader;
// hence the need to read some amount of data. At the same time we don't
// want to read the entire, potentially very large object file. For now,
// read 10K. Fix this!
var data = make([]byte, 10<<10)
n, err := reader.Read(data)
if err != nil && err != io.EOF {
return nil, err
}
return importer.ImportData(packages, data[:n])
}
// openGccgoExportFile was copied from gccgoimporter.
func openGccgoExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) {
f, err := os.Open(fpath)
if err != nil {
return
}
defer func() {
if err != nil {
f.Close()
}
}()
closer = f
var magic [4]byte
_, err = f.ReadAt(magic[:], 0)
if err != nil {
return
}
if string(magic[:]) == "v1;\n" {
// Raw export data.
reader = f
return
}
ef, err := elf.NewFile(f)
if err != nil {
return
}
sec := ef.Section(".go_export")
if sec == nil {
err = fmt.Errorf("%s: .go_export section not found", fpath)
return
}
reader = sec.Open()
return
}

143
cmd/godex/godex.go Normal file
View File

@ -0,0 +1,143 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"flag"
"fmt"
"os"
"strings"
"code.google.com/p/go.tools/go/types"
)
var (
source = flag.String("s", "", "only consider packages from this source")
verbose = flag.Bool("v", false, "verbose mode")
)
var (
importFailed = errors.New("import failed")
importers = make(map[string]types.Importer)
packages = make(map[string]*types.Package)
)
func usage() {
fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}")
flag.PrintDefaults()
os.Exit(2)
}
func report(msg string) {
fmt.Fprintln(os.Stderr, "error: "+msg)
os.Exit(2)
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() == 0 {
report("no package name, path, or file provided")
}
imp := tryImport
if *source != "" {
imp = importers[*source]
if imp == nil {
report("source must be one of: " + importersList())
}
}
for _, arg := range flag.Args() {
if *verbose {
fmt.Fprintf(os.Stderr, "(processing %s)\n", arg)
}
// determine import path, object name
var path, name string
elems := strings.Split(arg, ".")
switch len(elems) {
case 2:
name = elems[1]
fallthrough
case 1:
path = elems[0]
default:
fmt.Fprintf(os.Stderr, "ignoring %q: invalid path or (qualified) identifier\n", arg)
continue
}
// import package
pkg, err := imp(packages, path)
if err != nil {
fmt.Fprintf(os.Stderr, "ignoring %q: %s\n", path, err)
continue
}
// filter objects if needed
filter := exportFilter
if name != "" {
f := filter
filter = func(obj types.Object) bool {
// TODO(gri) perhaps use regular expression matching here?
return f(obj) && obj.Name() == name
}
}
// print contents
print(os.Stdout, pkg, filter)
}
}
// protect protects an importer imp from panics and returns the protected importer.
func protect(imp types.Importer) types.Importer {
return func(packages map[string]*types.Package, path string) (pkg *types.Package, err error) {
defer func() {
if recover() != nil {
pkg = nil
err = importFailed
}
}()
return imp(packages, path)
}
}
func tryImport(packages map[string]*types.Package, path string) (pkg *types.Package, err error) {
for source, imp := range importers {
if *verbose {
fmt.Fprintf(os.Stderr, "(trying as %s)\n", source)
}
pkg, err = imp(packages, path)
if err == nil {
break
}
}
return
}
func register(source string, imp types.Importer) {
if _, ok := importers[source]; ok {
panic(source + " importer already registered")
}
importers[source] = imp
}
func importersList() string {
var s string
for n := range importers {
if len(s) == 0 {
s = n
} else {
s = s + ", " + n
}
}
return s
}
func exportFilter(obj types.Object) bool {
return obj.Exported()
}

155
cmd/godex/print.go Normal file
View File

@ -0,0 +1,155 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"fmt"
"io"
"code.google.com/p/go.tools/go/types"
)
// TODO(gri) handle indentation
// TODO(gri) filter unexported fields of struct types?
// TODO(gri) use tabwriter for alignment?
func print(w io.Writer, pkg *types.Package, filter func(types.Object) bool) {
var p printer
p.pkg = pkg
p.printPackage(pkg, filter)
io.Copy(w, &p.buf)
}
type printer struct {
pkg *types.Package
buf bytes.Buffer
indent int
}
func (p *printer) print(s string) {
p.buf.WriteString(s)
}
func (p *printer) printf(format string, args ...interface{}) {
fmt.Fprintf(&p.buf, format, args...)
}
func (p *printer) printPackage(pkg *types.Package, filter func(types.Object) bool) {
// collect objects by kind
var (
consts []*types.Const
typez []*types.TypeName // types without methods
typem []*types.TypeName // types with methods
vars []*types.Var
funcs []*types.Func
)
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if !filter(obj) {
continue
}
switch obj := obj.(type) {
case *types.Const:
consts = append(consts, obj)
case *types.TypeName:
if obj.Type().(*types.Named).NumMethods() > 0 {
typem = append(typem, obj)
} else {
typez = append(typez, obj)
}
case *types.Var:
vars = append(vars, obj)
case *types.Func:
funcs = append(funcs, obj)
}
}
p.printf("package %s\n\n", pkg.Name())
if len(consts) > 0 {
p.print("const (\n")
for _, obj := range consts {
p.printObj(obj)
p.print("\n")
}
p.print(")\n\n")
}
if len(vars) > 0 {
p.print("var (\n")
for _, obj := range vars {
p.printObj(obj)
p.print("\n")
}
p.print(")\n\n")
}
if len(typez) > 0 {
p.print("type (\n")
for _, obj := range typez {
p.printf("\t%s ", obj.Name())
types.WriteType(&p.buf, p.pkg, obj.Type().Underlying())
p.print("\n")
}
p.print(")\n\n")
}
for _, obj := range typem {
p.printf("type %s ", obj.Name())
typ := obj.Type().(*types.Named)
types.WriteType(&p.buf, p.pkg, typ.Underlying())
p.print("\n")
for i, n := 0, typ.NumMethods(); i < n; i++ {
p.printFunc(typ.Method(i))
p.print("\n")
}
p.print("\n")
}
for _, obj := range funcs {
p.printFunc(obj)
p.print("\n")
}
p.print("\n")
}
func (p *printer) printObj(obj types.Object) {
p.printf("\t %s", obj.Name())
// don't write untyped types (for constants)
if typ := obj.Type(); typed(typ) {
p.print(" ")
types.WriteType(&p.buf, p.pkg, typ)
}
// write constant value
if obj, ok := obj.(*types.Const); ok {
p.printf(" = %s", obj.Val())
}
}
func (p *printer) printFunc(obj *types.Func) {
p.print("func ")
sig := obj.Type().(*types.Signature)
if recv := sig.Recv(); recv != nil {
p.print("(")
if name := recv.Name(); name != "" {
p.print(name)
p.print(" ")
}
types.WriteType(&p.buf, p.pkg, recv.Type())
p.print(") ")
}
p.print(obj.Name())
types.WriteSignature(&p.buf, p.pkg, sig)
}
func typed(typ types.Type) bool {
if t, ok := typ.Underlying().(*types.Basic); ok {
return t.Info()&types.IsUntyped == 0
}
return true
}

19
cmd/godex/source.go Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements access to export data from source.
package main
import (
"code.google.com/p/go.tools/go/types"
)
func init() {
register("source", protect(sourceImporter))
}
func sourceImporter(packages map[string]*types.Package, path string) (*types.Package, error) {
panic("unimplemented")
}