Skip to content

Commit

Permalink
interp: create real recursive types with unsafe type swapping
Browse files Browse the repository at this point in the history
As the unsafe and pointer methods in `reflect` are to be depreciated, and seeing no replacement functions, it is now forced that some unsafe is needed to replace this as when and interface is dereferenced it is made unsettable by reflect.

With this in mind, this adds real recursive types by hot swapping the struct field type on the fly. This removes a lot of compensation code, simplifying all previous cases.

**Note:** While the struct field type is swapped for the real type, the type string is not changed. Due to this, unsafe will recreate the same type.
  • Loading branch information
nrwiersma authored Aug 30, 2021
1 parent da922ce commit 4af992b
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 217 deletions.
52 changes: 52 additions & 0 deletions internal/unsafe2/unsafe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package unsafe2

import (
"reflect"
"unsafe"
)

type dummy struct{}

// DummyType represents a stand-in for a recursive type.
var DummyType = reflect.TypeOf(dummy{})

type rtype struct {
_ [48]byte
}

type emptyInterface struct {
typ *rtype
_ unsafe.Pointer
}

type structField struct {
_ int64
typ *rtype
_ uintptr
}

type structType struct {
rtype
_ int64
fields []structField
}

// SwapFieldType swaps the type of the struct field with the given type.
//
// The struct type must have been created at runtime. This is very unsafe.
func SwapFieldType(s reflect.Type, idx int, t reflect.Type) {
if s.Kind() != reflect.Struct || idx >= s.NumField() {
return
}

rtyp := unpackType(s)
styp := (*structType)(unsafe.Pointer(rtyp))
f := styp.fields[idx]
f.typ = unpackType(t)
styp.fields[idx] = f
}

func unpackType(t reflect.Type) *rtype {
v := reflect.New(t).Elem().Interface()
return (*emptyInterface)(unsafe.Pointer(&v)).typ
}
33 changes: 33 additions & 0 deletions internal/unsafe2/unsafe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package unsafe2_test

import (
"reflect"
"testing"

"github.com/traefik/yaegi/internal/unsafe2"
)

func TestSwapFieldType(t *testing.T) {
f := []reflect.StructField{
{
Name: "A",
Type: reflect.TypeOf(int(0)),
},
{
Name: "B",
Type: reflect.PtrTo(unsafe2.DummyType),
},
{
Name: "C",
Type: reflect.TypeOf(int64(0)),
},
}
typ := reflect.StructOf(f)
ntyp := reflect.PtrTo(typ)

unsafe2.SwapFieldType(typ, 1, ntyp)

if typ.Field(1).Type != ntyp {
t.Fatalf("unexpected field type: want %s; got %s", ntyp, typ.Field(1).Type)
}
}
50 changes: 2 additions & 48 deletions interp/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"regexp"
"strings"
"sync"
"unsafe"
)

// bltn type defines functions which run at CFG execution.
Expand Down Expand Up @@ -568,18 +567,6 @@ func convert(n *node) {
}
}

func isRecursiveType(t *itype, rtype reflect.Type) bool {
if t.cat == structT && rtype.Kind() == reflect.Interface {
return true
}
switch t.cat {
case aliasT, arrayT, mapT, ptrT, sliceT:
return isRecursiveType(t.val, t.val.rtype)
default:
return false
}
}

func assign(n *node) {
next := getExec(n.tnext)
dvalue := make([]func(*frame) reflect.Value, n.nleft)
Expand Down Expand Up @@ -1038,11 +1025,7 @@ func call(n *node) {
switch {
case n.child[0].recv != nil:
// Compute method receiver value.
if isRecursiveType(n.child[0].recv.node.typ, n.child[0].recv.node.typ.rtype) {
values = append(values, genValueRecvInterfacePtr(n.child[0]))
} else {
values = append(values, genValueRecv(n.child[0]))
}
values = append(values, genValueRecv(n.child[0]))
method = true
case len(n.child[0].child) > 0 && n.child[0].child[0].typ != nil && isInterfaceSrc(n.child[0].child[0].typ):
recvIndexLater = true
Expand Down Expand Up @@ -1096,8 +1079,6 @@ func call(n *node) {
values = append(values, genValueInterface(c))
case isInterfaceBin(arg):
values = append(values, genInterfaceWrapper(c, arg.rtype))
case isRecursiveType(c.typ, c.typ.rtype):
values = append(values, genValueRecursiveInterfacePtrValue(c))
default:
values = append(values, genValue(c))
}
Expand Down Expand Up @@ -1852,9 +1833,6 @@ func getIndexSeq(n *node) {
fnext := getExec(n.fnext)
n.exec = func(f *frame) bltn {
v := value(f)
if v.Type().Kind() == reflect.Interface && n.child[0].typ.recursive {
v = writableDeref(v)
}
r := v.FieldByIndex(index)
getFrame(f, l).data[i] = r
if r.Bool() {
Expand All @@ -1865,34 +1843,16 @@ func getIndexSeq(n *node) {
} else {
n.exec = func(f *frame) bltn {
v := value(f)
if v.Type().Kind() == reflect.Interface && n.child[0].typ.recursive {
v = writableDeref(v)
}
getFrame(f, l).data[i] = v.FieldByIndex(index)
return tnext
}
}
}

//go:nocheckptr
func writableDeref(v reflect.Value) reflect.Value {
// Here we have an interface to a struct. Any attempt to dereference it will
// make a copy of the struct. We need to get a Value to the actual struct.
// TODO: using unsafe is a temporary measure. Rethink this.
// TODO: InterfaceData has been depreciated, this is even less of a good idea now.
return reflect.NewAt(v.Elem().Type(), unsafe.Pointer(v.InterfaceData()[1])).Elem() //nolint:govet,staticcheck
}

func getPtrIndexSeq(n *node) {
index := n.val.([]int)
tnext := getExec(n.tnext)
var value func(*frame) reflect.Value
if isRecursiveType(n.child[0].typ, n.child[0].typ.rtype) {
v := genValue(n.child[0])
value = func(f *frame) reflect.Value { return v(f).Elem().Elem() }
} else {
value = genValue(n.child[0])
}
value := genValue(n.child[0])
i := n.findex
l := n.level

Expand Down Expand Up @@ -2546,8 +2506,6 @@ func doComposite(n *node, hasType bool, keyed bool) {
values[fieldIndex] = genValueAsFunctionWrapper(val)
case isArray(val.typ) && val.typ.val != nil && isInterfaceSrc(val.typ.val):
values[fieldIndex] = genValueInterfaceArray(val)
case isRecursiveType(ft, rft):
values[fieldIndex] = genValueRecursiveInterface(val, rft)
case isInterfaceSrc(ft) && !isEmptyInterface(ft):
values[fieldIndex] = genValueInterface(val)
case isInterface(ft):
Expand Down Expand Up @@ -2946,8 +2904,6 @@ func _append(n *node) {
values[i] = genValueInterface(arg)
case isInterfaceBin(n.typ.val):
values[i] = genInterfaceWrapper(arg, n.typ.val.rtype)
case isRecursiveType(n.typ.val, n.typ.val.rtype):
values[i] = genValueRecursiveInterface(arg, n.typ.val.rtype)
case arg.typ.untyped:
values[i] = genValueAs(arg, n.child[1].typ.TypeOf().Elem())
default:
Expand All @@ -2972,8 +2928,6 @@ func _append(n *node) {
value0 = genValueInterface(n.child[2])
case isInterfaceBin(elem):
value0 = genInterfaceWrapper(n.child[2], elem.rtype)
case isRecursiveType(elem, elem.rtype):
value0 = genValueRecursiveInterface(n.child[2], elem.rtype)
case n.child[2].typ.untyped:
value0 = genValueAs(n.child[2], n.child[1].typ.TypeOf().Elem())
default:
Expand Down
Loading

0 comments on commit 4af992b

Please sign in to comment.