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 <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Alan Donovan 2019-03-22 11:32:16 -04:00
parent cc8e56e55e
commit e779aa49e3
24 changed files with 304 additions and 986 deletions

View File

@ -8,16 +8,13 @@ package interp
// external or because they use "unsafe" or "reflect" operations. // external or because they use "unsafe" or "reflect" operations.
import ( import (
"go/types" "bytes"
"math" "math"
"os" "os"
"runtime" "runtime"
"strings" "strings"
"sync/atomic"
"time" "time"
"unsafe" "unicode/utf8"
"golang.org/x/tools/go/ssa"
) )
type externalFn func(fr *frame, args []value) value type externalFn func(fr *frame, args []value) value
@ -32,8 +29,6 @@ var externals = make(map[string]externalFn)
func init() { func init() {
// That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd]. // That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd].
for k, v := range map[string]externalFn{ 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).Bool": ext۰reflect۰Value۰Bool,
"(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr, "(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr,
"(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface, "(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface,
@ -69,107 +64,46 @@ func init() {
"(reflect.rtype).Out": ext۰reflect۰rtype۰Out, "(reflect.rtype).Out": ext۰reflect۰rtype۰Out,
"(reflect.rtype).Size": ext۰reflect۰rtype۰Size, "(reflect.rtype).Size": ext۰reflect۰rtype۰Size,
"(reflect.rtype).String": ext۰reflect۰rtype۰String, "(reflect.rtype).String": ext۰reflect۰rtype۰String,
"bytes.init": ext۰nop, // avoid asm dependency
"bytes.Equal": ext۰bytes۰Equal, "bytes.Equal": ext۰bytes۰Equal,
"bytes.IndexByte": ext۰bytes۰IndexByte, "bytes.IndexByte": ext۰bytes۰IndexByte,
"hash/crc32.haveSSE42": ext۰crc32۰haveSSE42, "fmt.Sprint": ext۰fmt۰Sprint,
"internal/cpu.cpuid": ext۰cpu۰cpuid,
"internal/syscall/unix.syscall_fcntl": ext۰syscall۰unix۰syscall_fcntl,
"math.Abs": ext۰math۰Abs, "math.Abs": ext۰math۰Abs,
"math.Exp": ext۰math۰Exp, "math.Exp": ext۰math۰Exp,
"math.Float32bits": ext۰math۰Float32bits, "math.Float32bits": ext۰math۰Float32bits,
"math.Float32frombits": ext۰math۰Float32frombits, "math.Float32frombits": ext۰math۰Float32frombits,
"math.Float64bits": ext۰math۰Float64bits, "math.Float64bits": ext۰math۰Float64bits,
"math.Float64frombits": ext۰math۰Float64frombits, "math.Float64frombits": ext۰math۰Float64frombits,
"math.Inf": ext۰math۰Inf,
"math.IsNaN": ext۰math۰IsNaN,
"math.Ldexp": ext۰math۰Ldexp, "math.Ldexp": ext۰math۰Ldexp,
"math.Log": ext۰math۰Log, "math.Log": ext۰math۰Log,
"math.Min": ext۰math۰Min, "math.Min": ext۰math۰Min,
"math.hasSSE4": ext۰math۰hasSSE4, "math.NaN": ext۰math۰NaN,
"math.hasVectorFacility": ext۰math۰hasVectorFacility, "os.Exit": ext۰os۰Exit,
"os.runtime_args": ext۰os۰runtime_args, "os.Getenv": ext۰os۰Getenv,
"os.runtime_beforeExit": ext۰nop,
"os/signal.init": ext۰nop,
"reflect.New": ext۰reflect۰New, "reflect.New": ext۰reflect۰New,
"reflect.SliceOf": ext۰reflect۰SliceOf, "reflect.SliceOf": ext۰reflect۰SliceOf,
"reflect.TypeOf": ext۰reflect۰TypeOf, "reflect.TypeOf": ext۰reflect۰TypeOf,
"reflect.ValueOf": ext۰reflect۰ValueOf, "reflect.ValueOf": ext۰reflect۰ValueOf,
"reflect.Zero": ext۰reflect۰Zero, "reflect.Zero": ext۰reflect۰Zero,
"reflect.init": ext۰reflect۰Init,
"reflect.valueInterface": ext۰reflect۰valueInterface,
"runtime.Breakpoint": ext۰runtime۰Breakpoint, "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.GC": ext۰runtime۰GC,
"runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS, "runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS,
"runtime.GOROOT": ext۰runtime۰GOROOT,
"runtime.Goexit": ext۰runtime۰Goexit, "runtime.Goexit": ext۰runtime۰Goexit,
"runtime.Gosched": ext۰runtime۰Gosched, "runtime.Gosched": ext۰runtime۰Gosched,
"runtime.init": ext۰nop,
"runtime.KeepAlive": ext۰nop,
"runtime.NumCPU": ext۰runtime۰NumCPU, "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.Count": ext۰strings۰Count,
"strings.Index": ext۰strings۰Index, "strings.Index": ext۰strings۰Index,
"strings.IndexByte": ext۰strings۰IndexByte, "strings.IndexByte": ext۰strings۰IndexByte,
"sync.runtime_Semacquire": ext۰nop, // unimplementable "strings.Replace": ext۰strings۰Replace,
"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.Sleep": ext۰time۰Sleep,
"time.now": ext۰time۰now, "unicode/utf8.DecodeRuneInString": ext۰unicode۰utf8۰DecodeRuneInString,
} { } {
externals[k] = v 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 ext۰bytes۰Equal(fr *frame, args []value) value {
// func Equal(a, b []byte) bool // func Equal(a, b []byte) bool
a := args[0].([]value) a := args[0].([]value)
@ -197,10 +131,6 @@ func ext۰bytes۰IndexByte(fr *frame, args []value) value {
return -1 return -1
} }
func ext۰crc32۰haveSSE42(fr *frame, args []value) value {
return false
}
func ext۰math۰Float64frombits(fr *frame, args []value) value { func ext۰math۰Float64frombits(fr *frame, args []value) value {
return math.Float64frombits(args[0].(uint64)) 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)) return math.Min(args[0].(float64), args[1].(float64))
} }
func ext۰math۰hasSSE4(fr *frame, args []value) value { func ext۰math۰NaN(fr *frame, args []value) value {
return false return math.NaN()
} }
func ext۰math۰hasVectorFacility(fr *frame, args []value) value { func ext۰math۰IsNaN(fr *frame, args []value) value {
return false 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 { 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)) 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 { func ext۰runtime۰Breakpoint(fr *frame, args []value) value {
runtime.Breakpoint() runtime.Breakpoint()
return nil 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 { 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)) return strings.Count(args[0].(string), args[1].(string))
} }
func ext۰strings۰IndexByte(fr *frame, args []value) value { 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)) return strings.IndexByte(args[0].(string), args[1].(byte))
} }
func ext۰strings۰Index(fr *frame, args []value) value { 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)) 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 { func ext۰runtime۰GOMAXPROCS(fr *frame, args []value) value {
// Ignore args[0]; don't let the interpreted program // Ignore args[0]; don't let the interpreted program
// set the interpreter's GOMAXPROCS! // set the interpreter's GOMAXPROCS!
@ -345,6 +217,10 @@ func ext۰runtime۰Goexit(fr *frame, args []value) value {
return nil return nil
} }
func ext۰runtime۰GOROOT(fr *frame, args []value) value {
return runtime.GOROOT()
}
func ext۰runtime۰GC(fr *frame, args []value) value { func ext۰runtime۰GC(fr *frame, args []value) value {
runtime.GC() runtime.GC()
return nil return nil
@ -359,187 +235,6 @@ func ext۰runtime۰NumCPU(fr *frame, args []value) value {
return runtime.NumCPU() 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 { func ext۰time۰Sleep(fr *frame, args []value) value {
time.Sleep(time.Duration(args[0].(int64))) time.Sleep(time.Duration(args[0].(int64)))
return nil return nil
@ -554,9 +249,42 @@ func valueToBytes(v value) []byte {
return b return b
} }
func ext۰testing۰MainStart(fr *frame, args []value) value { func ext۰os۰Getenv(fr *frame, args []value) value {
// We no longer support interpretation of the "testing" package name := args[0].(string)
// because it changes too often and uses low-level features that switch name {
// are a pain to emulate. case "GOSSAINTERP":
panic(`interpretation of the "testing" package is no longer supported`) 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()
} }

View File

@ -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)}
}

View File

@ -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)}
}

View File

@ -630,30 +630,6 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) {
panic("no global variable: " + pkg.Pkg.Path() + "." + name) 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. // Interpret interprets the Go program whose main package is mainpkg.
// mode specifies various interpreter options. filename and args are // mode specifies various interpreter options. filename and args are
// the initial values of os.Args for the target program. sizes is the // 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. // The SSA program must include the "runtime" package.
// //
func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) { 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{ i := &interpreter{
prog: mainpkg.Prog, prog: mainpkg.Prog,
globals: make(map[ssa.Value]*value), 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 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. // Top-level error handler.

View File

@ -2,18 +2,27 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build linux darwin
package interp_test 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 ( import (
"bytes" "bytes"
"fmt" "fmt"
"go/build" "go/build"
"go/types" "go/types"
"log"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -28,12 +37,10 @@ import (
// filenames comprising the main package of a program. // filenames comprising the main package of a program.
// They are ordered quickest-first, roughly. // They are ordered quickest-first, roughly.
// //
// TODO(adonovan): integrate into the $GOROOT/test driver scripts, // If a test in this list fails spuriously, remove it.
// golden file checking, etc.
var gorootTestTests = []string{ var gorootTestTests = []string{
"235.go", "235.go",
"alias1.go", "alias1.go",
"chancap.go",
"func5.go", "func5.go",
"func6.go", "func6.go",
"func7.go", "func7.go",
@ -63,12 +70,10 @@ var gorootTestTests = []string{
"bigmap.go", "bigmap.go",
"func.go", "func.go",
"reorder2.go", "reorder2.go",
"closure.go",
"gc.go", "gc.go",
"simassign.go", "simassign.go",
"iota.go", "iota.go",
"nilptr2.go", "nilptr2.go",
"goprint.go", // doesn't actually assert anything (cmpout)
"utf.go", "utf.go",
"method.go", "method.go",
"char_lit.go", "char_lit.go",
@ -86,52 +91,18 @@ var gorootTestTests = []string{
"convert.go", "convert.go",
"convT2X.go", "convT2X.go",
"switch.go", "switch.go",
"initialize.go",
"ddd.go", "ddd.go",
"blank.go", // partly disabled "blank.go", // partly disabled
"map.go",
"closedchan.go", "closedchan.go",
"divide.go", "divide.go",
"rename.go", "rename.go",
"const3.go",
"nil.go", "nil.go",
"recover.go", // reflection parts disabled
"recover1.go", "recover1.go",
"recover2.go", "recover2.go",
"recover3.go", "recover3.go",
"typeswitch1.go", "typeswitch1.go",
"floatcmp.go", "floatcmp.go",
"crlf.go", // doesn't actually assert anything (runoutput) "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/. // These are files in go.tools/go/ssa/interp/testdata/.
@ -150,31 +121,21 @@ var testdataTests = []string{
"recover.go", "recover.go",
"reflect.go", "reflect.go",
"static.go", "static.go",
"callstack.go",
} }
type successPredicate func(exitcode int, output string) error func run(t *testing.T, input string) bool {
t.Logf("Input: %s\n", input)
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)
start := time.Now() start := time.Now()
var inputs []string ctx := build.Default // copy
for _, i := range strings.Split(input, " ") { ctx.GOROOT = "testdata" // fake goroot
if strings.HasSuffix(i, ".go") { ctx.GOOS = "linux"
i = dir + i ctx.GOARCH = "amd64"
}
inputs = append(inputs, i)
}
var conf loader.Config conf := loader.Config{Build: &ctx}
if _, err := conf.FromArgs(inputs, true); err != nil { if _, err := conf.FromArgs([]string{input}, true); err != nil {
t.Errorf("FromArgs(%s) failed: %s", inputs, err) t.Errorf("FromArgs(%s) failed: %s", input, err)
return false return false
} }
@ -193,61 +154,43 @@ func run(t *testing.T, dir, input string, success successPredicate) bool {
interp.CapturedOutput = nil 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() iprog, err := conf.Load()
if err != nil { if err != nil {
t.Errorf("conf.Load(%s) failed: %s", inputs, err) t.Errorf("conf.Load(%s) failed: %s", input, err)
return false return false
} }
prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
prog.Build() prog.Build()
// Find first main or test package among the initial packages. mainPkg := prog.Package(iprog.Created[0].Pkg)
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
}
}
if mainPkg == nil { 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 = new(bytes.Buffer)
interp.CapturedOutput = &out
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, " ")) 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}, inputs[0], []string{}) exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{WordSize: 8, MaxAlign: 8}, input, []string{})
if exitCode != 0 {
// The definition of success varies with each file. t.Fatalf("interpreting %s: exit code was %d", input, exitCode)
if err := success(exitCode, out.String()); err != nil { }
t.Errorf("interp.Interpret(%s) failed: %s", inputs, err) // $GOROOT/test tests use this convention:
return false if strings.Contains(interp.CapturedOutput.String(), "BUG") {
t.Fatalf("interpreting %s: exited zero but output contained 'BUG'", input)
} }
hint = "" // call off the hounds hint = "" // call off the hounds
if false { if false {
fmt.Println(input, time.Since(start)) // test profiling t.Log(input, time.Since(start)) // test profiling
} }
return true return true
} }
const slash = string(os.PathSeparator)
func printFailures(failures []string) { func printFailures(failures []string) {
if failures != nil { if failures != nil {
fmt.Println("The following tests failed:") 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. // TestTestdataFiles runs the interpreter on testdata/*.go.
func TestTestdataFiles(t *testing.T) { func TestTestdataFiles(t *testing.T) {
var failures []string cwd, err := os.Getwd()
start := time.Now() if err != nil {
for _, input := range testdataTests { log.Fatal(err)
if testing.Short() && time.Since(start) > 30*time.Second {
printFailures(failures)
t.Skipf("timeout - aborting test")
} }
if !run(t, "testdata"+slash, input, success) {
var failures []string
for _, input := range testdataTests {
if !run(t, filepath.Join(cwd, "testdata", input)) {
failures = append(failures, input) failures = append(failures, input)
} }
} }
@ -285,34 +218,12 @@ func TestTestdataFiles(t *testing.T) {
// TestGorootTest runs the interpreter on $GOROOT/test/*.go. // TestGorootTest runs the interpreter on $GOROOT/test/*.go.
func TestGorootTest(t *testing.T) { func TestGorootTest(t *testing.T) {
if testing.Short() {
t.Skip() // too slow (~30s)
}
var failures []string var failures []string
for _, input := range gorootTestTests { 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) failures = append(failures, input)
} }
} }
printFailures(failures) 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")
}
}

View File

@ -10,6 +10,7 @@ import (
"go/constant" "go/constant"
"go/token" "go/token"
"go/types" "go/types"
"os"
"strings" "strings"
"sync" "sync"
"unsafe" "unsafe"
@ -918,21 +919,18 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value {
var CapturedOutput *bytes.Buffer var CapturedOutput *bytes.Buffer
var capturedOutputMu sync.Mutex 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 // The print/println built-ins and the write() system call funnel
// through here so they can be captured by the test driver. // through here so they can be captured by the test driver.
func write(fd int, b []byte) (int, error) { func print(b []byte) (int, error) {
// TODO(adonovan): fix: on Windows, std{out,err} are not 1, 2. if CapturedOutput != nil {
if CapturedOutput != nil && (fd == 1 || fd == 2) {
capturedOutputMu.Lock() capturedOutputMu.Lock()
CapturedOutput.Write(b) // ignore errors CapturedOutput.Write(b) // ignore errors
capturedOutputMu.Unlock() 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, // callBuiltin interprets a call to builtin fn with arguments args,
// returning its result. // returning its result.
func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value) value { 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 { if ln {
buf.WriteRune('\n') buf.WriteRune('\n')
} }
write(1, buf.Bytes()) print(buf.Bytes())
return nil return nil
case "len": case "len":

View File

@ -68,11 +68,6 @@ func makeReflectType(rt rtype) value {
return iface{rtypeType, rt} 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 { func ext۰reflect۰rtype۰Bits(fr *frame, args []value) value {
// Signature: func (t reflect.rtype) int // Signature: func (t reflect.rtype) int
rt := args[0].(rtype).t rt := args[0].(rtype).t

View File

@ -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.

View File

@ -1,11 +0,0 @@
package b
import "testing"
func NotATest(t *testing.T) {
t.Error("foo")
}
func NotABenchmark(b *testing.B) {
b.Error("wiz")
}

View File

@ -2,7 +2,10 @@
package main package main
import "fmt" import (
"errors"
"fmt"
)
func assert(b bool) { func assert(b bool) {
if !b { if !b {
@ -105,7 +108,7 @@ func regress1(x error) func() string {
// Regression test for b/7269: // Regression test for b/7269:
// taking the value of an interface method performs a nil check. // taking the value of an interface method performs a nil check.
func nilInterfaceMethodValue() { func nilInterfaceMethodValue() {
err := fmt.Errorf("ok") err := errors.New("ok")
f := err.Error f := err.Error
if got := f(); got != "ok" { if got := f(); got != "ok" {
panic(got) panic(got)
@ -119,7 +122,7 @@ func nilInterfaceMethodValue() {
defer func() { defer func() {
r := fmt.Sprint(recover()) r := fmt.Sprint(recover())
// runtime panic string varies across toolchains // 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" { r != "runtime error: invalid memory address or nil pointer dereference" {
panic("want runtime panic from nil interface method value, got " + r) panic("want runtime panic from nil interface method value, got " + r)
} }

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -9,6 +9,7 @@ package main
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
) )
func init() { func init() {
@ -171,7 +172,7 @@ func main() {
// fmt. // fmt.
const message = "Hello, World!" const message = "Hello, World!"
if fmt.Sprintf("%s, %s!", "Hello", "World") != message { if fmt.Sprint("Hello", ", ", "World", "!") != message {
panic("oops") panic("oops")
} }
@ -425,7 +426,7 @@ func init() {
// But two slices cannot be compared, even if one is nil. // But two slices cannot be compared, even if one is nil.
defer func() { defer func() {
r := fmt.Sprint(recover()) 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) panic("want panic from slice comparison, got " + r)
} }
}() }()
@ -508,7 +509,7 @@ func init() {
r := fmt.Sprint(recover()) r := fmt.Sprint(recover())
// Exact error varies by toolchain: // Exact error varies by toolchain:
if r != "runtime error: value method (main.T).f called using nil *main.T pointer" && 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) panic("want panic from call with nil receiver, got " + r)
} }
}() }()

View File

@ -23,7 +23,7 @@ func init() {
} }
s := "" s := ""
for _, r := range runes { for _, r := range runes {
s = fmt.Sprintf("%s%c", s, r) s += string(r)
} }
if s != "Hello, 世界" { if s != "Hello, 世界" {
panic(s) panic(s)

View File

@ -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 }

26
go/ssa/interp/testdata/src/fmt/fmt.go vendored Normal file
View File

@ -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")
}

13
go/ssa/interp/testdata/src/math/math.go vendored Normal file
View File

@ -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
}

5
go/ssa/interp/testdata/src/os/os.go vendored Normal file
View File

@ -0,0 +1,5 @@
package os
func Getenv(string) string
func Exit(int)

View File

@ -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

View File

@ -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()

View File

@ -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
}

View File

@ -0,0 +1,5 @@
package time
type Duration int64
func Sleep(Duration)

View File

@ -0,0 +1,9 @@
package utf8
func DecodeRuneInString(string) (rune, int)
func DecodeRune(b []byte) (rune, int) {
return DecodeRuneInString(string(b))
}
const RuneError = '\uFFFD'

View File

@ -0,0 +1 @@
package unsafe