From e779aa49e359eb9a328069406fafdb0d559c205d Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 22 Mar 2019 11:32:16 -0400 Subject: [PATCH] go/ssa/interp: make tests fast and robust The go/ssa/interp tests have been a maintenance nightmare for years because the interpreter requires intrinsics for all low-level or non-Go code functions, and the set of such functions in the standard library naturally changes from day to day. This CL finally drops support for interpreting real packages (which has anyway been broken for ages) and restricts the test suite to small programs that use a handful of simple functions in packages bytes, strings, errors, runtime, reflect, and unicode. These functions are declared in a tiny fake standard libary in testdata/src, and the implementations of these functions are provided by interpreter intrinsics that delegate to the real Go implementation---all their parameters and results are basic datatypes. The test suite is now very fast and should be easy to maintain going forward. It is still possible that a change to some file in $GOROOT/test/*.go adds a dependency to a symbol not present in our standard library, but this is rare. I will either delete the test or add the intrinsic on a case-by-case basis. We no longer attempt to interpret major functionality like fmt.Sprintf or "testing". The interpreter always pretends to be in linux/amd64 mode. Happy Christmas, Brad. ;) Fixes golang/go#27292 Change-Id: I715cf63e3534e2e0dab4666a5d7c669bf1d92674 Reviewed-on: https://go-review.googlesource.com/c/tools/+/168898 Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- go/ssa/interp/external.go | 534 +++++------------- go/ssa/interp/external_darwin.go | 35 -- go/ssa/interp/external_unix.go | 256 --------- go/ssa/interp/interp.go | 43 -- go/ssa/interp/interp_test.go | 175 ++---- go/ssa/interp/ops.go | 14 +- go/ssa/interp/reflect.go | 5 - go/ssa/interp/testdata/a_test.go | 17 - go/ssa/interp/testdata/b_test.go | 11 - go/ssa/interp/testdata/boundmeth.go | 9 +- go/ssa/interp/testdata/c_test.go | 17 - go/ssa/interp/testdata/callstack.go | 52 -- go/ssa/interp/testdata/coverage.go | 7 +- go/ssa/interp/testdata/range.go | 2 +- go/ssa/interp/testdata/src/errors/errors.go | 7 + go/ssa/interp/testdata/src/fmt/fmt.go | 26 + go/ssa/interp/testdata/src/math/math.go | 13 + go/ssa/interp/testdata/src/os/os.go | 5 + go/ssa/interp/testdata/src/reflect/reflect.go | 16 + go/ssa/interp/testdata/src/runtime/runtime.go | 22 + go/ssa/interp/testdata/src/strings/strings.go | 9 + go/ssa/interp/testdata/src/time/time.go | 5 + .../interp/testdata/src/unicode/utf8/utf8.go | 9 + go/ssa/interp/testdata/src/unsafe/unsafe.go | 1 + 24 files changed, 304 insertions(+), 986 deletions(-) delete mode 100644 go/ssa/interp/external_darwin.go delete mode 100644 go/ssa/interp/external_unix.go delete mode 100644 go/ssa/interp/testdata/a_test.go delete mode 100644 go/ssa/interp/testdata/b_test.go delete mode 100644 go/ssa/interp/testdata/c_test.go delete mode 100644 go/ssa/interp/testdata/callstack.go create mode 100644 go/ssa/interp/testdata/src/errors/errors.go create mode 100644 go/ssa/interp/testdata/src/fmt/fmt.go create mode 100644 go/ssa/interp/testdata/src/math/math.go create mode 100644 go/ssa/interp/testdata/src/os/os.go create mode 100644 go/ssa/interp/testdata/src/reflect/reflect.go create mode 100644 go/ssa/interp/testdata/src/runtime/runtime.go create mode 100644 go/ssa/interp/testdata/src/strings/strings.go create mode 100644 go/ssa/interp/testdata/src/time/time.go create mode 100644 go/ssa/interp/testdata/src/unicode/utf8/utf8.go create mode 100644 go/ssa/interp/testdata/src/unsafe/unsafe.go diff --git a/go/ssa/interp/external.go b/go/ssa/interp/external.go index 2c4dcc76dc4..735a14d9ce7 100644 --- a/go/ssa/interp/external.go +++ b/go/ssa/interp/external.go @@ -8,16 +8,13 @@ package interp // external or because they use "unsafe" or "reflect" operations. import ( - "go/types" + "bytes" "math" "os" "runtime" "strings" - "sync/atomic" "time" - "unsafe" - - "golang.org/x/tools/go/ssa" + "unicode/utf8" ) type externalFn func(fr *frame, args []value) value @@ -32,144 +29,81 @@ var externals = make(map[string]externalFn) func init() { // That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd]. for k, v := range map[string]externalFn{ - "(*sync.Pool).Get": ext۰sync۰Pool۰Get, - "(*sync.Pool).Put": ext۰nop, - "(reflect.Value).Bool": ext۰reflect۰Value۰Bool, - "(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr, - "(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface, - "(reflect.Value).Elem": ext۰reflect۰Value۰Elem, - "(reflect.Value).Field": ext۰reflect۰Value۰Field, - "(reflect.Value).Float": ext۰reflect۰Value۰Float, - "(reflect.Value).Index": ext۰reflect۰Value۰Index, - "(reflect.Value).Int": ext۰reflect۰Value۰Int, - "(reflect.Value).Interface": ext۰reflect۰Value۰Interface, - "(reflect.Value).IsNil": ext۰reflect۰Value۰IsNil, - "(reflect.Value).IsValid": ext۰reflect۰Value۰IsValid, - "(reflect.Value).Kind": ext۰reflect۰Value۰Kind, - "(reflect.Value).Len": ext۰reflect۰Value۰Len, - "(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex, - "(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys, - "(reflect.Value).NumField": ext۰reflect۰Value۰NumField, - "(reflect.Value).NumMethod": ext۰reflect۰Value۰NumMethod, - "(reflect.Value).Pointer": ext۰reflect۰Value۰Pointer, - "(reflect.Value).Set": ext۰reflect۰Value۰Set, - "(reflect.Value).String": ext۰reflect۰Value۰String, - "(reflect.Value).Type": ext۰reflect۰Value۰Type, - "(reflect.Value).Uint": ext۰reflect۰Value۰Uint, - "(reflect.error).Error": ext۰reflect۰error۰Error, - "(reflect.rtype).Bits": ext۰reflect۰rtype۰Bits, - "(reflect.rtype).Elem": ext۰reflect۰rtype۰Elem, - "(reflect.rtype).Field": ext۰reflect۰rtype۰Field, - "(reflect.rtype).In": ext۰reflect۰rtype۰In, - "(reflect.rtype).Kind": ext۰reflect۰rtype۰Kind, - "(reflect.rtype).NumField": ext۰reflect۰rtype۰NumField, - "(reflect.rtype).NumIn": ext۰reflect۰rtype۰NumIn, - "(reflect.rtype).NumMethod": ext۰reflect۰rtype۰NumMethod, - "(reflect.rtype).NumOut": ext۰reflect۰rtype۰NumOut, - "(reflect.rtype).Out": ext۰reflect۰rtype۰Out, - "(reflect.rtype).Size": ext۰reflect۰rtype۰Size, - "(reflect.rtype).String": ext۰reflect۰rtype۰String, - "bytes.init": ext۰nop, // avoid asm dependency - "bytes.Equal": ext۰bytes۰Equal, - "bytes.IndexByte": ext۰bytes۰IndexByte, - "hash/crc32.haveSSE42": ext۰crc32۰haveSSE42, - "internal/cpu.cpuid": ext۰cpu۰cpuid, - "internal/syscall/unix.syscall_fcntl": ext۰syscall۰unix۰syscall_fcntl, - "math.Abs": ext۰math۰Abs, - "math.Exp": ext۰math۰Exp, - "math.Float32bits": ext۰math۰Float32bits, - "math.Float32frombits": ext۰math۰Float32frombits, - "math.Float64bits": ext۰math۰Float64bits, - "math.Float64frombits": ext۰math۰Float64frombits, - "math.Ldexp": ext۰math۰Ldexp, - "math.Log": ext۰math۰Log, - "math.Min": ext۰math۰Min, - "math.hasSSE4": ext۰math۰hasSSE4, - "math.hasVectorFacility": ext۰math۰hasVectorFacility, - "os.runtime_args": ext۰os۰runtime_args, - "os.runtime_beforeExit": ext۰nop, - "os/signal.init": ext۰nop, - "reflect.New": ext۰reflect۰New, - "reflect.SliceOf": ext۰reflect۰SliceOf, - "reflect.TypeOf": ext۰reflect۰TypeOf, - "reflect.ValueOf": ext۰reflect۰ValueOf, - "reflect.Zero": ext۰reflect۰Zero, - "reflect.init": ext۰reflect۰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, - "runtime.Goexit": ext۰runtime۰Goexit, - "runtime.Gosched": ext۰runtime۰Gosched, - "runtime.init": ext۰nop, - "runtime.KeepAlive": ext۰nop, - "runtime.NumCPU": ext۰runtime۰NumCPU, - "runtime.NumGoroutine": ext۰runtime۰NumGoroutine, - "runtime.ReadMemStats": ext۰runtime۰ReadMemStats, - "runtime.SetFinalizer": ext۰nop, // ignore - "(*runtime.Func).Entry": ext۰runtime۰Func۰Entry, - "(*runtime.Func).FileLine": ext۰runtime۰Func۰FileLine, - "(*runtime.Func).Name": ext۰runtime۰Func۰Name, - "runtime.environ": ext۰runtime۰environ, - "runtime.getgoroot": ext۰runtime۰getgoroot, - "strings.init": ext۰nop, // avoid asm dependency - "strings.Count": ext۰strings۰Count, - "strings.Index": ext۰strings۰Index, - "strings.IndexByte": ext۰strings۰IndexByte, - "sync.runtime_Semacquire": ext۰nop, // unimplementable - "sync.runtime_Semrelease": ext۰nop, // unimplementable - "sync.runtime_Syncsemcheck": ext۰nop, // unimplementable - "sync.runtime_notifyListCheck": ext۰nop, - "sync.runtime_registerPoolCleanup": ext۰nop, - "sync/atomic.AddInt32": ext۰atomic۰AddInt32, - "sync/atomic.AddUint32": ext۰atomic۰AddUint32, - "sync/atomic.CompareAndSwapInt32": ext۰atomic۰CompareAndSwapInt32, - "sync/atomic.CompareAndSwapUint32": ext۰atomic۰CompareAndSwapUint32, - "sync/atomic.LoadInt32": ext۰atomic۰LoadInt32, - "sync/atomic.LoadUint32": ext۰atomic۰LoadUint32, - "sync/atomic.StoreInt32": ext۰atomic۰StoreInt32, - "sync/atomic.StoreUint32": ext۰atomic۰StoreUint32, - "sync/atomic.AddInt64": ext۰atomic۰AddInt64, - "sync/atomic.AddUint64": ext۰atomic۰AddUint64, - "sync/atomic.CompareAndSwapInt64": ext۰atomic۰CompareAndSwapInt64, - "sync/atomic.CompareAndSwapUint64": ext۰atomic۰CompareAndSwapUint64, - "sync/atomic.LoadInt64": ext۰atomic۰LoadInt64, - "sync/atomic.LoadUint64": ext۰atomic۰LoadUint64, - "sync/atomic.StoreInt64": ext۰atomic۰StoreInt64, - "sync/atomic.StoreUint64": ext۰atomic۰StoreUint64, - "(*sync/atomic.Value).Load": ext۰atomic۰ValueLoad, - "(*sync/atomic.Value).Store": ext۰atomic۰ValueStore, - "testing.MainStart": ext۰testing۰MainStart, - "time.Sleep": ext۰time۰Sleep, - "time.now": ext۰time۰now, + "(reflect.Value).Bool": ext۰reflect۰Value۰Bool, + "(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr, + "(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface, + "(reflect.Value).Elem": ext۰reflect۰Value۰Elem, + "(reflect.Value).Field": ext۰reflect۰Value۰Field, + "(reflect.Value).Float": ext۰reflect۰Value۰Float, + "(reflect.Value).Index": ext۰reflect۰Value۰Index, + "(reflect.Value).Int": ext۰reflect۰Value۰Int, + "(reflect.Value).Interface": ext۰reflect۰Value۰Interface, + "(reflect.Value).IsNil": ext۰reflect۰Value۰IsNil, + "(reflect.Value).IsValid": ext۰reflect۰Value۰IsValid, + "(reflect.Value).Kind": ext۰reflect۰Value۰Kind, + "(reflect.Value).Len": ext۰reflect۰Value۰Len, + "(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex, + "(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys, + "(reflect.Value).NumField": ext۰reflect۰Value۰NumField, + "(reflect.Value).NumMethod": ext۰reflect۰Value۰NumMethod, + "(reflect.Value).Pointer": ext۰reflect۰Value۰Pointer, + "(reflect.Value).Set": ext۰reflect۰Value۰Set, + "(reflect.Value).String": ext۰reflect۰Value۰String, + "(reflect.Value).Type": ext۰reflect۰Value۰Type, + "(reflect.Value).Uint": ext۰reflect۰Value۰Uint, + "(reflect.error).Error": ext۰reflect۰error۰Error, + "(reflect.rtype).Bits": ext۰reflect۰rtype۰Bits, + "(reflect.rtype).Elem": ext۰reflect۰rtype۰Elem, + "(reflect.rtype).Field": ext۰reflect۰rtype۰Field, + "(reflect.rtype).In": ext۰reflect۰rtype۰In, + "(reflect.rtype).Kind": ext۰reflect۰rtype۰Kind, + "(reflect.rtype).NumField": ext۰reflect۰rtype۰NumField, + "(reflect.rtype).NumIn": ext۰reflect۰rtype۰NumIn, + "(reflect.rtype).NumMethod": ext۰reflect۰rtype۰NumMethod, + "(reflect.rtype).NumOut": ext۰reflect۰rtype۰NumOut, + "(reflect.rtype).Out": ext۰reflect۰rtype۰Out, + "(reflect.rtype).Size": ext۰reflect۰rtype۰Size, + "(reflect.rtype).String": ext۰reflect۰rtype۰String, + "bytes.Equal": ext۰bytes۰Equal, + "bytes.IndexByte": ext۰bytes۰IndexByte, + "fmt.Sprint": ext۰fmt۰Sprint, + "math.Abs": ext۰math۰Abs, + "math.Exp": ext۰math۰Exp, + "math.Float32bits": ext۰math۰Float32bits, + "math.Float32frombits": ext۰math۰Float32frombits, + "math.Float64bits": ext۰math۰Float64bits, + "math.Float64frombits": ext۰math۰Float64frombits, + "math.Inf": ext۰math۰Inf, + "math.IsNaN": ext۰math۰IsNaN, + "math.Ldexp": ext۰math۰Ldexp, + "math.Log": ext۰math۰Log, + "math.Min": ext۰math۰Min, + "math.NaN": ext۰math۰NaN, + "os.Exit": ext۰os۰Exit, + "os.Getenv": ext۰os۰Getenv, + "reflect.New": ext۰reflect۰New, + "reflect.SliceOf": ext۰reflect۰SliceOf, + "reflect.TypeOf": ext۰reflect۰TypeOf, + "reflect.ValueOf": ext۰reflect۰ValueOf, + "reflect.Zero": ext۰reflect۰Zero, + "runtime.Breakpoint": ext۰runtime۰Breakpoint, + "runtime.GC": ext۰runtime۰GC, + "runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS, + "runtime.GOROOT": ext۰runtime۰GOROOT, + "runtime.Goexit": ext۰runtime۰Goexit, + "runtime.Gosched": ext۰runtime۰Gosched, + "runtime.NumCPU": ext۰runtime۰NumCPU, + "strings.Count": ext۰strings۰Count, + "strings.Index": ext۰strings۰Index, + "strings.IndexByte": ext۰strings۰IndexByte, + "strings.Replace": ext۰strings۰Replace, + "time.Sleep": ext۰time۰Sleep, + "unicode/utf8.DecodeRuneInString": ext۰unicode۰utf8۰DecodeRuneInString, } { externals[k] = v } } -// wrapError returns an interpreted 'error' interface value for err. -func wrapError(err error) value { - if err == nil { - return iface{} - } - return iface{t: errorType, v: err.Error()} -} - -func ext۰nop(fr *frame, args []value) value { return nil } - -func ext۰sync۰Pool۰Get(fr *frame, args []value) value { - Pool := fr.i.prog.ImportedPackage("sync").Type("Pool").Object() - _, newIndex, _ := types.LookupFieldOrMethod(Pool.Type(), false, Pool.Pkg(), "New") - - if New := (*args[0].(*value)).(structure)[newIndex[0]]; New != nil { - return call(fr.i, fr, 0, New, nil) - } - return nil -} - func ext۰bytes۰Equal(fr *frame, args []value) value { // func Equal(a, b []byte) bool a := args[0].([]value) @@ -197,10 +131,6 @@ func ext۰bytes۰IndexByte(fr *frame, args []value) value { return -1 } -func ext۰crc32۰haveSSE42(fr *frame, args []value) value { - return false -} - func ext۰math۰Float64frombits(fr *frame, args []value) value { return math.Float64frombits(args[0].(uint64)) } @@ -229,12 +159,16 @@ func ext۰math۰Min(fr *frame, args []value) value { return math.Min(args[0].(float64), args[1].(float64)) } -func ext۰math۰hasSSE4(fr *frame, args []value) value { - return false +func ext۰math۰NaN(fr *frame, args []value) value { + return math.NaN() } -func ext۰math۰hasVectorFacility(fr *frame, args []value) value { - return false +func ext۰math۰IsNaN(fr *frame, args []value) value { + return math.IsNaN(args[0].(float64)) +} + +func ext۰math۰Inf(fr *frame, args []value) value { + return math.Inf(args[0].(int)) } func ext۰math۰Ldexp(fr *frame, args []value) value { @@ -245,94 +179,32 @@ func ext۰math۰Log(fr *frame, args []value) value { return math.Log(args[0].(float64)) } -func ext۰os۰runtime_args(fr *frame, args []value) value { - return fr.i.osArgs -} - func ext۰runtime۰Breakpoint(fr *frame, args []value) value { runtime.Breakpoint() return nil } -func ext۰runtime۰Caller(fr *frame, args []value) value { - // 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. - // (Required to interpret the log package's tests.) - 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 && i < len(pc) { - pc[i] = uintptr(unsafe.Pointer(fr.fn)) - i++ - fr = fr.caller - } - return i -} - -func ext۰runtime۰FuncForPC(fr *frame, args []value) value { - // 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۰environ(fr *frame, args []value) value { - // This function also implements syscall.runtime_envs. - return environ -} - -func ext۰runtime۰getgoroot(fr *frame, args []value) value { - return os.Getenv("GOROOT") -} - func ext۰strings۰Count(fr *frame, args []value) value { - // Call compiled version to avoid asm dependency. return strings.Count(args[0].(string), args[1].(string)) } func ext۰strings۰IndexByte(fr *frame, args []value) value { - // Call compiled version to avoid asm dependency. return strings.IndexByte(args[0].(string), args[1].(byte)) } func ext۰strings۰Index(fr *frame, args []value) value { - // Call compiled version to avoid asm dependency. return strings.Index(args[0].(string), args[1].(string)) } +func ext۰strings۰Replace(fr *frame, args []value) value { + // func Replace(s, old, new string, n int) string + s := args[0].(string) + new := args[1].(string) + old := args[2].(string) + n := args[3].(int) + return strings.Replace(s, old, new, n) +} + func ext۰runtime۰GOMAXPROCS(fr *frame, args []value) value { // Ignore args[0]; don't let the interpreted program // set the interpreter's GOMAXPROCS! @@ -345,6 +217,10 @@ func ext۰runtime۰Goexit(fr *frame, args []value) value { return nil } +func ext۰runtime۰GOROOT(fr *frame, args []value) value { + return runtime.GOROOT() +} + func ext۰runtime۰GC(fr *frame, args []value) value { runtime.GC() return nil @@ -359,187 +235,6 @@ func ext۰runtime۰NumCPU(fr *frame, args []value) value { return runtime.NumCPU() } -func ext۰runtime۰NumGoroutine(fr *frame, args []value) value { - return int(atomic.LoadInt32(&fr.i.goroutines)) -} - -func ext۰runtime۰ReadMemStats(fr *frame, args []value) value { - // TODO(adonovan): populate args[0].(Struct) - return nil -} - -func ext۰atomic۰LoadUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(uint32) -} - -func ext۰atomic۰StoreUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(uint32) - return nil -} - -func ext۰atomic۰LoadInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(int32) -} - -func ext۰atomic۰StoreInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(int32) - return nil -} - -func ext۰atomic۰CompareAndSwapInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(int32) == args[1].(int32) { - *p = args[2].(int32) - return true - } - return false -} - -func ext۰atomic۰CompareAndSwapUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(uint32) == args[1].(uint32) { - *p = args[2].(uint32) - return true - } - return false -} - -func ext۰atomic۰AddInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(int32) + args[1].(int32) - *p = newv - return newv -} - -func ext۰atomic۰AddUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(uint32) + args[1].(uint32) - *p = newv - return newv -} - -func ext۰atomic۰LoadUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(uint64) -} - -func ext۰atomic۰StoreUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(uint64) - return nil -} - -func ext۰atomic۰LoadInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(int64) -} - -func ext۰atomic۰StoreInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(int64) - return nil -} - -func ext۰atomic۰CompareAndSwapInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(int64) == args[1].(int64) { - *p = args[2].(int64) - return true - } - return false -} - -func ext۰atomic۰CompareAndSwapUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(uint64) == args[1].(uint64) { - *p = args[2].(uint64) - return true - } - return false -} - -func ext۰atomic۰AddInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(int64) + args[1].(int64) - *p = newv - return newv -} - -func ext۰atomic۰AddUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(uint64) + args[1].(uint64) - *p = newv - return newv -} - -func ext۰atomic۰ValueLoad(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - // Receiver is *struct{v interface{}}. - return (*args[0].(*value)).(structure)[0] -} - -func ext۰atomic۰ValueStore(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - // Receiver is *struct{v interface{}}. - (*args[0].(*value)).(structure)[0] = args[1] - return nil -} - -func ext۰cpu۰cpuid(fr *frame, args []value) value { - return tuple{uint32(0), uint32(0), uint32(0), uint32(0)} -} - -func ext۰syscall۰unix۰syscall_fcntl(fr *frame, args []value) value { - return tuple{int(0), wrapError(nil)} -} - -// Pretend: type runtime.Func struct { entry *ssa.Function } - -func ext۰runtime۰Func۰FileLine(fr *frame, args []value) value { - // func (*runtime.Func) FileLine(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۰Func۰Name(fr *frame, args []value) value { - // func (*runtime.Func) Name() string - f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function) - if f != nil { - return f.String() - } - return "" -} - -func ext۰runtime۰Func۰Entry(fr *frame, args []value) value { - // func (*runtime.Func) Entry() uintptr - f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function) - return uintptr(unsafe.Pointer(f)) -} - -func ext۰time۰now(fr *frame, args []value) value { - nano := time.Now().UnixNano() - return tuple{int64(nano / 1e9), int32(nano % 1e9), int64(0)} -} - func ext۰time۰Sleep(fr *frame, args []value) value { time.Sleep(time.Duration(args[0].(int64))) return nil @@ -554,9 +249,42 @@ func valueToBytes(v value) []byte { return b } -func ext۰testing۰MainStart(fr *frame, args []value) value { - // We no longer support interpretation of the "testing" package - // because it changes too often and uses low-level features that - // are a pain to emulate. - panic(`interpretation of the "testing" package is no longer supported`) +func ext۰os۰Getenv(fr *frame, args []value) value { + name := args[0].(string) + switch name { + case "GOSSAINTERP": + return "1" + case "GOARCH": + return "amd64" + case "GOOS": + return "linux" + } + return os.Getenv(name) +} + +func ext۰os۰Exit(fr *frame, args []value) value { + panic(exitPanic(args[0].(int))) +} + +func ext۰unicode۰utf8۰DecodeRuneInString(fr *frame, args []value) value { + r, n := utf8.DecodeRuneInString(args[0].(string)) + return tuple{r, n} +} + +// A fake function for turning an arbitrary value into a string. +// Handles only the cases needed by the tests. +// Uses same logic as 'print' built-in. +func ext۰fmt۰Sprint(fr *frame, args []value) value { + buf := new(bytes.Buffer) + wasStr := false + for i, arg := range args[0].([]value) { + x := arg.(iface).v + _, isStr := x.(string) + if i > 0 && !wasStr && !isStr { + buf.WriteByte(' ') + } + wasStr = isStr + buf.WriteString(toString(x)) + } + return buf.String() } diff --git a/go/ssa/interp/external_darwin.go b/go/ssa/interp/external_darwin.go deleted file mode 100644 index 4713009306b..00000000000 --- a/go/ssa/interp/external_darwin.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2014 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. - -// +build darwin - -package interp - -import "syscall" - -func init() { - externals["syscall.Sysctl"] = ext۰syscall۰Sysctl - - fillStat = func(st *syscall.Stat_t, stat structure) { - stat[0] = st.Dev - stat[1] = st.Mode - stat[2] = st.Nlink - stat[3] = st.Ino - stat[4] = st.Uid - stat[5] = st.Gid - stat[6] = st.Rdev - // TODO(adonovan): fix: copy Timespecs. - // stat[8] = st.Atim - // stat[9] = st.Mtim - // stat[10] = st.Ctim - stat[12] = st.Size - stat[13] = st.Blocks - stat[14] = st.Blksize - } -} - -func ext۰syscall۰Sysctl(fr *frame, args []value) value { - r, err := syscall.Sysctl(args[0].(string)) - return tuple{r, wrapError(err)} -} diff --git a/go/ssa/interp/external_unix.go b/go/ssa/interp/external_unix.go deleted file mode 100644 index bfa39f68a2e..00000000000 --- a/go/ssa/interp/external_unix.go +++ /dev/null @@ -1,256 +0,0 @@ -// 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. - -// +build darwin linux - -package interp - -import "syscall" - -func init() { - for k, v := range map[string]externalFn{ - "os.Pipe": ext۰os۰Pipe, - "syscall.Close": ext۰syscall۰Close, - "syscall.Exit": ext۰syscall۰Exit, - "syscall.Fchown": ext۰syscall۰Fchown, - "syscall.Fstat": ext۰syscall۰Fstat, - "syscall.Ftruncate": ext۰syscall۰Ftruncate, - "syscall.Getpid": ext۰syscall۰Getpid, - "syscall.Getwd": ext۰syscall۰Getwd, - "syscall.Kill": ext۰syscall۰Kill, - "syscall.Link": ext۰syscall۰Link, - "syscall.Lstat": ext۰syscall۰Lstat, - "syscall.Mkdir": ext۰syscall۰Mkdir, - "syscall.Open": ext۰syscall۰Open, - "syscall.ParseDirent": ext۰syscall۰ParseDirent, - "syscall.RawSyscall": ext۰syscall۰RawSyscall, - "syscall.Read": ext۰syscall۰Read, - "syscall.ReadDirent": ext۰syscall۰ReadDirent, - "syscall.Readlink": ext۰syscall۰Readlink, - "syscall.Rmdir": ext۰syscall۰Rmdir, - "syscall.Seek": ext۰syscall۰Seek, - "syscall.Stat": ext۰syscall۰Stat, - "syscall.Symlink": ext۰syscall۰Symlink, - "syscall.Write": ext۰syscall۰Write, - "syscall.Unlink": ext۰syscall۰Unlink, - "syscall۰UtimesNano": ext۰syscall۰UtimesNano, - "syscall.setenv_c": ext۰nop, - "syscall.unsetenv_c": ext۰nop, - "syscall.runtime_envs": ext۰runtime۰environ, - } { - externals[k] = v - } - - syswrite = syscall.Write -} - -func ext۰os۰Pipe(fr *frame, args []value) value { - // func os.Pipe() (r *File, w *File, err error) - - // The portable POSIX pipe(2) call is good enough for our needs. - var p [2]int - if err := syscall.Pipe(p[:]); err != nil { - // TODO(adonovan): fix: return an *os.SyscallError. - return tuple{nil, nil, wrapError(err)} - } - - NewFile := fr.i.prog.ImportedPackage("os").Func("NewFile") - r := call(fr.i, fr, 0, NewFile, []value{uintptr(p[0]), "|0"}) - w := call(fr.i, fr, 0, NewFile, []value{uintptr(p[1]), "|1"}) - return tuple{r, w, wrapError(nil)} -} - -// overridden on darwin -var fillStat = func(st *syscall.Stat_t, stat structure) { - stat[0] = st.Dev - stat[1] = st.Ino - stat[2] = st.Nlink - stat[3] = st.Mode - stat[4] = st.Uid - stat[5] = st.Gid - stat[7] = st.Rdev - stat[8] = st.Size - stat[9] = st.Blksize - stat[10] = st.Blocks - // TODO(adonovan): fix: copy Timespecs. - // stat[11] = st.Atim - // stat[12] = st.Mtim - // stat[13] = st.Ctim -} - -func ext۰syscall۰Close(fr *frame, args []value) value { - // func Close(fd int) (err error) - return wrapError(syscall.Close(args[0].(int))) -} - -func ext۰syscall۰Exit(fr *frame, args []value) value { - panic(exitPanic(args[0].(int))) -} - -func ext۰syscall۰Fchown(fr *frame, args []value) value { - fd := args[0].(int) - uid := args[1].(int) - gid := args[2].(int) - return wrapError(syscall.Fchown(fd, uid, gid)) -} - -func ext۰syscall۰Fstat(fr *frame, args []value) value { - // func Fstat(fd int, stat *Stat_t) (err error) - fd := args[0].(int) - stat := (*args[1].(*value)).(structure) - - var st syscall.Stat_t - err := syscall.Fstat(fd, &st) - fillStat(&st, stat) - return wrapError(err) -} - -func ext۰syscall۰Ftruncate(fr *frame, args []value) value { - fd := args[0].(int) - length := args[1].(int64) - return wrapError(syscall.Ftruncate(fd, length)) -} - -func ext۰syscall۰Getpid(fr *frame, args []value) value { - return syscall.Getpid() -} - -func ext۰syscall۰Getwd(fr *frame, args []value) value { - s, err := syscall.Getwd() - return tuple{s, wrapError(err)} -} - -func ext۰syscall۰Kill(fr *frame, args []value) value { - // func Kill(pid int, sig Signal) (err error) - return wrapError(syscall.Kill(args[0].(int), syscall.Signal(args[1].(int)))) -} - -func ext۰syscall۰Link(fr *frame, args []value) value { - path := args[0].(string) - link := args[1].(string) - return wrapError(syscall.Link(path, link)) -} - -func ext۰syscall۰Lstat(fr *frame, args []value) value { - // func Lstat(name string, stat *Stat_t) (err error) - name := args[0].(string) - stat := (*args[1].(*value)).(structure) - - var st syscall.Stat_t - err := syscall.Lstat(name, &st) - fillStat(&st, stat) - return wrapError(err) -} - -func ext۰syscall۰Mkdir(fr *frame, args []value) value { - path := args[0].(string) - mode := args[1].(uint32) - return wrapError(syscall.Mkdir(path, mode)) -} - -func ext۰syscall۰Open(fr *frame, args []value) value { - // func Open(path string, mode int, perm uint32) (fd int, err error) { - path := args[0].(string) - mode := args[1].(int) - perm := args[2].(uint32) - fd, err := syscall.Open(path, mode, perm) - return tuple{fd, wrapError(err)} -} - -func ext۰syscall۰ParseDirent(fr *frame, args []value) value { - // func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) - max := args[1].(int) - var names []string - for _, iname := range args[2].([]value) { - names = append(names, iname.(string)) - } - consumed, count, newnames := syscall.ParseDirent(valueToBytes(args[0]), max, names) - var inewnames []value - for _, newname := range newnames { - inewnames = append(inewnames, newname) - } - return tuple{consumed, count, inewnames} -} - -func ext۰syscall۰RawSyscall(fr *frame, args []value) value { - return tuple{uintptr(0), uintptr(0), uintptr(syscall.ENOSYS)} -} - -func ext۰syscall۰Read(fr *frame, args []value) value { - // func Read(fd int, p []byte) (n int, err error) - fd := args[0].(int) - p := args[1].([]value) - b := make([]byte, len(p)) - n, err := syscall.Read(fd, b) - for i := 0; i < n; i++ { - p[i] = b[i] - } - return tuple{n, wrapError(err)} -} - -func ext۰syscall۰ReadDirent(fr *frame, args []value) value { - // func ReadDirent(fd int, buf []byte) (n int, err error) - fd := args[0].(int) - p := args[1].([]value) - b := make([]byte, len(p)) - n, err := syscall.ReadDirent(fd, b) - for i := 0; i < n; i++ { - p[i] = b[i] - } - return tuple{n, wrapError(err)} -} - -func ext۰syscall۰Readlink(fr *frame, args []value) value { - path := args[0].(string) - buf := valueToBytes(args[1]) - n, err := syscall.Readlink(path, buf) - return tuple{n, wrapError(err)} -} - -func ext۰syscall۰Rmdir(fr *frame, args []value) value { - return wrapError(syscall.Rmdir(args[0].(string))) -} - -func ext۰syscall۰Seek(fr *frame, args []value) value { - fd := args[0].(int) - offset := args[1].(int64) - whence := args[2].(int) - new, err := syscall.Seek(fd, offset, whence) - return tuple{new, wrapError(err)} -} - -func ext۰syscall۰Stat(fr *frame, args []value) value { - // func Stat(name string, stat *Stat_t) (err error) - name := args[0].(string) - stat := (*args[1].(*value)).(structure) - - var st syscall.Stat_t - err := syscall.Stat(name, &st) - fillStat(&st, stat) - return wrapError(err) -} - -func ext۰syscall۰Symlink(fr *frame, args []value) value { - path := args[0].(string) - link := args[1].(string) - return wrapError(syscall.Symlink(path, link)) -} - -func ext۰syscall۰Unlink(fr *frame, args []value) value { - return wrapError(syscall.Unlink(args[0].(string))) -} - -func ext۰syscall۰UtimesNano(fr *frame, args []value) value { - path := args[0].(string) - var ts [2]syscall.Timespec - err := syscall.UtimesNano(path, ts[:]) - // TODO(adonovan): copy the Timespecs into args[1] - return wrapError(err) -} - -func ext۰syscall۰Write(fr *frame, args []value) value { - // func Write(fd int, p []byte) (n int, err error) - n, err := write(args[0].(int), valueToBytes(args[1])) - return tuple{n, wrapError(err)} -} diff --git a/go/ssa/interp/interp.go b/go/ssa/interp/interp.go index c0929609b32..76b7ca3a2fc 100644 --- a/go/ssa/interp/interp.go +++ b/go/ssa/interp/interp.go @@ -630,30 +630,6 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) { panic("no global variable: " + pkg.Pkg.Path() + "." + name) } -var environ []value - -func init() { - for _, s := range os.Environ() { - environ = append(environ, s) - } - environ = append(environ, "GOSSAINTERP=1") - environ = append(environ, "GOARCH="+runtime.GOARCH) -} - -// deleteBodies delete the bodies of all standalone functions except the -// specified ones. A missing intrinsic leads to a clear runtime error. -func deleteBodies(pkg *ssa.Package, except ...string) { - keep := make(map[string]bool) - for _, e := range except { - keep[e] = true - } - for _, mem := range pkg.Members { - if fn, ok := mem.(*ssa.Function); ok && !keep[fn.Name()] { - fn.Blocks = nil - } - } -} - // 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 @@ -665,11 +641,6 @@ func deleteBodies(pkg *ssa.Package, except ...string) { // The SSA program must include the "runtime" package. // func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) { - if syswrite == nil { - fmt.Fprintln(os.Stderr, "Interpret: unsupported platform.") - return 1 - } - i := &interpreter{ prog: mainpkg.Prog, globals: make(map[ssa.Value]*value), @@ -699,20 +670,6 @@ func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename stri i.globals[v] = &cell } } - - // Ad-hoc initialization for magic system variables. - switch pkg.Pkg.Path() { - case "syscall": - setGlobal(i, pkg, "envs", environ) - - case "reflect": - deleteBodies(pkg, "DeepEqual", "deepValueEqual") - - case "runtime": - sz := sizes.Sizeof(pkg.Pkg.Scope().Lookup("MemStats").Type()) - setGlobal(i, pkg, "sizeof_C_MStats", uintptr(sz)) - deleteBodies(pkg, "GOROOT", "gogetenv") - } } // Top-level error handler. diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go index 07bb53c2b0f..c1e30ec1940 100644 --- a/go/ssa/interp/interp_test.go +++ b/go/ssa/interp/interp_test.go @@ -2,18 +2,27 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux darwin - package interp_test +// This test runs the SSA interpreter over sample Go programs. +// Because the interpreter requires intrinsics for assembly +// functions and many low-level runtime routines, it is inherently +// not robust to evolutionary change in the standard library. +// Therefore the test cases are restricted to programs that +// use a fake standard library in testdata/src containing a tiny +// subset of simple functions useful for writing assertions. +// +// We no longer attempt to interpret any real standard packages such as +// fmt or testing, as it proved too fragile. + import ( "bytes" "fmt" "go/build" "go/types" + "log" "os" "path/filepath" - "runtime" "strings" "testing" "time" @@ -28,12 +37,10 @@ import ( // filenames comprising the main package of a program. // They are ordered quickest-first, roughly. // -// TODO(adonovan): integrate into the $GOROOT/test driver scripts, -// golden file checking, etc. +// If a test in this list fails spuriously, remove it. var gorootTestTests = []string{ "235.go", "alias1.go", - "chancap.go", "func5.go", "func6.go", "func7.go", @@ -63,12 +70,10 @@ var gorootTestTests = []string{ "bigmap.go", "func.go", "reorder2.go", - "closure.go", "gc.go", "simassign.go", "iota.go", "nilptr2.go", - "goprint.go", // doesn't actually assert anything (cmpout) "utf.go", "method.go", "char_lit.go", @@ -86,52 +91,18 @@ var gorootTestTests = []string{ "convert.go", "convT2X.go", "switch.go", - "initialize.go", "ddd.go", "blank.go", // partly disabled - "map.go", "closedchan.go", "divide.go", "rename.go", - "const3.go", "nil.go", - "recover.go", // reflection parts disabled "recover1.go", "recover2.go", "recover3.go", "typeswitch1.go", "floatcmp.go", "crlf.go", // doesn't actually assert anything (runoutput) - // Slow tests follow. - "bom.go", // ~1.7s - "gc1.go", // ~1.7s - "cmplxdivide.go cmplxdivide1.go", // ~2.4s - - // Working, but not worth enabling: - // "append.go", // works, but slow (15s). - // "gc2.go", // works, but slow, and cheats on the memory check. - // "sigchld.go", // works, but only on POSIX. - // "peano.go", // works only up to n=9, and slow even then. - // "stack.go", // works, but too slow (~30s) by default. - // "solitaire.go", // works, but too slow (~30s). - // "const.go", // works but for but one bug: constant folder doesn't consider representations. - // "init1.go", // too slow (80s) and not that interesting. Cheats on ReadMemStats check too. - // "rotate.go rotate0.go", // emits source for a test - // "rotate.go rotate1.go", // emits source for a test - // "rotate.go rotate2.go", // emits source for a test - // "rotate.go rotate3.go", // emits source for a test - // "64bit.go", // emits source for a test - // "run.go", // test driver, not a test. - - // Broken. TODO(adonovan): fix. - // copy.go // very slow; but with N=4 quickly crashes, slice index out of range. - // nilptr.go // interp: V > uintptr not implemented. Slow test, lots of mem - // args.go // works, but requires specific os.Args from the driver. - // index.go // a template, not a real test. - // mallocfin.go // SetFinalizer not implemented. - - // TODO(adonovan): add tests from $GOROOT/test/* subtrees: - // bench chan bugs fixedbugs interface ken. } // These are files in go.tools/go/ssa/interp/testdata/. @@ -150,31 +121,21 @@ var testdataTests = []string{ "recover.go", "reflect.go", "static.go", - "callstack.go", } -type successPredicate func(exitcode int, output string) error - -func run(t *testing.T, dir, input string, success successPredicate) bool { - t.Skip("https://golang.org/issue/27292") - if runtime.GOOS == "darwin" { - t.Skip("skipping on darwin until https://golang.org/issue/23166 is fixed") - } - fmt.Printf("Input: %s\n", input) +func run(t *testing.T, input string) bool { + t.Logf("Input: %s\n", input) start := time.Now() - var inputs []string - for _, i := range strings.Split(input, " ") { - if strings.HasSuffix(i, ".go") { - i = dir + i - } - inputs = append(inputs, i) - } + ctx := build.Default // copy + ctx.GOROOT = "testdata" // fake goroot + ctx.GOOS = "linux" + ctx.GOARCH = "amd64" - var conf loader.Config - if _, err := conf.FromArgs(inputs, true); err != nil { - t.Errorf("FromArgs(%s) failed: %s", inputs, err) + conf := loader.Config{Build: &ctx} + if _, err := conf.FromArgs([]string{input}, true); err != nil { + t.Errorf("FromArgs(%s) failed: %s", input, err) return false } @@ -193,61 +154,43 @@ func run(t *testing.T, dir, input string, success successPredicate) bool { interp.CapturedOutput = nil }() - hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", strings.Join(inputs, " ")) + hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", input) iprog, err := conf.Load() if err != nil { - t.Errorf("conf.Load(%s) failed: %s", inputs, err) + t.Errorf("conf.Load(%s) failed: %s", input, err) return false } prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) prog.Build() - // Find first main or test package among the initial packages. - var mainPkg *ssa.Package - for _, info := range iprog.InitialPackages() { - if info.Pkg.Path() == "runtime" { - continue // not an initial package - } - p := prog.Package(info.Pkg) - if p.Pkg.Name() == "main" && p.Func("main") != nil { - mainPkg = p - break - } - - mainPkg = prog.CreateTestMainPackage(p) - if mainPkg != nil { - break - } - } + mainPkg := prog.Package(iprog.Created[0].Pkg) if mainPkg == nil { - t.Fatalf("no main or test packages among initial packages: %s", inputs) + t.Fatalf("not a main package: %s", input) } - var out bytes.Buffer - interp.CapturedOutput = &out + interp.CapturedOutput = new(bytes.Buffer) - hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -test -run --interp=T %s\n", strings.Join(inputs, " ")) - exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{WordSize: 8, MaxAlign: 8}, inputs[0], []string{}) - - // The definition of success varies with each file. - if err := success(exitCode, out.String()); err != nil { - t.Errorf("interp.Interpret(%s) failed: %s", inputs, err) - return false + hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -test -run --interp=T %s\n", input) + exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{WordSize: 8, MaxAlign: 8}, input, []string{}) + if exitCode != 0 { + t.Fatalf("interpreting %s: exit code was %d", input, exitCode) + } + // $GOROOT/test tests use this convention: + if strings.Contains(interp.CapturedOutput.String(), "BUG") { + t.Fatalf("interpreting %s: exited zero but output contained 'BUG'", input) } hint = "" // call off the hounds if false { - fmt.Println(input, time.Since(start)) // test profiling + t.Log(input, time.Since(start)) // test profiling } return true } -const slash = string(os.PathSeparator) - func printFailures(failures []string) { if failures != nil { fmt.Println("The following tests failed:") @@ -257,26 +200,16 @@ func printFailures(failures []string) { } } -func success(exitcode int, output string) error { - if exitcode != 0 { - return fmt.Errorf("exit code was %d", exitcode) - } - if strings.Contains(output, "BUG") { - return fmt.Errorf("exited zero but output contained 'BUG'") - } - return nil -} - // TestTestdataFiles runs the interpreter on testdata/*.go. func TestTestdataFiles(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + var failures []string - start := time.Now() for _, input := range testdataTests { - if testing.Short() && time.Since(start) > 30*time.Second { - printFailures(failures) - t.Skipf("timeout - aborting test") - } - if !run(t, "testdata"+slash, input, success) { + if !run(t, filepath.Join(cwd, "testdata", input)) { failures = append(failures, input) } } @@ -285,34 +218,12 @@ func TestTestdataFiles(t *testing.T) { // TestGorootTest runs the interpreter on $GOROOT/test/*.go. func TestGorootTest(t *testing.T) { - if testing.Short() { - t.Skip() // too slow (~30s) - } - var failures []string for _, input := range gorootTestTests { - if !run(t, filepath.Join(build.Default.GOROOT, "test")+slash, input, success) { + if !run(t, filepath.Join(build.Default.GOROOT, "test", input)) { failures = append(failures, input) } } printFailures(failures) } - -// CreateTestMainPackage should return nil if there were no tests. -func TestNullTestmainPackage(t *testing.T) { - var conf loader.Config - conf.CreateFromFilenames("", "testdata/b_test.go") - iprog, err := conf.Load() - if err != nil { - t.Fatalf("CreatePackages failed: %s", err) - } - prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) - mainPkg := prog.Package(iprog.Created[0].Pkg) - if mainPkg.Func("main") != nil { - t.Fatalf("unexpected main function") - } - if prog.CreateTestMainPackage(mainPkg) != nil { - t.Fatalf("CreateTestMainPackage returned non-nil") - } -} diff --git a/go/ssa/interp/ops.go b/go/ssa/interp/ops.go index 334d5b7b4d1..ca004d71ead 100644 --- a/go/ssa/interp/ops.go +++ b/go/ssa/interp/ops.go @@ -10,6 +10,7 @@ import ( "go/constant" "go/token" "go/types" + "os" "strings" "sync" "unsafe" @@ -918,21 +919,18 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value { var CapturedOutput *bytes.Buffer var capturedOutputMu sync.Mutex -// write writes bytes b to the target program's file descriptor fd. +// write writes bytes b to the target program's standard output. // The print/println built-ins and the write() system call funnel // through here so they can be captured by the test driver. -func write(fd int, b []byte) (int, error) { - // TODO(adonovan): fix: on Windows, std{out,err} are not 1, 2. - if CapturedOutput != nil && (fd == 1 || fd == 2) { +func print(b []byte) (int, error) { + if CapturedOutput != nil { capturedOutputMu.Lock() CapturedOutput.Write(b) // ignore errors capturedOutputMu.Unlock() } - return syswrite(fd, b) + return os.Stdout.Write(b) } -var syswrite func(int, []byte) (int, error) // set on darwin/linux only - // callBuiltin interprets a call to builtin fn with arguments args, // returning its result. func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value) value { @@ -987,7 +985,7 @@ func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value if ln { buf.WriteRune('\n') } - write(1, buf.Bytes()) + print(buf.Bytes()) return nil case "len": diff --git a/go/ssa/interp/reflect.go b/go/ssa/interp/reflect.go index 2b828996815..0a4465b0b86 100644 --- a/go/ssa/interp/reflect.go +++ b/go/ssa/interp/reflect.go @@ -68,11 +68,6 @@ func makeReflectType(rt rtype) value { return iface{rtypeType, rt} } -func ext۰reflect۰Init(fr *frame, args []value) value { - // Signature: func() - return nil -} - func ext۰reflect۰rtype۰Bits(fr *frame, args []value) value { // Signature: func (t reflect.rtype) int rt := args[0].(rtype).t diff --git a/go/ssa/interp/testdata/a_test.go b/go/ssa/interp/testdata/a_test.go deleted file mode 100644 index 844ec5cdc60..00000000000 --- a/go/ssa/interp/testdata/a_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package a - -import "testing" - -func TestFoo(t *testing.T) { - t.Error("foo") -} - -func TestBar(t *testing.T) { - t.Error("bar") -} - -func BenchmarkWiz(b *testing.B) { - b.Error("wiz") -} - -// Don't test Examples since that testing package needs pipe(2) for that. diff --git a/go/ssa/interp/testdata/b_test.go b/go/ssa/interp/testdata/b_test.go deleted file mode 100644 index 4a30e96a852..00000000000 --- a/go/ssa/interp/testdata/b_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package b - -import "testing" - -func NotATest(t *testing.T) { - t.Error("foo") -} - -func NotABenchmark(b *testing.B) { - b.Error("wiz") -} diff --git a/go/ssa/interp/testdata/boundmeth.go b/go/ssa/interp/testdata/boundmeth.go index 255cc60703c..69937f9d3c7 100644 --- a/go/ssa/interp/testdata/boundmeth.go +++ b/go/ssa/interp/testdata/boundmeth.go @@ -2,7 +2,10 @@ package main -import "fmt" +import ( + "errors" + "fmt" +) func assert(b bool) { if !b { @@ -105,7 +108,7 @@ func regress1(x error) func() string { // Regression test for b/7269: // taking the value of an interface method performs a nil check. func nilInterfaceMethodValue() { - err := fmt.Errorf("ok") + err := errors.New("ok") f := err.Error if got := f(); got != "ok" { panic(got) @@ -119,7 +122,7 @@ func nilInterfaceMethodValue() { defer func() { r := fmt.Sprint(recover()) // runtime panic string varies across toolchains - if r != "runtime error: interface conversion: interface is nil, not error" && + if r != "interface conversion: interface is nil, not error" && r != "runtime error: invalid memory address or nil pointer dereference" { panic("want runtime panic from nil interface method value, got " + r) } diff --git a/go/ssa/interp/testdata/c_test.go b/go/ssa/interp/testdata/c_test.go deleted file mode 100644 index ad80b910890..00000000000 --- a/go/ssa/interp/testdata/c_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package c_test - -import ( - "os" - "testing" -) - -func TestC(t *testing.T) { - println("TestC") -} - -func TestMain(m *testing.M) { - println("TestMain start") - code := m.Run() - println("TestMain end") - os.Exit(code) -} diff --git a/go/ssa/interp/testdata/callstack.go b/go/ssa/interp/testdata/callstack.go deleted file mode 100644 index 56f3b28124b..00000000000 --- a/go/ssa/interp/testdata/callstack.go +++ /dev/null @@ -1,52 +0,0 @@ -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, "$") || 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) - } -} diff --git a/go/ssa/interp/testdata/coverage.go b/go/ssa/interp/testdata/coverage.go index 37ef95c6c73..00c7165cbb7 100644 --- a/go/ssa/interp/testdata/coverage.go +++ b/go/ssa/interp/testdata/coverage.go @@ -9,6 +9,7 @@ package main import ( "fmt" "reflect" + "strings" ) func init() { @@ -171,7 +172,7 @@ func main() { // fmt. const message = "Hello, World!" - if fmt.Sprintf("%s, %s!", "Hello", "World") != message { + if fmt.Sprint("Hello", ", ", "World", "!") != message { panic("oops") } @@ -425,7 +426,7 @@ func init() { // But two slices cannot be compared, even if one is nil. defer func() { r := fmt.Sprint(recover()) - if r != "runtime error: comparing uncomparable type []string" { + if !(strings.Contains(r, "compar") && strings.Contains(r, "[]string")) { panic("want panic from slice comparison, got " + r) } }() @@ -508,7 +509,7 @@ func init() { r := fmt.Sprint(recover()) // Exact error varies by toolchain: if r != "runtime error: value method (main.T).f called using nil *main.T pointer" && - r != "value method main.T.f called using nil *T pointer" { + r != "value method (main.T).f called using nil *main.T pointer" { panic("want panic from call with nil receiver, got " + r) } }() diff --git a/go/ssa/interp/testdata/range.go b/go/ssa/interp/testdata/range.go index da8a421e629..a1063366aae 100644 --- a/go/ssa/interp/testdata/range.go +++ b/go/ssa/interp/testdata/range.go @@ -23,7 +23,7 @@ func init() { } s := "" for _, r := range runes { - s = fmt.Sprintf("%s%c", s, r) + s += string(r) } if s != "Hello, 世界" { panic(s) diff --git a/go/ssa/interp/testdata/src/errors/errors.go b/go/ssa/interp/testdata/src/errors/errors.go new file mode 100644 index 00000000000..2377563ca28 --- /dev/null +++ b/go/ssa/interp/testdata/src/errors/errors.go @@ -0,0 +1,7 @@ +package errors + +func New(text string) error { return errorString{text} } + +type errorString struct{ s string } + +func (e errorString) Error() string { return e.s } diff --git a/go/ssa/interp/testdata/src/fmt/fmt.go b/go/ssa/interp/testdata/src/fmt/fmt.go new file mode 100644 index 00000000000..2185eb7081e --- /dev/null +++ b/go/ssa/interp/testdata/src/fmt/fmt.go @@ -0,0 +1,26 @@ +package fmt + +func Sprint(args ...interface{}) string + +func Print(args ...interface{}) { + for i, arg := range args { + if i > 0 { + print(" ") + } + print(Sprint(arg)) + } +} + +func Println(args ...interface{}) { + Print(args...) + println() +} + +// formatting is too complex to fake + +func Printf(args ...interface{}) string { + panic("Printf is not supported") +} +func Sprintf(format string, args ...interface{}) string { + panic("Sprintf is not supported") +} diff --git a/go/ssa/interp/testdata/src/math/math.go b/go/ssa/interp/testdata/src/math/math.go new file mode 100644 index 00000000000..f51e5f572a7 --- /dev/null +++ b/go/ssa/interp/testdata/src/math/math.go @@ -0,0 +1,13 @@ +package math + +func NaN() float64 + +func Inf(int) float64 + +func IsNaN(float64) bool + +func Float64bits(float64) uint64 + +func Signbit(x float64) bool { + return Float64bits(x)&(1<<63) != 0 +} diff --git a/go/ssa/interp/testdata/src/os/os.go b/go/ssa/interp/testdata/src/os/os.go new file mode 100644 index 00000000000..555ef549164 --- /dev/null +++ b/go/ssa/interp/testdata/src/os/os.go @@ -0,0 +1,5 @@ +package os + +func Getenv(string) string + +func Exit(int) diff --git a/go/ssa/interp/testdata/src/reflect/reflect.go b/go/ssa/interp/testdata/src/reflect/reflect.go new file mode 100644 index 00000000000..f6c4e2794e0 --- /dev/null +++ b/go/ssa/interp/testdata/src/reflect/reflect.go @@ -0,0 +1,16 @@ +package reflect + +type Type interface { + String() string +} + +type Value struct { +} + +func (Value) String() string + +func SliceOf(Type) Type + +func TypeOf(interface{}) Type + +func ValueOf(interface{}) Value diff --git a/go/ssa/interp/testdata/src/runtime/runtime.go b/go/ssa/interp/testdata/src/runtime/runtime.go new file mode 100644 index 00000000000..c60c7fc29a3 --- /dev/null +++ b/go/ssa/interp/testdata/src/runtime/runtime.go @@ -0,0 +1,22 @@ +package runtime + +// An errorString represents a runtime error described by a single string. +type errorString string + +func (e errorString) RuntimeError() {} + +func (e errorString) Error() string { + return "runtime error: " + string(e) +} + +func Breakpoint() + +type Error interface { + error + RuntimeError() +} + +const GOOS = "linux" +const GOARCH = "amd64" + +func GC() diff --git a/go/ssa/interp/testdata/src/strings/strings.go b/go/ssa/interp/testdata/src/strings/strings.go new file mode 100644 index 00000000000..dd86dcf4fb6 --- /dev/null +++ b/go/ssa/interp/testdata/src/strings/strings.go @@ -0,0 +1,9 @@ +package strings + +func Replace(s, old, new string, n int) string + +func Index(haystack, needle string) int + +func Contains(haystack, needle string) bool { + return Index(haystack, needle) >= 0 +} diff --git a/go/ssa/interp/testdata/src/time/time.go b/go/ssa/interp/testdata/src/time/time.go new file mode 100644 index 00000000000..66aa9ab172c --- /dev/null +++ b/go/ssa/interp/testdata/src/time/time.go @@ -0,0 +1,5 @@ +package time + +type Duration int64 + +func Sleep(Duration) diff --git a/go/ssa/interp/testdata/src/unicode/utf8/utf8.go b/go/ssa/interp/testdata/src/unicode/utf8/utf8.go new file mode 100644 index 00000000000..0e44f7cb923 --- /dev/null +++ b/go/ssa/interp/testdata/src/unicode/utf8/utf8.go @@ -0,0 +1,9 @@ +package utf8 + +func DecodeRuneInString(string) (rune, int) + +func DecodeRune(b []byte) (rune, int) { + return DecodeRuneInString(string(b)) +} + +const RuneError = '\uFFFD' diff --git a/go/ssa/interp/testdata/src/unsafe/unsafe.go b/go/ssa/interp/testdata/src/unsafe/unsafe.go new file mode 100644 index 00000000000..2d727856329 --- /dev/null +++ b/go/ssa/interp/testdata/src/unsafe/unsafe.go @@ -0,0 +1 @@ +package unsafe