go.tools/go/ssa/interp: implement reflection over callstack (now that we have access to it).

+ tests.

Fixes golang/go#6041

Also: move global var _sizes to a field of interpreter.

LGTM=gri
R=gri
CC=golang-codereviews
https://golang.org/cl/56530046
This commit is contained in:
Alan Donovan 2014-01-27 15:39:17 -05:00
parent fbb3d81367
commit 4dcb74e810
5 changed files with 137 additions and 39 deletions

View File

@ -13,6 +13,9 @@ import (
"runtime"
"syscall"
"time"
"unsafe"
"code.google.com/p/go.tools/go/ssa"
)
type externalFn func(fr *frame, args []value) value
@ -27,9 +30,6 @@ var externals map[string]externalFn
func init() {
// That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd].
externals = map[string]externalFn{
"(*runtime.Func).Entry": ext۰runtime۰Func۰Entry,
"(*runtime.Func).FileLine": ext۰runtime۰Func۰FileLine,
"(*runtime.Func).Name": ext۰runtime۰Func۰Name,
"(*sync.Pool).Get": ext۰sync۰Pool۰Get,
"(*sync.Pool).Put": ext۰sync۰Pool۰Put,
"(reflect.Value).Bool": ext۰reflect۰Value۰Bool,
@ -82,6 +82,7 @@ func init() {
"reflect.valueInterface": ext۰reflect۰valueInterface,
"runtime.Breakpoint": ext۰runtime۰Breakpoint,
"runtime.Caller": ext۰runtime۰Caller,
"runtime.Callers": ext۰runtime۰Callers,
"runtime.FuncForPC": ext۰runtime۰FuncForPC,
"runtime.GC": ext۰runtime۰GC,
"runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS,
@ -89,10 +90,12 @@ func init() {
"runtime.NumCPU": ext۰runtime۰NumCPU,
"runtime.ReadMemStats": ext۰runtime۰ReadMemStats,
"runtime.SetFinalizer": ext۰runtime۰SetFinalizer,
"runtime.funcentry_go": ext۰runtime۰funcentry_go,
"runtime.funcline_go": ext۰runtime۰funcline_go,
"runtime.funcname_go": ext۰runtime۰funcname_go,
"runtime.getgoroot": ext۰runtime۰getgoroot,
"strings.IndexByte": ext۰strings۰IndexByte,
"sync.runtime_Syncsemcheck": ext۰sync۰runtime_Syncsemcheck,
"sync.runtime_registerPool": ext۰sync۰runtime_registerPool,
"sync/atomic.AddInt32": ext۰atomic۰AddInt32,
"sync/atomic.CompareAndSwapInt32": ext۰atomic۰CompareAndSwapInt32,
"sync/atomic.LoadInt32": ext۰atomic۰LoadInt32,
@ -126,18 +129,6 @@ func wrapError(err error) value {
return iface{t: errorType, v: err.Error()}
}
func ext۰runtime۰Func۰Entry(fr *frame, args []value) value {
return 0
}
func ext۰runtime۰Func۰FileLine(fr *frame, args []value) value {
return tuple{"unknown.go", -1}
}
func ext۰runtime۰Func۰Name(fr *frame, args []value) value {
return "unknown"
}
func ext۰sync۰Pool۰Get(fr *frame, args []value) value {
if New := (*args[0].(*value)).(structure)[4]; New != nil {
return call(fr.i, fr, 0, New, nil)
@ -214,16 +205,57 @@ func ext۰runtime۰Breakpoint(fr *frame, args []value) value {
}
func ext۰runtime۰Caller(fr *frame, args []value) value {
// TODO(adonovan): actually inspect the stack.
return tuple{0, "somefile.go", 42, true}
// func Caller(skip int) (pc uintptr, file string, line int, ok bool)
skip := 1 + args[0].(int)
for i := 0; i < skip; i++ {
if fr != nil {
fr = fr.caller
}
}
var pc uintptr
var file string
var line int
var ok bool
if fr != nil {
fn := fr.fn
// TODO(adonovan): use pc/posn of current instruction, not start of fn.
pc = uintptr(unsafe.Pointer(fn))
posn := fn.Prog.Fset.Position(fn.Pos())
file = posn.Filename
line = posn.Line
ok = true
}
return tuple{pc, file, line, ok}
}
func ext۰runtime۰Callers(fr *frame, args []value) value {
// Callers(skip int, pc []uintptr) int
skip := args[0].(int)
pc := args[1].([]value)
for i := 0; i < skip; i++ {
if fr != nil {
fr = fr.caller
}
}
i := 0
for fr != nil {
pc[i] = uintptr(unsafe.Pointer(fr.fn))
i++
fr = fr.caller
}
return i
}
func ext۰runtime۰FuncForPC(fr *frame, args []value) value {
// TODO(adonovan): actually inspect the stack.
return (*value)(nil)
//tuple{0, "somefile.go", 42, true}
//
//func FuncForPC(pc uintptr) *Func
// FuncForPC(pc uintptr) *Func
pc := args[0].(uintptr)
var fn *ssa.Function
if pc != 0 {
fn = (*ssa.Function)(unsafe.Pointer(pc)) // indeed unsafe!
}
var Func value
Func = structure{fn} // a runtime.Func
return &Func
}
func ext۰runtime۰getgoroot(fr *frame, args []value) value {
@ -242,10 +274,6 @@ func ext۰strings۰IndexByte(fr *frame, args []value) value {
return -1
}
func ext۰sync۰runtime_registerPool(fr *frame, args []value) value {
return nil
}
func ext۰sync۰runtime_Syncsemcheck(fr *frame, args []value) value {
return nil
}
@ -317,12 +345,32 @@ func ext۰runtime۰SetFinalizer(fr *frame, args []value) value {
return nil // ignore
}
func ext۰runtime۰funcline_go(fr *frame, args []value) value {
// func funcline_go(*Func, uintptr) (string, int)
f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function)
pc := args[1].(uintptr)
_ = pc
if f != nil {
// TODO(adonovan): use position of current instruction, not fn.
posn := f.Prog.Fset.Position(f.Pos())
return tuple{posn.Filename, posn.Line}
}
return tuple{"", 0}
}
func ext۰runtime۰funcname_go(fr *frame, args []value) value {
// TODO(adonovan): actually inspect the stack.
return (*value)(nil)
//tuple{0, "somefile.go", 42, true}
//
//func FuncForPC(pc uintptr) *Func
// func funcname_go(*Func) string
f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function)
if f != nil {
return f.String()
}
return ""
}
func ext۰runtime۰funcentry_go(fr *frame, args []value) value {
// func funcentry_go(*Func) uintptr
f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function)
return uintptr(unsafe.Pointer(f))
}
func ext۰time۰now(fr *frame, args []value) value {

View File

@ -82,6 +82,7 @@ type interpreter struct {
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
}
type deferred struct {
@ -622,10 +623,6 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) {
panic("no global variable: " + pkg.Object.Path() + "." + name)
}
// _sizes is the effective type-sizing function.
// TODO(adonovan): avoid global state.
var _sizes types.Sizes
// 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
@ -637,11 +634,11 @@ var _sizes types.Sizes
// The SSA program must include the "runtime" package.
//
func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) {
_sizes = sizes
i := &interpreter{
prog: mainpkg.Prog,
globals: make(map[ssa.Value]*value),
mode: mode,
sizes: sizes,
}
runtimePkg := i.prog.ImportedPackage("runtime")
if runtimePkg == nil {

View File

@ -143,6 +143,7 @@ var testdataTests = []string{
"methprom.go",
"mrvchain.go",
"recover.go",
"callstack.go",
}
// These are files in $GOROOT/src/pkg/.

View File

@ -81,7 +81,7 @@ func ext۰reflect۰rtype۰Bits(fr *frame, args []value) value {
if !ok {
panic(fmt.Sprintf("reflect.Type.Bits(%T): non-basic type", rt))
}
return _sizes.Sizeof(basic) * 8
return fr.i.sizes.Sizeof(basic) * 8
}
func ext۰reflect۰rtype۰Elem(fr *frame, args []value) value {
@ -135,7 +135,7 @@ func ext۰reflect۰rtype۰Out(fr *frame, args []value) value {
func ext۰reflect۰rtype۰Size(fr *frame, args []value) value {
// Signature: func (t reflect.rtype) uintptr
return uint64(_sizes.Sizeof(args[0].(rtype).t))
return uint64(fr.i.sizes.Sizeof(args[0].(rtype).t))
}
func ext۰reflect۰rtype۰String(fr *frame, args []value) value {

52
go/ssa/interp/testdata/callstack.go vendored Normal file
View File

@ -0,0 +1,52 @@
package main
import (
"fmt"
"path"
"runtime"
"strings"
)
var stack string
func f() {
pc := make([]uintptr, 6)
pc = pc[:runtime.Callers(1, pc)]
for _, f := range pc {
Func := runtime.FuncForPC(f)
name := Func.Name()
if strings.Contains(name, "func") {
name = "func" // anon funcs vary across toolchains
}
file, line := Func.FileLine(0)
stack += fmt.Sprintf("%s at %s:%d\n", name, path.Base(file), line)
}
}
func g() { f() }
func h() { g() }
func i() { func() { h() }() }
// Hack: the 'func' and the call to Caller are on the same line,
// to paper over differences between toolchains.
// (The interpreter's location info isn't yet complete.)
func runtimeCaller0() (uintptr, string, int, bool) { return runtime.Caller(0) }
func main() {
i()
if stack != `main.f at callstack.go:12
main.g at callstack.go:26
main.h at callstack.go:27
func at callstack.go:28
main.i at callstack.go:28
main.main at callstack.go:35
` {
panic("unexpected stack: " + stack)
}
pc, file, line, _ := runtimeCaller0()
got := fmt.Sprintf("%s @ %s:%d", runtime.FuncForPC(pc).Name(), path.Base(file), line)
if got != "main.runtimeCaller0 @ callstack.go:33" {
panic("runtime.Caller: " + got)
}
}