feature: unsafe type conversion

This commit is contained in:
Nicholas Wiersma
2020-06-18 18:14:03 +02:00
committed by GitHub
parent 0643762852
commit f19b7563ea
12 changed files with 202 additions and 3 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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