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
diff --git a/cmd/godex/doc.go b/cmd/godex/doc.go
new file mode 100644
index 0000000..3570a2d
--- /dev/null
+++ b/cmd/godex/doc.go
@@ -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
diff --git a/cmd/godex/gc.go b/cmd/godex/gc.go
new file mode 100644
index 0000000..ef7d450
--- /dev/null
+++ b/cmd/godex/gc.go
@@ -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))
+}
diff --git a/cmd/godex/gccgo.go b/cmd/godex/gccgo.go
new file mode 100644
index 0000000..8c200e6
--- /dev/null
+++ b/cmd/godex/gccgo.go
@@ -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
+}
diff --git a/cmd/godex/godex.go b/cmd/godex/godex.go
new file mode 100644
index 0000000..21f7552
--- /dev/null
+++ b/cmd/godex/godex.go
@@ -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()
+}
diff --git a/cmd/godex/print.go b/cmd/godex/print.go
new file mode 100644
index 0000000..438b3f7
--- /dev/null
+++ b/cmd/godex/print.go
@@ -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
+}
diff --git a/cmd/godex/source.go b/cmd/godex/source.go
new file mode 100644
index 0000000..0c263d5
--- /dev/null
+++ b/cmd/godex/source.go
@@ -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")
+}