aboutsummaryrefslogtreecommitdiff
path: root/go/ssa/interp
diff options
context:
space:
mode:
Diffstat (limited to 'go/ssa/interp')
-rw-r--r--go/ssa/interp/interp.go65
-rw-r--r--go/ssa/interp/interp_go120_test.go12
-rw-r--r--go/ssa/interp/interp_test.go97
-rw-r--r--go/ssa/interp/map.go2
-rw-r--r--go/ssa/interp/ops.go87
-rw-r--r--go/ssa/interp/reflect.go6
-rw-r--r--go/ssa/interp/testdata/boundmeth.go3
-rw-r--r--go/ssa/interp/testdata/convert.go9
-rw-r--r--go/ssa/interp/testdata/deepequal.go93
-rw-r--r--go/ssa/interp/testdata/fixedbugs/issue52342.go17
-rw-r--r--go/ssa/interp/testdata/fixedbugs/issue52835.go27
-rw-r--r--go/ssa/interp/testdata/fixedbugs/issue55086.go132
-rw-r--r--go/ssa/interp/testdata/slice2array.go92
-rw-r--r--go/ssa/interp/testdata/slice2arrayptr.go2
-rw-r--r--go/ssa/interp/testdata/src/encoding/encoding.go15
-rw-r--r--go/ssa/interp/testdata/src/log/log.go8
-rw-r--r--go/ssa/interp/testdata/src/reflect/deepequal.go109
-rw-r--r--go/ssa/interp/testdata/src/reflect/reflect.go13
-rw-r--r--go/ssa/interp/testdata/typeassert.go32
-rw-r--r--go/ssa/interp/testdata/width32.go42
-rw-r--r--go/ssa/interp/testdata/zeros.go45
21 files changed, 835 insertions, 73 deletions
diff --git a/go/ssa/interp/interp.go b/go/ssa/interp/interp.go
index bf7862289..58cac4642 100644
--- a/go/ssa/interp/interp.go
+++ b/go/ssa/interp/interp.go
@@ -76,16 +76,16 @@ type methodSet map[string]*ssa.Function
// State shared between all interpreted goroutines.
type interpreter struct {
- osArgs []value // the value of os.Args
- prog *ssa.Program // the SSA program
- globals map[ssa.Value]*value // addresses of global variables (immutable)
- mode Mode // interpreter options
- reflectPackage *ssa.Package // the fake reflect package
- errorMethods methodSet // the method set of reflect.error, which implements the error interface.
- rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface.
- runtimeErrorString types.Type // the runtime.errorString type
- sizes types.Sizes // the effective type-sizing function
- goroutines int32 // atomically updated
+ osArgs []value // the value of os.Args
+ prog *ssa.Program // the SSA program
+ globals map[*ssa.Global]*value // addresses of global variables (immutable)
+ mode Mode // interpreter options
+ reflectPackage *ssa.Package // the fake reflect package
+ errorMethods methodSet // the method set of reflect.error, which implements the error interface.
+ rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface.
+ runtimeErrorString types.Type // the runtime.errorString type
+ sizes types.Sizes // the effective type-sizing function
+ goroutines int32 // atomically updated
}
type deferred struct {
@@ -131,7 +131,6 @@ func (fr *frame) get(key ssa.Value) value {
// runDefer runs a deferred call d.
// It always returns normally, but may set or clear fr.panic.
-//
func (fr *frame) runDefer(d *deferred) {
if fr.i.mode&EnableTracing != 0 {
fmt.Fprintf(os.Stderr, "%s: invoking deferred function call\n",
@@ -160,7 +159,6 @@ func (fr *frame) runDefer(d *deferred) {
//
// If there was no initial state of panic, or it was recovered from,
// runDefers returns normally.
-//
func (fr *frame) runDefers() {
for d := fr.defers; d != nil; d = d.tail {
fr.runDefer(d)
@@ -279,7 +277,7 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
}()
case *ssa.MakeChan:
- fr.env[instr] = make(chan value, asInt(fr.get(instr.Size)))
+ fr.env[instr] = make(chan value, asInt64(fr.get(instr.Size)))
case *ssa.Alloc:
var addr *value
@@ -294,17 +292,20 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
*addr = zero(deref(instr.Type()))
case *ssa.MakeSlice:
- slice := make([]value, asInt(fr.get(instr.Cap)))
+ slice := make([]value, asInt64(fr.get(instr.Cap)))
tElt := instr.Type().Underlying().(*types.Slice).Elem()
for i := range slice {
slice[i] = zero(tElt)
}
- fr.env[instr] = slice[:asInt(fr.get(instr.Len))]
+ fr.env[instr] = slice[:asInt64(fr.get(instr.Len))]
case *ssa.MakeMap:
- reserve := 0
+ var reserve int64
if instr.Reserve != nil {
- reserve = asInt(fr.get(instr.Reserve))
+ reserve = asInt64(fr.get(instr.Reserve))
+ }
+ if !fitsInt(reserve, fr.i.sizes) {
+ panic(fmt.Sprintf("ssa.MakeMap.Reserve value %d does not fit in int", reserve))
}
fr.env[instr] = makeMap(instr.Type().Underlying().(*types.Map).Key(), reserve)
@@ -325,15 +326,25 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
idx := fr.get(instr.Index)
switch x := x.(type) {
case []value:
- fr.env[instr] = &x[asInt(idx)]
+ fr.env[instr] = &x[asInt64(idx)]
case *value: // *array
- fr.env[instr] = &(*x).(array)[asInt(idx)]
+ fr.env[instr] = &(*x).(array)[asInt64(idx)]
default:
panic(fmt.Sprintf("unexpected x type in IndexAddr: %T", x))
}
case *ssa.Index:
- fr.env[instr] = fr.get(instr.X).(array)[asInt(fr.get(instr.Index))]
+ x := fr.get(instr.X)
+ idx := fr.get(instr.Index)
+
+ switch x := x.(type) {
+ case array:
+ fr.env[instr] = x[asInt64(idx)]
+ case string:
+ fr.env[instr] = x[asInt64(idx)]
+ default:
+ panic(fmt.Sprintf("unexpected x type in Index: %T", x))
+ }
case *ssa.Lookup:
fr.env[instr] = lookup(instr, fr.get(instr.X), fr.get(instr.Index))
@@ -426,7 +437,6 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
// prepareCall determines the function value and argument values for a
// function call in a Call, Go or Defer instruction, performing
// interface method lookup if needed.
-//
func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
v := fr.get(call.Value)
if call.Method == nil {
@@ -455,7 +465,6 @@ func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
// call interprets a call to a function (function, builtin or closure)
// fn with arguments args, returning its result.
// callpos is the position of the callsite.
-//
func call(i *interpreter, caller *frame, callpos token.Pos, fn value, args []value) value {
switch fn := fn.(type) {
case *ssa.Function:
@@ -481,7 +490,6 @@ func loc(fset *token.FileSet, pos token.Pos) string {
// callSSA interprets a call to function fn with arguments args,
// and lexical environment env, returning its result.
// callpos is the position of the callsite.
-//
func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, args []value, env []value) value {
if i.mode&EnableTracing != 0 {
fset := fn.Prog.Fset
@@ -510,6 +518,12 @@ func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function,
panic("no code for function: " + name)
}
}
+
+ // generic function body?
+ if fn.TypeParams().Len() > 0 && len(fn.TypeArgs()) == 0 {
+ panic("interp requires ssa.BuilderMode to include InstantiateGenerics to execute generics")
+ }
+
fr.env = make(map[ssa.Value]value)
fr.block = fn.Blocks[0]
fr.locals = make([]value, len(fn.Locals))
@@ -548,7 +562,6 @@ func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function,
// After a recovered panic in a function with NRPs, fr.result is
// undefined and fr.block contains the block at which to resume
// control.
-//
func runFrame(fr *frame) {
defer func() {
if fr.block == nil {
@@ -641,10 +654,12 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) {
//
// The SSA program must include the "runtime" package.
//
+// Type parameterized functions must have been built with
+// InstantiateGenerics in the ssa.BuilderMode to be interpreted.
func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) {
i := &interpreter{
prog: mainpkg.Prog,
- globals: make(map[ssa.Value]*value),
+ globals: make(map[*ssa.Global]*value),
mode: mode,
sizes: sizes,
goroutines: 1,
diff --git a/go/ssa/interp/interp_go120_test.go b/go/ssa/interp/interp_go120_test.go
new file mode 100644
index 000000000..d8eb2c213
--- /dev/null
+++ b/go/ssa/interp/interp_go120_test.go
@@ -0,0 +1,12 @@
+// Copyright 2021 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.
+
+//go:build go1.20
+// +build go1.20
+
+package interp_test
+
+func init() {
+ testdataTests = append(testdataTests, "slice2array.go")
+}
diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go
index 1b43742c8..70ddceec7 100644
--- a/go/ssa/interp/interp_test.go
+++ b/go/ssa/interp/interp_test.go
@@ -31,6 +31,7 @@ import (
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/interp"
"golang.org/x/tools/go/ssa/ssautil"
+ "golang.org/x/tools/internal/typeparams"
)
// Each line contains a space-separated list of $GOROOT/test/
@@ -111,6 +112,7 @@ var testdataTests = []string{
"complit.go",
"convert.go",
"coverage.go",
+ "deepequal.go",
"defer.go",
"fieldprom.go",
"ifaceconv.go",
@@ -122,6 +124,24 @@ var testdataTests = []string{
"recover.go",
"reflect.go",
"static.go",
+ "width32.go",
+
+ "fixedbugs/issue52342.go",
+}
+
+func init() {
+ if typeparams.Enabled {
+ testdataTests = append(testdataTests, "fixedbugs/issue52835.go")
+ testdataTests = append(testdataTests, "fixedbugs/issue55086.go")
+ testdataTests = append(testdataTests, "typeassert.go")
+ testdataTests = append(testdataTests, "zeros.go")
+ }
+}
+
+// Specific GOARCH to use for a test case in go.tools/go/ssa/interp/testdata/.
+// Defaults to amd64 otherwise.
+var testdataArchs = map[string]string{
+ "width32.go": "386",
}
func run(t *testing.T, input string) bool {
@@ -139,6 +159,9 @@ func run(t *testing.T, input string) bool {
ctx.GOROOT = "testdata" // fake goroot
ctx.GOOS = "linux"
ctx.GOARCH = "amd64"
+ if arch, ok := testdataArchs[filepath.Base(input)]; ok {
+ ctx.GOARCH = arch
+ }
conf := loader.Config{Build: &ctx}
if _, err := conf.FromArgs([]string{input}, true); err != nil {
@@ -169,7 +192,9 @@ func run(t *testing.T, input string) bool {
return false
}
- prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
+ bmode := ssa.InstantiateGenerics | ssa.SanityCheckFunctions
+ // bmode |= ssa.PrintFunctions // enable for debugging
+ prog := ssautil.CreateProgram(iprog, bmode)
prog.Build()
mainPkg := prog.Package(iprog.Created[0].Pkg)
@@ -179,8 +204,12 @@ func run(t *testing.T, input string) bool {
interp.CapturedOutput = new(bytes.Buffer)
+ sizes := types.SizesFor("gc", ctx.GOARCH)
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{})
+ var imode interp.Mode // default mode
+ // imode |= interp.DisableRecover // enable for debugging
+ // imode |= interp.EnableTracing // enable for debugging
+ exitCode := interp.Interpret(mainPkg, imode, sizes, input, []string{})
if exitCode != 0 {
t.Fatalf("interpreting %s: exit code was %d", input, exitCode)
}
@@ -213,7 +242,6 @@ func TestTestdataFiles(t *testing.T) {
if err != nil {
log.Fatal(err)
}
-
var failures []string
for _, input := range testdataTests {
if !run(t, filepath.Join(cwd, "testdata", input)) {
@@ -234,3 +262,66 @@ func TestGorootTest(t *testing.T) {
}
printFailures(failures)
}
+
+// TestTypeparamTest runs the interpreter on runnable examples
+// in $GOROOT/test/typeparam/*.go.
+
+func TestTypeparamTest(t *testing.T) {
+ if !typeparams.Enabled {
+ return
+ }
+
+ // Skip known failures for the given reason.
+ // TODO(taking): Address these.
+ skip := map[string]string{
+ "chans.go": "interp tests do not support runtime.SetFinalizer",
+ "issue23536.go": "unknown reason",
+ "issue376214.go": "unknown issue with variadic cast on bytes",
+ "issue48042.go": "interp tests do not handle reflect.Value.SetInt",
+ "issue47716.go": "interp tests do not handle unsafe.Sizeof",
+ "issue50419.go": "interp tests do not handle dispatch to String() correctly",
+ "issue51733.go": "interp does not handle unsafe casts",
+ "ordered.go": "math.NaN() comparisons not being handled correctly",
+ "orderedmap.go": "interp tests do not support runtime.SetFinalizer",
+ "stringer.go": "unknown reason",
+ "issue48317.go": "interp tests do not support encoding/json",
+ "issue48318.go": "interp tests do not support encoding/json",
+ "issue58513.go": "interp tests do not support runtime.Caller",
+ }
+ // Collect all of the .go files in dir that are runnable.
+ dir := filepath.Join(build.Default.GOROOT, "test", "typeparam")
+ list, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var inputs []string
+ for _, entry := range list {
+ if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
+ continue // Consider standalone go files.
+ }
+ if reason := skip[entry.Name()]; reason != "" {
+ t.Logf("skipping %q due to %s.", entry.Name(), reason)
+ continue
+ }
+ input := filepath.Join(dir, entry.Name())
+ src, err := os.ReadFile(input)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Only build test files that can be compiled, or compiled and run.
+ if bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// rundir")) {
+ inputs = append(inputs, input)
+ } else {
+ t.Logf("Not a `// run` file: %s", entry.Name())
+ }
+ }
+
+ var failures []string
+ for _, input := range inputs {
+ t.Log("running", input)
+ if !run(t, input) {
+ failures = append(failures, input)
+ }
+ }
+ printFailures(failures)
+}
diff --git a/go/ssa/interp/map.go b/go/ssa/interp/map.go
index 92ccf9034..f5d5f230b 100644
--- a/go/ssa/interp/map.go
+++ b/go/ssa/interp/map.go
@@ -38,7 +38,7 @@ type hashmap struct {
// makeMap returns an empty initialized map of key type kt,
// preallocating space for reserve elements.
-func makeMap(kt types.Type, reserve int) value {
+func makeMap(kt types.Type, reserve int64) value {
if usesBuiltinMap(kt) {
return make(map[value]value, reserve)
}
diff --git a/go/ssa/interp/ops.go b/go/ssa/interp/ops.go
index 3bc6a4e32..39830bc8f 100644
--- a/go/ssa/interp/ops.go
+++ b/go/ssa/interp/ops.go
@@ -34,9 +34,10 @@ type exitPanic int
// constValue returns the value of the constant with the
// dynamic type tag appropriate for c.Type().
func constValue(c *ssa.Const) value {
- if c.IsNil() {
- return zero(c.Type()) // typed nil
+ if c.Value == nil {
+ return zero(c.Type()) // typed zero
}
+ // c is not a type parameter so it's underlying type is basic.
if t, ok := c.Type().Underlying().(*types.Basic); ok {
// TODO(adonovan): eliminate untyped constants from SSA form.
@@ -87,34 +88,46 @@ func constValue(c *ssa.Const) value {
panic(fmt.Sprintf("constValue: %s", c))
}
-// asInt converts x, which must be an integer, to an int suitable for
-// use as a slice or array index or operand to make().
-func asInt(x value) int {
+// fitsInt returns true if x fits in type int according to sizes.
+func fitsInt(x int64, sizes types.Sizes) bool {
+ intSize := sizes.Sizeof(types.Typ[types.Int])
+ if intSize < sizes.Sizeof(types.Typ[types.Int64]) {
+ maxInt := int64(1)<<(intSize-1) - 1
+ minInt := -int64(1) << (intSize - 1)
+ return minInt <= x && x <= maxInt
+ }
+ return true
+}
+
+// asInt64 converts x, which must be an integer, to an int64.
+//
+// Callers that need a value directly usable as an int should combine this with fitsInt().
+func asInt64(x value) int64 {
switch x := x.(type) {
case int:
- return x
+ return int64(x)
case int8:
- return int(x)
+ return int64(x)
case int16:
- return int(x)
+ return int64(x)
case int32:
- return int(x)
+ return int64(x)
case int64:
- return int(x)
+ return x
case uint:
- return int(x)
+ return int64(x)
case uint8:
- return int(x)
+ return int64(x)
case uint16:
- return int(x)
+ return int64(x)
case uint32:
- return int(x)
+ return int64(x)
case uint64:
- return int(x)
+ return int64(x)
case uintptr:
- return int(x)
+ return int64(x)
}
- panic(fmt.Sprintf("cannot convert %T to int", x))
+ panic(fmt.Sprintf("cannot convert %T to int64", x))
}
// asUint64 converts x, which must be an unsigned integer, to a uint64
@@ -268,19 +281,19 @@ func slice(x, lo, hi, max value) value {
Cap = cap(a)
}
- l := 0
+ l := int64(0)
if lo != nil {
- l = asInt(lo)
+ l = asInt64(lo)
}
- h := Len
+ h := int64(Len)
if hi != nil {
- h = asInt(hi)
+ h = asInt64(hi)
}
- m := Cap
+ m := int64(Cap)
if max != nil {
- m = asInt(max)
+ m = asInt64(max)
}
switch x := x.(type) {
@@ -295,7 +308,7 @@ func slice(x, lo, hi, max value) value {
panic(fmt.Sprintf("slice: unexpected X type: %T", x))
}
-// lookup returns x[idx] where x is a map or string.
+// lookup returns x[idx] where x is a map.
func lookup(instr *ssa.Lookup, x, idx value) value {
switch x := x.(type) { // map or string
case map[value]value, *hashmap:
@@ -315,8 +328,6 @@ func lookup(instr *ssa.Lookup, x, idx value) value {
v = tuple{v, ok}
}
return v
- case string:
- return x[asInt(idx)]
}
panic(fmt.Sprintf("unexpected x type in Lookup: %T", x))
}
@@ -324,7 +335,6 @@ func lookup(instr *ssa.Lookup, x, idx value) value {
// binop implements all arithmetic and logical binary operators for
// numeric datatypes and strings. Both operands must have identical
// dynamic type.
-//
func binop(op token.Token, t types.Type, x, y value) value {
switch op {
case token.ADD:
@@ -798,7 +808,6 @@ func binop(op token.Token, t types.Type, x, y value) value {
// appropriate for type t.
// If t is a reference type, at most one of x or y may be a nil value
// of that type.
-//
func eqnil(t types.Type, x, y value) bool {
switch t.Underlying().(type) {
case *types.Map, *types.Signature, *types.Slice:
@@ -907,7 +916,6 @@ func unop(instr *ssa.UnOp, x value) value {
// typeAssert checks whether dynamic type of itf is instr.AssertedType.
// It returns the extracted value on success, and panics on failure,
// unless instr.CommaOk, in which case it always returns a "value,ok" tuple.
-//
func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value {
var v value
err := ""
@@ -924,6 +932,8 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value {
} else {
err = fmt.Sprintf("interface conversion: interface is %s, not %s", itf.t, instr.AssertedType)
}
+ // Note: if instr.Underlying==true ever becomes reachable from interp check that
+ // types.Identical(itf.t.Underlying(), instr.AssertedType)
if err != "" {
if !instr.CommaOk {
@@ -944,7 +954,6 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value {
// failure if "BUG" appears in the combined stdout/stderr output, even
// if it exits zero. This is a global variable shared by all
// interpreters in the same process.)
-//
var CapturedOutput *bytes.Buffer
var capturedOutputMu sync.Mutex
@@ -1117,10 +1126,11 @@ func rangeIter(x value, t types.Type) iter {
// widen widens a basic typed value x to the widest type of its
// category, one of:
-// bool, int64, uint64, float64, complex128, string.
+//
+// bool, int64, uint64, float64, complex128, string.
+//
// This is inefficient but reduces the size of the cross-product of
// cases we have to consider.
-//
func widen(x value) value {
switch y := x.(type) {
case bool, int64, uint64, float64, complex128, string, unsafe.Pointer:
@@ -1154,7 +1164,6 @@ func widen(x value) value {
// conv converts the value x of type t_src to type t_dst and returns
// the result.
// Possible cases are described with the ssa.Convert operator.
-//
func conv(t_dst, t_src types.Type, x value) value {
ut_src := t_src.Underlying()
ut_dst := t_dst.Underlying()
@@ -1388,18 +1397,15 @@ func conv(t_dst, t_src types.Type, x value) value {
// sliceToArrayPointer converts the value x of type slice to type t_dst
// a pointer to array and returns the result.
func sliceToArrayPointer(t_dst, t_src types.Type, x value) value {
- utSrc := t_src.Underlying()
- utDst := t_dst.Underlying()
-
- if _, ok := utSrc.(*types.Slice); ok {
- if utSrc, ok := utDst.(*types.Pointer); ok {
- if arr, ok := utSrc.Elem().(*types.Array); ok {
+ if _, ok := t_src.Underlying().(*types.Slice); ok {
+ if ptr, ok := t_dst.Underlying().(*types.Pointer); ok {
+ if arr, ok := ptr.Elem().Underlying().(*types.Array); ok {
x := x.([]value)
if arr.Len() > int64(len(x)) {
panic("array length is greater than slice length")
}
if x == nil {
- return zero(utSrc)
+ return zero(t_dst)
}
v := value(array(x[:arr.Len()]))
return &v
@@ -1413,7 +1419,6 @@ func sliceToArrayPointer(t_dst, t_src types.Type, x value) value {
// checkInterface checks that the method set of x implements the
// interface itype.
// On success it returns "", on failure, an error message.
-//
func checkInterface(i *interpreter, itype *types.Interface, x iface) string {
if meth, _ := types.MissingMethod(x.t, itype, true); meth != nil {
return fmt.Sprintf("interface conversion: %v is not %v: missing method %s",
diff --git a/go/ssa/interp/reflect.go b/go/ssa/interp/reflect.go
index 0a4465b0b..9f2f9e1e4 100644
--- a/go/ssa/interp/reflect.go
+++ b/go/ssa/interp/reflect.go
@@ -407,7 +407,11 @@ func ext۰reflect۰Value۰Elem(fr *frame, args []value) value {
case iface:
return makeReflectValue(x.t, x.v)
case *value:
- return makeReflectValue(rV2T(args[0]).t.Underlying().(*types.Pointer).Elem(), *x)
+ var v value
+ if x != nil {
+ v = *x
+ }
+ return makeReflectValue(rV2T(args[0]).t.Underlying().(*types.Pointer).Elem(), v)
default:
panic(fmt.Sprintf("reflect.(Value).Elem(%T)", x))
}
diff --git a/go/ssa/interp/testdata/boundmeth.go b/go/ssa/interp/testdata/boundmeth.go
index 69937f9d3..47b940685 100644
--- a/go/ssa/interp/testdata/boundmeth.go
+++ b/go/ssa/interp/testdata/boundmeth.go
@@ -123,7 +123,8 @@ func nilInterfaceMethodValue() {
r := fmt.Sprint(recover())
// runtime panic string varies across toolchains
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" &&
+ r != "method value: interface is nil" {
panic("want runtime panic from nil interface method value, got " + r)
}
}()
diff --git a/go/ssa/interp/testdata/convert.go b/go/ssa/interp/testdata/convert.go
index 0dcf13bdd..76310405f 100644
--- a/go/ssa/interp/testdata/convert.go
+++ b/go/ssa/interp/testdata/convert.go
@@ -22,6 +22,15 @@ func main() {
},
"runtime error: negative shift amount",
)
+ wantPanic(
+ func() {
+ const maxInt32 = 1<<31 - 1
+ var idx int64 = maxInt32*2 + 8
+ x := make([]int, 16)
+ _ = x[idx]
+ },
+ "runtime error: runtime error: index out of range [4294967302] with length 16",
+ )
}
func wantPanic(fn func(), s string) {
diff --git a/go/ssa/interp/testdata/deepequal.go b/go/ssa/interp/testdata/deepequal.go
new file mode 100644
index 000000000..4fad2d657
--- /dev/null
+++ b/go/ssa/interp/testdata/deepequal.go
@@ -0,0 +1,93 @@
+// This interpreter test is designed to test the test copy of DeepEqual.
+//
+// Validate this file with 'go run' after editing.
+
+package main
+
+import "reflect"
+
+func assert(cond bool) {
+ if !cond {
+ panic("failed")
+ }
+}
+
+type X int
+type Y struct {
+ y *Y
+ z [3]int
+}
+
+var (
+ a = []int{0, 1, 2, 3}
+ b = []X{0, 1, 2, 3}
+ c = map[int]string{0: "zero", 1: "one"}
+ d = map[X]string{0: "zero", 1: "one"}
+ e = &Y{}
+ f = (*Y)(nil)
+ g = &Y{y: e}
+ h *Y
+)
+
+func init() {
+ h = &Y{} // h->h
+ h.y = h
+}
+
+func main() {
+ assert(reflect.DeepEqual(nil, nil))
+ assert(reflect.DeepEqual((*int)(nil), (*int)(nil)))
+ assert(!reflect.DeepEqual(nil, (*int)(nil)))
+
+ assert(reflect.DeepEqual(0, 0))
+ assert(!reflect.DeepEqual(0, int64(0)))
+
+ assert(!reflect.DeepEqual("", 0))
+
+ assert(reflect.DeepEqual(a, []int{0, 1, 2, 3}))
+ assert(!reflect.DeepEqual(a, []int{0, 1, 2}))
+ assert(!reflect.DeepEqual(a, []int{0, 1, 0, 3}))
+
+ assert(reflect.DeepEqual(b, []X{0, 1, 2, 3}))
+ assert(!reflect.DeepEqual(b, []X{0, 1, 0, 3}))
+
+ assert(reflect.DeepEqual(c, map[int]string{0: "zero", 1: "one"}))
+ assert(!reflect.DeepEqual(c, map[int]string{0: "zero", 1: "one", 2: "two"}))
+ assert(!reflect.DeepEqual(c, map[int]string{1: "one", 2: "two"}))
+ assert(!reflect.DeepEqual(c, map[int]string{1: "one"}))
+
+ assert(reflect.DeepEqual(d, map[X]string{0: "zero", 1: "one"}))
+ assert(!reflect.DeepEqual(d, map[int]string{0: "zero", 1: "one"}))
+
+ assert(reflect.DeepEqual(e, &Y{}))
+ assert(reflect.DeepEqual(e, &Y{z: [3]int{0, 0, 0}}))
+ assert(!reflect.DeepEqual(e, &Y{z: [3]int{0, 1, 0}}))
+
+ assert(reflect.DeepEqual(f, (*Y)(nil)))
+ assert(!reflect.DeepEqual(f, nil))
+
+ // eq_h -> eq_h. Pointer structure and elements are equal so DeepEqual.
+ eq_h := &Y{}
+ eq_h.y = eq_h
+ assert(reflect.DeepEqual(h, eq_h))
+
+ // deepeq_h->h->h. Pointed to elem of (deepeq_h, h) are (h,h). (h,h) are deep equal so h and deepeq_h are DeepEqual.
+ deepeq_h := &Y{}
+ deepeq_h.y = h
+ assert(reflect.DeepEqual(h, deepeq_h))
+
+ distinct := []interface{}{a, b, c, d, e, f, g, h}
+ for x := range distinct {
+ for y := range distinct {
+ assert((x == y) == reflect.DeepEqual(distinct[x], distinct[y]))
+ }
+ }
+
+ // anonymous struct types.
+ assert(reflect.DeepEqual(struct{}{}, struct{}{}))
+ assert(reflect.DeepEqual(struct{ x int }{1}, struct{ x int }{1}))
+ assert(!reflect.DeepEqual(struct{ x int }{}, struct{ x int }{5}))
+ assert(!reflect.DeepEqual(struct{ x, y int }{0, 1}, struct{ x int }{0}))
+ assert(reflect.DeepEqual(struct{ x, y int }{2, 3}, struct{ x, y int }{2, 3}))
+ assert(!reflect.DeepEqual(struct{ x, y int }{4, 5}, struct{ x, y int }{4, 6}))
+}
diff --git a/go/ssa/interp/testdata/fixedbugs/issue52342.go b/go/ssa/interp/testdata/fixedbugs/issue52342.go
new file mode 100644
index 000000000..2e1cc63cf
--- /dev/null
+++ b/go/ssa/interp/testdata/fixedbugs/issue52342.go
@@ -0,0 +1,17 @@
+package main
+
+func main() {
+ var d byte
+
+ d = 1
+ d <<= 256
+ if d != 0 {
+ panic(d)
+ }
+
+ d = 1
+ d >>= 256
+ if d != 0 {
+ panic(d)
+ }
+}
diff --git a/go/ssa/interp/testdata/fixedbugs/issue52835.go b/go/ssa/interp/testdata/fixedbugs/issue52835.go
new file mode 100644
index 000000000..f1d99abb7
--- /dev/null
+++ b/go/ssa/interp/testdata/fixedbugs/issue52835.go
@@ -0,0 +1,27 @@
+package main
+
+var called bool
+
+type I interface {
+ Foo()
+}
+
+type A struct{}
+
+func (a A) Foo() {
+ called = true
+}
+
+func lambda[X I]() func() func() {
+ return func() func() {
+ var x X
+ return x.Foo
+ }
+}
+
+func main() {
+ lambda[A]()()()
+ if !called {
+ panic(called)
+ }
+}
diff --git a/go/ssa/interp/testdata/fixedbugs/issue55086.go b/go/ssa/interp/testdata/fixedbugs/issue55086.go
new file mode 100644
index 000000000..84c81e91a
--- /dev/null
+++ b/go/ssa/interp/testdata/fixedbugs/issue55086.go
@@ -0,0 +1,132 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func a() (r string) {
+ s := "initial"
+ var p *struct{ i int }
+ defer func() {
+ recover()
+ r = s
+ }()
+
+ s, p.i = "set", 2 // s must be set before p.i panics
+ return "unreachable"
+}
+
+func b() (r string) {
+ s := "initial"
+ fn := func() []int { panic("") }
+ defer func() {
+ recover()
+ r = s
+ }()
+
+ s, fn()[0] = "set", 2 // fn() panics before any assignment occurs
+ return "unreachable"
+}
+
+func c() (r string) {
+ s := "initial"
+ var p map[int]int
+ defer func() {
+ recover()
+ r = s
+ }()
+
+ s, p[0] = "set", 2 //s must be set before p[0] index panics"
+ return "unreachable"
+}
+
+func d() (r string) {
+ s := "initial"
+ var p map[int]int
+ defer func() {
+ recover()
+ r = s
+ }()
+ fn := func() int { panic("") }
+
+ s, p[0] = "set", fn() // fn() panics before s is set
+ return "unreachable"
+}
+
+func e() (r string) {
+ s := "initial"
+ p := map[int]int{}
+ defer func() {
+ recover()
+ r = s
+ }()
+ fn := func() int { panic("") }
+
+ s, p[fn()] = "set", 0 // fn() panics before any assignment occurs
+ return "unreachable"
+}
+
+func f() (r string) {
+ s := "initial"
+ p := []int{}
+ defer func() {
+ recover()
+ r = s
+ }()
+
+ s, p[1] = "set", 0 // p[1] panics after s is set
+ return "unreachable"
+}
+
+func g() (r string) {
+ s := "initial"
+ p := map[any]any{}
+ defer func() {
+ recover()
+ r = s
+ }()
+ var i any = func() {}
+ s, p[i] = "set", 0 // p[i] panics after s is set
+ return "unreachable"
+}
+
+func h() (r string) {
+ fail := false
+ defer func() {
+ recover()
+ if fail {
+ r = "fail"
+ } else {
+ r = "success"
+ }
+ }()
+
+ type T struct{ f int }
+ var p *struct{ *T }
+
+ // The implicit "p.T" operand should be evaluated in phase 1 (and panic),
+ // before the "fail = true" assignment in phase 2.
+ fail, p.f = true, 0
+ return "unreachable"
+}
+
+func main() {
+ for _, test := range []struct {
+ fn func() string
+ want string
+ desc string
+ }{
+ {a, "set", "s must be set before p.i panics"},
+ {b, "initial", "p() panics before s is set"},
+ {c, "set", "s must be set before p[0] index panics"},
+ {d, "initial", "fn() panics before s is set"},
+ {e, "initial", "fn() panics before s is set"},
+ {f, "set", "p[1] panics after s is set"},
+ {g, "set", "p[i] panics after s is set"},
+ {h, "success", "p.T panics before fail is set"},
+ } {
+ if test.fn() != test.want {
+ panic(test.desc)
+ }
+ }
+}
diff --git a/go/ssa/interp/testdata/slice2array.go b/go/ssa/interp/testdata/slice2array.go
new file mode 100644
index 000000000..84e6b7330
--- /dev/null
+++ b/go/ssa/interp/testdata/slice2array.go
@@ -0,0 +1,92 @@
+// Copyright 2022 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.
+
+// Test for slice to array conversion introduced in go1.20
+// See: https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
+
+package main
+
+func main() {
+ s := make([]byte, 3, 4)
+ s[0], s[1], s[2] = 2, 3, 5
+ a := ([2]byte)(s)
+ s[0] = 7
+
+ if a != [2]byte{2, 3} {
+ panic("converted from non-nil slice to array")
+ }
+
+ {
+ var s []int
+ a := ([0]int)(s)
+ if a != [0]int{} {
+ panic("zero len array is not equal")
+ }
+ }
+
+ if emptyToEmptyDoesNotPanic() {
+ panic("no panic expected from emptyToEmptyDoesNotPanic()")
+ }
+ if !threeToFourDoesPanic() {
+ panic("panic expected from threeToFourDoesPanic()")
+ }
+
+ if !fourPanicsWhileOneDoesNot[[4]int]() {
+ panic("panic expected from fourPanicsWhileOneDoesNot[[4]int]()")
+ }
+ if fourPanicsWhileOneDoesNot[[1]int]() {
+ panic("no panic expected from fourPanicsWhileOneDoesNot[[1]int]()")
+ }
+
+ if !fourPanicsWhileZeroDoesNot[[4]int]() {
+ panic("panic expected from fourPanicsWhileZeroDoesNot[[4]int]()")
+ }
+ if fourPanicsWhileZeroDoesNot[[0]int]() {
+ panic("no panic expected from fourPanicsWhileZeroDoesNot[[0]int]()")
+ }
+}
+
+func emptyToEmptyDoesNotPanic() (raised bool) {
+ defer func() {
+ if e := recover(); e != nil {
+ raised = true
+ }
+ }()
+ var s []int
+ _ = ([0]int)(s)
+ return false
+}
+
+func threeToFourDoesPanic() (raised bool) {
+ defer func() {
+ if e := recover(); e != nil {
+ raised = true
+ }
+ }()
+ s := make([]int, 3, 5)
+ _ = ([4]int)(s)
+ return false
+}
+
+func fourPanicsWhileOneDoesNot[T [1]int | [4]int]() (raised bool) {
+ defer func() {
+ if e := recover(); e != nil {
+ raised = true
+ }
+ }()
+ s := make([]int, 3, 5)
+ _ = T(s)
+ return false
+}
+
+func fourPanicsWhileZeroDoesNot[T [0]int | [4]int]() (raised bool) {
+ defer func() {
+ if e := recover(); e != nil {
+ raised = true
+ }
+ }()
+ var s []int
+ _ = T(s)
+ return false
+}
diff --git a/go/ssa/interp/testdata/slice2arrayptr.go b/go/ssa/interp/testdata/slice2arrayptr.go
index ff2d9b55c..d9d8804d3 100644
--- a/go/ssa/interp/testdata/slice2arrayptr.go
+++ b/go/ssa/interp/testdata/slice2arrayptr.go
@@ -32,6 +32,8 @@ func main() {
},
"runtime error: array length is greater than slice length",
)
+
+ f()
}
type arr [2]int
diff --git a/go/ssa/interp/testdata/src/encoding/encoding.go b/go/ssa/interp/testdata/src/encoding/encoding.go
new file mode 100644
index 000000000..73e9de494
--- /dev/null
+++ b/go/ssa/interp/testdata/src/encoding/encoding.go
@@ -0,0 +1,15 @@
+package encoding
+
+type BinaryMarshaler interface {
+ MarshalBinary() (data []byte, err error)
+}
+type BinaryUnmarshaler interface {
+ UnmarshalBinary(data []byte) error
+}
+
+type TextMarshaler interface {
+ MarshalText() (text []byte, err error)
+}
+type TextUnmarshaler interface {
+ UnmarshalText(text []byte) error
+}
diff --git a/go/ssa/interp/testdata/src/log/log.go b/go/ssa/interp/testdata/src/log/log.go
index 8897c1d21..9a57e8c1c 100644
--- a/go/ssa/interp/testdata/src/log/log.go
+++ b/go/ssa/interp/testdata/src/log/log.go
@@ -8,8 +8,16 @@ import (
func Println(v ...interface{}) {
fmt.Println(v...)
}
+func Printf(format string, v ...interface{}) {
+ fmt.Printf(format, v...)
+}
func Fatalln(v ...interface{}) {
Println(v...)
os.Exit(1)
}
+
+func Fatalf(format string, v ...interface{}) {
+ Printf(format, v...)
+ os.Exit(1)
+}
diff --git a/go/ssa/interp/testdata/src/reflect/deepequal.go b/go/ssa/interp/testdata/src/reflect/deepequal.go
new file mode 100644
index 000000000..a48e4dafa
--- /dev/null
+++ b/go/ssa/interp/testdata/src/reflect/deepequal.go
@@ -0,0 +1,109 @@
+package reflect
+
+// Not an actual implementation of DeepEqual. This is a model that supports
+// the bare minimum needed to get through testing interp.
+//
+// Does not handle cycles.
+//
+// Note: unclear if reflect.go can support this.
+func DeepEqual(x, y interface{}) bool {
+ if x == nil || y == nil {
+ return x == y
+ }
+ v1 := ValueOf(x)
+ v2 := ValueOf(y)
+
+ return deepValueEqual(v1, v2, make(map[visit]bool))
+}
+
+// Key for the visitedMap in deepValueEqual.
+type visit struct {
+ a1, a2 uintptr
+ typ Type
+}
+
+func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
+ if !v1.IsValid() || !v2.IsValid() {
+ return v1.IsValid() == v2.IsValid()
+ }
+ if v1.Type() != v2.Type() {
+ return false
+ }
+
+ // Short circuit on reference types that can lead to cycles in comparison.
+ switch v1.Kind() {
+ case Pointer, Map, Slice, Interface:
+ k := visit{v1.Pointer(), v2.Pointer(), v1.Type()} // Not safe for moving GC.
+ if visited[k] {
+ // The comparison algorithm assumes that all checks in progress are true when it reencounters them.
+ return true
+ }
+ visited[k] = true
+ }
+
+ switch v1.Kind() {
+ case Array:
+ for i := 0; i < v1.Len(); i++ {
+ if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
+ return false
+ }
+ }
+ return true
+ case Slice:
+ if v1.IsNil() != v2.IsNil() {
+ return false
+ }
+ if v1.Len() != v2.Len() {
+ return false
+ }
+ if v1.Pointer() == v2.Pointer() {
+ return true
+ }
+ for i := 0; i < v1.Len(); i++ {
+ if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
+ return false
+ }
+ }
+ return true
+ case Interface:
+ if v1.IsNil() || v2.IsNil() {
+ return v1.IsNil() == v2.IsNil()
+ }
+ return deepValueEqual(v1.Elem(), v2.Elem(), visited)
+ case Ptr:
+ if v1.Pointer() == v2.Pointer() {
+ return true
+ }
+ return deepValueEqual(v1.Elem(), v2.Elem(), visited)
+ case Struct:
+ for i, n := 0, v1.NumField(); i < n; i++ {
+ if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {
+ return false
+ }
+ }
+ return true
+ case Map:
+ if v1.IsNil() != v2.IsNil() {
+ return false
+ }
+ if v1.Len() != v2.Len() {
+ return false
+ }
+ if v1.Pointer() == v2.Pointer() {
+ return true
+ }
+ for _, k := range v1.MapKeys() {
+ val1 := v1.MapIndex(k)
+ val2 := v2.MapIndex(k)
+ if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {
+ return false
+ }
+ }
+ return true
+ case Func:
+ return v1.IsNil() && v2.IsNil()
+ default:
+ // Normal equality suffices
+ return v1.Interface() == v2.Interface() // try interface comparison as a fallback.
+ }
+}
diff --git a/go/ssa/interp/testdata/src/reflect/reflect.go b/go/ssa/interp/testdata/src/reflect/reflect.go
index 8a23d272f..207e7dcfd 100644
--- a/go/ssa/interp/testdata/src/reflect/reflect.go
+++ b/go/ssa/interp/testdata/src/reflect/reflect.go
@@ -11,9 +11,20 @@ type Value struct {
func (Value) String() string
-func (Value) Elem() string
+func (Value) Elem() Value
func (Value) Kind() Kind
func (Value) Int() int64
+func (Value) IsValid() bool
+func (Value) IsNil() bool
+func (Value) Len() int
+func (Value) Pointer() uintptr
+func (Value) Index(i int) Value
+func (Value) Type() Type
+func (Value) Field(int) Value
+func (Value) MapIndex(Value) Value
+func (Value) MapKeys() []Value
+func (Value) NumField() int
+func (Value) Interface() interface{}
func SliceOf(Type) Type
diff --git a/go/ssa/interp/testdata/typeassert.go b/go/ssa/interp/testdata/typeassert.go
new file mode 100644
index 000000000..792a7558f
--- /dev/null
+++ b/go/ssa/interp/testdata/typeassert.go
@@ -0,0 +1,32 @@
+// Tests of type asserts.
+// Requires type parameters.
+package typeassert
+
+type fooer interface{ foo() string }
+
+type X int
+
+func (_ X) foo() string { return "x" }
+
+func f[T fooer](x T) func() string {
+ return x.foo
+}
+
+func main() {
+ if f[X](0)() != "x" {
+ panic("f[X]() != 'x'")
+ }
+
+ p := false
+ func() {
+ defer func() {
+ if recover() != nil {
+ p = true
+ }
+ }()
+ f[fooer](nil) // panics on x.foo when T is an interface and nil.
+ }()
+ if !p {
+ panic("f[fooer] did not panic")
+ }
+}
diff --git a/go/ssa/interp/testdata/width32.go b/go/ssa/interp/testdata/width32.go
new file mode 100644
index 000000000..a032ba44c
--- /dev/null
+++ b/go/ssa/interp/testdata/width32.go
@@ -0,0 +1,42 @@
+// Copyright 2022 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.
+
+// Test interpretation on 32 bit widths.
+
+package main
+
+func main() {
+ mapSize()
+}
+
+func mapSize() {
+ // Tests for the size argument of make on a map type.
+ const tooBigFor32 = 1<<33 - 1
+ wantPanic(
+ func() {
+ _ = make(map[int]int, int64(tooBigFor32))
+ },
+ "runtime error: ssa.MakeMap.Reserve value 8589934591 does not fit in int",
+ )
+
+ // TODO: Enable the following if sizeof(int) can be different for host and target.
+ // _ = make(map[int]int, tooBigFor32)
+ //
+ // Second arg to make in `make(map[int]int, tooBigFor32)` is an untyped int and
+ // is converted into an int explicitly in ssa.
+ // This has a different value on 32 and 64 bit systems.
+}
+
+func wantPanic(fn func(), s string) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ panic("expected panic")
+ }
+ if got := err.(error).Error(); got != s {
+ panic("expected panic " + s + " got " + got)
+ }
+ }()
+ fn()
+}
diff --git a/go/ssa/interp/testdata/zeros.go b/go/ssa/interp/testdata/zeros.go
new file mode 100644
index 000000000..509c78a36
--- /dev/null
+++ b/go/ssa/interp/testdata/zeros.go
@@ -0,0 +1,45 @@
+// Copyright 2022 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.
+
+// Test interpretation on zero values with type params.
+package zeros
+
+func assert(cond bool, msg string) {
+ if !cond {
+ panic(msg)
+ }
+}
+
+func tp0[T int | string | float64]() T { return T(0) }
+
+func tpFalse[T ~bool]() T { return T(false) }
+
+func tpEmptyString[T string | []byte]() T { return T("") }
+
+func tpNil[T *int | []byte]() T { return T(nil) }
+
+func main() {
+ // zero values
+ var zi int
+ var zf float64
+ var zs string
+
+ assert(zi == int(0), "zero value of int is int(0)")
+ assert(zf == float64(0), "zero value of float64 is float64(0)")
+ assert(zs != string(0), "zero value of string is not string(0)")
+
+ assert(zi == tp0[int](), "zero value of int is int(0)")
+ assert(zf == tp0[float64](), "zero value of float64 is float64(0)")
+ assert(zs != tp0[string](), "zero value of string is not string(0)")
+
+ assert(zf == -0.0, "constant -0.0 is converted to 0.0")
+
+ assert(!tpFalse[bool](), "zero value of bool is false")
+
+ assert(tpEmptyString[string]() == zs, `zero value of string is string("")`)
+ assert(len(tpEmptyString[[]byte]()) == 0, `[]byte("") is empty`)
+
+ assert(tpNil[*int]() == nil, "nil is nil")
+ assert(tpNil[[]byte]() == nil, "nil is nil")
+}