Skip to content

Commit

Permalink
Support certain cases of unsafe.Pointer
Browse files Browse the repository at this point in the history
Nil pointers can be serialized. Pointers that refer
to objects in the serialization graph are also
supported.
  • Loading branch information
chriso committed Nov 7, 2023
1 parent 8227dcf commit d8b85a2
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 2 deletions.
32 changes: 30 additions & 2 deletions types/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@ func serializeAny(s *Serializer, t reflect.Type, p unsafe.Pointer) {
serializeMap(s, t, p)
case reflect.Pointer:
serializePointer(s, t, p)
case reflect.UnsafePointer:
serializeUnsafePointer(s, p)
case reflect.Slice:
serializeSlice(s, t, p)
case reflect.Struct:
serializeStruct(s, t, p)
case reflect.Func:
serializeFunc(s, t, p)
// Chan
// UnsafePointer
default:
panic(fmt.Errorf("reflection cannot serialize type %s", t))
}
Expand Down Expand Up @@ -129,6 +130,8 @@ func deserializeAny(d *Deserializer, t reflect.Type, p unsafe.Pointer) {
deserializeInterface(d, t, p)
case reflect.Pointer:
deserializePointer(d, t, p)
case reflect.UnsafePointer:
deserializeUnsafePointer(d, p)
case reflect.Array:
deserializeArray(d, t, p)
case reflect.Slice:
Expand Down Expand Up @@ -174,6 +177,9 @@ func serializePointedAt(s *Serializer, t reflect.Type, p unsafe.Pointer) {
// If this pointer does not belong to any region, write a negative
// offset to flag it is on its own, and write its data.
if !r.valid() {
if t == nil {
panic("cannot serialize unsafe.Pointer pointing to region of unknown size")
}
serializeVarint(s, -1)
serializeAny(s, t, p)
return
Expand Down Expand Up @@ -359,6 +365,26 @@ func deserializePointer(d *Deserializer, t reflect.Type, p unsafe.Pointer) {
r.Elem().Set(ep)
}

func serializeUnsafePointer(s *Serializer, p unsafe.Pointer) {
if p == nil {
serializePointedAt(s, nil, nil)
} else {
serializePointedAt(s, nil, *(*unsafe.Pointer)(p))
}
}

var unsafePointerType = reflect.TypeOf(unsafe.Pointer(nil))

func deserializeUnsafePointer(d *Deserializer, p unsafe.Pointer) {
r := reflect.NewAt(unsafePointerType, p)

ep := deserializePointedAt(d, unsafePointerType)
if !ep.IsNil() {
up := ep.UnsafePointer()
r.Elem().Set(reflect.ValueOf(up))
}
}

func serializeStruct(s *Serializer, t reflect.Type, p unsafe.Pointer) {
serializeStructFields(s, p, t.NumField(), t.Field)
}
Expand Down Expand Up @@ -476,9 +502,11 @@ func deserializeInterface(d *Deserializer, t reflect.Type, p unsafe.Pointer) {
ep := deserializePointedAt(d, et)

// Store the result in the interface
r := reflect.NewAt(t, p)
if !ep.IsNil() {
r := reflect.NewAt(t, p)
r.Elem().Set(ep.Elem())
} else {
r.Elem().Set(reflect.Zero(et))
}
}

Expand Down
20 changes: 20 additions & 0 deletions types/serde_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func TestReflect(t *testing.T) {
"",
struct{}{},
errors.New("test"),
unsafe.Pointer(nil),
}

for _, x := range cases {
Expand Down Expand Up @@ -143,6 +144,25 @@ func TestReflect(t *testing.T) {
})
}

func TestReflectUnsafePointer(t *testing.T) {
type unsafePointerStruct struct{ p unsafe.Pointer }
var selfRef unsafePointerStruct
selfRef.p = unsafe.Pointer(&selfRef)

b := Serialize(&selfRef)
out, b, err := Deserialize(b)
if err != nil {
t.Fatal(err)
} else if len(b) > 0 {
t.Fatalf("leftover bytes: %d", len(b))
}

res := out.(*unsafePointerStruct)
if unsafe.Pointer(res) != unsafe.Pointer(res.p) {
t.Errorf("unsafe.Pointer was not restored correctly")
}
}

func TestErrors(t *testing.T) {
s := struct {
X5 error
Expand Down

0 comments on commit d8b85a2

Please sign in to comment.