diff --git a/README.md b/README.md index 505f5707..6c6c31ee 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![release](https://img.shields.io/github/tag-date/traefik/yaegi.svg?label=alpha)](https://github.com/traefik/yaegi/releases) [![Build Status](https://github.com/traefik/yaegi/actions/workflows/main.yml/badge.svg)](https://github.com/traefik/yaegi/actions/workflows/main.yml) [![GoDoc](https://godoc.org/github.com/traefik/yaegi?status.svg)](https://pkg.go.dev/mod/github.com/traefik/yaegi) -[![Discourse status](https://img.shields.io/discourse/https/community.traefik.io/status?label=Community&style=social)](https://community.traefik.io/c/yaegi) Yaegi is Another Elegant Go Interpreter. It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go runtime. diff --git a/_test/unsafe10.go b/_test/unsafe10.go new file mode 100644 index 00000000..b5847d5a --- /dev/null +++ b/_test/unsafe10.go @@ -0,0 +1,17 @@ +package main + +import "unsafe" + +type T struct { + X uint64 + Y uint64 +} + +func f(off uintptr) { println(off) } + +func main() { + f(unsafe.Offsetof(T{}.Y)) +} + +// Output: +// 8 diff --git a/_test/unsafe6.go b/_test/unsafe6.go index 747bd07b..b5109acb 100644 --- a/_test/unsafe6.go +++ b/_test/unsafe6.go @@ -21,3 +21,6 @@ func main() { fmt.Println(i) } + +// Output: +// 5 diff --git a/_test/unsafe8.go b/_test/unsafe8.go new file mode 100644 index 00000000..6c182b2b --- /dev/null +++ b/_test/unsafe8.go @@ -0,0 +1,18 @@ +package main + +import "unsafe" + +type T struct { + i uint64 +} + +var d T + +var b [unsafe.Sizeof(d)]byte + +func main() { + println(len(b)) +} + +// Output: +// 8 diff --git a/interp/cfg.go b/interp/cfg.go index 7b350a4e..8bc45910 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -45,6 +45,13 @@ var constBltn = map[string]func(*node){ const nilIdent = "nil" +func init() { + // Use init() to avoid initialization cycles for the following constant builtins. + constBltn[bltnAlignof] = alignof + constBltn[bltnOffsetof] = offsetof + constBltn[bltnSizeof] = sizeof +} + // cfg generates a control flow graph (CFG) from AST (wiring successors in AST) // and pre-compute frame sizes and indexes for all un-named (temporary) and named // variables. A list of nodes of init functions is returned. @@ -422,7 +429,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string case funcDecl: // Do not allow function declarations without body. if len(n.child) < 4 { - err = n.cfgErrorf("function declaration without body is unsupported (linkname or assembly can not be interpreted).") + err = n.cfgErrorf("missing function body") return false } n.val = n @@ -1154,6 +1161,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string case n.typ.cat == builtinT: n.findex = notInFrame n.val = nil + switch bname { + case "unsafe.alignOf", "unsafe.Offsetof", "unsafe.Sizeof": + n.gen = nop + } case n.anc.kind == returnStmt: // Store result directly to frame output location, to avoid a frame copy. n.findex = 0 @@ -1186,8 +1197,8 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string default: n.findex = sc.add(n.typ) } - if op, ok := constBltn[bname]; ok && n.anc.action != aAssign { - op(n) // pre-compute non-assigned constant : + if op, ok := constBltn[bname]; ok { + op(n) } case c0.isType(sc): @@ -1268,21 +1279,6 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } } - case isOffsetof(c0): - if len(n.child) != 2 || n.child[1].kind != selectorExpr || !isStruct(n.child[1].child[0].typ) { - err = n.cfgErrorf("Offsetof argument: invalid expression") - break - } - c1 := n.child[1] - field, ok := c1.child[0].typ.rtype.FieldByName(c1.child[1].ident) - if !ok { - err = n.cfgErrorf("struct does not contain field: %s", c1.child[1].ident) - break - } - n.typ = valueTOf(reflect.TypeOf(field.Offset)) - n.rval = reflect.ValueOf(field.Offset) - n.gen = nop - default: // The call may be on a generic function. In that case, replace the // generic function AST by an instantiated one before going further. @@ -1816,6 +1812,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } else { n.typ = valueTOf(fixPossibleConstType(s.Type()), withUntyped(isValueUntyped(s))) n.rval = s + if pkg == "unsafe" && (name == "AlignOf" || name == "Offsetof" || name == "Sizeof") { + n.sym = &symbol{kind: bltnSym, node: n, rval: s} + n.ident = pkg + "." + name + } } n.action = aGetSym n.gen = nop @@ -2794,10 +2794,6 @@ func isBinCall(n *node, sc *scope) bool { return c0.typ.cat == valueT && c0.typ.rtype.Kind() == reflect.Func } -func isOffsetof(n *node) bool { - return n.typ != nil && n.typ.cat == valueT && n.rval.String() == "Offsetof" -} - func mustReturnValue(n *node) bool { if len(n.child) < 3 { return false @@ -3136,3 +3132,24 @@ func isBlank(n *node) bool { } return n.ident == "_" } + +func alignof(n *node) { + n.gen = nop + n.typ = n.scope.getType("uintptr") + n.rval = reflect.ValueOf(uintptr(n.child[1].typ.TypeOf().Align())) +} + +func offsetof(n *node) { + n.gen = nop + n.typ = n.scope.getType("uintptr") + c1 := n.child[1] + if field, ok := c1.child[0].typ.rtype.FieldByName(c1.child[1].ident); ok { + n.rval = reflect.ValueOf(field.Offset) + } +} + +func sizeof(n *node) { + n.gen = nop + n.typ = n.scope.getType("uintptr") + n.rval = reflect.ValueOf(n.child[1].typ.TypeOf().Size()) +} diff --git a/interp/interp.go b/interp/interp.go index 64f3bf21..b5cbd032 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -405,21 +405,24 @@ func New(options Options) *Interpreter { } const ( - bltnAppend = "append" - bltnCap = "cap" - bltnClose = "close" - bltnComplex = "complex" - bltnImag = "imag" - bltnCopy = "copy" - bltnDelete = "delete" - bltnLen = "len" - bltnMake = "make" - bltnNew = "new" - bltnPanic = "panic" - bltnPrint = "print" - bltnPrintln = "println" - bltnReal = "real" - bltnRecover = "recover" + bltnAlignof = "unsafe.Alignof" + bltnAppend = "append" + bltnCap = "cap" + bltnClose = "close" + bltnComplex = "complex" + bltnImag = "imag" + bltnCopy = "copy" + bltnDelete = "delete" + bltnLen = "len" + bltnMake = "make" + bltnNew = "new" + bltnOffsetof = "unsafe.Offsetof" + bltnPanic = "panic" + bltnPrint = "print" + bltnPrintln = "println" + bltnReal = "real" + bltnRecover = "recover" + bltnSizeof = "unsafe.Sizeof" ) func initUniverse() *scope { diff --git a/interp/interp_eval_test.go b/interp/interp_eval_test.go index b54c5d41..c4ce5990 100644 --- a/interp/interp_eval_test.go +++ b/interp/interp_eval_test.go @@ -708,7 +708,7 @@ func TestEvalCall(t *testing.T) { {src: ` test := func(a, b int) int { return a } blah := func() (int, float64) { return 1, 1.1 } a := test(blah())`, err: "3:15: cannot use func() (int,float64) as type (int,int)"}, - {src: "func f()", err: "function declaration without body is unsupported"}, + {src: "func f()", err: "missing function body"}, }) } diff --git a/interp/type.go b/interp/type.go index 77fc4b3b..39b5f903 100644 --- a/interp/type.go +++ b/interp/type.go @@ -492,6 +492,9 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, } return nil, err } + if !c0.rval.IsValid() { + return nil, c0.cfgErrorf("undefined array size") + } if length, ok = c0.rval.Interface().(int); !ok { v, ok := c0.rval.Interface().(constant.Value) if !ok { @@ -1339,7 +1342,7 @@ func (t *itype) numOut() int { } case builtinT: switch t.name { - case "append", "cap", "complex", "copy", "imag", "len", "make", "new", "real", "recover": + case "append", "cap", "complex", "copy", "imag", "len", "make", "new", "real", "recover", "unsafe.Alignof", "unsafe.Offsetof", "unsafe.Sizeof": return 1 } } diff --git a/interp/typecheck.go b/interp/typecheck.go index a51a3fc2..3ebcd2a3 100644 --- a/interp/typecheck.go +++ b/interp/typecheck.go @@ -723,21 +723,24 @@ var builtinFuncs = map[string]struct { args int variadic bool }{ - bltnAppend: {args: 1, variadic: true}, - bltnCap: {args: 1, variadic: false}, - bltnClose: {args: 1, variadic: false}, - bltnComplex: {args: 2, variadic: false}, - bltnImag: {args: 1, variadic: false}, - bltnCopy: {args: 2, variadic: false}, - bltnDelete: {args: 2, variadic: false}, - bltnLen: {args: 1, variadic: false}, - bltnMake: {args: 1, variadic: true}, - bltnNew: {args: 1, variadic: false}, - bltnPanic: {args: 1, variadic: false}, - bltnPrint: {args: 0, variadic: true}, - bltnPrintln: {args: 0, variadic: true}, - bltnReal: {args: 1, variadic: false}, - bltnRecover: {args: 0, variadic: false}, + bltnAlignof: {args: 1, variadic: false}, + bltnAppend: {args: 1, variadic: true}, + bltnCap: {args: 1, variadic: false}, + bltnClose: {args: 1, variadic: false}, + bltnComplex: {args: 2, variadic: false}, + bltnImag: {args: 1, variadic: false}, + bltnCopy: {args: 2, variadic: false}, + bltnDelete: {args: 2, variadic: false}, + bltnLen: {args: 1, variadic: false}, + bltnMake: {args: 1, variadic: true}, + bltnNew: {args: 1, variadic: false}, + bltnOffsetof: {args: 1, variadic: false}, + bltnPanic: {args: 1, variadic: false}, + bltnPrint: {args: 0, variadic: true}, + bltnPrintln: {args: 0, variadic: true}, + bltnReal: {args: 1, variadic: false}, + bltnRecover: {args: 0, variadic: false}, + bltnSizeof: {args: 1, variadic: false}, } func (check typecheck) builtin(name string, n *node, child []*node, ellipsis bool) error { @@ -927,7 +930,7 @@ func (check typecheck) builtin(name string, n *node, child []*node, ellipsis boo return err } } - case bltnRecover, bltnNew: + case bltnRecover, bltnNew, bltnAlignof, bltnOffsetof, bltnSizeof: // Nothing to do. default: return n.cfgErrorf("unsupported builtin %s", name) @@ -1093,6 +1096,9 @@ func (check typecheck) convertUntyped(n *node, typ *itype) error { return convErr } return nil + case n.typ.isNil() && typ.id() == "unsafe.Pointer": + n.typ = typ + return nil default: return convErr } diff --git a/stdlib/unsafe/unsafe.go b/stdlib/unsafe/unsafe.go index 732598e5..495a3ec6 100644 --- a/stdlib/unsafe/unsafe.go +++ b/stdlib/unsafe/unsafe.go @@ -20,10 +20,13 @@ func init() { "convert": reflect.ValueOf(convert), } - // Add builtin functions to unsafe. + Symbols["unsafe/unsafe"]["Add"] = reflect.ValueOf(add) + + // Add builtin functions to unsafe, also implemented in interp/cfg.go. Symbols["unsafe/unsafe"]["Sizeof"] = reflect.ValueOf(sizeof) Symbols["unsafe/unsafe"]["Alignof"] = reflect.ValueOf(alignof) - Symbols["unsafe/unsafe"]["Offsetof"] = reflect.ValueOf("Offsetof") // This symbol is handled directly in interpreter. + // The following is used for signature check only. + Symbols["unsafe/unsafe"]["Offsetof"] = reflect.ValueOf(func(interface{}) uintptr { return 0 }) } func convert(from, to reflect.Type) func(src, dest reflect.Value) { @@ -50,6 +53,10 @@ func convert(from, to reflect.Type) func(src, dest reflect.Value) { } } +func add(ptr unsafe.Pointer, l int) unsafe.Pointer { + return unsafe.Add(ptr, l) +} + func sizeof(i interface{}) uintptr { return reflect.ValueOf(i).Type().Size() }