diff --git a/cmd/godex/doc.go b/cmd/godex/doc.go new file mode 100644 index 00000000..3570a2d2 --- /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 00000000..ef7d4506 --- /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 00000000..8c200e64 --- /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 00000000..21f75520 --- /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 00000000..438b3f79 --- /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 00000000..0c263d56 --- /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") +}