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
|
||||
check:
|
||||
golangci-lint run
|
||||
@@ -18,6 +26,6 @@ generate: gen_all_syscall
|
||||
|
||||
tests:
|
||||
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
|
||||
|
||||
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/stdlib"
|
||||
"github.com/containous/yaegi/stdlib/unsafe"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var interactive bool
|
||||
var useUnsafe bool
|
||||
var tags string
|
||||
var cmd string
|
||||
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(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
|
||||
flag.Usage = func() {
|
||||
@@ -112,6 +115,9 @@ func main() {
|
||||
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, BuildTags: strings.Split(tags, ",")})
|
||||
i.Use(stdlib.Symbols)
|
||||
i.Use(interp.Symbols)
|
||||
if useUnsafe {
|
||||
i.Use(unsafe.Symbols)
|
||||
}
|
||||
|
||||
if cmd != `` {
|
||||
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
|
||||
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
|
||||
pkgNames map[string]string // package names, indexed by path
|
||||
done chan struct{} // for cancellation of channel operations
|
||||
|
||||
convert []convertFn // converters from symbols
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -466,8 +471,26 @@ func (interp *Interpreter) getWrapper(t reflect.Type) reflect.Type {
|
||||
// they can be used in interpreted code
|
||||
func (interp *Interpreter) Use(values Exports) {
|
||||
for k, v := range values {
|
||||
if k != "github.com/containous/yaegi" {
|
||||
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.
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/containous/yaegi/interp"
|
||||
"github.com/containous/yaegi/stdlib"
|
||||
"github.com/containous/yaegi/stdlib/unsafe"
|
||||
)
|
||||
|
||||
func TestInterpConsistencyBuild(t *testing.T) {
|
||||
@@ -104,6 +105,7 @@ func TestInterpConsistencyBuild(t *testing.T) {
|
||||
i.Name = filePath
|
||||
i.Use(stdlib.Symbols)
|
||||
i.Use(interp.Symbols)
|
||||
i.Use(unsafe.Symbols)
|
||||
|
||||
_, err = i.Eval(string(src))
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/containous/yaegi/interp"
|
||||
"github.com/containous/yaegi/stdlib"
|
||||
"github.com/containous/yaegi/stdlib/unsafe"
|
||||
)
|
||||
|
||||
func TestFile(t *testing.T) {
|
||||
@@ -56,6 +57,7 @@ func runCheck(t *testing.T, p string) {
|
||||
i.Name = p
|
||||
i.Use(interp.Symbols)
|
||||
i.Use(stdlib.Symbols)
|
||||
i.Use(unsafe.Symbols)
|
||||
|
||||
_, err = i.Eval(string(src))
|
||||
if errWanted {
|
||||
|
||||
@@ -329,6 +329,22 @@ func convert(n *node) {
|
||||
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 {
|
||||
dest(f).Set(value(f).Convert(typ))
|
||||
return next
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// Package unsafe provides wrapper of standard library unsafe package to be imported natively in Yaegi.
|
||||
package unsafe
|
||||
|
||||
import "reflect"
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Symbols stores the map of unsafe package symbols
|
||||
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": 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
|
||||
|
||||
Reference in New Issue
Block a user