diff options
Diffstat (limited to 'go/ssa/interp')
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") +} |