1
0
mirror of https://github.com/golang/go synced 2024-11-18 14:04:45 -07:00
go/cmd/ssadump/main.go
Alan Donovan 27709f6afe cmd/ssadump: use go/packages
Unlike go/loader's FromArgs, go/packages doesn't return the
non-package arguments, so we add a new repeated flag -arg=x -arg=y to
specify them.

Because we cannot add packages to a packages.Load query, we emit an
error if -run is specified and "runtime" is not among the
dependencies.

There is no easy way to distinguish synthetic test main packages
from regular main packages, so we no longer try.
This makes -test and -run orthogonal.

Change-Id: Ibd69b993c05de62df01dde52218a9e266cd63a71
Reviewed-on: https://go-review.googlesource.com/128837
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2018-08-10 20:16:25 +00:00

188 lines
4.9 KiB
Go

// 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
}
cfg.TypeChecker.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")
}
// Create SSA-form program representation.
prog, pkgs := ssautil.Packages(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, cfg.TypeChecker.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 }