feature: unsafe type conversion
This commit is contained in:
10
Makefile
10
Makefile
@@ -1,3 +1,11 @@
|
|||||||
|
GO_VERSION=$(shell go version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1,2)
|
||||||
|
|
||||||
|
RACE_TEST_GCFLAGS=
|
||||||
|
ifneq ($(GO_VERSION), 1.13)
|
||||||
|
# support for unsafe means we cannot do unsafe pointer arithmetic checks
|
||||||
|
RACE_TEST_GCFLAGS=-gcflags=all=-d=checkptr=0
|
||||||
|
endif
|
||||||
|
|
||||||
# Static linting of source files. See .golangci.toml for options
|
# Static linting of source files. See .golangci.toml for options
|
||||||
check:
|
check:
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
@@ -18,6 +26,6 @@ generate: gen_all_syscall
|
|||||||
|
|
||||||
tests:
|
tests:
|
||||||
GO111MODULE=off go test -v ./...
|
GO111MODULE=off go test -v ./...
|
||||||
GO111MODULE=off go test -race ./interp
|
GO111MODULE=off go test -race $(RACE_TEST_GCFLAGS) -short ./interp
|
||||||
|
|
||||||
.PHONY: check gen_all_syscall gen_tests
|
.PHONY: check gen_all_syscall gen_tests
|
||||||
|
|||||||
15
_test/unsafe0.go
Normal file
15
_test/unsafe0.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
str := "foobar"
|
||||||
|
|
||||||
|
p := unsafe.Pointer(&str)
|
||||||
|
str2 := *(*string)(p)
|
||||||
|
|
||||||
|
println(str2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// foobar
|
||||||
20
_test/unsafe1.go
Normal file
20
_test/unsafe1.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s := &S{Name: "foobar"}
|
||||||
|
|
||||||
|
p := unsafe.Pointer(s)
|
||||||
|
|
||||||
|
s2 := (*S)(p)
|
||||||
|
|
||||||
|
println(s2.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// foobar
|
||||||
20
_test/unsafe2.go
Normal file
20
_test/unsafe2.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
str := "foobar"
|
||||||
|
|
||||||
|
ptr := unsafe.Pointer(&str)
|
||||||
|
p := uintptr(ptr)
|
||||||
|
|
||||||
|
s1 := fmt.Sprintf("%x", ptr)
|
||||||
|
s2 := fmt.Sprintf("%x", p)
|
||||||
|
println(s1 == s2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
26
_test/unsafe3.go
Normal file
26
_test/unsafe3.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SSize = 16
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
X int
|
||||||
|
Y int
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var sBuf [SSize]byte
|
||||||
|
s := (*S)(unsafe.Pointer(&sBuf[0]))
|
||||||
|
|
||||||
|
s.X = 2
|
||||||
|
s.Y = 4
|
||||||
|
|
||||||
|
fmt.Println(sBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// [2 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0]
|
||||||
29
_test/unsafe4.go
Normal file
29
_test/unsafe4.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SSize = 24
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
X int
|
||||||
|
Y int
|
||||||
|
Z int
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
arr := []S{
|
||||||
|
{X: 1},
|
||||||
|
{X: 2},
|
||||||
|
{X: 3},
|
||||||
|
}
|
||||||
|
addr := unsafe.Pointer(&arr[0])
|
||||||
|
s := *(*S)(unsafe.Pointer(uintptr(addr) + SSize*2))
|
||||||
|
|
||||||
|
fmt.Println(s.X)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 3
|
||||||
@@ -91,13 +91,16 @@ import (
|
|||||||
|
|
||||||
"github.com/containous/yaegi/interp"
|
"github.com/containous/yaegi/interp"
|
||||||
"github.com/containous/yaegi/stdlib"
|
"github.com/containous/yaegi/stdlib"
|
||||||
|
"github.com/containous/yaegi/stdlib/unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var interactive bool
|
var interactive bool
|
||||||
|
var useUnsafe bool
|
||||||
var tags string
|
var tags string
|
||||||
var cmd string
|
var cmd string
|
||||||
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
|
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
|
||||||
|
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
|
||||||
flag.StringVar(&tags, "tags", "", "set a list of build tags")
|
flag.StringVar(&tags, "tags", "", "set a list of build tags")
|
||||||
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
|
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
@@ -112,6 +115,9 @@ func main() {
|
|||||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
||||||
i.Use(stdlib.Symbols)
|
i.Use(stdlib.Symbols)
|
||||||
i.Use(interp.Symbols)
|
i.Use(interp.Symbols)
|
||||||
|
if useUnsafe {
|
||||||
|
i.Use(unsafe.Symbols)
|
||||||
|
}
|
||||||
|
|
||||||
if cmd != `` {
|
if cmd != `` {
|
||||||
i.REPL(strings.NewReader(cmd), os.Stderr)
|
i.REPL(strings.NewReader(cmd), os.Stderr)
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ func (f *frame) clone() *frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convertFn is the signature of a symbol converter
|
||||||
|
type convertFn func(from, to reflect.Type) func(src, dest reflect.Value)
|
||||||
|
|
||||||
// Exports stores the map of binary packages per package path
|
// Exports stores the map of binary packages per package path
|
||||||
type Exports map[string]map[string]reflect.Value
|
type Exports map[string]map[string]reflect.Value
|
||||||
|
|
||||||
@@ -138,6 +141,8 @@ type Interpreter struct {
|
|||||||
srcPkg imports // source packages used in interpreter, indexed by path
|
srcPkg imports // source packages used in interpreter, indexed by path
|
||||||
pkgNames map[string]string // package names, indexed by path
|
pkgNames map[string]string // package names, indexed by path
|
||||||
done chan struct{} // for cancellation of channel operations
|
done chan struct{} // for cancellation of channel operations
|
||||||
|
|
||||||
|
convert []convertFn // converters from symbols
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -466,8 +471,26 @@ func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
|
|||||||
// they can be used in interpreted code
|
// they can be used in interpreted code
|
||||||
func (interp *Interpreter) Use(values Exports) {
|
func (interp *Interpreter) Use(values Exports) {
|
||||||
for k, v := range values {
|
for k, v := range values {
|
||||||
|
if k != "github.com/containous/yaegi" {
|
||||||
interp.binPkg[k] = v
|
interp.binPkg[k] = v
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if con, ok := extractConverter(v["convert"]); ok {
|
||||||
|
interp.convert = append(interp.convert, con)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractConverter(v reflect.Value) (convertFn, bool) {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
fn, ok := v.Interface().(func(from, to reflect.Type) func(src, dest reflect.Value))
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return fn, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// REPL performs a Read-Eval-Print-Loop on input reader.
|
// REPL performs a Read-Eval-Print-Loop on input reader.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containous/yaegi/interp"
|
"github.com/containous/yaegi/interp"
|
||||||
"github.com/containous/yaegi/stdlib"
|
"github.com/containous/yaegi/stdlib"
|
||||||
|
"github.com/containous/yaegi/stdlib/unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInterpConsistencyBuild(t *testing.T) {
|
func TestInterpConsistencyBuild(t *testing.T) {
|
||||||
@@ -104,6 +105,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
|||||||
i.Name = filePath
|
i.Name = filePath
|
||||||
i.Use(stdlib.Symbols)
|
i.Use(stdlib.Symbols)
|
||||||
i.Use(interp.Symbols)
|
i.Use(interp.Symbols)
|
||||||
|
i.Use(unsafe.Symbols)
|
||||||
|
|
||||||
_, err = i.Eval(string(src))
|
_, err = i.Eval(string(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containous/yaegi/interp"
|
"github.com/containous/yaegi/interp"
|
||||||
"github.com/containous/yaegi/stdlib"
|
"github.com/containous/yaegi/stdlib"
|
||||||
|
"github.com/containous/yaegi/stdlib/unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFile(t *testing.T) {
|
func TestFile(t *testing.T) {
|
||||||
@@ -56,6 +57,7 @@ func runCheck(t *testing.T, p string) {
|
|||||||
i.Name = p
|
i.Name = p
|
||||||
i.Use(interp.Symbols)
|
i.Use(interp.Symbols)
|
||||||
i.Use(stdlib.Symbols)
|
i.Use(stdlib.Symbols)
|
||||||
|
i.Use(unsafe.Symbols)
|
||||||
|
|
||||||
_, err = i.Eval(string(src))
|
_, err = i.Eval(string(src))
|
||||||
if errWanted {
|
if errWanted {
|
||||||
|
|||||||
@@ -329,6 +329,22 @@ func convert(n *node) {
|
|||||||
value = genValue(c)
|
value = genValue(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, con := range n.interp.convert {
|
||||||
|
if c.typ.rtype == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := con(c.typ.rtype, typ)
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n.exec = func(f *frame) bltn {
|
||||||
|
fn(value(f), dest(f))
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n.exec = func(f *frame) bltn {
|
n.exec = func(f *frame) bltn {
|
||||||
dest(f).Set(value(f).Convert(typ))
|
dest(f).Set(value(f).Convert(typ))
|
||||||
return next
|
return next
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
// Package unsafe provides wrapper of standard library unsafe package to be imported natively in Yaegi.
|
// Package unsafe provides wrapper of standard library unsafe package to be imported natively in Yaegi.
|
||||||
package unsafe
|
package unsafe
|
||||||
|
|
||||||
import "reflect"
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
// Symbols stores the map of unsafe package symbols
|
// Symbols stores the map of unsafe package symbols
|
||||||
var Symbols = map[string]map[string]reflect.Value{}
|
var Symbols = map[string]map[string]reflect.Value{}
|
||||||
@@ -12,6 +15,35 @@ func init() {
|
|||||||
Symbols["github.com/containous/yaegi/stdlib/unsafe"] = map[string]reflect.Value{
|
Symbols["github.com/containous/yaegi/stdlib/unsafe"] = map[string]reflect.Value{
|
||||||
"Symbols": reflect.ValueOf(Symbols),
|
"Symbols": reflect.ValueOf(Symbols),
|
||||||
}
|
}
|
||||||
|
Symbols["github.com/containous/yaegi"] = map[string]reflect.Value{
|
||||||
|
"convert": reflect.ValueOf(convert),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert(from, to reflect.Type) func(src, dest reflect.Value) {
|
||||||
|
switch {
|
||||||
|
case to.Kind() == reflect.UnsafePointer && from.Kind() == reflect.Uintptr:
|
||||||
|
return func(src, dest reflect.Value) {
|
||||||
|
dest.SetPointer(unsafe.Pointer(src.Interface().(uintptr))) //nolint:govet
|
||||||
|
}
|
||||||
|
case to.Kind() == reflect.UnsafePointer:
|
||||||
|
return func(src, dest reflect.Value) {
|
||||||
|
dest.SetPointer(unsafe.Pointer(src.Pointer()))
|
||||||
|
}
|
||||||
|
case to.Kind() == reflect.Uintptr && from.Kind() == reflect.UnsafePointer:
|
||||||
|
return func(src, dest reflect.Value) {
|
||||||
|
ptr := src.Interface().(unsafe.Pointer)
|
||||||
|
dest.Set(reflect.ValueOf(uintptr(ptr)))
|
||||||
|
}
|
||||||
|
case from.Kind() == reflect.UnsafePointer:
|
||||||
|
return func(src, dest reflect.Value) {
|
||||||
|
ptr := src.Interface().(unsafe.Pointer)
|
||||||
|
v := reflect.NewAt(dest.Type().Elem(), ptr)
|
||||||
|
dest.Set(v)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate ../../cmd/goexports/goexports unsafe
|
//go:generate ../../cmd/goexports/goexports unsafe
|
||||||
|
|||||||
Reference in New Issue
Block a user