github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/cmd/ssadump/main.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // ssadump: a tool for displaying and interpreting the SSA form of Go programs.
     6  package main // import "github.com/jhump/golang-x-tools/cmd/ssadump"
     7  
     8  import (
     9  	"flag"
    10  	"fmt"
    11  	"go/build"
    12  	"go/types"
    13  	"os"
    14  	"runtime"
    15  	"runtime/pprof"
    16  
    17  	"github.com/jhump/golang-x-tools/go/buildutil"
    18  	"github.com/jhump/golang-x-tools/go/packages"
    19  	"github.com/jhump/golang-x-tools/go/ssa"
    20  	"github.com/jhump/golang-x-tools/go/ssa/interp"
    21  	"github.com/jhump/golang-x-tools/go/ssa/ssautil"
    22  )
    23  
    24  // flags
    25  var (
    26  	mode = ssa.BuilderMode(0)
    27  
    28  	testFlag = flag.Bool("test", false, "include implicit test packages and executables")
    29  
    30  	runFlag = flag.Bool("run", false, "interpret the SSA program")
    31  
    32  	interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter.
    33  The value is a sequence of zero or more more of these letters:
    34  R	disable [R]ecover() from panic; show interpreter crash instead.
    35  T	[T]race execution of the program.  Best for single-threaded programs!
    36  `)
    37  
    38  	cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
    39  
    40  	args stringListValue
    41  )
    42  
    43  func init() {
    44  	flag.Var(&mode, "build", ssa.BuilderModeDoc)
    45  	flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
    46  	flag.Var(&args, "arg", "add argument to interpreted program")
    47  }
    48  
    49  const usage = `SSA builder and interpreter.
    50  Usage: ssadump [-build=[DBCSNFL]] [-test] [-run] [-interp=[TR]] [-arg=...] package...
    51  Use -help flag to display options.
    52  
    53  Examples:
    54  % ssadump -build=F hello.go              # dump SSA form of a single package
    55  % ssadump -build=F -test fmt             # dump SSA form of a package and its tests
    56  % ssadump -run -interp=T hello.go        # interpret a program, with tracing
    57  
    58  The -run flag causes ssadump to run the first package named main.
    59  
    60  Interpretation of the standard "testing" package is no longer supported.
    61  `
    62  
    63  func main() {
    64  	if err := doMain(); err != nil {
    65  		fmt.Fprintf(os.Stderr, "ssadump: %s\n", err)
    66  		os.Exit(1)
    67  	}
    68  }
    69  
    70  func doMain() error {
    71  	flag.Parse()
    72  	if len(flag.Args()) == 0 {
    73  		fmt.Fprint(os.Stderr, usage)
    74  		os.Exit(1)
    75  	}
    76  
    77  	cfg := &packages.Config{
    78  		Mode:  packages.LoadSyntax,
    79  		Tests: *testFlag,
    80  	}
    81  
    82  	// Choose types.Sizes from conf.Build.
    83  	// TODO(adonovan): remove this when go/packages provides a better way.
    84  	var wordSize int64 = 8
    85  	switch build.Default.GOARCH {
    86  	case "386", "arm":
    87  		wordSize = 4
    88  	}
    89  	sizes := &types.StdSizes{
    90  		MaxAlign: 8,
    91  		WordSize: wordSize,
    92  	}
    93  
    94  	var interpMode interp.Mode
    95  	for _, c := range *interpFlag {
    96  		switch c {
    97  		case 'T':
    98  			interpMode |= interp.EnableTracing
    99  		case 'R':
   100  			interpMode |= interp.DisableRecover
   101  		default:
   102  			return fmt.Errorf("unknown -interp option: '%c'", c)
   103  		}
   104  	}
   105  
   106  	// Profiling support.
   107  	if *cpuprofile != "" {
   108  		f, err := os.Create(*cpuprofile)
   109  		if err != nil {
   110  			fmt.Fprintln(os.Stderr, err)
   111  			os.Exit(1)
   112  		}
   113  		pprof.StartCPUProfile(f)
   114  		defer pprof.StopCPUProfile()
   115  	}
   116  
   117  	// Load, parse and type-check the initial packages,
   118  	// and, if -run, their dependencies.
   119  	if *runFlag {
   120  		cfg.Mode = packages.LoadAllSyntax
   121  	}
   122  	initial, err := packages.Load(cfg, flag.Args()...)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	if len(initial) == 0 {
   127  		return fmt.Errorf("no packages")
   128  	}
   129  	if packages.PrintErrors(initial) > 0 {
   130  		return fmt.Errorf("packages contain errors")
   131  	}
   132  
   133  	// Create SSA-form program representation.
   134  	prog, pkgs := ssautil.AllPackages(initial, mode)
   135  
   136  	for i, p := range pkgs {
   137  		if p == nil {
   138  			return fmt.Errorf("cannot build SSA for package %s", initial[i])
   139  		}
   140  	}
   141  
   142  	if !*runFlag {
   143  		// Build and display only the initial packages
   144  		// (and synthetic wrappers).
   145  		for _, p := range pkgs {
   146  			p.Build()
   147  		}
   148  
   149  	} else {
   150  		// Run the interpreter.
   151  		// Build SSA for all packages.
   152  		prog.Build()
   153  
   154  		// The interpreter needs the runtime package.
   155  		// It is a limitation of go/packages that
   156  		// we cannot add "runtime" to its initial set,
   157  		// we can only check that it is present.
   158  		if prog.ImportedPackage("runtime") == nil {
   159  			return fmt.Errorf("-run: program does not depend on runtime")
   160  		}
   161  
   162  		if runtime.GOARCH != build.Default.GOARCH {
   163  			return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)",
   164  				build.Default.GOARCH, runtime.GOARCH)
   165  		}
   166  
   167  		// Run first main package.
   168  		for _, main := range ssautil.MainPackages(pkgs) {
   169  			fmt.Fprintf(os.Stderr, "Running: %s\n", main.Pkg.Path())
   170  			os.Exit(interp.Interpret(main, interpMode, sizes, main.Pkg.Path(), args))
   171  		}
   172  		return fmt.Errorf("no main package")
   173  	}
   174  	return nil
   175  }
   176  
   177  // stringListValue is a flag.Value that accumulates strings.
   178  // e.g. --flag=one --flag=two would produce []string{"one", "two"}.
   179  type stringListValue []string
   180  
   181  func newStringListValue(val []string, p *[]string) *stringListValue {
   182  	*p = val
   183  	return (*stringListValue)(p)
   184  }
   185  
   186  func (ss *stringListValue) Get() interface{} { return []string(*ss) }
   187  
   188  func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
   189  
   190  func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }