Skip to content

Commit

Permalink
Don't decode CBOR tag data to interface (tinygo only)
Browse files Browse the repository at this point in the history
tinygo v0.33 doesn't implement Type.AssignableTo(), which is
needed under the hood when decoding registered CBOR tag data
to Go interface.

More details in tinygo-org/tinygo#4277.

This commit returns error of UnmarshalTypeError type when
decoding registered CBOR tag data to Go interface.

This change can be reverted after tinygo implements
Type.AssignalbeTo().
  • Loading branch information
fxamacker committed Sep 8, 2024
1 parent 950ea6d commit e1bc59b
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 21 deletions.
3 changes: 1 addition & 2 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -1386,8 +1386,7 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin

registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
if registeredType != nil {
if registeredType.Implements(tInfo.nonPtrType) ||
reflect.PtrTo(registeredType).Implements(tInfo.nonPtrType) {
if implements(registeredType, tInfo.nonPtrType) {
v.Set(reflect.New(registeredType))
v = v.Elem()
tInfo = getTypeInfo(registeredType)
Expand Down
13 changes: 13 additions & 0 deletions decode_not_tinygo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

//go:build !tinygo

package cbor

import "reflect"

func implements(concreteType reflect.Type, interfaceType reflect.Type) bool {
return concreteType.Implements(interfaceType) ||
reflect.PointerTo(concreteType).Implements(interfaceType)
}
60 changes: 60 additions & 0 deletions decode_not_tinygo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,63 @@ func TestUnmarshalDeepNesting(t *testing.T) {
&readback, root)
}
}

func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
if err != nil {
t.Error(err)
}
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
if err != nil {
t.Error(err)
}

encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
decMode, _ := DecOptions{}.DecModeWithTags(tags)

v1 := A1{Field: &C{Field: 5}}
data1, err := encMode.Marshal(v1)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}

v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}

testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
if err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", tc.data, err)
}
if !reflect.DeepEqual(tc.unmarshalToObj, tc.wantValue) {
t.Errorf("Unmarshal(0x%x) = %v, want %v", tc.data, tc.unmarshalToObj, tc.wantValue)
}
})
}
}
20 changes: 1 addition & 19 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7698,7 +7698,7 @@ type A2 struct {
Fields []B
}

func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
func TestUnmarshalRegisteredTagToConcreteType(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
Expand All @@ -7719,36 +7719,18 @@ func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}

v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}

testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "concrete type",
data: data1,
unmarshalToObj: &A1{Field: &C{}},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}

for _, tc := range testCases {
Expand Down
18 changes: 18 additions & 0 deletions decode_tinygo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

//go:build tinygo

package cbor

import "reflect"

// tinygo v0.33 doesn't implement Type.AssignableTo() and it panics.
// Type.AssignableTo() is used under the hood for Type.Implements().
//
// More details in https://github.com/tinygo-org/tinygo/issues/4277.
//
// implements() always returns false until tinygo implements Type.AssignableTo().
func implements(concreteType reflect.Type, interfaceType reflect.Type) bool {
return false
}
59 changes: 59 additions & 0 deletions decode_tinygo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,62 @@ func TestUnmarshalDeepNesting(t *testing.T) {
&readback, root)
}
}

func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
if err != nil {
t.Error(err)
}
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
if err != nil {
t.Error(err)
}

encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
decMode, _ := DecOptions{}.DecModeWithTags(tags)

v1 := A1{Field: &C{Field: 5}}
data1, err := encMode.Marshal(v1)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}

v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}

testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
if err == nil {
t.Errorf("Unmarshal(0x%x) returned no error, expect error", tc.data)
} else if _, ok := err.(*UnmarshalTypeError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", tc.data, err)
}
})
}
}

0 comments on commit e1bc59b

Please sign in to comment.