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:
parent
fbb3d81367
commit
4dcb74e810
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -143,6 +143,7 @@ var testdataTests = []string{
|
|||
"methprom.go",
|
||||
"mrvchain.go",
|
||||
"recover.go",
|
||||
"callstack.go",
|
||||
}
|
||||
|
||||
// These are files in $GOROOT/src/pkg/.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue