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:
@@ -5,7 +5,6 @@
|
||||
[](https://github.com/traefik/yaegi/releases)
|
||||
[](https://github.com/traefik/yaegi/actions/workflows/main.yml)
|
||||
[](https://pkg.go.dev/mod/github.com/traefik/yaegi)
|
||||
[](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
17
_test/unsafe10.go
Normal 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
|
||||
@@ -21,3 +21,6 @@ func main() {
|
||||
|
||||
fmt.Println(i)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// 5
|
||||
|
||||
18
_test/unsafe8.go
Normal file
18
_test/unsafe8.go
Normal 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
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user