From dc7c64ba8804266a50e00677e96595947869238c Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Wed, 26 Apr 2023 10:16:05 +0200 Subject: [PATCH] interp: improve support of unsafe Unsafe functions such as `unsafe.Alignof`, `unsafe.Offsetof` and `unsafe.Sizeof` can be used for type declarations early on during compile, and as such, must be treated as builtins returning constants at compile time. It is still necessary to explicitely enable unsafe support in yaegi. The support of `unsafe.Add` has also been added. Fixes #1544. --- README.md | 1 - _test/unsafe10.go | 17 +++++++++++ _test/unsafe6.go | 3 ++ _test/unsafe8.go | 18 +++++++++++ interp/cfg.go | 61 ++++++++++++++++++++++++-------------- interp/interp.go | 33 +++++++++++---------- interp/interp_eval_test.go | 2 +- interp/type.go | 5 +++- interp/typecheck.go | 38 ++++++++++++++---------- stdlib/unsafe/unsafe.go | 11 +++++-- 10 files changed, 131 insertions(+), 58 deletions(-) create mode 100644 _test/unsafe10.go create mode 100644 _test/unsafe8.go 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() }