--- /dev/null
+// 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.
+
+// Package ssa/interp defines an interpreter for the SSA
+// representation of Go programs.
+//
+// This interpreter is provided as an adjunct for testing the SSA
+// construction algorithm. Its purpose is to provide a minimal
+// metacircular implementation of the dynamic semantics of each SSA
+// instruction. It is not, and will never be, a production-quality Go
+// interpreter.
+//
+// The following is a partial list of Go features that are currently
+// unsupported or incomplete in the interpreter.
+//
+// * Unsafe operations, including all uses of unsafe.Pointer, are
+// impossible to support given the "boxed" value representation we
+// have chosen.
+//
+// * The reflect package is only partially implemented.
+//
+// * The "testing" package is no longer supported because it
+// depends on low-level details that change too often.
+//
+// * "sync/atomic" operations are not atomic due to the "boxed" value
+// representation: it is not possible to read, modify and write an
+// interface value atomically. As a consequence, Mutexes are currently
+// broken.
+//
+// * recover is only partially implemented. Also, the interpreter
+// makes no attempt to distinguish target panics from interpreter
+// crashes.
+//
+// * the sizes of the int, uint and uintptr types in the target
+// program are assumed to be the same as those of the interpreter
+// itself.
+//
+// * all values occupy space, even those of types defined by the spec
+// to have zero size, e.g. struct{}. This can cause asymptotic
+// performance degradation.
+//
+// * os.Exit is implemented using panic, causing deferred functions to
+// run.
+package interp // import "golang.org/x/tools/go/ssa/interp"
+
+import (
+ "fmt"
+ "go/token"
+ "go/types"
+ "os"
+ "reflect"
+ "runtime"
+ "sync/atomic"
+
+ "golang.org/x/tools/go/ssa"
+)
+
+type continuation int
+
+const (
+ kNext continuation = iota
+ kReturn
+ kJump
+)
+
+// Mode is a bitmask of options affecting the interpreter.
+type Mode uint
+
+const (
+ DisableRecover Mode = 1 << iota // Disable recover() in target programs; show interpreter crash instead.
+ EnableTracing // Print a trace of all instructions as they are interpreted.
+)
+
+type methodSet map[string]*ssa.Function
+
+// State shared between all interpreted goroutines.
+type interpreter struct {
+ osArgs []value // the value of os.Args
+ prog *ssa.Program // the SSA program
+ globals map[ssa.Value]*value // addresses of global variables (immutable)
+ mode Mode // interpreter options
+ reflectPackage *ssa.Package // the fake reflect package
+ errorMethods methodSet // the method set of reflect.error, which implements the error interface.
+ rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface.
+ runtimeErrorString types.Type // the runtime.errorString type
+ sizes types.Sizes // the effective type-sizing function
+ goroutines int32 // atomically updated
+}
+
+type deferred struct {
+ fn value
+ args []value
+ instr *ssa.Defer
+ tail *deferred
+}
+
+type frame struct {
+ i *interpreter
+ caller *frame
+ fn *ssa.Function
+ block, prevBlock *ssa.BasicBlock
+ env map[ssa.Value]value // dynamic values of SSA variables
+ locals []value
+ defers *deferred
+ result value
+ panicking bool
+ panic interface{}
+}
+
+func (fr *frame) get(key ssa.Value) value {
+ switch key := key.(type) {
+ case nil:
+ // Hack; simplifies handling of optional attributes
+ // such as ssa.Slice.{Low,High}.
+ return nil
+ case *ssa.Function, *ssa.Builtin:
+ return key
+ case *ssa.Const:
+ return constValue(key)
+ case *ssa.Global:
+ if r, ok := fr.i.globals[key]; ok {
+ return r
+ }
+ }
+ if r, ok := fr.env[key]; ok {
+ return r
+ }
+ panic(fmt.Sprintf("get: no value for %T: %v", key, key.Name()))
+}
+
+// runDefer runs a deferred call d.
+// It always returns normally, but may set or clear fr.panic.
+//
+func (fr *frame) runDefer(d *deferred) {
+ if fr.i.mode&EnableTracing != 0 {
+ fmt.Fprintf(os.Stderr, "%s: invoking deferred function call\n",
+ fr.i.prog.Fset.Position(d.instr.Pos()))
+ }
+ var ok bool
+ defer func() {
+ if !ok {
+ // Deferred call created a new state of panic.
+ fr.panicking = true
+ fr.panic = recover()
+ }
+ }()
+ call(fr.i, fr, d.instr.Pos(), d.fn, d.args)
+ ok = true
+}
+
+// runDefers executes fr's deferred function calls in LIFO order.
+//
+// On entry, fr.panicking indicates a state of panic; if
+// true, fr.panic contains the panic value.
+//
+// On completion, if a deferred call started a panic, or if no
+// deferred call recovered from a previous state of panic, then
+// runDefers itself panics after the last deferred call has run.
+//
+// If there was no initial state of panic, or it was recovered from,
+// runDefers returns normally.
+//
+func (fr *frame) runDefers() {
+ for d := fr.defers; d != nil; d = d.tail {
+ fr.runDefer(d)
+ }
+ fr.defers = nil
+ if fr.panicking {
+ panic(fr.panic) // new panic, or still panicking
+ }
+}
+
+// lookupMethod returns the method set for type typ, which may be one
+// of the interpreter's fake types.
+func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Function {
+ switch typ {
+ case rtypeType:
+ return i.rtypeMethods[meth.Id()]
+ case errorType:
+ return i.errorMethods[meth.Id()]
+ }
+ return i.prog.LookupMethod(typ, meth.Pkg(), meth.Name())
+}
+
+// visitInstr interprets a single ssa.Instruction within the activation
+// record frame. It returns a continuation value indicating where to
+// read the next instruction from.
+func visitInstr(fr *frame, instr ssa.Instruction) continuation {
+ switch instr := instr.(type) {
+ case *ssa.DebugRef:
+ // no-op
+
+ case *ssa.UnOp:
+ fr.env[instr] = unop(instr, fr.get(instr.X))
+
+ case *ssa.BinOp:
+ fr.env[instr] = binop(instr.Op, instr.X.Type(), fr.get(instr.X), fr.get(instr.Y))
+
+ case *ssa.Call:
+ fn, args := prepareCall(fr, &instr.Call)
+ fr.env[instr] = call(fr.i, fr, instr.Pos(), fn, args)
+
+ case *ssa.ChangeInterface:
+ fr.env[instr] = fr.get(instr.X)
+
+ case *ssa.ChangeType:
+ fr.env[instr] = fr.get(instr.X) // (can't fail)
+
+ case *ssa.Convert:
+ fr.env[instr] = conv(instr.Type(), instr.X.Type(), fr.get(instr.X))
+
+ case *ssa.MakeInterface:
+ fr.env[instr] = iface{t: instr.X.Type(), v: fr.get(instr.X)}
+
+ case *ssa.Extract:
+ fr.env[instr] = fr.get(instr.Tuple).(tuple)[instr.Index]
+
+ case *ssa.Slice:
+ fr.env[instr] = slice(fr.get(instr.X), fr.get(instr.Low), fr.get(instr.High), fr.get(instr.Max))
+
+ case *ssa.Return:
+ switch len(instr.Results) {
+ case 0:
+ case 1:
+ fr.result = fr.get(instr.Results[0])
+ default:
+ var res []value
+ for _, r := range instr.Results {
+ res = append(res, fr.get(r))
+ }
+ fr.result = tuple(res)
+ }
+ fr.block = nil
+ return kReturn
+
+ case *ssa.RunDefers:
+ fr.runDefers()
+
+ case *ssa.Panic:
+ panic(targetPanic{fr.get(instr.X)})
+
+ case *ssa.Send:
+ fr.get(instr.Chan).(chan value) <- fr.get(instr.X)
+
+ case *ssa.Store:
+ store(deref(instr.Addr.Type()), fr.get(instr.Addr).(*value), fr.get(instr.Val))
+
+ case *ssa.If:
+ succ := 1
+ if fr.get(instr.Cond).(bool) {
+ succ = 0
+ }
+ fr.prevBlock, fr.block = fr.block, fr.block.Succs[succ]
+ return kJump
+
+ case *ssa.Jump:
+ fr.prevBlock, fr.block = fr.block, fr.block.Succs[0]
+ return kJump
+
+ case *ssa.Defer:
+ fn, args := prepareCall(fr, &instr.Call)
+ fr.defers = &deferred{
+ fn: fn,
+ args: args,
+ instr: instr,
+ tail: fr.defers,
+ }
+
+ case *ssa.Go:
+ fn, args := prepareCall(fr, &instr.Call)
+ atomic.AddInt32(&fr.i.goroutines, 1)
+ go func() {
+ call(fr.i, nil, instr.Pos(), fn, args)
+ atomic.AddInt32(&fr.i.goroutines, -1)
+ }()
+
+ case *ssa.MakeChan:
+ fr.env[instr] = make(chan value, asInt(fr.get(instr.Size)))
+
+ case *ssa.Alloc:
+ var addr *value
+ if instr.Heap {
+ // new
+ addr = new(value)
+ fr.env[instr] = addr
+ } else {
+ // local
+ addr = fr.env[instr].(*value)
+ }
+ *addr = zero(deref(instr.Type()))
+
+ case *ssa.MakeSlice:
+ slice := make([]value, asInt(fr.get(instr.Cap)))
+ tElt := instr.Type().Underlying().(*types.Slice).Elem()
+ for i := range slice {
+ slice[i] = zero(tElt)
+ }
+ fr.env[instr] = slice[:asInt(fr.get(instr.Len))]
+
+ case *ssa.MakeMap:
+ reserve := 0
+ if instr.Reserve != nil {
+ reserve = asInt(fr.get(instr.Reserve))
+ }
+ fr.env[instr] = makeMap(instr.Type().Underlying().(*types.Map).Key(), reserve)
+
+ case *ssa.Range:
+ fr.env[instr] = rangeIter(fr.get(instr.X), instr.X.Type())
+
+ case *ssa.Next:
+ fr.env[instr] = fr.get(instr.Iter).(iter).next()
+
+ case *ssa.FieldAddr:
+ fr.env[instr] = &(*fr.get(instr.X).(*value)).(structure)[instr.Field]
+
+ case *ssa.Field:
+ fr.env[instr] = fr.get(instr.X).(structure)[instr.Field]
+
+ case *ssa.IndexAddr:
+ x := fr.get(instr.X)
+ idx := fr.get(instr.Index)
+ switch x := x.(type) {
+ case []value:
+ fr.env[instr] = &x[asInt(idx)]
+ case *value: // *array
+ fr.env[instr] = &(*x).(array)[asInt(idx)]
+ default:
+ panic(fmt.Sprintf("unexpected x type in IndexAddr: %T", x))
+ }
+
+ case *ssa.Index:
+ fr.env[instr] = fr.get(instr.X).(array)[asInt(fr.get(instr.Index))]
+
+ case *ssa.Lookup:
+ fr.env[instr] = lookup(instr, fr.get(instr.X), fr.get(instr.Index))
+
+ case *ssa.MapUpdate:
+ m := fr.get(instr.Map)
+ key := fr.get(instr.Key)
+ v := fr.get(instr.Value)
+ switch m := m.(type) {
+ case map[value]value:
+ m[key] = v
+ case *hashmap:
+ m.insert(key.(hashable), v)
+ default:
+ panic(fmt.Sprintf("illegal map type: %T", m))
+ }
+
+ case *ssa.TypeAssert:
+ fr.env[instr] = typeAssert(fr.i, instr, fr.get(instr.X).(iface))
+
+ case *ssa.MakeClosure:
+ var bindings []value
+ for _, binding := range instr.Bindings {
+ bindings = append(bindings, fr.get(binding))
+ }
+ fr.env[instr] = &closure{instr.Fn.(*ssa.Function), bindings}
+
+ case *ssa.Phi:
+ for i, pred := range instr.Block().Preds {
+ if fr.prevBlock == pred {
+ fr.env[instr] = fr.get(instr.Edges[i])
+ break
+ }
+ }
+
+ case *ssa.Select:
+ var cases []reflect.SelectCase
+ if !instr.Blocking {
+ cases = append(cases, reflect.SelectCase{
+ Dir: reflect.SelectDefault,
+ })
+ }
+ for _, state := range instr.States {
+ var dir reflect.SelectDir
+ if state.Dir == types.RecvOnly {
+ dir = reflect.SelectRecv
+ } else {
+ dir = reflect.SelectSend
+ }
+ var send reflect.Value
+ if state.Send != nil {
+ send = reflect.ValueOf(fr.get(state.Send))
+ }
+ cases = append(cases, reflect.SelectCase{
+ Dir: dir,
+ Chan: reflect.ValueOf(fr.get(state.Chan)),
+ Send: send,
+ })
+ }
+ chosen, recv, recvOk := reflect.Select(cases)
+ if !instr.Blocking {
+ chosen-- // default case should have index -1.
+ }
+ r := tuple{chosen, recvOk}
+ for i, st := range instr.States {
+ if st.Dir == types.RecvOnly {
+ var v value
+ if i == chosen && recvOk {
+ // No need to copy since send makes an unaliased copy.
+ v = recv.Interface().(value)
+ } else {
+ v = zero(st.Chan.Type().Underlying().(*types.Chan).Elem())
+ }
+ r = append(r, v)
+ }
+ }
+ fr.env[instr] = r
+
+ default:
+ panic(fmt.Sprintf("unexpected instruction: %T", instr))
+ }
+
+ // if val, ok := instr.(ssa.Value); ok {
+ // fmt.Println(toString(fr.env[val])) // debugging
+ // }
+
+ return kNext
+}
+
+// prepareCall determines the function value and argument values for a
+// function call in a Call, Go or Defer instruction, performing
+// interface method lookup if needed.
+//
+func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
+ v := fr.get(call.Value)
+ if call.Method == nil {
+ // Function call.
+ fn = v
+ } else {
+ // Interface method invocation.
+ recv := v.(iface)
+ if recv.t == nil {
+ panic("method invoked on nil interface")
+ }
+ if f := lookupMethod(fr.i, recv.t, call.Method); f == nil {
+ // Unreachable in well-typed programs.
+ panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, call.Method))
+ } else {
+ fn = f
+ }
+ args = append(args, recv.v)
+ }
+ for _, arg := range call.Args {
+ args = append(args, fr.get(arg))
+ }
+ return
+}
+
+// call interprets a call to a function (function, builtin or closure)
+// fn with arguments args, returning its result.
+// callpos is the position of the callsite.
+//
+func call(i *interpreter, caller *frame, callpos token.Pos, fn value, args []value) value {
+ switch fn := fn.(type) {
+ case *ssa.Function:
+ if fn == nil {
+ panic("call of nil function") // nil of func type
+ }
+ return callSSA(i, caller, callpos, fn, args, nil)
+ case *closure:
+ return callSSA(i, caller, callpos, fn.Fn, args, fn.Env)
+ case *ssa.Builtin:
+ return callBuiltin(caller, callpos, fn, args)
+ }
+ panic(fmt.Sprintf("cannot call %T", fn))
+}
+
+func loc(fset *token.FileSet, pos token.Pos) string {
+ if pos == token.NoPos {
+ return ""
+ }
+ return " at " + fset.Position(pos).String()
+}
+
+// callSSA interprets a call to function fn with arguments args,
+// and lexical environment env, returning its result.
+// callpos is the position of the callsite.
+//
+func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, args []value, env []value) value {
+ if i.mode&EnableTracing != 0 {
+ fset := fn.Prog.Fset
+ // TODO(adonovan): fix: loc() lies for external functions.
+ fmt.Fprintf(os.Stderr, "Entering %s%s.\n", fn, loc(fset, fn.Pos()))
+ suffix := ""
+ if caller != nil {
+ suffix = ", resuming " + caller.fn.String() + loc(fset, callpos)
+ }
+ defer fmt.Fprintf(os.Stderr, "Leaving %s%s.\n", fn, suffix)
+ }
+ fr := &frame{
+ i: i,
+ caller: caller, // for panic/recover
+ fn: fn,
+ }
+ if fn.Parent() == nil {
+ name := fn.String()
+ if ext := externals[name]; ext != nil {
+ if i.mode&EnableTracing != 0 {
+ fmt.Fprintln(os.Stderr, "\t(external)")
+ }
+ return ext(fr, args)
+ }
+ if fn.Blocks == nil {
+ panic("no code for function: " + name)
+ }
+ }
+ fr.env = make(map[ssa.Value]value)
+ fr.block = fn.Blocks[0]
+ fr.locals = make([]value, len(fn.Locals))
+ for i, l := range fn.Locals {
+ fr.locals[i] = zero(deref(l.Type()))
+ fr.env[l] = &fr.locals[i]
+ }
+ for i, p := range fn.Params {
+ fr.env[p] = args[i]
+ }
+ for i, fv := range fn.FreeVars {
+ fr.env[fv] = env[i]
+ }
+ for fr.block != nil {
+ runFrame(fr)
+ }
+ // Destroy the locals to avoid accidental use after return.
+ for i := range fn.Locals {
+ fr.locals[i] = bad{}
+ }
+ return fr.result
+}
+
+// runFrame executes SSA instructions starting at fr.block and
+// continuing until a return, a panic, or a recovered panic.
+//
+// After a panic, runFrame panics.
+//
+// After a normal return, fr.result contains the result of the call
+// and fr.block is nil.
+//
+// A recovered panic in a function without named return parameters
+// (NRPs) becomes a normal return of the zero value of the function's
+// result type.
+//
+// After a recovered panic in a function with NRPs, fr.result is
+// undefined and fr.block contains the block at which to resume
+// control.
+//
+func runFrame(fr *frame) {
+ defer func() {
+ if fr.block == nil {
+ return // normal return
+ }
+ if fr.i.mode&DisableRecover != 0 {
+ return // let interpreter crash
+ }
+ fr.panicking = true
+ fr.panic = recover()
+ if fr.i.mode&EnableTracing != 0 {
+ fmt.Fprintf(os.Stderr, "Panicking: %T %v.\n", fr.panic, fr.panic)
+ }
+ fr.runDefers()
+ fr.block = fr.fn.Recover
+ }()
+
+ for {
+ if fr.i.mode&EnableTracing != 0 {
+ fmt.Fprintf(os.Stderr, ".%s:\n", fr.block)
+ }
+ block:
+ for _, instr := range fr.block.Instrs {
+ if fr.i.mode&EnableTracing != 0 {
+ if v, ok := instr.(ssa.Value); ok {
+ fmt.Fprintln(os.Stderr, "\t", v.Name(), "=", instr)
+ } else {
+ fmt.Fprintln(os.Stderr, "\t", instr)
+ }
+ }
+ switch visitInstr(fr, instr) {
+ case kReturn:
+ return
+ case kNext:
+ // no-op
+ case kJump:
+ break block
+ }
+ }
+ }
+}
+
+// doRecover implements the recover() built-in.
+func doRecover(caller *frame) value {
+ // recover() must be exactly one level beneath the deferred
+ // function (two levels beneath the panicking function) to
+ // have any effect. Thus we ignore both "defer recover()" and
+ // "defer f() -> g() -> recover()".
+ if caller.i.mode&DisableRecover == 0 &&
+ caller != nil && !caller.panicking &&
+ caller.caller != nil && caller.caller.panicking {
+ caller.caller.panicking = false
+ p := caller.caller.panic
+ caller.caller.panic = nil
+
+ // TODO(adonovan): support runtime.Goexit.
+ switch p := p.(type) {
+ case targetPanic:
+ // The target program explicitly called panic().
+ return p.v
+ case runtime.Error:
+ // The interpreter encountered a runtime error.
+ return iface{caller.i.runtimeErrorString, p.Error()}
+ case string:
+ // The interpreter explicitly called panic().
+ return iface{caller.i.runtimeErrorString, p}
+ default:
+ panic(fmt.Sprintf("unexpected panic type %T in target call to recover()", p))
+ }
+ }
+ return iface{}
+}
+
+// setGlobal sets the value of a system-initialized global variable.
+func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) {
+ if g, ok := i.globals[pkg.Var(name)]; ok {
+ *g = v
+ return
+ }
+ panic("no global variable: " + pkg.Pkg.Path() + "." + name)
+}
+
+// Interpret interprets the Go program whose main package is mainpkg.
+// mode specifies various interpreter options. filename and args are
+// the initial values of os.Args for the target program. sizes is the
+// effective type-sizing function for this program.
+//
+// Interpret returns the exit code of the program: 2 for panic (like
+// gc does), or the argument to os.Exit for normal termination.
+//
+// The SSA program must include the "runtime" package.
+//
+func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) {
+ i := &interpreter{
+ prog: mainpkg.Prog,
+ globals: make(map[ssa.Value]*value),
+ mode: mode,
+ sizes: sizes,
+ goroutines: 1,
+ }
+ runtimePkg := i.prog.ImportedPackage("runtime")
+ if runtimePkg == nil {
+ panic("ssa.Program doesn't include runtime package")
+ }
+ i.runtimeErrorString = runtimePkg.Type("errorString").Object().Type()
+
+ initReflect(i)
+
+ i.osArgs = append(i.osArgs, filename)
+ for _, arg := range args {
+ i.osArgs = append(i.osArgs, arg)
+ }
+
+ for _, pkg := range i.prog.AllPackages() {
+ // Initialize global storage.
+ for _, m := range pkg.Members {
+ switch v := m.(type) {
+ case *ssa.Global:
+ cell := zero(deref(v.Type()))
+ i.globals[v] = &cell
+ }
+ }
+ }
+
+ // Top-level error handler.
+ exitCode = 2
+ defer func() {
+ if exitCode != 2 || i.mode&DisableRecover != 0 {
+ return
+ }
+ switch p := recover().(type) {
+ case exitPanic:
+ exitCode = int(p)
+ return
+ case targetPanic:
+ fmt.Fprintln(os.Stderr, "panic:", toString(p.v))
+ case runtime.Error:
+ fmt.Fprintln(os.Stderr, "panic:", p.Error())
+ case string:
+ fmt.Fprintln(os.Stderr, "panic:", p)
+ default:
+ fmt.Fprintf(os.Stderr, "panic: unexpected type: %T: %v\n", p, p)
+ }
+
+ // TODO(adonovan): dump panicking interpreter goroutine?
+ // buf := make([]byte, 0x10000)
+ // runtime.Stack(buf, false)
+ // fmt.Fprintln(os.Stderr, string(buf))
+ // (Or dump panicking target goroutine?)
+ }()
+
+ // Run!
+ call(i, nil, token.NoPos, mainpkg.Func("init"), nil)
+ if mainFn := mainpkg.Func("main"); mainFn != nil {
+ call(i, nil, token.NoPos, mainFn, nil)
+ exitCode = 0
+ } else {
+ fmt.Fprintln(os.Stderr, "No main function.")
+ exitCode = 1
+ }
+ return
+}
+
+// deref returns a pointer's element type; otherwise it returns typ.
+// TODO(adonovan): Import from ssa?
+func deref(typ types.Type) types.Type {
+ if p, ok := typ.Underlying().(*types.Pointer); ok {
+ return p.Elem()
+ }
+ return typ
+}