| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // ssadump: a tool for displaying and interpreting the SSA form of Go programs. |
| package main // import "golang.org/x/tools/cmd/ssadump" |
| |
| import ( |
| "flag" |
| "fmt" |
| "go/build" |
| "go/types" |
| "os" |
| "runtime" |
| "runtime/pprof" |
| |
| "golang.org/x/tools/go/buildutil" |
| "golang.org/x/tools/go/packages" |
| "golang.org/x/tools/go/ssa" |
| "golang.org/x/tools/go/ssa/interp" |
| "golang.org/x/tools/go/ssa/ssautil" |
| ) |
| |
| // flags |
| var ( |
| mode = ssa.BuilderMode(0) |
| |
| testFlag = flag.Bool("test", false, "include implicit test packages and executables") |
| |
| runFlag = flag.Bool("run", false, "interpret the SSA program") |
| |
| interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter. |
| The value is a sequence of zero or more more of these letters: |
| R disable [R]ecover() from panic; show interpreter crash instead. |
| T [T]race execution of the program. Best for single-threaded programs! |
| `) |
| |
| cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") |
| |
| args stringListValue |
| ) |
| |
| func init() { |
| flag.Var(&mode, "build", ssa.BuilderModeDoc) |
| flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) |
| flag.Var(&args, "arg", "add argument to interpreted program") |
| } |
| |
| const usage = `SSA builder and interpreter. |
| Usage: ssadump [-build=[DBCSNFL]] [-test] [-run] [-interp=[TR]] [-arg=...] package... |
| Use -help flag to display options. |
| |
| Examples: |
| % ssadump -build=F hello.go # dump SSA form of a single package |
| % ssadump -build=F -test fmt # dump SSA form of a package and its tests |
| % ssadump -run -interp=T hello.go # interpret a program, with tracing |
| |
| The -run flag causes ssadump to run the first package named main. |
| |
| Interpretation of the standard "testing" package is no longer supported. |
| ` |
| |
| func main() { |
| if err := doMain(); err != nil { |
| fmt.Fprintf(os.Stderr, "ssadump: %s\n", err) |
| os.Exit(1) |
| } |
| } |
| |
| func doMain() error { |
| flag.Parse() |
| if len(flag.Args()) == 0 { |
| fmt.Fprint(os.Stderr, usage) |
| os.Exit(1) |
| } |
| |
| cfg := &packages.Config{ |
| Mode: packages.LoadSyntax, |
| Tests: *testFlag, |
| } |
| |
| // Choose types.Sizes from conf.Build. |
| // TODO(adonovan): remove this when go/packages provides a better way. |
| var wordSize int64 = 8 |
| switch build.Default.GOARCH { |
| case "386", "arm": |
| wordSize = 4 |
| } |
| sizes := &types.StdSizes{ |
| MaxAlign: 8, |
| WordSize: wordSize, |
| } |
| |
| var interpMode interp.Mode |
| for _, c := range *interpFlag { |
| switch c { |
| case 'T': |
| interpMode |= interp.EnableTracing |
| case 'R': |
| interpMode |= interp.DisableRecover |
| default: |
| return fmt.Errorf("unknown -interp option: '%c'", c) |
| } |
| } |
| |
| // Profiling support. |
| if *cpuprofile != "" { |
| f, err := os.Create(*cpuprofile) |
| if err != nil { |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } |
| pprof.StartCPUProfile(f) |
| defer pprof.StopCPUProfile() |
| } |
| |
| // Load, parse and type-check the initial packages, |
| // and, if -run, their dependencies. |
| if *runFlag { |
| cfg.Mode = packages.LoadAllSyntax |
| } |
| initial, err := packages.Load(cfg, flag.Args()...) |
| if err != nil { |
| return err |
| } |
| if len(initial) == 0 { |
| return fmt.Errorf("no packages") |
| } |
| if packages.PrintErrors(initial) > 0 { |
| return fmt.Errorf("packages contain errors") |
| } |
| |
| // Create SSA-form program representation. |
| prog, pkgs := ssautil.AllPackages(initial, mode) |
| |
| for i, p := range pkgs { |
| if p == nil { |
| return fmt.Errorf("cannot build SSA for package %s", initial[i]) |
| } |
| } |
| |
| if !*runFlag { |
| // Build and display only the initial packages |
| // (and synthetic wrappers). |
| for _, p := range pkgs { |
| p.Build() |
| } |
| |
| } else { |
| // Run the interpreter. |
| // Build SSA for all packages. |
| prog.Build() |
| |
| // The interpreter needs the runtime package. |
| // It is a limitation of go/packages that |
| // we cannot add "runtime" to its initial set, |
| // we can only check that it is present. |
| if prog.ImportedPackage("runtime") == nil { |
| return fmt.Errorf("-run: program does not depend on runtime") |
| } |
| |
| if runtime.GOARCH != build.Default.GOARCH { |
| return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)", |
| build.Default.GOARCH, runtime.GOARCH) |
| } |
| |
| // Run first main package. |
| for _, main := range ssautil.MainPackages(pkgs) { |
| fmt.Fprintf(os.Stderr, "Running: %s\n", main.Pkg.Path()) |
| os.Exit(interp.Interpret(main, interpMode, sizes, main.Pkg.Path(), args)) |
| } |
| return fmt.Errorf("no main package") |
| } |
| return nil |
| } |
| |
| // stringListValue is a flag.Value that accumulates strings. |
| // e.g. --flag=one --flag=two would produce []string{"one", "two"}. |
| type stringListValue []string |
| |
| func newStringListValue(val []string, p *[]string) *stringListValue { |
| *p = val |
| return (*stringListValue)(p) |
| } |
| |
| func (ss *stringListValue) Get() interface{} { return []string(*ss) } |
| |
| func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) } |
| |
| func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil } |