Skip to content

Commit

Permalink
interp: improve support of unsafe
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mvertes committed Apr 26, 2023
1 parent d6ad13a commit dc7c64b
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 58 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 17 additions & 0 deletions _test/unsafe10.go
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions _test/unsafe6.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ func main() {

fmt.Println(i)
}

// Output:
// 5
18 changes: 18 additions & 0 deletions _test/unsafe8.go
Original file line number Diff line number Diff line change
@@ -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
61 changes: 39 additions & 22 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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())
}
33 changes: 18 additions & 15 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion interp/interp_eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
})
}

Expand Down
5 changes: 4 additions & 1 deletion interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
Expand Down
38 changes: 22 additions & 16 deletions interp/typecheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
11 changes: 9 additions & 2 deletions stdlib/unsafe/unsafe.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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()
}
Expand Down

0 comments on commit dc7c64b

Please sign in to comment.