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.
This commit is contained in:
Marc Vertes
2023-04-26 10:16:05 +02:00
committed by GitHub
parent d6ad13acea
commit dc7c64ba88
10 changed files with 131 additions and 58 deletions

View File

@@ -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.

17
_test/unsafe10.go Normal file
View File

@@ -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

View File

@@ -21,3 +21,6 @@ func main() {
fmt.Println(i)
}
// Output:
// 5

18
_test/unsafe8.go Normal file
View File

@@ -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

View File

@@ -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())
}

View File

@@ -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 {

View File

@@ -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"},
})
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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()
}